@alleyboss/micropay-solana-x402-paywall 1.0.1 → 2.0.1
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/README.md +100 -167
- package/dist/client/index.cjs +99 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +112 -0
- package/dist/client/index.d.ts +112 -0
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client-CSZHI8o8.d.ts +32 -0
- package/dist/client-vRr48m2x.d.cts +32 -0
- package/dist/index.cjs +696 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +674 -16
- package/dist/index.js.map +1 -1
- package/dist/memory-Daxkczti.d.cts +29 -0
- package/dist/memory-Daxkczti.d.ts +29 -0
- package/dist/middleware/index.cjs +261 -0
- package/dist/middleware/index.cjs.map +1 -0
- package/dist/middleware/index.d.cts +90 -0
- package/dist/middleware/index.d.ts +90 -0
- package/dist/middleware/index.js +255 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/nextjs-BK0pVb9Y.d.ts +78 -0
- package/dist/nextjs-Bm272Jkj.d.cts +78 -0
- package/dist/{client-kfCr7G-P.d.cts → payment-CTxdtqmc.d.cts} +23 -34
- package/dist/{client-kfCr7G-P.d.ts → payment-CTxdtqmc.d.ts} +23 -34
- package/dist/pricing/index.cjs +142 -0
- package/dist/pricing/index.cjs.map +1 -0
- package/dist/pricing/index.d.cts +111 -0
- package/dist/pricing/index.d.ts +111 -0
- package/dist/pricing/index.js +133 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/session/index.d.cts +29 -1
- package/dist/session/index.d.ts +29 -1
- package/dist/{index-uxMb72hH.d.cts → session-D2IoWAWV.d.cts} +1 -27
- package/dist/{index-uxMb72hH.d.ts → session-D2IoWAWV.d.ts} +1 -27
- package/dist/solana/index.cjs +193 -0
- package/dist/solana/index.cjs.map +1 -1
- package/dist/solana/index.d.cts +60 -3
- package/dist/solana/index.d.ts +60 -3
- package/dist/solana/index.js +190 -1
- package/dist/solana/index.js.map +1 -1
- package/dist/store/index.cjs +99 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.cts +38 -0
- package/dist/store/index.d.ts +38 -0
- package/dist/store/index.js +96 -0
- package/dist/store/index.js.map +1 -0
- package/dist/utils/index.cjs +68 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +30 -0
- package/dist/utils/index.d.ts +30 -0
- package/dist/utils/index.js +65 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/x402/index.cjs +2 -1
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +2 -1
- package/dist/x402/index.d.ts +2 -1
- package/dist/x402/index.js +2 -1
- package/dist/x402/index.js.map +1 -1
- package/package.json +56 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { jwtVerify } from 'jose';
|
|
2
|
+
import 'uuid';
|
|
3
|
+
|
|
4
|
+
// src/session/core.ts
|
|
5
|
+
var MIN_SECRET_LENGTH = 32;
|
|
6
|
+
function getSecretKey(secret) {
|
|
7
|
+
if (!secret || typeof secret !== "string") {
|
|
8
|
+
throw new Error("Session secret is required");
|
|
9
|
+
}
|
|
10
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
11
|
+
throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);
|
|
12
|
+
}
|
|
13
|
+
return new TextEncoder().encode(secret);
|
|
14
|
+
}
|
|
15
|
+
function validateWalletAddress(address) {
|
|
16
|
+
if (!address || typeof address !== "string") return false;
|
|
17
|
+
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
18
|
+
return base58Regex.test(address);
|
|
19
|
+
}
|
|
20
|
+
async function validateSession(token, secret) {
|
|
21
|
+
if (!token || typeof token !== "string") {
|
|
22
|
+
return { valid: false, reason: "Invalid token format" };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const { payload } = await jwtVerify(token, getSecretKey(secret));
|
|
26
|
+
const sessionPayload = payload;
|
|
27
|
+
if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {
|
|
28
|
+
return { valid: false, reason: "Malformed session payload" };
|
|
29
|
+
}
|
|
30
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
31
|
+
if (sessionPayload.exp < now) {
|
|
32
|
+
return { valid: false, reason: "Session expired" };
|
|
33
|
+
}
|
|
34
|
+
if (!validateWalletAddress(sessionPayload.sub)) {
|
|
35
|
+
return { valid: false, reason: "Invalid session data" };
|
|
36
|
+
}
|
|
37
|
+
const session = {
|
|
38
|
+
id: sessionPayload.sid,
|
|
39
|
+
walletAddress: sessionPayload.sub,
|
|
40
|
+
unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],
|
|
41
|
+
siteWideUnlock: Boolean(sessionPayload.siteWide),
|
|
42
|
+
createdAt: sessionPayload.iat ?? 0,
|
|
43
|
+
expiresAt: sessionPayload.exp
|
|
44
|
+
};
|
|
45
|
+
return { valid: true, session };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return { valid: false, reason: "Invalid session" };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/middleware/nextjs.ts
|
|
52
|
+
function matchesProtectedPath(path, patterns) {
|
|
53
|
+
for (const pattern of patterns) {
|
|
54
|
+
const regexPattern = pattern.replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE_STAR}}/g, ".*");
|
|
55
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
56
|
+
if (regex.test(path)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
async function checkPaywallAccess(path, sessionToken, config) {
|
|
63
|
+
if (!matchesProtectedPath(path, config.protectedPaths)) {
|
|
64
|
+
return { allowed: true };
|
|
65
|
+
}
|
|
66
|
+
if (!sessionToken) {
|
|
67
|
+
return {
|
|
68
|
+
allowed: false,
|
|
69
|
+
reason: "No session token",
|
|
70
|
+
requiresPayment: true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const validation = await validateSession(sessionToken, config.sessionSecret);
|
|
74
|
+
if (!validation.valid || !validation.session) {
|
|
75
|
+
return {
|
|
76
|
+
allowed: false,
|
|
77
|
+
reason: validation.reason || "Invalid session",
|
|
78
|
+
requiresPayment: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
allowed: true,
|
|
83
|
+
session: validation.session
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function createPaywallMiddleware(config) {
|
|
87
|
+
const { cookieName = "x402_session" } = config;
|
|
88
|
+
return async function middleware(request) {
|
|
89
|
+
const url = new URL(request.url);
|
|
90
|
+
const path = url.pathname;
|
|
91
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
92
|
+
const cookies = Object.fromEntries(
|
|
93
|
+
cookieHeader.split(";").map((c) => {
|
|
94
|
+
const [key, ...vals] = c.trim().split("=");
|
|
95
|
+
return [key, vals.join("=")];
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
const sessionToken = cookies[cookieName];
|
|
99
|
+
const result = await checkPaywallAccess(path, sessionToken, config);
|
|
100
|
+
if (!result.allowed && result.requiresPayment) {
|
|
101
|
+
const body = config.custom402Response ? config.custom402Response(path) : {
|
|
102
|
+
error: "Payment Required",
|
|
103
|
+
message: "This resource requires payment to access",
|
|
104
|
+
path
|
|
105
|
+
};
|
|
106
|
+
return new Response(JSON.stringify(body), {
|
|
107
|
+
status: 402,
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json"
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function withPaywall(handler, options) {
|
|
117
|
+
const { sessionSecret, cookieName = "x402_session", articleId } = options;
|
|
118
|
+
return async function protectedHandler(request) {
|
|
119
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
120
|
+
const cookies = Object.fromEntries(
|
|
121
|
+
cookieHeader.split(";").map((c) => {
|
|
122
|
+
const [key, ...vals] = c.trim().split("=");
|
|
123
|
+
return [key, vals.join("=")];
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
const sessionToken = cookies[cookieName];
|
|
127
|
+
if (!sessionToken) {
|
|
128
|
+
return new Response(
|
|
129
|
+
JSON.stringify({ error: "Payment Required", message: "No session token" }),
|
|
130
|
+
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const validation = await validateSession(sessionToken, sessionSecret);
|
|
134
|
+
if (!validation.valid || !validation.session) {
|
|
135
|
+
return new Response(
|
|
136
|
+
JSON.stringify({ error: "Payment Required", message: validation.reason }),
|
|
137
|
+
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (articleId) {
|
|
141
|
+
const { session } = validation;
|
|
142
|
+
const hasAccess = session.siteWideUnlock || session.unlockedArticles.includes(articleId);
|
|
143
|
+
if (!hasAccess) {
|
|
144
|
+
return new Response(
|
|
145
|
+
JSON.stringify({ error: "Payment Required", message: "Article not unlocked" }),
|
|
146
|
+
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return handler(request, validation.session);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/middleware/express.ts
|
|
155
|
+
function matchPath(path, patterns) {
|
|
156
|
+
for (const pattern of patterns) {
|
|
157
|
+
const regex = pattern.replace(/\*\*/g, "<<<GLOBSTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<GLOBSTAR>>>/g, ".*");
|
|
158
|
+
if (new RegExp(`^${regex}$`).test(path)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
function hasGetMethod(headers) {
|
|
165
|
+
return typeof headers.get === "function";
|
|
166
|
+
}
|
|
167
|
+
function getHeader(headers, name) {
|
|
168
|
+
if (hasGetMethod(headers)) {
|
|
169
|
+
return headers.get(name) ?? void 0;
|
|
170
|
+
}
|
|
171
|
+
const value = headers[name];
|
|
172
|
+
return typeof value === "string" ? value : void 0;
|
|
173
|
+
}
|
|
174
|
+
function extractToken(req, config) {
|
|
175
|
+
const { cookieName = "x402_session", headerName } = config;
|
|
176
|
+
if (headerName) {
|
|
177
|
+
const token = getHeader(req.headers, headerName.toLowerCase());
|
|
178
|
+
if (token) return token;
|
|
179
|
+
}
|
|
180
|
+
const authHeader = getHeader(req.headers, "authorization");
|
|
181
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
182
|
+
return authHeader.slice(7);
|
|
183
|
+
}
|
|
184
|
+
if (req.cookies?.[cookieName]) {
|
|
185
|
+
return req.cookies[cookieName];
|
|
186
|
+
}
|
|
187
|
+
const cookieHeader = getHeader(req.headers, "cookie");
|
|
188
|
+
if (cookieHeader) {
|
|
189
|
+
const match = cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${cookieName}=`));
|
|
190
|
+
if (match) {
|
|
191
|
+
return match.slice(cookieName.length + 1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
function createExpressMiddleware(config) {
|
|
197
|
+
const { sessionSecret, protectedPaths, onPaymentRequired, onAccessGranted } = config;
|
|
198
|
+
return async function paywallMiddleware(req, res, next) {
|
|
199
|
+
const path = req.path || req.url?.split("?")[0] || "/";
|
|
200
|
+
if (!matchPath(path, protectedPaths)) {
|
|
201
|
+
next();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const token = extractToken(req, config);
|
|
205
|
+
if (!token) {
|
|
206
|
+
if (onPaymentRequired) {
|
|
207
|
+
onPaymentRequired(req, res);
|
|
208
|
+
} else {
|
|
209
|
+
sendPaymentRequired(res, "No session token");
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const validation = await validateSession(token, sessionSecret);
|
|
214
|
+
if (!validation.valid || !validation.session) {
|
|
215
|
+
if (onPaymentRequired) {
|
|
216
|
+
onPaymentRequired(req, res);
|
|
217
|
+
} else {
|
|
218
|
+
sendPaymentRequired(res, validation.reason || "Invalid session");
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
req.session = validation.session;
|
|
223
|
+
if (onAccessGranted) {
|
|
224
|
+
onAccessGranted(req, validation.session);
|
|
225
|
+
}
|
|
226
|
+
next();
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function sendPaymentRequired(res, reason) {
|
|
230
|
+
const body = JSON.stringify({
|
|
231
|
+
error: "Payment Required",
|
|
232
|
+
message: reason
|
|
233
|
+
});
|
|
234
|
+
const statusFn = res.status;
|
|
235
|
+
const jsonFn = res.json;
|
|
236
|
+
if (statusFn && jsonFn) {
|
|
237
|
+
const chainedRes = statusFn.call(res, 402);
|
|
238
|
+
if (chainedRes?.json) {
|
|
239
|
+
chainedRes.json({ error: "Payment Required", message: reason });
|
|
240
|
+
}
|
|
241
|
+
} else if (res.statusCode !== void 0 && res.setHeader && res.end) {
|
|
242
|
+
res.statusCode = 402;
|
|
243
|
+
res.setHeader("Content-Type", "application/json");
|
|
244
|
+
res.end(body);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function createFastifyPlugin(config) {
|
|
248
|
+
return async function paywallPlugin(fastify) {
|
|
249
|
+
fastify.addHook("preHandler", createExpressMiddleware(config));
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export { checkPaywallAccess, createExpressMiddleware, createFastifyPlugin, createPaywallMiddleware, withPaywall };
|
|
254
|
+
//# sourceMappingURL=index.js.map
|
|
255
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/session/core.ts","../../src/middleware/nextjs.ts","../../src/middleware/express.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Session management with JWT (framework-agnostic core)\n// SECURITY: Uses jose library with HS256, constant-time validation, input sanitization\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n// Maximum articles per session to prevent unbounded growth\nconst MAX_ARTICLES_PER_SESSION = 100;\n\n// Minimum secret length for security\nconst MIN_SECRET_LENGTH = 32;\n\n/**\n * Get the secret key for JWT signing\n * SECURITY: Enforces minimum secret length\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (!secret || typeof secret !== 'string') {\n throw new Error('Session secret is required');\n }\n if (secret.length < MIN_SECRET_LENGTH) {\n throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Validate wallet address format (base58, 32-44 chars)\n * SECURITY: Prevents injection via wallet address field\n */\nfunction validateWalletAddress(address: string): boolean {\n if (!address || typeof address !== 'string') return false;\n // Solana addresses are base58, typically 32-44 characters\n const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n return base58Regex.test(address);\n}\n\n/**\n * Validate article ID format\n * SECURITY: Prevents injection via articleId field\n */\nfunction validateArticleId(articleId: string): boolean {\n if (!articleId || typeof articleId !== 'string') return false;\n // Allow alphanumeric, hyphens, underscores, max 128 chars\n if (articleId.length > 128) return false;\n const safeIdRegex = /^[a-zA-Z0-9_-]+$/;\n return safeIdRegex.test(articleId);\n}\n\n/**\n * Create a new session after successful payment\n * SECURITY: Validates inputs, enforces limits\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n // Input validation\n if (!validateWalletAddress(walletAddress)) {\n throw new Error('Invalid wallet address format');\n }\n if (!validateArticleId(articleId)) {\n throw new Error('Invalid article ID format');\n }\n if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {\n throw new Error('Session duration must be between 1 and 720 hours');\n }\n\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: Boolean(siteWide),\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide: session.siteWideUnlock,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n * SECURITY: jose library handles timing-safe comparison internally\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n // Input validation\n if (!token || typeof token !== 'string') {\n return { valid: false, reason: 'Invalid token format' };\n }\n\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n // Validate required fields exist\n if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {\n return { valid: false, reason: 'Malformed session payload' };\n }\n\n // Check expiration (jose already checks, but double-check)\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n // Validate wallet address format from token\n if (!validateWalletAddress(sessionPayload.sub)) {\n return { valid: false, reason: 'Invalid session data' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],\n siteWideUnlock: Boolean(sessionPayload.siteWide),\n createdAt: sessionPayload.iat ?? 0,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n // SECURITY: Don't expose internal error details\n return { valid: false, reason: 'Invalid session' };\n }\n}\n\n/**\n * Add an article to an existing session\n * SECURITY: Enforces article limit to prevent token bloat\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n // Validate article ID\n if (!validateArticleId(articleId)) {\n return null;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n // SECURITY: Enforce maximum articles per session\n if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {\n return null;\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n if (!validateArticleId(articleId)) {\n return false;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n","// Next.js Middleware for Paywall\n// Zero-boilerplate integration for protected routes\n\nimport type { SignatureStore } from '../store';\nimport type { SessionData } from '../types';\nimport { validateSession } from '../session';\n\n/**\n * Configuration for paywall middleware\n */\nexport interface PaywallMiddlewareConfig {\n /** Session secret for JWT validation */\n sessionSecret: string;\n /** Protected path patterns (glob-like) */\n protectedPaths: string[];\n /** Cookie name for session token */\n cookieName?: string;\n /** Optional signature store for anti-replay */\n signatureStore?: SignatureStore;\n /** Custom 402 response body */\n custom402Response?: (path: string) => object;\n}\n\n/**\n * Result of middleware check\n */\nexport interface MiddlewareResult {\n /** Whether access is allowed */\n allowed: boolean;\n /** Session data if valid */\n session?: SessionData;\n /** Reason for denial */\n reason?: string;\n /** Should respond with 402 */\n requiresPayment?: boolean;\n}\n\n/**\n * Check if path matches any protected pattern\n */\nfunction matchesProtectedPath(path: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n // Simple glob matching: * matches anything, ** matches path segments\n const regexPattern = pattern\n .replace(/\\*\\*/g, '{{DOUBLE_STAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/{{DOUBLE_STAR}}/g, '.*');\n\n const regex = new RegExp(`^${regexPattern}$`);\n if (regex.test(path)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check paywall access for a request\n * Framework-agnostic core logic\n */\nexport async function checkPaywallAccess(\n path: string,\n sessionToken: string | undefined,\n config: PaywallMiddlewareConfig\n): Promise<MiddlewareResult> {\n // Check if path is protected\n if (!matchesProtectedPath(path, config.protectedPaths)) {\n return { allowed: true };\n }\n\n // No session token\n if (!sessionToken) {\n return {\n allowed: false,\n reason: 'No session token',\n requiresPayment: true,\n };\n }\n\n // Validate session\n const validation = await validateSession(sessionToken, config.sessionSecret);\n\n if (!validation.valid || !validation.session) {\n return {\n allowed: false,\n reason: validation.reason || 'Invalid session',\n requiresPayment: true,\n };\n }\n\n return {\n allowed: true,\n session: validation.session,\n };\n}\n\n/**\n * Create Next.js middleware handler\n * \n * @example\n * ```typescript\n * // middleware.ts\n * import { createPaywallMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';\n * \n * export const middleware = createPaywallMiddleware({\n * sessionSecret: process.env.SESSION_SECRET!,\n * protectedPaths: ['/api/premium/*', '/api/content/*'],\n * });\n * \n * export const config = { matcher: ['/api/premium/:path*', '/api/content/:path*'] };\n * ```\n */\nexport function createPaywallMiddleware(config: PaywallMiddlewareConfig) {\n const { cookieName = 'x402_session' } = config;\n\n return async function middleware(request: Request): Promise<Response | null> {\n const url = new URL(request.url);\n const path = url.pathname;\n\n // Get session from cookie header\n const cookieHeader = request.headers.get('cookie') || '';\n const cookies = Object.fromEntries(\n cookieHeader.split(';').map(c => {\n const [key, ...vals] = c.trim().split('=');\n return [key, vals.join('=')];\n })\n );\n const sessionToken = cookies[cookieName];\n\n const result = await checkPaywallAccess(path, sessionToken, config);\n\n if (!result.allowed && result.requiresPayment) {\n // Return 402 Payment Required\n const body = config.custom402Response\n ? config.custom402Response(path)\n : {\n error: 'Payment Required',\n message: 'This resource requires payment to access',\n path,\n };\n\n return new Response(JSON.stringify(body), {\n status: 402,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n }\n\n // Allow request to continue\n return null;\n };\n}\n\n/**\n * API route wrapper for Next.js App Router\n * \n * @example\n * ```typescript\n * // app/api/premium/route.ts\n * import { withPaywall } from '@alleyboss/micropay-solana-x402-paywall/middleware';\n * \n * async function handler(request: Request, session: SessionData) {\n * return Response.json({ content: 'Premium content', wallet: session.walletAddress });\n * }\n * \n * export const GET = withPaywall(handler, {\n * sessionSecret: process.env.SESSION_SECRET!,\n * articleId: 'article-123', // Optional: check specific article unlock\n * });\n * ```\n */\nexport function withPaywall<T>(\n handler: (request: Request, session: SessionData) => Promise<T>,\n options: {\n sessionSecret: string;\n cookieName?: string;\n articleId?: string;\n }\n): (request: Request) => Promise<Response | T> {\n const { sessionSecret, cookieName = 'x402_session', articleId } = options;\n\n return async function protectedHandler(request: Request): Promise<Response | T> {\n // Extract session token from cookie\n const cookieHeader = request.headers.get('cookie') || '';\n const cookies = Object.fromEntries(\n cookieHeader.split(';').map(c => {\n const [key, ...vals] = c.trim().split('=');\n return [key, vals.join('=')];\n })\n );\n const sessionToken = cookies[cookieName];\n\n if (!sessionToken) {\n return new Response(\n JSON.stringify({ error: 'Payment Required', message: 'No session token' }),\n { status: 402, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Validate session\n const validation = await validateSession(sessionToken, sessionSecret);\n\n if (!validation.valid || !validation.session) {\n return new Response(\n JSON.stringify({ error: 'Payment Required', message: validation.reason }),\n { status: 402, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n // Check article-specific unlock if required\n if (articleId) {\n const { session } = validation;\n const hasAccess = session.siteWideUnlock || session.unlockedArticles.includes(articleId);\n\n if (!hasAccess) {\n return new Response(\n JSON.stringify({ error: 'Payment Required', message: 'Article not unlocked' }),\n { status: 402, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n // Call the actual handler with session\n return handler(request, validation.session);\n };\n}\n","// Express/Node.js Middleware\n// Universal middleware compatible with Express, Fastify, Koa adapters\n\nimport type { SignatureStore } from '../store';\nimport type { SessionData } from '../types';\nimport { validateSession } from '../session';\n\n/**\n * Generic HTTP request/response interfaces for framework compatibility\n * Works with Express, Fastify, raw Node.js http, etc.\n */\nexport interface GenericRequest {\n url?: string;\n path?: string;\n headers: Record<string, string | string[] | undefined> | { get(name: string): string | undefined };\n cookies?: Record<string, string>;\n}\n\nexport interface GenericResponse {\n status?(code: number): GenericResponse;\n statusCode?: number;\n json?(body: object): void;\n send?(body: string): void;\n setHeader?(name: string, value: string): void;\n end?(body?: string): void;\n}\n\nexport type NextFunction = (error?: Error) => void;\n\n/**\n * Express-style middleware configuration\n */\nexport interface ExpressPaywallConfig {\n /** Session secret for JWT validation */\n sessionSecret: string;\n /** Protected path patterns (glob-like) */\n protectedPaths: string[];\n /** Cookie name for session token (default: 'x402_session') */\n cookieName?: string;\n /** Optional signature store */\n signatureStore?: SignatureStore;\n /** Header name for session token (alternative to cookie) */\n headerName?: string;\n /** Custom 402 response */\n onPaymentRequired?: (req: GenericRequest, res: GenericResponse) => void;\n /** Called when access is granted */\n onAccessGranted?: (req: GenericRequest, session: SessionData) => void;\n}\n\n/**\n * Augmented request with session data\n */\nexport interface PaywallRequest extends GenericRequest {\n session?: SessionData;\n}\n\n/**\n * Simple glob pattern matching (supports * and **)\n */\nfunction matchPath(path: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n const regex = pattern\n .replace(/\\*\\*/g, '<<<GLOBSTAR>>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<<GLOBSTAR>>>/g, '.*');\n\n if (new RegExp(`^${regex}$`).test(path)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if headers object has a get method (Fetch/Next.js style)\n */\nfunction hasGetMethod(headers: GenericRequest['headers']): headers is { get(name: string): string | undefined } {\n return typeof (headers as { get?: unknown }).get === 'function';\n}\n\n/**\n * Get header value from either style of headers object\n */\nfunction getHeader(headers: GenericRequest['headers'], name: string): string | undefined {\n if (hasGetMethod(headers)) {\n return headers.get(name) ?? undefined;\n }\n const value = (headers as Record<string, string | string[] | undefined>)[name];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Extract session token from request\n */\nfunction extractToken(req: GenericRequest, config: ExpressPaywallConfig): string | undefined {\n const { cookieName = 'x402_session', headerName } = config;\n\n // Try custom header first (for API clients)\n if (headerName) {\n const token = getHeader(req.headers, headerName.toLowerCase());\n if (token) return token;\n }\n\n // Try Authorization header (Bearer token)\n const authHeader = getHeader(req.headers, 'authorization');\n if (authHeader?.startsWith('Bearer ')) {\n return authHeader.slice(7);\n }\n\n // Try cookies\n if (req.cookies?.[cookieName]) {\n return req.cookies[cookieName];\n }\n\n // Parse cookie header manually if cookies not pre-parsed\n const cookieHeader = getHeader(req.headers, 'cookie');\n if (cookieHeader) {\n const match = cookieHeader.split(';')\n .map(c => c.trim())\n .find(c => c.startsWith(`${cookieName}=`));\n\n if (match) {\n return match.slice(cookieName.length + 1);\n }\n }\n\n return undefined;\n}\n\n/**\n * Create Express-compatible paywall middleware\n * Also works with Connect, Polka, and similar frameworks\n * \n * @example\n * ```typescript\n * import express from 'express';\n * import { createExpressMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';\n * \n * const app = express();\n * \n * app.use('/api/premium', createExpressMiddleware({\n * sessionSecret: process.env.SESSION_SECRET!,\n * protectedPaths: ['/**'],\n * }));\n * \n * app.get('/api/premium/content', (req, res) => {\n * res.json({ content: 'Premium!', wallet: req.session?.walletAddress });\n * });\n * ```\n */\nexport function createExpressMiddleware(config: ExpressPaywallConfig) {\n const { sessionSecret, protectedPaths, onPaymentRequired, onAccessGranted } = config;\n\n return async function paywallMiddleware(\n req: PaywallRequest,\n res: GenericResponse,\n next: NextFunction\n ): Promise<void> {\n const path = req.path || req.url?.split('?')[0] || '/';\n\n // Check if path needs protection\n if (!matchPath(path, protectedPaths)) {\n next();\n return;\n }\n\n // Extract token\n const token = extractToken(req, config);\n\n if (!token) {\n if (onPaymentRequired) {\n onPaymentRequired(req, res);\n } else {\n sendPaymentRequired(res, 'No session token');\n }\n return;\n }\n\n // Validate session\n const validation = await validateSession(token, sessionSecret);\n\n if (!validation.valid || !validation.session) {\n if (onPaymentRequired) {\n onPaymentRequired(req, res);\n } else {\n sendPaymentRequired(res, validation.reason || 'Invalid session');\n }\n return;\n }\n\n // Attach session to request\n req.session = validation.session;\n\n if (onAccessGranted) {\n onAccessGranted(req, validation.session);\n }\n\n next();\n };\n}\n\n/**\n * Send 402 Payment Required response\n */\nfunction sendPaymentRequired(res: GenericResponse, reason: string): void {\n const body = JSON.stringify({\n error: 'Payment Required',\n message: reason,\n });\n\n // Check for Express-style response (has both status and json methods)\n const statusFn = res.status;\n const jsonFn = res.json;\n\n if (statusFn && jsonFn) {\n // Express-style: res.status(402).json(...)\n const chainedRes = statusFn.call(res, 402);\n if (chainedRes?.json) {\n chainedRes.json({ error: 'Payment Required', message: reason });\n }\n } else if (res.statusCode !== undefined && res.setHeader && res.end) {\n // Raw Node.js http\n res.statusCode = 402;\n res.setHeader('Content-Type', 'application/json');\n res.end(body);\n }\n}\n\n/**\n * Fastify-compatible plugin factory\n * \n * @example\n * ```typescript\n * import Fastify from 'fastify';\n * import { createFastifyPlugin } from '@alleyboss/micropay-solana-x402-paywall/middleware';\n * \n * const fastify = Fastify();\n * fastify.register(createFastifyPlugin({\n * sessionSecret: process.env.SESSION_SECRET!,\n * protectedPaths: ['/api/premium/*'],\n * }));\n * ```\n */\nexport function createFastifyPlugin(config: ExpressPaywallConfig) {\n return async function paywallPlugin(fastify: any) {\n fastify.addHook('preHandler', createExpressMiddleware(config));\n };\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { S as SignatureStore } from './memory-Daxkczti.js';
|
|
2
|
+
import { S as SessionData } from './session-D2IoWAWV.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for paywall middleware
|
|
6
|
+
*/
|
|
7
|
+
interface PaywallMiddlewareConfig {
|
|
8
|
+
/** Session secret for JWT validation */
|
|
9
|
+
sessionSecret: string;
|
|
10
|
+
/** Protected path patterns (glob-like) */
|
|
11
|
+
protectedPaths: string[];
|
|
12
|
+
/** Cookie name for session token */
|
|
13
|
+
cookieName?: string;
|
|
14
|
+
/** Optional signature store for anti-replay */
|
|
15
|
+
signatureStore?: SignatureStore;
|
|
16
|
+
/** Custom 402 response body */
|
|
17
|
+
custom402Response?: (path: string) => object;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of middleware check
|
|
21
|
+
*/
|
|
22
|
+
interface MiddlewareResult {
|
|
23
|
+
/** Whether access is allowed */
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
/** Session data if valid */
|
|
26
|
+
session?: SessionData;
|
|
27
|
+
/** Reason for denial */
|
|
28
|
+
reason?: string;
|
|
29
|
+
/** Should respond with 402 */
|
|
30
|
+
requiresPayment?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check paywall access for a request
|
|
34
|
+
* Framework-agnostic core logic
|
|
35
|
+
*/
|
|
36
|
+
declare function checkPaywallAccess(path: string, sessionToken: string | undefined, config: PaywallMiddlewareConfig): Promise<MiddlewareResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Create Next.js middleware handler
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // middleware.ts
|
|
43
|
+
* import { createPaywallMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
44
|
+
*
|
|
45
|
+
* export const middleware = createPaywallMiddleware({
|
|
46
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
47
|
+
* protectedPaths: ['/api/premium/*', '/api/content/*'],
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* export const config = { matcher: ['/api/premium/:path*', '/api/content/:path*'] };
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createPaywallMiddleware(config: PaywallMiddlewareConfig): (request: Request) => Promise<Response | null>;
|
|
54
|
+
/**
|
|
55
|
+
* API route wrapper for Next.js App Router
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // app/api/premium/route.ts
|
|
60
|
+
* import { withPaywall } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
61
|
+
*
|
|
62
|
+
* async function handler(request: Request, session: SessionData) {
|
|
63
|
+
* return Response.json({ content: 'Premium content', wallet: session.walletAddress });
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* export const GET = withPaywall(handler, {
|
|
67
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
68
|
+
* articleId: 'article-123', // Optional: check specific article unlock
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function withPaywall<T>(handler: (request: Request, session: SessionData) => Promise<T>, options: {
|
|
73
|
+
sessionSecret: string;
|
|
74
|
+
cookieName?: string;
|
|
75
|
+
articleId?: string;
|
|
76
|
+
}): (request: Request) => Promise<Response | T>;
|
|
77
|
+
|
|
78
|
+
export { type MiddlewareResult as M, type PaywallMiddlewareConfig as P, checkPaywallAccess as a, createPaywallMiddleware as c, withPaywall as w };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { S as SignatureStore } from './memory-Daxkczti.cjs';
|
|
2
|
+
import { S as SessionData } from './session-D2IoWAWV.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for paywall middleware
|
|
6
|
+
*/
|
|
7
|
+
interface PaywallMiddlewareConfig {
|
|
8
|
+
/** Session secret for JWT validation */
|
|
9
|
+
sessionSecret: string;
|
|
10
|
+
/** Protected path patterns (glob-like) */
|
|
11
|
+
protectedPaths: string[];
|
|
12
|
+
/** Cookie name for session token */
|
|
13
|
+
cookieName?: string;
|
|
14
|
+
/** Optional signature store for anti-replay */
|
|
15
|
+
signatureStore?: SignatureStore;
|
|
16
|
+
/** Custom 402 response body */
|
|
17
|
+
custom402Response?: (path: string) => object;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of middleware check
|
|
21
|
+
*/
|
|
22
|
+
interface MiddlewareResult {
|
|
23
|
+
/** Whether access is allowed */
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
/** Session data if valid */
|
|
26
|
+
session?: SessionData;
|
|
27
|
+
/** Reason for denial */
|
|
28
|
+
reason?: string;
|
|
29
|
+
/** Should respond with 402 */
|
|
30
|
+
requiresPayment?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check paywall access for a request
|
|
34
|
+
* Framework-agnostic core logic
|
|
35
|
+
*/
|
|
36
|
+
declare function checkPaywallAccess(path: string, sessionToken: string | undefined, config: PaywallMiddlewareConfig): Promise<MiddlewareResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Create Next.js middleware handler
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // middleware.ts
|
|
43
|
+
* import { createPaywallMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
44
|
+
*
|
|
45
|
+
* export const middleware = createPaywallMiddleware({
|
|
46
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
47
|
+
* protectedPaths: ['/api/premium/*', '/api/content/*'],
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* export const config = { matcher: ['/api/premium/:path*', '/api/content/:path*'] };
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createPaywallMiddleware(config: PaywallMiddlewareConfig): (request: Request) => Promise<Response | null>;
|
|
54
|
+
/**
|
|
55
|
+
* API route wrapper for Next.js App Router
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // app/api/premium/route.ts
|
|
60
|
+
* import { withPaywall } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
61
|
+
*
|
|
62
|
+
* async function handler(request: Request, session: SessionData) {
|
|
63
|
+
* return Response.json({ content: 'Premium content', wallet: session.walletAddress });
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* export const GET = withPaywall(handler, {
|
|
67
|
+
* sessionSecret: process.env.SESSION_SECRET!,
|
|
68
|
+
* articleId: 'article-123', // Optional: check specific article unlock
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function withPaywall<T>(handler: (request: Request, session: SessionData) => Promise<T>, options: {
|
|
73
|
+
sessionSecret: string;
|
|
74
|
+
cookieName?: string;
|
|
75
|
+
articleId?: string;
|
|
76
|
+
}): (request: Request) => Promise<Response | T>;
|
|
77
|
+
|
|
78
|
+
export { type MiddlewareResult as M, type PaywallMiddlewareConfig as P, checkPaywallAccess as a, createPaywallMiddleware as c, withPaywall as w };
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import { Connection } from '@solana/web3.js';
|
|
2
|
-
|
|
3
1
|
/** x402 network identifiers for Solana */
|
|
4
2
|
type X402Network = 'solana-devnet' | 'solana-mainnet';
|
|
5
3
|
/** Solana network types */
|
|
6
4
|
type SolanaNetwork = 'devnet' | 'mainnet-beta';
|
|
5
|
+
/** SPL Token asset specification */
|
|
6
|
+
interface SPLTokenAsset {
|
|
7
|
+
/** Token mint address */
|
|
8
|
+
mint: string;
|
|
9
|
+
/** Token decimals (default: 6 for USDC/USDT) */
|
|
10
|
+
decimals?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Asset types for payments */
|
|
13
|
+
type PaymentAsset = 'native' | 'usdc' | 'usdt' | SPLTokenAsset;
|
|
14
|
+
/** Known SPL token mint addresses */
|
|
15
|
+
declare const TOKEN_MINTS: {
|
|
16
|
+
/** USDC on mainnet */
|
|
17
|
+
readonly USDC_MAINNET: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
18
|
+
/** USDC on devnet */
|
|
19
|
+
readonly USDC_DEVNET: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
20
|
+
/** USDT on mainnet */
|
|
21
|
+
readonly USDT_MAINNET: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
|
|
22
|
+
};
|
|
7
23
|
/** Payment requirement for x402 protocol */
|
|
8
24
|
interface PaymentRequirement {
|
|
9
25
|
/** Payment scheme - currently only 'exact' is supported */
|
|
10
26
|
scheme: 'exact';
|
|
11
27
|
/** Network identifier for x402 */
|
|
12
28
|
network: X402Network;
|
|
13
|
-
/** Amount in
|
|
29
|
+
/** Amount in smallest unit as string (lamports for SOL, base units for tokens) */
|
|
14
30
|
maxAmountRequired: string;
|
|
15
31
|
/** URL of the protected resource */
|
|
16
32
|
resource: string;
|
|
@@ -22,11 +38,12 @@ interface PaymentRequirement {
|
|
|
22
38
|
payTo: string;
|
|
23
39
|
/** Maximum time in seconds to complete payment */
|
|
24
40
|
maxTimeoutSeconds: number;
|
|
25
|
-
/** Asset type - 'native' for SOL */
|
|
26
|
-
asset:
|
|
41
|
+
/** Asset type - 'native' for SOL, 'usdc', 'usdt', or custom mint */
|
|
42
|
+
asset: PaymentAsset;
|
|
27
43
|
/** Additional metadata */
|
|
28
44
|
extra?: {
|
|
29
45
|
name?: string;
|
|
46
|
+
articleId?: string;
|
|
30
47
|
[key: string]: unknown;
|
|
31
48
|
};
|
|
32
49
|
}
|
|
@@ -81,32 +98,4 @@ interface ArticlePaymentConfig {
|
|
|
81
98
|
description?: string;
|
|
82
99
|
}
|
|
83
100
|
|
|
84
|
-
|
|
85
|
-
interface SolanaClientConfig {
|
|
86
|
-
/** Network to connect to */
|
|
87
|
-
network: SolanaNetwork;
|
|
88
|
-
/** Custom RPC URL (optional) */
|
|
89
|
-
rpcUrl?: string;
|
|
90
|
-
/** Tatum.io API key for RPC (optional) */
|
|
91
|
-
tatumApiKey?: string;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get or create a Solana connection
|
|
95
|
-
* Uses singleton pattern with network-aware caching
|
|
96
|
-
*/
|
|
97
|
-
declare function getConnection(config: SolanaClientConfig): Connection;
|
|
98
|
-
/**
|
|
99
|
-
* Reset the cached connection
|
|
100
|
-
* Useful for testing or network switching
|
|
101
|
-
*/
|
|
102
|
-
declare function resetConnection(): void;
|
|
103
|
-
/**
|
|
104
|
-
* Check if network is mainnet
|
|
105
|
-
*/
|
|
106
|
-
declare function isMainnet(network: SolanaNetwork): boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Convert Solana network to x402 network identifier
|
|
109
|
-
*/
|
|
110
|
-
declare function toX402Network(network: SolanaNetwork): 'solana-devnet' | 'solana-mainnet';
|
|
111
|
-
|
|
112
|
-
export { type ArticlePaymentConfig as A, type PaymentRequirement as P, type SolanaNetwork as S, type VerificationRequest as V, type X402Network as X, type PaymentPayload as a, type VerificationResponse as b, type PaymentStatus as c, type SolanaClientConfig as d, getConnection as g, isMainnet as i, resetConnection as r, toX402Network as t };
|
|
101
|
+
export { type ArticlePaymentConfig as A, type PaymentRequirement as P, type SolanaNetwork as S, TOKEN_MINTS as T, type VerificationRequest as V, type X402Network as X, type PaymentPayload as a, type VerificationResponse as b, type PaymentStatus as c, type PaymentAsset as d, type SPLTokenAsset as e };
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import { Connection } from '@solana/web3.js';
|
|
2
|
-
|
|
3
1
|
/** x402 network identifiers for Solana */
|
|
4
2
|
type X402Network = 'solana-devnet' | 'solana-mainnet';
|
|
5
3
|
/** Solana network types */
|
|
6
4
|
type SolanaNetwork = 'devnet' | 'mainnet-beta';
|
|
5
|
+
/** SPL Token asset specification */
|
|
6
|
+
interface SPLTokenAsset {
|
|
7
|
+
/** Token mint address */
|
|
8
|
+
mint: string;
|
|
9
|
+
/** Token decimals (default: 6 for USDC/USDT) */
|
|
10
|
+
decimals?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Asset types for payments */
|
|
13
|
+
type PaymentAsset = 'native' | 'usdc' | 'usdt' | SPLTokenAsset;
|
|
14
|
+
/** Known SPL token mint addresses */
|
|
15
|
+
declare const TOKEN_MINTS: {
|
|
16
|
+
/** USDC on mainnet */
|
|
17
|
+
readonly USDC_MAINNET: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
18
|
+
/** USDC on devnet */
|
|
19
|
+
readonly USDC_DEVNET: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
20
|
+
/** USDT on mainnet */
|
|
21
|
+
readonly USDT_MAINNET: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
|
|
22
|
+
};
|
|
7
23
|
/** Payment requirement for x402 protocol */
|
|
8
24
|
interface PaymentRequirement {
|
|
9
25
|
/** Payment scheme - currently only 'exact' is supported */
|
|
10
26
|
scheme: 'exact';
|
|
11
27
|
/** Network identifier for x402 */
|
|
12
28
|
network: X402Network;
|
|
13
|
-
/** Amount in
|
|
29
|
+
/** Amount in smallest unit as string (lamports for SOL, base units for tokens) */
|
|
14
30
|
maxAmountRequired: string;
|
|
15
31
|
/** URL of the protected resource */
|
|
16
32
|
resource: string;
|
|
@@ -22,11 +38,12 @@ interface PaymentRequirement {
|
|
|
22
38
|
payTo: string;
|
|
23
39
|
/** Maximum time in seconds to complete payment */
|
|
24
40
|
maxTimeoutSeconds: number;
|
|
25
|
-
/** Asset type - 'native' for SOL */
|
|
26
|
-
asset:
|
|
41
|
+
/** Asset type - 'native' for SOL, 'usdc', 'usdt', or custom mint */
|
|
42
|
+
asset: PaymentAsset;
|
|
27
43
|
/** Additional metadata */
|
|
28
44
|
extra?: {
|
|
29
45
|
name?: string;
|
|
46
|
+
articleId?: string;
|
|
30
47
|
[key: string]: unknown;
|
|
31
48
|
};
|
|
32
49
|
}
|
|
@@ -81,32 +98,4 @@ interface ArticlePaymentConfig {
|
|
|
81
98
|
description?: string;
|
|
82
99
|
}
|
|
83
100
|
|
|
84
|
-
|
|
85
|
-
interface SolanaClientConfig {
|
|
86
|
-
/** Network to connect to */
|
|
87
|
-
network: SolanaNetwork;
|
|
88
|
-
/** Custom RPC URL (optional) */
|
|
89
|
-
rpcUrl?: string;
|
|
90
|
-
/** Tatum.io API key for RPC (optional) */
|
|
91
|
-
tatumApiKey?: string;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get or create a Solana connection
|
|
95
|
-
* Uses singleton pattern with network-aware caching
|
|
96
|
-
*/
|
|
97
|
-
declare function getConnection(config: SolanaClientConfig): Connection;
|
|
98
|
-
/**
|
|
99
|
-
* Reset the cached connection
|
|
100
|
-
* Useful for testing or network switching
|
|
101
|
-
*/
|
|
102
|
-
declare function resetConnection(): void;
|
|
103
|
-
/**
|
|
104
|
-
* Check if network is mainnet
|
|
105
|
-
*/
|
|
106
|
-
declare function isMainnet(network: SolanaNetwork): boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Convert Solana network to x402 network identifier
|
|
109
|
-
*/
|
|
110
|
-
declare function toX402Network(network: SolanaNetwork): 'solana-devnet' | 'solana-mainnet';
|
|
111
|
-
|
|
112
|
-
export { type ArticlePaymentConfig as A, type PaymentRequirement as P, type SolanaNetwork as S, type VerificationRequest as V, type X402Network as X, type PaymentPayload as a, type VerificationResponse as b, type PaymentStatus as c, type SolanaClientConfig as d, getConnection as g, isMainnet as i, resetConnection as r, toX402Network as t };
|
|
101
|
+
export { type ArticlePaymentConfig as A, type PaymentRequirement as P, type SolanaNetwork as S, TOKEN_MINTS as T, type VerificationRequest as V, type X402Network as X, type PaymentPayload as a, type VerificationResponse as b, type PaymentStatus as c, type PaymentAsset as d, type SPLTokenAsset as e };
|