trustsource 0.2.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/src/server.ts ADDED
@@ -0,0 +1,318 @@
1
+ import "dotenv/config";
2
+ import express from "express";
3
+ import cors from "cors";
4
+ import rateLimit from "express-rate-limit";
5
+ import { paymentMiddleware, x402ResourceServer } from "@x402/express";
6
+ import { registerExactEvmScheme } from "@x402/evm/exact/server";
7
+ import { HTTPFacilitatorClient } from "@x402/core/server";
8
+ import { createFacilitatorConfig } from "@coinbase/x402";
9
+ import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
10
+ import trustscoreRouter from "./routes/trustscore.js";
11
+ import sslcheckRouter from "./routes/sslcheck.js";
12
+ import headersRouter from "./routes/headers.js";
13
+ import robotsRouter from "./routes/robots.js";
14
+ import openApiRouter from "./openapi.js";
15
+ import path from "path";
16
+ import { fileURLToPath } from "url";
17
+
18
+ // ─── Path helpers ─────────────────────────────────────────────────────────────
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // ─── Config ───────────────────────────────────────────────────────────────────
24
+
25
+ const PORT = process.env.PORT || 3000;
26
+ const PAY_TO = process.env.PAY_TO_ADDRESS as `0x${string}`;
27
+ const NETWORK = (process.env.NETWORK || "eip155:84532") as `${string}:${string}`;
28
+ const FACILITATOR = process.env.FACILITATOR_URL || "https://x402.org/facilitator";
29
+ const IS_MAINNET = NETWORK === "eip155:8453";
30
+
31
+ const PAID_PATHS = new Set(["/trustscore", "/sslcheck", "/headers", "/robots"]);
32
+
33
+ if (!PAY_TO || !PAY_TO.startsWith("0x")) {
34
+ console.error("❌ PAY_TO_ADDRESS is missing or invalid in .env");
35
+ process.exit(1);
36
+ }
37
+
38
+ // ─── x402 Setup ───────────────────────────────────────────────────────────────
39
+
40
+ const facilitatorClient = IS_MAINNET && process.env.CDP_API_KEY_ID
41
+ ? new HTTPFacilitatorClient(
42
+ createFacilitatorConfig(
43
+ process.env.CDP_API_KEY_ID,
44
+ process.env.CDP_API_KEY_SECRET!
45
+ )
46
+ )
47
+ : new HTTPFacilitatorClient({ url: FACILITATOR });
48
+
49
+ const resourceServer = new x402ResourceServer(facilitatorClient);
50
+ registerExactEvmScheme(resourceServer);
51
+
52
+ // ─── App ──────────────────────────────────────────────────────────────────────
53
+
54
+ const app = express();
55
+ app.use(cors());
56
+ app.use(express.json());
57
+
58
+ // Trust Railway's single proxy hop — needed for req.protocol to detect HTTPS
59
+ // and for x402 to build the resource URL correctly in 402 responses.
60
+ app.set("trust proxy", 1);
61
+
62
+ // Rate limiter — uses a custom keyGenerator so we don't depend on trust proxy
63
+ // for IP detection (more secure than blanket trusting forwarded headers).
64
+ const limiter = rateLimit({
65
+ windowMs: 60 * 1000,
66
+ max: 60,
67
+ standardHeaders: true,
68
+ legacyHeaders: false,
69
+ keyGenerator: (req) => {
70
+ return (req.headers["cf-connecting-ip"] as string) ||
71
+ (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() ||
72
+ req.ip ||
73
+ "unknown";
74
+ },
75
+ validate: {
76
+ trustProxy: false, // we set it intentionally to 1
77
+ keyGeneratorIpFallback: false, // we use our own IP detection
78
+ },
79
+ });
80
+
81
+ // Safety net: ensure 402 responses always carry the PAYMENT-REQUIRED header.
82
+ // @x402/express in some configurations puts the v2 payload in the body only —
83
+ // this catches that case so Bazaar discovery validation passes.
84
+ app.use((_req, res, next) => {
85
+ const originalJson = res.json.bind(res);
86
+ res.json = (body: unknown) => {
87
+ if (res.statusCode === 402 &&
88
+ body && typeof body === "object" &&
89
+ (body as { x402Version?: number }).x402Version === 2 &&
90
+ !res.getHeader("PAYMENT-REQUIRED")) {
91
+ const encoded = Buffer.from(JSON.stringify(body)).toString("base64");
92
+ res.setHeader("PAYMENT-REQUIRED", encoded);
93
+ }
94
+ return originalJson(body);
95
+ };
96
+ next();
97
+ });
98
+
99
+ // ─── Settlement observability ────────────────────────────────────────────────
100
+ // Emits a structured JSON log on every request to a paid route, with the final
101
+ // status code so you can distinguish 402-issued from 200-settled. Greppable in
102
+ // Railway logs:
103
+ // { "evt": "request" ... "status": 200 } ← successful settlement
104
+ // { "evt": "request" ... "status": 402 } ← 402 issued, client never retried
105
+ // { "evt": "request" ... "status": 429 } ← rate-limited
106
+ // Filter your own test IPs with: grep -v '"ip":"YOUR_TEST_IP"'
107
+ app.use((req, res, next) => {
108
+ if (!PAID_PATHS.has(req.path)) return next();
109
+ const startedAt = Date.now();
110
+ res.on("finish", () => {
111
+ const ip =
112
+ (req.headers["cf-connecting-ip"] as string) ||
113
+ (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() ||
114
+ req.ip ||
115
+ "unknown";
116
+ const log = {
117
+ evt: "request",
118
+ path: req.path,
119
+ query: req.query,
120
+ status: res.statusCode,
121
+ settled: res.statusCode === 200,
122
+ ip,
123
+ ua: (req.headers["user-agent"] as string)?.slice(0, 120) ?? null,
124
+ durationMs: Date.now() - startedAt,
125
+ ts: new Date().toISOString(),
126
+ };
127
+ console.log(JSON.stringify(log));
128
+ });
129
+ next();
130
+ });
131
+
132
+ // ─── Free routes ─────────────────────────────────────────────────────────────
133
+
134
+ app.get("/", (req, res) => {
135
+ const wantHtml = req.headers.accept?.includes("text/html") ?? false;
136
+ if (wantHtml) {
137
+ res.sendFile(path.resolve("public/index.html"));
138
+ return;
139
+ }
140
+ res.json({
141
+ name: "TrustSource API",
142
+ description: "Domain trust, SSL, security, and crawler-policy intelligence for AI agents — powered by x402",
143
+ version: "0.3.0",
144
+ endpoints: {
145
+ "GET /trustscore": {
146
+ description: "Domain trust and safety scoring — WHOIS, DNS, TLD, registrar",
147
+ price: "$0.003 USDC",
148
+ params: { domain: "string" },
149
+ example: "/trustscore?domain=example.com",
150
+ },
151
+ "GET /sslcheck": {
152
+ description: "SSL/TLS certificate intelligence — chain, expiry, crypto, TLS version",
153
+ price: "$0.002 USDC",
154
+ params: { domain: "string" },
155
+ example: "/sslcheck?domain=example.com",
156
+ },
157
+ "GET /headers": {
158
+ description: "HTTP security header audit — HSTS, CSP, X-Frame-Options, A+/F grade",
159
+ price: "$0.003 USDC",
160
+ params: { url: "string" },
161
+ example: "/headers?url=https://example.com",
162
+ },
163
+ "GET /robots": {
164
+ description: "robots.txt intelligence — crawl rules, AI bot policies, sitemap discovery",
165
+ price: "$0.002 USDC",
166
+ params: { domain: "string" },
167
+ example: "/robots?domain=example.com",
168
+ },
169
+ },
170
+ payment: {
171
+ protocol: "x402",
172
+ currency: "USDC",
173
+ network: NETWORK,
174
+ facilitator: FACILITATOR,
175
+ payTo: PAY_TO,
176
+ },
177
+ links: {
178
+ docs: "https://api.trustsource.cc/openapi.json",
179
+ api: "https://api.trustsource.cc",
180
+ web: "https://trustsource.cc",
181
+ bazaar: "https://agentic.market",
182
+ contact: "mailto:hello@trustsource.cc",
183
+ },
184
+ });
185
+ });
186
+
187
+ app.get("/health", (_req, res) => {
188
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
189
+ });
190
+
191
+ app.use(openApiRouter);
192
+
193
+ // ─── x402 Paywall ─────────────────────────────────────────────────────────────
194
+
195
+ app.use(
196
+ paymentMiddleware(
197
+ {
198
+ "GET /trustscore": {
199
+ accepts: [{ scheme: "exact", price: "$0.003", network: NETWORK, payTo: PAY_TO }],
200
+ description: "Domain trust and safety scoring — returns 0–100 score, tier (TRUSTED/MODERATE/CAUTION/HIGH_RISK), domain age, DNS records, registrar. For agents verifying URLs before transacting.",
201
+ mimeType: "application/json",
202
+ extensions: {
203
+ ...declareDiscoveryExtension({
204
+ input: { domain: "google.com" },
205
+ inputSchema: {
206
+ properties: { domain: { type: "string", description: "Domain or full URL" } },
207
+ required: ["domain"],
208
+ },
209
+ output: {
210
+ example: {
211
+ domain: "google.com", score: 90, tier: "TRUSTED",
212
+ breakdown: { domainAge: 30, tld: 20, dnsPresence: 30, registrar: 10 },
213
+ },
214
+ },
215
+ }),
216
+ },
217
+ },
218
+ "GET /sslcheck": {
219
+ accepts: [{ scheme: "exact", price: "$0.002", network: NETWORK, payTo: PAY_TO }],
220
+ description: "SSL/TLS certificate intelligence — returns 0–100 score, tier (VALID/WEAK/EXPIRING/EXPIRED/UNTRUSTED/INVALID), chain details, expiry, signature, TLS protocol, cipher quality.",
221
+ mimeType: "application/json",
222
+ extensions: {
223
+ ...declareDiscoveryExtension({
224
+ input: { domain: "google.com" },
225
+ inputSchema: {
226
+ properties: { domain: { type: "string" } },
227
+ required: ["domain"],
228
+ },
229
+ output: {
230
+ example: {
231
+ domain: "google.com", score: 100, tier: "VALID",
232
+ breakdown: { chainValid: 30, trustedCa: 25, notExpired: 25, strongCrypto: 10, modernTls: 10 },
233
+ },
234
+ },
235
+ }),
236
+ },
237
+ },
238
+ "GET /headers": {
239
+ accepts: [{ scheme: "exact", price: "$0.003", network: NETWORK, payTo: PAY_TO }],
240
+ description: "HTTP security headers analyzer — returns A+ to F grade with 0–100 score plus structured analysis of HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy, server header disclosure. For agents auditing site security posture.",
241
+ mimeType: "application/json",
242
+ extensions: {
243
+ ...declareDiscoveryExtension({
244
+ input: { url: "https://example.com" },
245
+ inputSchema: {
246
+ properties: { url: { type: "string" } },
247
+ required: ["url"],
248
+ },
249
+ output: {
250
+ example: {
251
+ url: "https://example.com", grade: "A", score: 82, maxScore: 100,
252
+ },
253
+ },
254
+ }),
255
+ },
256
+ },
257
+ "GET /robots": {
258
+ accepts: [{ scheme: "exact", price: "$0.002", network: NETWORK, payTo: PAY_TO }],
259
+ description: "robots.txt intelligence — parses crawl rules and detects AI bot policies (GPTBot, ClaudeBot, Google-Extended, PerplexityBot, etc.). Returns tier (OPEN/SELECTIVE/BLOCKED_AI/BLOCKED_ALL/NO_ROBOTS_TXT), per-bot allow/disallow analysis, sitemap URLs. For crawler agents that need to respect site policies.",
260
+ mimeType: "application/json",
261
+ extensions: {
262
+ ...declareDiscoveryExtension({
263
+ input: { domain: "example.com" },
264
+ inputSchema: {
265
+ properties: { domain: { type: "string" } },
266
+ required: ["domain"],
267
+ },
268
+ output: {
269
+ example: {
270
+ domain: "example.com", exists: true, tier: "SELECTIVE", aiFriendly: true,
271
+ ai: { knownBotsChecked: 24, knownBotsBlocked: 5, knownBotsPartial: 2 },
272
+ },
273
+ },
274
+ }),
275
+ },
276
+ },
277
+ },
278
+ resourceServer,
279
+ )
280
+ );
281
+
282
+ // ─── Paid routes ──────────────────────────────────────────────────────────────
283
+
284
+ app.use(limiter);
285
+ app.use(trustscoreRouter);
286
+ app.use(sslcheckRouter);
287
+ app.use(headersRouter);
288
+ app.use(robotsRouter);
289
+
290
+ // ─── 404 ─────────────────────────────────────────────────────────────────────
291
+
292
+ app.use((_req, res) => {
293
+ res.status(404).json({ error: "Not found" });
294
+ });
295
+
296
+ // ─── Start ────────────────────────────────────────────────────────────────────
297
+
298
+ app.listen(PORT, () => {
299
+ console.log(`
300
+ ╔═════════════════════════════════════════════════════════════════════════╗
301
+ ║ TrustSource API — Server Running ║
302
+ ╠═════════════════════════════════════════════════════════════════════════╣
303
+ ║ URL : http://localhost:${PORT} ║
304
+ ║ Network : ${NETWORK} ${IS_MAINNET ? "(MAINNET 🟢)" : "(TESTNET ✓) "} ║
305
+ ║ Pay to : ${PAY_TO.slice(0, 10)}... ║
306
+ ║ Facilitator: ${IS_MAINNET ? "CDP (production) " : "x402.org (public) "}║
307
+ ╠═════════════════════════════════════════════════════════════════════════╣
308
+ ║ Endpoints: ║
309
+ ║ GET / → Landing / API info (free) ║
310
+ ║ GET /health → Health check (free) ║
311
+ ║ GET /openapi.json → OpenAPI spec (free) ║
312
+ ║ GET /trustscore → Domain score (0.003 USDC)║
313
+ ║ GET /sslcheck → SSL/TLS check (0.002 USDC)║
314
+ ║ GET /headers → Header audit (0.003 USDC)║
315
+ ║ GET /robots → robots.txt + AI (0.002 USDC)║
316
+ ╚══════════════════════════════════════════════════════╝
317
+ `);
318
+ });
@@ -0,0 +1,4 @@
1
+ declare module 'whois-json' {
2
+ function whois(domain: string): Promise<Record<string, string>>;
3
+ export default whois;
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }