nanocrawl-next 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.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * nanocrawl-next — one-line x402 paywall for Next.js.
3
+ *
4
+ * Usage:
5
+ * // middleware.ts
6
+ * import { withNanocrawl } from 'nanocrawl-next'
7
+ * export const middleware = withNanocrawl({
8
+ * sellerWallet: process.env.NANOCRAWL_SELLER_WALLET,
9
+ * priceUsdc: 0.001,
10
+ * })
11
+ * export const config = { matcher: ['/products/:path*'] }
12
+ */
13
+ import { NextRequest, NextResponse } from "next/server";
14
+ export interface NanocrawlConfig {
15
+ /** Your EVM wallet address to receive payments */
16
+ sellerWallet: string;
17
+ /** Price per page in USDC (default: 0.001) */
18
+ priceUsdc?: number;
19
+ /** Routes that are always free, even for crawlers */
20
+ freeRoutes?: string[];
21
+ /** Path to your verify handler (default: /api/nanocrawl-verify) */
22
+ verifyPath?: string;
23
+ }
24
+ /**
25
+ * Create a Next.js middleware that gates content for AI crawlers.
26
+ * Humans pass through with zero friction. Crawlers get a 402 or pay.
27
+ */
28
+ export declare function withNanocrawl(userConfig: NanocrawlConfig): (request: NextRequest) => NextResponse;
29
+ export type { NextRequest, NextResponse };
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * nanocrawl-next — one-line x402 paywall for Next.js.
3
+ *
4
+ * Usage:
5
+ * // middleware.ts
6
+ * import { withNanocrawl } from 'nanocrawl-next'
7
+ * export const middleware = withNanocrawl({
8
+ * sellerWallet: process.env.NANOCRAWL_SELLER_WALLET,
9
+ * priceUsdc: 0.001,
10
+ * })
11
+ * export const config = { matcher: ['/products/:path*'] }
12
+ */
13
+ import { NextResponse } from "next/server";
14
+ // Arc Testnet constants
15
+ const ARC = {
16
+ caip2: "eip155:5042002",
17
+ usdc: "0x3600000000000000000000000000000000000000",
18
+ gatewayWallet: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
19
+ };
20
+ const BOT_PATTERN = /bot|crawl|spider|claude|gpt|anthropic|openai|perplexity|cohere|bytespider|ccbot|chatgpt|fetch|curl|wget|httpx|python-requests|python-urllib|node-fetch|axios|undici|go-http-client|scrapy|httpclient|java\/|libwww|mechanize|phantom|headless|puppeteer|playwright|selenium/i;
21
+ function isCrawler(request) {
22
+ if (request.headers.get("payment-signature"))
23
+ return true;
24
+ if (request.headers.get("x-nanocrawl-capable"))
25
+ return true;
26
+ const ua = request.headers.get("user-agent") ?? "";
27
+ if (BOT_PATTERN.test(ua))
28
+ return true;
29
+ const hasAcceptLang = request.headers.get("accept-language");
30
+ const hasSecFetch = request.headers.get("sec-fetch-dest");
31
+ if (!hasAcceptLang && !hasSecFetch)
32
+ return true;
33
+ if (ua.length > 0 && ua.length < 20)
34
+ return true;
35
+ if (ua.length === 0)
36
+ return true;
37
+ return false;
38
+ }
39
+ function build402(request, config) {
40
+ const amount = Math.round(config.priceUsdc * 1_000_000).toString();
41
+ const paymentRequired = {
42
+ x402Version: 2,
43
+ resource: {
44
+ url: request.url,
45
+ mimeType: "application/json",
46
+ description: "NanoCrawl paid content",
47
+ },
48
+ accepts: [
49
+ {
50
+ scheme: "exact",
51
+ network: ARC.caip2,
52
+ asset: ARC.usdc,
53
+ amount,
54
+ payTo: config.sellerWallet,
55
+ maxTimeoutSeconds: 345600,
56
+ extra: {
57
+ name: "GatewayWalletBatched",
58
+ version: "1",
59
+ verifyingContract: ARC.gatewayWallet,
60
+ },
61
+ },
62
+ ],
63
+ };
64
+ const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
65
+ return new NextResponse(JSON.stringify(paymentRequired, null, 2), {
66
+ status: 402,
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "PAYMENT-REQUIRED": encoded,
70
+ },
71
+ });
72
+ }
73
+ /**
74
+ * Create a Next.js middleware that gates content for AI crawlers.
75
+ * Humans pass through with zero friction. Crawlers get a 402 or pay.
76
+ */
77
+ export function withNanocrawl(userConfig) {
78
+ const config = {
79
+ priceUsdc: 0.001,
80
+ freeRoutes: [],
81
+ verifyPath: "/api/nanocrawl-verify",
82
+ ...userConfig,
83
+ };
84
+ return function middleware(request) {
85
+ const path = request.nextUrl.pathname;
86
+ // Free routes pass through
87
+ if (config.freeRoutes.some((r) => path === r || path.startsWith(r + "/"))) {
88
+ return NextResponse.next();
89
+ }
90
+ // Humans pass through
91
+ if (!isCrawler(request)) {
92
+ return NextResponse.next();
93
+ }
94
+ // Crawler with payment → route to verification
95
+ if (request.headers.get("payment-signature")) {
96
+ const verifyUrl = new URL(config.verifyPath, request.url);
97
+ verifyUrl.searchParams.set("resource", path);
98
+ return NextResponse.rewrite(verifyUrl);
99
+ }
100
+ // Crawler without payment → 402
101
+ return build402(request, config);
102
+ };
103
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * nanocrawl-next/verify — payment verification handler.
3
+ *
4
+ * Usage:
5
+ * // app/api/nanocrawl-verify/route.ts
6
+ * import { createVerifyHandler } from 'nanocrawl-next/verify'
7
+ * export const GET = createVerifyHandler({
8
+ * sellerWallet: process.env.NANOCRAWL_SELLER_WALLET,
9
+ * priceUsdc: 0.001,
10
+ * getContent: async (path) => {
11
+ * // Return the content for the given path as JSON
12
+ * return { title: 'My Article', body: '...' }
13
+ * },
14
+ * })
15
+ */
16
+ import { NextRequest, NextResponse } from "next/server";
17
+ export interface VerifyConfig {
18
+ /** Your EVM wallet address */
19
+ sellerWallet: string;
20
+ /** Price per page in USDC (default: 0.001) */
21
+ priceUsdc?: number;
22
+ /**
23
+ * Return content for a given path. If not provided,
24
+ * the handler returns a generic success response.
25
+ */
26
+ getContent?: (path: string) => Promise<unknown>;
27
+ }
28
+ /**
29
+ * Create a Next.js route handler that verifies x402 payments
30
+ * via Circle Gateway and serves content.
31
+ */
32
+ export declare function createVerifyHandler(userConfig: VerifyConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
package/dist/verify.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * nanocrawl-next/verify — payment verification handler.
3
+ *
4
+ * Usage:
5
+ * // app/api/nanocrawl-verify/route.ts
6
+ * import { createVerifyHandler } from 'nanocrawl-next/verify'
7
+ * export const GET = createVerifyHandler({
8
+ * sellerWallet: process.env.NANOCRAWL_SELLER_WALLET,
9
+ * priceUsdc: 0.001,
10
+ * getContent: async (path) => {
11
+ * // Return the content for the given path as JSON
12
+ * return { title: 'My Article', body: '...' }
13
+ * },
14
+ * })
15
+ */
16
+ import { NextResponse } from "next/server";
17
+ // @ts-ignore
18
+ import { BatchFacilitatorClient } from "@circle-fin/x402-batching/server";
19
+ const ARC = {
20
+ caip2: "eip155:5042002",
21
+ usdc: "0x3600000000000000000000000000000000000000",
22
+ gatewayWallet: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
23
+ };
24
+ const facilitator = new BatchFacilitatorClient({
25
+ url: "https://gateway-api-testnet.circle.com",
26
+ });
27
+ /**
28
+ * Create a Next.js route handler that verifies x402 payments
29
+ * via Circle Gateway and serves content.
30
+ */
31
+ export function createVerifyHandler(userConfig) {
32
+ const config = { priceUsdc: 0.001, ...userConfig };
33
+ return async function GET(request) {
34
+ const paymentSigHeader = request.headers.get("payment-signature");
35
+ if (!paymentSigHeader) {
36
+ return NextResponse.json({ error: "Missing PAYMENT-SIGNATURE header" }, { status: 402 });
37
+ }
38
+ let paymentPayload;
39
+ try {
40
+ paymentPayload = JSON.parse(Buffer.from(paymentSigHeader, "base64").toString("utf-8"));
41
+ }
42
+ catch {
43
+ return NextResponse.json({ error: "Invalid PAYMENT-SIGNATURE encoding" }, { status: 400 });
44
+ }
45
+ const amount = Math.round(config.priceUsdc * 1_000_000).toString();
46
+ const requirements = {
47
+ scheme: "exact",
48
+ network: ARC.caip2,
49
+ asset: ARC.usdc,
50
+ amount,
51
+ payTo: config.sellerWallet,
52
+ maxTimeoutSeconds: 345600,
53
+ extra: {
54
+ name: "GatewayWalletBatched",
55
+ version: "1",
56
+ verifyingContract: ARC.gatewayWallet,
57
+ },
58
+ };
59
+ try {
60
+ const settlement = (await facilitator.settle(paymentPayload, requirements));
61
+ if (!settlement.success) {
62
+ return NextResponse.json({ error: "Payment settlement failed", reason: settlement.errorReason }, { status: 402 });
63
+ }
64
+ const auth = paymentPayload?.payload?.authorization;
65
+ const paymentResponse = {
66
+ success: true,
67
+ transaction: settlement.transaction,
68
+ network: ARC.caip2,
69
+ payer: auth?.from,
70
+ };
71
+ const encoded = Buffer.from(JSON.stringify(paymentResponse)).toString("base64");
72
+ // Serve content
73
+ const resourcePath = request.nextUrl.searchParams.get("resource") ?? "/";
74
+ const content = config.getContent
75
+ ? await config.getContent(resourcePath)
76
+ : { paid: true, path: resourcePath, transaction: settlement.transaction };
77
+ return NextResponse.json(content, {
78
+ headers: { "PAYMENT-RESPONSE": encoded },
79
+ });
80
+ }
81
+ catch (err) {
82
+ const msg = err instanceof Error ? err.message : String(err);
83
+ return NextResponse.json({ error: "Settlement error", message: msg }, { status: 500 });
84
+ }
85
+ };
86
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "nanocrawl-next",
3
+ "version": "0.1.0",
4
+ "description": "One-line x402 paywall for Next.js — AI crawlers pay per page, humans browse free",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./verify": "./dist/verify.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@circle-fin/x402-batching": "^2.0.4",
19
+ "@x402/core": "^2.3.0",
20
+ "@x402/evm": "^2.9.0",
21
+ "viem": "^2.21.0"
22
+ },
23
+ "peerDependencies": {
24
+ "next": ">=14.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.0.0",
28
+ "@types/react": "^19.2.14",
29
+ "next": "^16.2.2",
30
+ "react": "^19.2.4",
31
+ "react-dom": "^19.2.4",
32
+ "typescript": "^5.7.0"
33
+ }
34
+ }