@x402sentinel/x402 0.1.3 → 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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://sentinel.valeocash.com/favicon.ico" width="80" />
2
+ <img src="https://sentinel.valeocash.com/sentinel_logo.png" width="80" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -22,6 +22,92 @@
22
22
 
23
23
  **One line to add. Zero config to start.**
24
24
 
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ npm install @x402sentinel/x402
29
+ ```
30
+
31
+ ```typescript
32
+ import { sentinel } from "@x402sentinel/x402";
33
+
34
+ const fetch = sentinel(globalThis.fetch);
35
+
36
+ // That's it. Every x402 payment is now tracked.
37
+ // View your data: sentinel.valeocash.com
38
+ ```
39
+
40
+ ### With options (optional)
41
+
42
+ ```typescript
43
+ const fetch = sentinel(globalThis.fetch, {
44
+ agentId: "my-agent",
45
+ apiKey: "your-api-key", // from sentinel.valeocash.com/dashboard/settings
46
+ });
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Framework Packages
52
+
53
+ One-liner integrations for popular frameworks:
54
+
55
+ | Package | Install |
56
+ |---------|---------|
57
+ | **Express** | `npm install @x402sentinel/express` |
58
+ | **Next.js** | `npm install @x402sentinel/next` |
59
+ | **LangChain** | `npm install @x402sentinel/langchain` |
60
+ | **Vercel AI SDK** | `npm install @x402sentinel/vercel-ai` |
61
+
62
+ ### Express
63
+
64
+ ```typescript
65
+ import express from "express";
66
+ import { sentinelMiddleware } from "@x402sentinel/express";
67
+
68
+ const app = express();
69
+ app.use(sentinelMiddleware());
70
+
71
+ app.get("/data", async (req, res) => {
72
+ const data = await req.sentinelFetch("https://api.example.com/paid");
73
+ res.json(await data.json());
74
+ });
75
+ ```
76
+
77
+ ### Next.js
78
+
79
+ ```typescript
80
+ // app/api/data/route.ts
81
+ import { withSentinel } from "@x402sentinel/next";
82
+
83
+ export const GET = withSentinel(async (req, sentinelFetch) => {
84
+ const res = await sentinelFetch("https://api.example.com/paid");
85
+ return Response.json(await res.json());
86
+ });
87
+ ```
88
+
89
+ ### LangChain
90
+
91
+ ```typescript
92
+ import { SentinelX402Tool } from "@x402sentinel/langchain";
93
+
94
+ const tools = [new SentinelX402Tool({ agentId: "research-agent" })];
95
+ ```
96
+
97
+ ### Vercel AI SDK
98
+
99
+ ```typescript
100
+ import { sentinelX402Tool } from "@x402sentinel/vercel-ai";
101
+
102
+ const result = await generateText({
103
+ model: openai("gpt-4"),
104
+ tools: { x402: sentinelX402Tool({ agentId: "my-agent" }) },
105
+ prompt: "Fetch the latest weather data",
106
+ });
107
+ ```
108
+
109
+ ---
110
+
25
111
  ## The Problem
26
112
 
27
113
  AI agents are spending real money autonomously. The [x402 protocol](https://github.com/coinbase/x402) enables internet-native payments, but provides no built-in audit trail, budget controls, or compliance tooling.
@@ -34,7 +120,27 @@ Sentinel fixes this with a single line of code.
34
120
 
35
121
  ---
36
122
 
37
- ## Quick Start
123
+ ## Features
124
+
125
+ | Feature | Description |
126
+ |---------|-------------|
127
+ | **Budget Enforcement** | Per-call, hourly, daily, and lifetime spend limits. Blocks before payment. |
128
+ | **Spike Detection** | Flags payments that exceed N x the rolling average |
129
+ | **Audit Trails** | Every payment logged with agent ID, team, endpoint, tx hash, timing |
130
+ | **Cryptographic Receipts** | HMAC-SHA256 signed proof for every payment |
131
+ | **Endpoint Filtering** | Allowlist/blocklist URL patterns |
132
+ | **Approval Workflows** | Require human approval above a threshold |
133
+ | **Storage Backends** | In-memory (default), JSONL file, or remote API |
134
+ | **Dashboard Queries** | Query spend by agent, team, endpoint, time range |
135
+ | **CSV/JSON Export** | Export audit data for compliance reviews |
136
+ | **Zero Config** | Works with no API key. Claim your data later. |
137
+ | **Drop-in Wrapper** | One line change. Remove Sentinel, code works identically. |
138
+
139
+ ---
140
+
141
+ ## Advanced Configuration
142
+
143
+ For full control, use `wrapWithSentinel` directly:
38
144
 
39
145
  ### Before (plain x402)
40
146
 
@@ -54,67 +160,34 @@ const response = await fetchWithPayment("https://api.example.com/paid");
54
160
  ```ts
55
161
  import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
56
162
  import { registerExactEvmScheme } from "@x402/evm/exact/client";
57
- import { wrapWithSentinel, standardPolicy } from "@x402sentinel/x402"; // <-- add
163
+ import { wrapWithSentinel, standardPolicy } from "@x402sentinel/x402";
58
164
 
59
165
  const client = new x402Client();
60
166
  registerExactEvmScheme(client, { signer });
61
167
  const fetchWithPayment = wrapFetchWithPayment(fetch, client);
62
168
 
63
- const secureFetch = wrapWithSentinel(fetchWithPayment, { // <-- wrap
169
+ const secureFetch = wrapWithSentinel(fetchWithPayment, {
64
170
  agentId: "agent-weather-001",
65
171
  budget: standardPolicy(),
66
172
  });
67
173
 
68
- const response = await secureFetch("https://api.example.com/paid"); // same API
174
+ const response = await secureFetch("https://api.example.com/paid");
69
175
  ```
70
176
 
71
- That's it. Same `fetch` interface. Budget enforcement + audit trail included.
72
-
73
- ### Install
74
-
75
- ```bash
76
- npm install @x402sentinel/x402
77
- # or
78
- pnpm add @x402sentinel/x402
79
- ```
80
-
81
- ---
82
-
83
- ## Features
84
-
85
- | Feature | Description |
86
- |---------|-------------|
87
- | **Budget Enforcement** | Per-call, hourly, daily, and lifetime spend limits. Blocks before payment. |
88
- | **Spike Detection** | Flags payments that exceed N× the rolling average |
89
- | **Audit Trails** | Every payment logged with agent ID, team, endpoint, tx hash, timing |
90
- | **Endpoint Filtering** | Allowlist/blocklist URL patterns |
91
- | **Approval Workflows** | Require human approval above a threshold |
92
- | **Storage Backends** | In-memory (default), JSONL file, or remote API |
93
- | **Dashboard Queries** | Query spend by agent, team, endpoint, time range |
94
- | **CSV/JSON Export** | Export audit data for compliance reviews |
95
- | **Zero Dependencies** | No runtime deps beyond x402 peer deps |
96
- | **Drop-in Wrapper** | One line change. Remove Sentinel, code works identically. |
97
-
98
- ---
99
-
100
- ## Configuration
177
+ ### Full Configuration
101
178
 
102
179
  ```ts
103
180
  const secureFetch = wrapWithSentinel(fetchWithPayment, {
104
- // Required
105
181
  agentId: "agent-weather-001",
106
-
107
- // Optional identity
108
182
  team: "data-ops",
109
183
  humanSponsor: "alice@company.com",
110
184
 
111
- // Budget policy
112
185
  budget: {
113
- maxPerCall: "1.00", // max USDC per single payment
114
- maxPerHour: "25.00", // hourly rolling cap
115
- maxPerDay: "200.00", // daily rolling cap
116
- maxTotal: "10000.00", // lifetime cap
117
- spikeThreshold: 3.0, // flag if > 3× rolling average
186
+ maxPerCall: "1.00",
187
+ maxPerHour: "25.00",
188
+ maxPerDay: "200.00",
189
+ maxTotal: "10000.00",
190
+ spikeThreshold: 3.0,
118
191
  allowedEndpoints: ["https://api.trusted.com/*"],
119
192
  blockedEndpoints: ["https://api.sketchy.com/*"],
120
193
  requireApproval: {
@@ -123,10 +196,9 @@ const secureFetch = wrapWithSentinel(fetchWithPayment, {
123
196
  },
124
197
  },
125
198
 
126
- // Audit settings
127
199
  audit: {
128
200
  enabled: true,
129
- storage: new MemoryStorage(), // or FileStorage, ApiStorage
201
+ storage: new MemoryStorage(),
130
202
  redactFields: ["secret_key"],
131
203
  enrichment: {
132
204
  staticTags: ["production"],
@@ -136,14 +208,12 @@ const secureFetch = wrapWithSentinel(fetchWithPayment, {
136
208
  },
137
209
  },
138
210
 
139
- // Lifecycle hooks
140
211
  hooks: {
141
212
  afterPayment: async (record) => { /* log to DataDog */ },
142
213
  onBudgetExceeded: async (violation) => { /* page on-call */ },
143
214
  onAnomaly: async (anomaly) => { /* alert Slack */ },
144
215
  },
145
216
 
146
- // Custom metadata on every record
147
217
  metadata: { environment: "production", cost_center: "ENG-2024" },
148
218
  });
149
219
  ```
@@ -192,9 +262,8 @@ try {
192
262
  } catch (err) {
193
263
  if (err instanceof SentinelBudgetError) {
194
264
  console.log(err.message);
195
- // "Budget exceeded: $2.50 spent of $5.00 hourly limit on agent-weather-001"
196
- console.log(err.violation.type); // "hourly"
197
- console.log(err.violation.limit); // "5.00"
265
+ console.log(err.violation.type);
266
+ console.log(err.violation.limit);
198
267
  }
199
268
  }
200
269
  ```
@@ -207,12 +276,12 @@ Every payment produces an `AuditRecord` with:
207
276
 
208
277
  ```ts
209
278
  {
210
- id: "a1b2c3d4e5f6g7h8", // deterministic hash
279
+ id: "a1b2c3d4e5f6g7h8",
211
280
  agent_id: "agent-weather-001",
212
281
  team: "data-ops",
213
282
  human_sponsor: "alice@company.com",
214
- amount: "0.50", // human-readable USDC
215
- amount_raw: "500000", // base units
283
+ amount: "0.50",
284
+ amount_raw: "500000",
216
285
  asset: "USDC",
217
286
  network: "eip155:8453",
218
287
  tx_hash: "0xabc...",
@@ -239,7 +308,7 @@ const memory = new MemoryStorage(10_000);
239
308
  // JSONL file — persistent, append-only
240
309
  const file = new FileStorage(".valeo/audit.jsonl");
241
310
 
242
- // Remote API — batched writes to api.valeo.money
311
+ // Remote API — batched writes to sentinel.valeocash.com
243
312
  const api = new ApiStorage({ apiKey: "val_..." });
244
313
  ```
245
314
 
@@ -258,7 +327,7 @@ const records = await logger.query({
258
327
  });
259
328
 
260
329
  const summary = await logger.summarize({ team: "data-ops" });
261
- console.log(summary.total_spend); // "$1,234.56"
330
+ console.log(summary.total_spend);
262
331
 
263
332
  const csv = await logger.exportCSV();
264
333
  ```
@@ -272,17 +341,8 @@ import { SentinelDashboard } from "@x402sentinel/x402/dashboard";
272
341
 
273
342
  const dashboard = new SentinelDashboard({ storage: myStorage });
274
343
 
275
- // Spend reports
276
- const report = await dashboard.getSpend({
277
- agentId: "bot-1",
278
- range: "last_day",
279
- });
280
- console.log(report.totalSpend, report.count);
281
-
282
- // Agent summaries
344
+ const report = await dashboard.getSpend({ agentId: "bot-1", range: "last_day" });
283
345
  const agents = await dashboard.getAgents();
284
-
285
- // Alerts (violations + anomalies)
286
346
  const alerts = await dashboard.getAlerts();
287
347
  ```
288
348
 
@@ -290,23 +350,15 @@ Dashboard queries run locally against your storage backend. No remote API requir
290
350
 
291
351
  ---
292
352
 
293
- ## Integration Guide
294
-
295
- Sentinel wraps **any** x402-compatible fetch. It works with:
296
-
297
- - `@x402/fetch` + `x402Client` (current)
298
- - `x402-fetch` + viem wallet (legacy)
299
- - Any function with the `fetch` signature that handles x402 internally
353
+ ## API Reference
300
354
 
301
- The wrapper never touches the response body. It reads only headers (`PAYMENT-RESPONSE`, `PAYMENT-REQUIRED`) for audit data.
355
+ ### `sentinel(fetch, options?): typeof fetch`
302
356
 
303
- ---
304
-
305
- ## API Reference
357
+ Zero-config wrapper. Works with no API key. Call `sentinel(globalThis.fetch)` and every x402 payment is tracked.
306
358
 
307
359
  ### `wrapWithSentinel(fetch, config): typeof fetch`
308
360
 
309
- Wraps an x402 fetch function with Sentinel instrumentation. Returns a drop-in replacement.
361
+ Full-config wrapper with budget enforcement, audit trails, hooks, and metadata.
310
362
 
311
363
  ### `BudgetManager`
312
364
 
@@ -353,11 +405,16 @@ Sentinel is an SDK + Dashboard + API + Docs in a single monorepo:
353
405
 
354
406
  ```
355
407
  ├── / SDK (@x402sentinel/x402)
408
+ ├── packages/ Framework integrations
409
+ │ ├── express/ @x402sentinel/express
410
+ │ ├── next/ @x402sentinel/next
411
+ │ ├── langchain/ @x402sentinel/langchain
412
+ │ └── vercel-ai/ @x402sentinel/vercel-ai
356
413
  ├── app/ Next.js 15 application
357
414
  │ ├── /login API key authentication
358
415
  │ ├── /dashboard Real-time analytics dashboard
359
416
  │ ├── /docs Full documentation site
360
- │ └── /api/v1/* REST API (18 endpoints)
417
+ │ └── /api/v1/* REST API (20+ endpoints)
361
418
  ├── Dockerfile Production Docker image
362
419
  └── docker-compose.yml One-command deployment
363
420
  ```
@@ -397,7 +454,7 @@ Login with demo API key: `sk_sentinel_demo_000`
397
454
  - Escrow and pre-commitment flows
398
455
  - Treasury management integration
399
456
  - On-chain agent identity verification
400
- - Real-time dashboard at [app.valeo.money](https://app.valeo.money)
457
+ - Real-time dashboard at [sentinel.valeocash.com](https://sentinel.valeocash.com)
401
458
 
402
459
  ---
403
460
 
package/dist/index.cjs CHANGED
@@ -924,76 +924,6 @@ function wrapWithSentinel(fetchWithPayment, config) {
924
924
  };
925
925
  return sentinelFetch;
926
926
  }
927
- var FileStorage = class {
928
- filePath;
929
- buffer = [];
930
- flushThreshold;
931
- flushTimer = null;
932
- constructor(filePath = ".valeo/audit.jsonl", flushThreshold = 100) {
933
- this.filePath = filePath;
934
- this.flushThreshold = flushThreshold;
935
- this.ensureDir();
936
- this.startAutoFlush();
937
- }
938
- async write(record) {
939
- this.buffer.push(record);
940
- if (this.buffer.length >= this.flushThreshold) {
941
- await this.flush();
942
- }
943
- }
944
- async query(query) {
945
- await this.flush();
946
- const all = this.readAll();
947
- let results = all.filter((r) => matchesQuery(r, query));
948
- const offset = query.offset ?? 0;
949
- const limit = query.limit ?? results.length;
950
- return results.slice(offset, offset + limit);
951
- }
952
- async summarize(query) {
953
- await this.flush();
954
- const records = this.readAll().filter((r) => matchesQuery(r, query));
955
- return buildSummary(records, query);
956
- }
957
- async count(query) {
958
- await this.flush();
959
- return this.readAll().filter((r) => matchesQuery(r, query)).length;
960
- }
961
- async getById(id) {
962
- await this.flush();
963
- return this.readAll().find((r) => r.id === id) ?? null;
964
- }
965
- /** Write buffered records to disk */
966
- async flush() {
967
- if (this.buffer.length === 0) return;
968
- const lines = this.buffer.map((r) => JSON.stringify(r)).join("\n") + "\n";
969
- fs.appendFileSync(this.filePath, lines, "utf-8");
970
- this.buffer = [];
971
- }
972
- /** Stop the auto-flush timer (for clean shutdown) */
973
- destroy() {
974
- if (this.flushTimer) {
975
- clearInterval(this.flushTimer);
976
- this.flushTimer = null;
977
- }
978
- }
979
- readAll() {
980
- if (!fs.existsSync(this.filePath)) return [];
981
- const content = fs.readFileSync(this.filePath, "utf-8");
982
- return content.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
983
- }
984
- ensureDir() {
985
- const dir = path.dirname(this.filePath);
986
- if (!fs.existsSync(dir)) {
987
- fs.mkdirSync(dir, { recursive: true });
988
- }
989
- }
990
- startAutoFlush() {
991
- this.flushTimer = setInterval(() => {
992
- void this.flush();
993
- }, 5e3);
994
- this.flushTimer.unref();
995
- }
996
- };
997
927
 
998
928
  // src/audit/storage/api.ts
999
929
  var ApiStorage = class {
@@ -1115,6 +1045,115 @@ function sleep(ms) {
1115
1045
  return new Promise((resolve) => setTimeout(resolve, ms));
1116
1046
  }
1117
1047
 
1048
+ // src/sentinel.ts
1049
+ function sentinel(fetchFn, options) {
1050
+ const agentId = options?.agentId || `agent-${Date.now().toString(36)}`;
1051
+ const apiKey = options?.apiKey || "anonymous";
1052
+ const baseUrl = options?.baseUrl || "https://sentinel.valeocash.com";
1053
+ const wrapped = wrapWithSentinel(fetchFn, {
1054
+ agentId,
1055
+ budget: {
1056
+ maxPerCall: "10.00",
1057
+ maxPerHour: "100.00",
1058
+ maxPerDay: "1000.00"
1059
+ },
1060
+ audit: {
1061
+ storage: new ApiStorage({
1062
+ baseUrl: `${baseUrl}/api/v1`,
1063
+ apiKey
1064
+ })
1065
+ }
1066
+ });
1067
+ if (apiKey === "anonymous") {
1068
+ let hasLoggedOnce = false;
1069
+ const sentinelFetch = async (input, init) => {
1070
+ const response = await wrapped(input, init);
1071
+ if (!hasLoggedOnce && (response.headers.has("payment-response") || response.headers.has("x-payment-response"))) {
1072
+ hasLoggedOnce = true;
1073
+ console.log(`
1074
+ \u2713 Payment tracked by Sentinel
1075
+ Agent: ${agentId}
1076
+ View: ${baseUrl}/agent/${agentId}
1077
+ Claim your data: ${baseUrl}/claim
1078
+ `);
1079
+ }
1080
+ return response;
1081
+ };
1082
+ return sentinelFetch;
1083
+ }
1084
+ return wrapped;
1085
+ }
1086
+ var FileStorage = class {
1087
+ filePath;
1088
+ buffer = [];
1089
+ flushThreshold;
1090
+ flushTimer = null;
1091
+ constructor(filePath = ".valeo/audit.jsonl", flushThreshold = 100) {
1092
+ this.filePath = filePath;
1093
+ this.flushThreshold = flushThreshold;
1094
+ this.ensureDir();
1095
+ this.startAutoFlush();
1096
+ }
1097
+ async write(record) {
1098
+ this.buffer.push(record);
1099
+ if (this.buffer.length >= this.flushThreshold) {
1100
+ await this.flush();
1101
+ }
1102
+ }
1103
+ async query(query) {
1104
+ await this.flush();
1105
+ const all = this.readAll();
1106
+ let results = all.filter((r) => matchesQuery(r, query));
1107
+ const offset = query.offset ?? 0;
1108
+ const limit = query.limit ?? results.length;
1109
+ return results.slice(offset, offset + limit);
1110
+ }
1111
+ async summarize(query) {
1112
+ await this.flush();
1113
+ const records = this.readAll().filter((r) => matchesQuery(r, query));
1114
+ return buildSummary(records, query);
1115
+ }
1116
+ async count(query) {
1117
+ await this.flush();
1118
+ return this.readAll().filter((r) => matchesQuery(r, query)).length;
1119
+ }
1120
+ async getById(id) {
1121
+ await this.flush();
1122
+ return this.readAll().find((r) => r.id === id) ?? null;
1123
+ }
1124
+ /** Write buffered records to disk */
1125
+ async flush() {
1126
+ if (this.buffer.length === 0) return;
1127
+ const lines = this.buffer.map((r) => JSON.stringify(r)).join("\n") + "\n";
1128
+ fs.appendFileSync(this.filePath, lines, "utf-8");
1129
+ this.buffer = [];
1130
+ }
1131
+ /** Stop the auto-flush timer (for clean shutdown) */
1132
+ destroy() {
1133
+ if (this.flushTimer) {
1134
+ clearInterval(this.flushTimer);
1135
+ this.flushTimer = null;
1136
+ }
1137
+ }
1138
+ readAll() {
1139
+ if (!fs.existsSync(this.filePath)) return [];
1140
+ const content = fs.readFileSync(this.filePath, "utf-8");
1141
+ return content.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
1142
+ }
1143
+ ensureDir() {
1144
+ const dir = path.dirname(this.filePath);
1145
+ if (!fs.existsSync(dir)) {
1146
+ fs.mkdirSync(dir, { recursive: true });
1147
+ }
1148
+ }
1149
+ startAutoFlush() {
1150
+ this.flushTimer = setInterval(() => {
1151
+ void this.flush();
1152
+ }, 5e3);
1153
+ this.flushTimer.unref();
1154
+ }
1155
+ };
1156
+
1118
1157
  exports.ApiStorage = ApiStorage;
1119
1158
  exports.AuditLogger = AuditLogger;
1120
1159
  exports.BudgetManager = BudgetManager;
@@ -1127,6 +1166,7 @@ exports.SentinelError = SentinelError;
1127
1166
  exports.conservativePolicy = conservativePolicy;
1128
1167
  exports.customPolicy = customPolicy;
1129
1168
  exports.liberalPolicy = liberalPolicy;
1169
+ exports.sentinel = sentinel;
1130
1170
  exports.standardPolicy = standardPolicy;
1131
1171
  exports.unlimitedPolicy = unlimitedPolicy;
1132
1172
  exports.validateConfig = validateConfig;