@solinkify/gate
v0.5.0
Published
Creator middleware (Next.js, Express, Cloudflare Workers) — blocks AI scrapers with HTTP 402 paywall, monetizes ethical AI agents on Solana.
Maintainers
Readme
@solinkify/gate
A toll booth between AI scrapers and your content. Add one middleware — mainstream AI crawlers (ChatGPT / Claude / Perplexity) get an x402 v2 paywall (CAIP-2 networks, PAYMENT-REQUIRED wire header), while ethical AI agents auto-pay USDC on Solana to get in. Agentic payments for any stack.
- 🛡️ AI bot detection via User-Agent (OpenAI, Anthropic, Perplexity, Google, and more)
- 💸 Stablecoin (USDC) payments into an on-chain escrow — 99% goes to the creator
- ⚡ Adapters for Next.js, Express/Node, Cloudflare Workers, Astro, SvelteKit, Hono, Nuxt, Remix, Fastify, Lambda@Edge, and any Fetch runtime (one core, many frameworks)
Install
npm install @solinkify/gate
# or: pnpm add @solinkify/gatePeer dependency: Next.js ≥ 14 (optional — only for the Next.js adapter; Express & Workers don't need Next).
1. Register your endpoint (once)
Open solinkify.com/gate/setup → connect a wallet → register an endpoint (you get an endpointId + a price per request). This is what binds payments to your content on-chain.
2. Add the middleware (1-paste)
Create middleware.ts at the root of your Next.js project:
import { protectFromAI } from '@solinkify/gate';
export const middleware = protectFromAI({
wallet: 'YOUR_CREATOR_WALLET', // Solana wallet that receives 99%
price: 0.001, // price per request (USDC)
endpointId: 'my-blog', // from step 1
});
export const config = {
matcher: '/:path*', // run on every route (scope with protectedPaths)
};Done. Requests from AI scrapers → 402 + paywall message; regular visitors & browsers → pass through untouched.
Configuration (GateConfig)
| Option | Required | Default | Description |
|---|---|---|---|
| wallet | ✅ | — | Creator's Solana wallet (receives 99%) |
| price | ✅ | — | Price per request (token units, e.g. 0.001 USDC) |
| endpointId | ⚠️ | '' | On-chain endpoint id (step 1) — required so payments bind to your endpoint |
| tokenMint | | USDC mainnet | Settlement SPL mint (override for devnet) |
| apiUrl | | https://api.solinkify.com | Verification API |
| protectedPaths | | ['/*'] | Paths to protect (glob: /articles/*) |
| excludePaths | | [] | Paths always let through |
| detection | | 'basic' | 'basic' (block AI, SEO-safe) · 'strict' (also block search engines) · 'strict+' (aggressive: inspect browser UAs for headless/scraper signals) |
| blockDatacenterIps | | false | strict+ only: block browser-UA requests from datacenter IPs (AWS/GCP/…). Opt-in (may hit legitimate VPN users). |
| allowBots | | [] | Bot whitelist (e.g. ['Googlebot']) to protect SEO |
| verifyBotIps | | true | Verify the client IP against the bot's official published ranges (Google/Bing). A "Googlebot" from a fake IP → blocked (closes the spoofing hole). Needs the adapter to supply an IP; when unverifiable → normal behaviour (safe). |
| trustProxy | | true | Trust x-forwarded-for/x-real-ip as the client IP. Correct behind Vercel/Cloudflare/nginx (which OVERWRITE the header). ⚠️ Set false if your app takes connections STRAIGHT from the internet — a direct client can forge XFF to bypass verify-IP/rate-limit/datacenter checks; with false only the socket IP is used. |
| customMessage | | — | Custom paywall message |
Devnet vs mainnet (switch-ready)
Default = mainnet USDC. To test on devnet, override:
protectFromAI({
wallet: 'YOUR_WALLET',
price: 0.001,
endpointId: 'my-blog',
tokenMint: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // USDC devnet
});Express / Node
import express from 'express';
import { protectFromAIExpress } from '@solinkify/gate/express';
const app = express();
app.use(protectFromAIExpress({
wallet: 'YOUR_CREATOR_WALLET',
price: 0.001,
endpointId: 'my-blog',
}));
app.get('/articles/:id', (req, res) => {
// req.solinkify = { verified, botName } is set for agents that paid
res.send('Protected content');
});Same GateConfig as Next.js. This adapter is zero-dependency (duck-typed req/res) — compatible with Express & Connect, no @types/express needed. If the middleware itself hits an unexpected error, the request fails open (passes) so your site never goes down because of the gate.
Astro / SvelteKit / Hono
// Astro — src/middleware.ts
import { createAstroGate } from '@solinkify/gate/astro';
export const onRequest = createAstroGate({ wallet, endpointId, price: 0.001 });
// SvelteKit — src/hooks.server.ts
import { createSvelteKitGate } from '@solinkify/gate/sveltekit';
export const handle = createSvelteKitGate({ wallet, endpointId, price: 0.001 });
// Hono (Workers/Deno/Bun/Node/edge)
import { createHonoGate } from '@solinkify/gate/hono';
app.use('/articles/*', createHonoGate({ wallet, endpointId, price: 0.001 }));Nuxt / Remix
// Nuxt (Nitro) — server/middleware/solinkify.ts
import { createNuxtGate } from '@solinkify/gate/nuxt';
export default createNuxtGate({ wallet, endpointId, price: 0.001 });
// Remix / React Router — app/root.tsx
import { createRemixGate } from '@solinkify/gate/remix';
const gate = createRemixGate({ wallet, endpointId, price: 0.001 });
export async function loader({ request }) {
await gate(request); // throws a 402 Response for unpaid AI scrapers
return json({ /* ... */ });
}Fastify
import Fastify from 'fastify';
import { createFastifyGate } from '@solinkify/gate/fastify';
const app = Fastify();
app.addHook('onRequest', createFastifyGate({ wallet, endpointId, price: 0.001 }));Any other Fetch runtime (Deno / Bun / edge) — guardFetch
Any handler with a Web Request can gate in ~5 lines:
import { resolveConfig } from '@solinkify/gate/core';
import { guardFetch } from '@solinkify/gate/fetch';
const gate = resolveConfig({ wallet, endpointId, price: 0.001 });
// Deno
Deno.serve((req) => guardFetch(req, gate, () => new Response('protected content')));
// Bun
Bun.serve({ fetch: (req) => guardFetch(req, gate, () => new Response('protected content')) });
// Vercel Edge / Netlify Edge Functions / Fastly Compute (JS) — same signature:
export default (req: Request) => guardFetch(req, gate, () => fetch(req));AWS CloudFront (Lambda@Edge)
Gate ANY origin (S3 static site, ALB, legacy server) without touching its code — attach to the viewer-request event:
// index.mjs — Lambda@Edge (region us-east-1)
import { createLambdaEdgeGate } from '@solinkify/gate/lambda-edge';
export const handler = createLambdaEdgeGate({ wallet, endpointId, price: 0.001 });Cloudflare Workers
import { createWorkerHandler } from '@solinkify/gate/worker';
export default { fetch: createWorkerHandler() };Configure via environment variables (SOLINKIFY_WALLET, SOLINKIFY_PRICE, SOLINKIFY_ENDPOINT_ID, …). See src/worker.ts.
Build your own adapter (framework-agnostic core)
Every adapter is built on one dependency-free core:
import { resolveConfig, evaluateGate } from '@solinkify/gate/core';
const cfg = resolveConfig({ wallet, price: 0.001, endpointId: 'my-blog' });
const result = await evaluateGate(
{ pathname, userAgent, paymentId, payerPubkey },
cfg,
);
// result.action === 'block' → send 402 (result.manifest + result.headers)
// result.action === 'next' → pass through (result.verified carries metadata)Tiered pricing & multi-stablecoin
Different prices per path (each tier binds to its own on-chain endpoint) + pick your stablecoin:
import { protectFromAI } from '@solinkify/gate';
import { stablecoinMint } from '@solinkify/gate';
export const middleware = protectFromAI({
wallet: 'YOUR_WALLET',
endpointId: 'standard', // default for other paths
price: 0.001,
tokenMint: stablecoinMint('USDC'), // or 'USDT'
protectedPaths: ['/articles/*', '/premium/*'],
tiers: [
{ pattern: '/premium/*', endpointId: 'premium', price: 0.01 }, // premium costs more
],
});The first matching tier wins; no match → the top-level endpointId/price/tokenMint apply. Agents pay — and are verified — against that same tier's endpoint + price. Register each endpointId on-chain (one per tier). stablecoinMint(symbol, 'devnet'|'mainnet') saves you memorizing mint addresses.
Anti-abuse layers 4–5 (opt-in): JS challenge (PoW) + rate limiting
For scrapers whose UA is honest but NOT a known bot (curl, python, HTTP libraries) and for bulk scraping:
protectFromAI({
wallet, endpointId, price: 0.001,
challenge: true, // Layer 4: JS proof-of-work interstitial
challengeSecret: process.env.GATE_CHALLENGE_SECRET, // REQUIRED for multi-instance
rateLimit: { max: 120, windowSecs: 60 }, // Layer 5: per-IP rate anomaly
});challenge— all non-bot traffic must pass a PoW page once per hour (HMAC clearance cookie, bound to IP + expiry). Real browsers clear it in ±1 second; non-browsers never do. Known AI bots STILL get the 402 (they're the monetization target); robots.txt/sitemap/discovery are never challenged. ⚠️ Aggressive — humans see a brief splash on first visit.rateLimit— above the threshold → 402 paywall. Fails open: a rate backend outage never blocks humans.
Pre-paid balance & subscriptions (no per-request transaction)
Besides pay-per-request, agents get two access modes that need no transaction per request — both work automatically, zero extra middleware config:
- Pre-paid — the agent deposits stablecoin once into its own on-chain
balance, then sends
x-solinkify-payer+x-solinkify-prepaidheaders. The Solinkify backend debits exactly your endpoint'spriceper request (fail-closed, on-chain split, 99% still yours) — it shows up in your dashboard like any payment. - Subscription — you attach a plan per endpoint (price × duration) from the
dashboard → Manage Endpoints
panel. The agent pays once (
subscribe, 99% straight to you) then accesses with thex-solinkify-subscriptionheader until the plan expires.
The 402 manifest and /.well-known/solinkify advertise both via the
access_modes field, so ethical agents discover them on their own. Agent
side: see @solinkify/gate-sdk
(depositPrepaid, subscribe, and a GateClient that automatically uses the
prepaid balance when it covers the price).
Edit prices, enable/disable endpoints, and manage subscription plans — all from the Manage Endpoints panel, no CLI required.
Delivery records (opt-in): prove what was served
x402 proves a payment happened and says nothing about what came back. Turn this on and the gate hashes the body it served for a paid request and files that hash against the payment id. The paying agent files its own hash of what it received, and anyone holding the payment id can ask whether the two agree.
protectFromAI({
wallet: 'YOUR_WALLET',
price: 0.001,
deliveryRecords: true, // off by default
deliveryMaxBytes: 8 * 1024 * 1024, // skip bodies larger than this
});Paid responses come back with x-solinkify-content-sha256, and the 402 manifest
and /.well-known/solinkify both advertise delivery_records: true so agents
know to expect one. Read the result:
curl https://api.solinkify.com/api/gate/delivery/<payment_id>
# → { creator: {...}, agent: {...}, match: true }What it proves. That neither side can serve one thing and later claim
another. match: false is evidence; match: null means only one side filed.
It is NOT proof the content was correct: a creator that serves garbage can
record the hash of that garbage.
Cost. Hashing needs the whole body, so responses are buffered up to
deliveryMaxBytes (larger ones stream as usual and are skipped). Filing is
fire-and-forget and never delays or fails the response.
Where it works. Adapters that can see the response body: guardFetch,
Astro, SvelteKit, Hono, Cloudflare Workers, Express, Fastify, Python
(WSGI + ASGI), and the reverse proxy. Next.js middleware, Nuxt, Lambda@Edge and
the WordPress plugin never touch the body, so the flag is ignored there and only
the agent side can be filed.
SEO-safe by default (+ robots.txt)
The two-tier detector protects your SEO:
- All AI gets blocked (402): GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, Gemini/Vertex, Meta AI, CCBot, Bytespider, DeepSeek, and more — plus AI-search & agent tooling (Firecrawl, etc.).
- Traditional search engines always pass (default
basicmode): Googlebot, bingbot, Baidu, Yandex, PetalBot → ranking & indexing untouched. /robots.txt&/sitemap*.xmlare always crawlable (never 402'd).
⚠️
detection: 'strict'also blocks Googlebot/bingbot → hurts SEO. The SDK emits aconsole.warn; if you truly need strict, whitelist search engines:allowBots: ['Googlebot', 'Bingbot'].
Google/Apple AI can't be blocked via User-Agent (Googlebot & Applebot serve search + AI from the same UA). Opt out of their AI training via robots.txt (Google-Extended, Applebot-Extended) — without hurting search. The generator is included:
// app/robots.ts (Next.js)
import { generateRobotsTxt } from '@solinkify/gate/robots';
export function GET() {
return new Response(
generateRobotsTxt({ sitemap: 'https://example.com/sitemap.xml' }),
{ headers: { 'Content-Type': 'text/plain' } },
);
}Result: every robots.txt-respecting AI crawler gets Disallow, while User-agent: * stays Allow: /. Layered defense = the 402 gate (UA) + robots.txt (declarative).
How it works (the 402 flow)
- An AI scraper sends a request → the middleware detects the bot User-Agent.
- It answers HTTP 402 + a payment manifest (
escrow_address,payment_id,endpoint_id,token_mint). - Mainstream AI shows a "content is protected" message (it can't pay — intended).
- An ethical AI agent (using
@solinkify/gate-sdk) → auto-pays into escrow → retries with proof → gets the content. Agents with a pre-paid balance / active subscription skip the payment step entirely (see above). - Escrow release: 99% creator, the rest is the protocol fee (enforced on-chain).
Track earnings
Real-time at solinkify.com/gate/earnings.
License
MIT © Solinkify
