@reinconsole/mock-rails 0.1.0 → 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 +1 -1
- package/dist/index.d.ts +178 -7
- package/dist/index.js +433 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -47,6 +47,8 @@ var DecisionId = prefixedId("dec");
|
|
|
47
47
|
var ReceiptId = prefixedId("rcp");
|
|
48
48
|
var SessionId = prefixedId("ses");
|
|
49
49
|
var GateReceiptId = prefixedId("grc");
|
|
50
|
+
var ApiKeyId = prefixedId("key");
|
|
51
|
+
var ApproverKeyId = prefixedId("apk");
|
|
50
52
|
var EnforcementMode = z.enum(["observed", "sdk", "session-key"]);
|
|
51
53
|
var AgentWallet = z.object({
|
|
52
54
|
chain: Chain,
|
|
@@ -54,12 +56,15 @@ var AgentWallet = z.object({
|
|
|
54
56
|
mode: EnforcementMode
|
|
55
57
|
});
|
|
56
58
|
var AgentStatus = z.enum(["active", "frozen"]);
|
|
59
|
+
var AgentLabel = z.string().max(40).regex(/^[a-z0-9][a-z0-9-]*$/, 'label must be a lowercase slug like "research" or "prod-trading"');
|
|
57
60
|
var Agent = z.object({
|
|
58
61
|
id: AgentId,
|
|
59
62
|
orgId: OrgId,
|
|
60
63
|
name: z.string().min(1).max(200),
|
|
61
64
|
/** On-chain ERC-8004 identity, if the agent is registered. */
|
|
62
65
|
erc8004Id: z.string().optional(),
|
|
66
|
+
/** Semantic grouping labels — the targets of policy `appliesTo.labels`. */
|
|
67
|
+
labels: z.array(AgentLabel).max(16).refine((ls) => new Set(ls).size === ls.length, { message: "labels must be unique" }).default([]),
|
|
63
68
|
wallets: z.array(AgentWallet).default([]),
|
|
64
69
|
status: AgentStatus.default("active"),
|
|
65
70
|
createdAt: z.coerce.date()
|
|
@@ -150,6 +155,17 @@ var SettledPayment = z.object({
|
|
|
150
155
|
feePaid: DecimalString.optional(),
|
|
151
156
|
confirmedAt: z.coerce.date()
|
|
152
157
|
});
|
|
158
|
+
var SettlementReport = z.object({
|
|
159
|
+
intentId: IntentId,
|
|
160
|
+
/** The settling transaction, when the observer saw one. */
|
|
161
|
+
txHash: z.string().optional(),
|
|
162
|
+
chain: Chain.optional(),
|
|
163
|
+
/** The amount as SETTLED, when known — may differ from the amount allowed. */
|
|
164
|
+
amount: DecimalString.optional(),
|
|
165
|
+
/** Free-form observer tag, e.g. "indexer", "guard", "facilitator". */
|
|
166
|
+
source: z.string().min(1).max(64).optional(),
|
|
167
|
+
confirmedAt: z.coerce.date()
|
|
168
|
+
});
|
|
153
169
|
var ReceiptSettlement = z.object({
|
|
154
170
|
txHash: z.string().optional(),
|
|
155
171
|
networkId: z.string().optional(),
|
|
@@ -208,15 +224,58 @@ var Condition = z.object({
|
|
|
208
224
|
txCount: z.object({ window: Window, gt: z.number().int().nonnegative() }).optional(),
|
|
209
225
|
/** Vendor host matches one of these patterns (supports `*` globs). */
|
|
210
226
|
vendorHostIn: z.array(z.string()).optional(),
|
|
227
|
+
/**
|
|
228
|
+
* Resource PATH matches one of these patterns (supports `*` globs, e.g.
|
|
229
|
+
* "/v1/reports/*"). A full-URL resource is reduced to its pathname before
|
|
230
|
+
* matching (see `resourcePathOf`), so the same pattern works whether the
|
|
231
|
+
* vendor declares "https://api.x.com/v1/answer" or "/v1/answer". Pair
|
|
232
|
+
* with `vendorHostIn` to scope a path rule to specific vendors.
|
|
233
|
+
*/
|
|
234
|
+
resourceIn: z.array(z.string()).optional(),
|
|
211
235
|
/** First time Rein has seen this vendor for the agent. */
|
|
212
236
|
vendorFirstSeen: z.boolean().optional(),
|
|
213
237
|
/** Vendor reputation score is below this threshold (Phase 3 hook). */
|
|
214
238
|
vendorReputationLt: z.number().min(0).max(100).optional(),
|
|
215
239
|
/** Amount is more than `gt`-times the observed median for this resource. */
|
|
216
|
-
amountVsResourceMedian: z.object({ gt: Multiplier }).optional()
|
|
240
|
+
amountVsResourceMedian: z.object({ gt: Multiplier }).optional(),
|
|
241
|
+
/**
|
|
242
|
+
* Cumulative spend attributed to THIS intent's `taskContext.taskId`
|
|
243
|
+
* (prior spend on the task plus this payment) exceeds `gt`. Scopes a
|
|
244
|
+
* budget to one unit of work rather than to a window: a research run that
|
|
245
|
+
* is meant to cost a dollar cannot quietly cost fifty, however slowly.
|
|
246
|
+
*
|
|
247
|
+
* An intent carrying no `taskId` cannot be attributed to a task, so the
|
|
248
|
+
* predicate never triggers for one. Requiring attribution is a separate,
|
|
249
|
+
* deliberate rule (deny on `taskIdMissing`), not a side effect of setting
|
|
250
|
+
* a budget — otherwise every untagged probe payment would trip every
|
|
251
|
+
* task budget in the policy.
|
|
252
|
+
*/
|
|
253
|
+
taskBudget: z.object({ gt: DecimalString }).optional(),
|
|
254
|
+
/** The intent carries no `taskContext.taskId` (or an empty one). */
|
|
255
|
+
taskIdMissing: z.boolean().optional()
|
|
217
256
|
}).refine((c) => Object.values(c).some((v) => v !== void 0), {
|
|
218
257
|
message: "a condition must specify at least one predicate"
|
|
219
258
|
});
|
|
259
|
+
var Breaker = z.object({
|
|
260
|
+
id: z.string().min(1),
|
|
261
|
+
/** The trailing span the tripwires measure over. */
|
|
262
|
+
window: Window,
|
|
263
|
+
/**
|
|
264
|
+
* Trip when the window would hold MORE than this many transactions.
|
|
265
|
+
* `10` permits ten in the window and escalates the eleventh.
|
|
266
|
+
*/
|
|
267
|
+
txCount: z.number().int().positive().optional(),
|
|
268
|
+
/**
|
|
269
|
+
* Trip when the window's spend would exceed this total. `"50.00"` permits
|
|
270
|
+
* a window totalling exactly 50.00 and escalates the payment that would
|
|
271
|
+
* carry it past. Prospective, like every other spend predicate: the
|
|
272
|
+
* payment that would breach the cap is the one that escalates, rather
|
|
273
|
+
* than the innocent one after it.
|
|
274
|
+
*/
|
|
275
|
+
valueCap: DecimalString.optional()
|
|
276
|
+
}).refine((b) => b.txCount !== void 0 || b.valueCap !== void 0, {
|
|
277
|
+
message: "a breaker must specify at least one of txCount / valueCap"
|
|
278
|
+
});
|
|
220
279
|
var Rule = z.object({
|
|
221
280
|
id: z.string().min(1),
|
|
222
281
|
allow: Condition.optional(),
|
|
@@ -229,6 +288,13 @@ var PolicyDefault = z.enum(["allow", "deny"]);
|
|
|
229
288
|
var AppliesTo = z.object({
|
|
230
289
|
/** Agent id patterns (supports `*` globs, e.g. "agt_research_*"). */
|
|
231
290
|
agents: z.array(z.string()).optional(),
|
|
291
|
+
/**
|
|
292
|
+
* Agent label patterns (supports `*` globs) — semantic targeting. Matches
|
|
293
|
+
* when the agent carries at least one label matching any pattern. Requires
|
|
294
|
+
* the agent DOCUMENT: an intent from an unregistered agent never matches a
|
|
295
|
+
* labels-targeted policy (and so fails closed when no other policy applies).
|
|
296
|
+
*/
|
|
297
|
+
labels: z.array(z.string()).optional(),
|
|
232
298
|
chains: z.array(Chain).optional()
|
|
233
299
|
});
|
|
234
300
|
var Escalation = z.object({
|
|
@@ -241,11 +307,134 @@ var Policy = z.object({
|
|
|
241
307
|
version: z.string().default("1"),
|
|
242
308
|
appliesTo: AppliesTo.default({}),
|
|
243
309
|
rules: z.array(Rule).default([]),
|
|
310
|
+
/**
|
|
311
|
+
* Behavioral breakers evaluated alongside the rules. A tripped breaker
|
|
312
|
+
* escalates at the same precedence as an `escalate` rule — so an explicit
|
|
313
|
+
* DENY still wins, and an ALLOW rule cannot wave past a tripped breaker.
|
|
314
|
+
*/
|
|
315
|
+
breakers: z.array(Breaker).default([]),
|
|
244
316
|
default: PolicyDefault.default("deny"),
|
|
245
317
|
/** Below this amount, fail-open is permitted during a policy-service outage. */
|
|
246
318
|
denyFloor: DecimalString.default("0.05"),
|
|
247
319
|
escalation: Escalation.optional()
|
|
248
320
|
});
|
|
321
|
+
var LivenessSource = z.enum([
|
|
322
|
+
/** It submitted an intent — ANY outcome, including a denial. */
|
|
323
|
+
"intent",
|
|
324
|
+
/** It reported in explicitly, having nothing to buy. */
|
|
325
|
+
"heartbeat"
|
|
326
|
+
]);
|
|
327
|
+
var DEFAULT_LIVENESS_GRACE_MS = 6e4;
|
|
328
|
+
var LivenessExpectation = z.object({
|
|
329
|
+
agentId: AgentId,
|
|
330
|
+
/** Longest silence still considered normal, e.g. '15m'. */
|
|
331
|
+
interval: Window,
|
|
332
|
+
/** Extra slack past `interval` before the silence is an alarm. */
|
|
333
|
+
graceMs: z.number().int().nonnegative().default(DEFAULT_LIVENESS_GRACE_MS),
|
|
334
|
+
/**
|
|
335
|
+
* When watching began. An agent that has NEVER been seen is measured from
|
|
336
|
+
* here, so a watch on an agent that never starts does raise an alarm — a
|
|
337
|
+
* detector that never came up is exactly as dead as one that stopped.
|
|
338
|
+
*/
|
|
339
|
+
since: z.coerce.date(),
|
|
340
|
+
/** What this agent is supposed to be doing, shown beside the alarm. */
|
|
341
|
+
note: z.string().max(200).optional()
|
|
342
|
+
});
|
|
343
|
+
var LivenessWatchInput = LivenessExpectation.omit({ since: true }).extend({
|
|
344
|
+
graceMs: z.number().int().nonnegative().optional()
|
|
345
|
+
});
|
|
346
|
+
var Heartbeat = z.object({
|
|
347
|
+
agentId: AgentId,
|
|
348
|
+
/** When the agent was alive. Defaults to arrival time. */
|
|
349
|
+
at: z.coerce.date().optional(),
|
|
350
|
+
/** Free-form note from the reporter, e.g. "poll cycle 41 ok". */
|
|
351
|
+
note: z.string().max(200).optional()
|
|
352
|
+
});
|
|
353
|
+
var LivenessStatus = z.enum(["alive", "late", "missing", "unknown"]);
|
|
354
|
+
var ApiKeyScope = z.enum(["read", "evaluate", "approve", "admin"]);
|
|
355
|
+
var ApiKey = z.object({
|
|
356
|
+
id: ApiKeyId,
|
|
357
|
+
name: z.string().min(1).max(200),
|
|
358
|
+
scopes: z.array(ApiKeyScope).min(1),
|
|
359
|
+
createdAt: z.coerce.date(),
|
|
360
|
+
/** Last successful authentication, for spotting keys that can be retired. */
|
|
361
|
+
lastUsedAt: z.coerce.date().optional(),
|
|
362
|
+
/** Set by revoke(); a revoked key never authenticates again. */
|
|
363
|
+
revokedAt: z.coerce.date().optional(),
|
|
364
|
+
/** Set by rotate(); with `previousSecretExpiresAt` it dates the overlap. */
|
|
365
|
+
rotatedAt: z.coerce.date().optional()
|
|
366
|
+
});
|
|
367
|
+
var ApiKeyRecord = ApiKey.extend({
|
|
368
|
+
secretHash: z.string(),
|
|
369
|
+
previousSecretHash: z.string().optional(),
|
|
370
|
+
previousSecretExpiresAt: z.coerce.date().optional()
|
|
371
|
+
});
|
|
372
|
+
var ApprovalVerdict = z.enum(["approve", "reject"]);
|
|
373
|
+
var ApprovalStatus = z.enum(["pending", "approved", "rejected", "expired"]);
|
|
374
|
+
var ApproverKey = z.object({
|
|
375
|
+
id: ApproverKeyId,
|
|
376
|
+
orgId: OrgId,
|
|
377
|
+
name: z.string().min(1).max(200),
|
|
378
|
+
/** ed25519 public key, SPKI PEM — the same encoding as the decision log's. */
|
|
379
|
+
publicKey: z.string().min(1),
|
|
380
|
+
algorithm: z.literal("ed25519").default("ed25519"),
|
|
381
|
+
createdAt: z.coerce.date(),
|
|
382
|
+
revokedAt: z.coerce.date().optional()
|
|
383
|
+
});
|
|
384
|
+
var ApprovalRequest = z.object({
|
|
385
|
+
/** The `escalate` decision this request answers (primary key). */
|
|
386
|
+
decisionId: DecisionId,
|
|
387
|
+
intentId: IntentId,
|
|
388
|
+
/** Binds the approval to the exact transfer — the signed bytes commit to it. */
|
|
389
|
+
intentHash: z.string(),
|
|
390
|
+
agentId: AgentId,
|
|
391
|
+
vendorHost: z.string(),
|
|
392
|
+
resource: z.string(),
|
|
393
|
+
amount: DecimalString,
|
|
394
|
+
asset: Asset,
|
|
395
|
+
chain: Chain,
|
|
396
|
+
/**
|
|
397
|
+
* Task attribution copied from the intent. Carried so an approved payment
|
|
398
|
+
* still counts against its `taskBudget` — an escalation that lost its task
|
|
399
|
+
* id on the way through would make the budget silently under-count exactly
|
|
400
|
+
* the payments a human had to look at.
|
|
401
|
+
*/
|
|
402
|
+
taskId: z.string().optional(),
|
|
403
|
+
/** Why policy escalated, verbatim from the decision. */
|
|
404
|
+
reason: z.string(),
|
|
405
|
+
/**
|
|
406
|
+
* Breaker ids whose trip contributed to this escalation. An approval of
|
|
407
|
+
* this request resets exactly these — moving their counting floor to the
|
|
408
|
+
* approval instant — so a human waving one payment through also clears the
|
|
409
|
+
* behavior that stopped it, rather than being asked again immediately.
|
|
410
|
+
* Empty when ordinary rules did the escalating.
|
|
411
|
+
*/
|
|
412
|
+
breakers: z.array(z.string()).default([]),
|
|
413
|
+
status: ApprovalStatus,
|
|
414
|
+
createdAt: z.coerce.date(),
|
|
415
|
+
/** Past this instant the request denies, whatever arrives afterwards. */
|
|
416
|
+
expiresAt: z.coerce.date(),
|
|
417
|
+
resolvedAt: z.coerce.date().optional(),
|
|
418
|
+
/** Which registered key signed — absent when the TTL lapsed instead. */
|
|
419
|
+
approverKeyId: ApproverKeyId.optional(),
|
|
420
|
+
/**
|
|
421
|
+
* The follow-up decision appended to the chain when this resolved. That
|
|
422
|
+
* decision, not this record, is the voucher a signer verifies: the original
|
|
423
|
+
* `escalate` decision is never rewritten.
|
|
424
|
+
*/
|
|
425
|
+
finalDecisionId: DecisionId.optional()
|
|
426
|
+
});
|
|
427
|
+
var ApprovalChallenges = z.object({
|
|
428
|
+
approve: z.string(),
|
|
429
|
+
reject: z.string()
|
|
430
|
+
});
|
|
431
|
+
var ApprovalGrant = z.object({
|
|
432
|
+
decisionId: DecisionId,
|
|
433
|
+
intentHash: z.string(),
|
|
434
|
+
verdict: ApprovalVerdict,
|
|
435
|
+
approverKeyId: ApproverKeyId,
|
|
436
|
+
signature: z.string().min(1)
|
|
437
|
+
});
|
|
249
438
|
var ReputationSubject = z.object({
|
|
250
439
|
kind: z.enum(["agent", "vendor"]),
|
|
251
440
|
id: z.string()
|
|
@@ -307,6 +496,36 @@ var ReinEvent = z.discriminatedUnion("type", [
|
|
|
307
496
|
network: z.string()
|
|
308
497
|
}),
|
|
309
498
|
z.object({ type: z.literal("gate.settled"), at: z.coerce.date(), receipt: GateReceipt }),
|
|
499
|
+
z.object({
|
|
500
|
+
type: z.literal("approval.requested"),
|
|
501
|
+
at: z.coerce.date(),
|
|
502
|
+
request: ApprovalRequest
|
|
503
|
+
}),
|
|
504
|
+
z.object({
|
|
505
|
+
type: z.literal("approval.resolved"),
|
|
506
|
+
at: z.coerce.date(),
|
|
507
|
+
request: ApprovalRequest,
|
|
508
|
+
/** The follow-up allow/deny decision this resolution appended. */
|
|
509
|
+
decision: Decision
|
|
510
|
+
}),
|
|
511
|
+
z.object({
|
|
512
|
+
type: z.literal("liveness.missing"),
|
|
513
|
+
at: z.coerce.date(),
|
|
514
|
+
agentId: AgentId,
|
|
515
|
+
expectation: LivenessExpectation,
|
|
516
|
+
/** How long the agent has been silent when the alarm was raised. */
|
|
517
|
+
silentMs: z.number().int().nonnegative(),
|
|
518
|
+
/** Last sighting, absent when the agent was never seen at all. */
|
|
519
|
+
lastSeenAt: z.coerce.date().optional()
|
|
520
|
+
}),
|
|
521
|
+
z.object({
|
|
522
|
+
type: z.literal("liveness.recovered"),
|
|
523
|
+
at: z.coerce.date(),
|
|
524
|
+
agentId: AgentId,
|
|
525
|
+
/** How long the silence lasted, measured to this sighting. */
|
|
526
|
+
silentMs: z.number().int().nonnegative(),
|
|
527
|
+
source: LivenessSource
|
|
528
|
+
}),
|
|
310
529
|
z.object({
|
|
311
530
|
type: z.literal("gate.refused"),
|
|
312
531
|
at: z.coerce.date(),
|
|
@@ -351,8 +570,79 @@ var MockLedger = class {
|
|
|
351
570
|
|
|
352
571
|
// ../../packages/sdk/dist/index.js
|
|
353
572
|
import { z as z2 } from "zod";
|
|
354
|
-
var
|
|
355
|
-
|
|
573
|
+
var BreakerState = z2.object({
|
|
574
|
+
breaker: Breaker,
|
|
575
|
+
policyId: z2.string(),
|
|
576
|
+
txCount: z2.number(),
|
|
577
|
+
sum: z2.string(),
|
|
578
|
+
countingFrom: z2.number(),
|
|
579
|
+
resetAt: z2.number().optional(),
|
|
580
|
+
tripped: z2.boolean(),
|
|
581
|
+
reason: z2.string().optional()
|
|
582
|
+
});
|
|
583
|
+
var LivenessState = z2.object({
|
|
584
|
+
agentId: z2.string(),
|
|
585
|
+
expectation: LivenessExpectation,
|
|
586
|
+
lastSeenAt: z2.number().optional(),
|
|
587
|
+
lastSource: LivenessSource.optional(),
|
|
588
|
+
silentSince: z2.number(),
|
|
589
|
+
silentMs: z2.number(),
|
|
590
|
+
dueAt: z2.number(),
|
|
591
|
+
overdueAt: z2.number(),
|
|
592
|
+
status: LivenessStatus,
|
|
593
|
+
alertedAt: z2.number().optional()
|
|
594
|
+
});
|
|
595
|
+
var Health = z2.object({
|
|
596
|
+
status: z2.string(),
|
|
597
|
+
publicKey: z2.string(),
|
|
598
|
+
/** "api-key" once the engine demands a credential; "none" while it does not. */
|
|
599
|
+
auth: z2.string().optional(),
|
|
600
|
+
approvals: z2.string().optional()
|
|
601
|
+
});
|
|
602
|
+
var EvaluateResponse = z2.object({
|
|
603
|
+
intent: PaymentIntent,
|
|
604
|
+
decision: Decision,
|
|
605
|
+
/** Present when an escalation was parked for a signed verdict. */
|
|
606
|
+
approval: ApprovalRequest.optional()
|
|
607
|
+
});
|
|
608
|
+
var ApprovalView = z2.object({
|
|
609
|
+
request: ApprovalRequest,
|
|
610
|
+
challenges: ApprovalChallenges,
|
|
611
|
+
/** The follow-up allow/deny decision, once the request has resolved. */
|
|
612
|
+
decision: Decision.optional()
|
|
613
|
+
});
|
|
614
|
+
var ResolveResponse = z2.object({ request: ApprovalRequest, decision: Decision });
|
|
615
|
+
var AllowanceGap = z2.object({
|
|
616
|
+
intentId: z2.string(),
|
|
617
|
+
decisionId: z2.string().optional(),
|
|
618
|
+
agentId: z2.string(),
|
|
619
|
+
host: z2.string(),
|
|
620
|
+
resource: z2.string(),
|
|
621
|
+
amount: z2.string(),
|
|
622
|
+
taskId: z2.string().optional(),
|
|
623
|
+
allowedAt: z2.number(),
|
|
624
|
+
ageMs: z2.number(),
|
|
625
|
+
state: z2.enum(["in-flight", "unsettled"])
|
|
626
|
+
});
|
|
627
|
+
var ReconciliationReport = z2.object({
|
|
628
|
+
from: z2.number(),
|
|
629
|
+
to: z2.number(),
|
|
630
|
+
window: z2.string(),
|
|
631
|
+
graceMs: z2.number(),
|
|
632
|
+
allowed: z2.number(),
|
|
633
|
+
allowedValue: z2.string(),
|
|
634
|
+
settled: z2.number(),
|
|
635
|
+
settledValue: z2.string(),
|
|
636
|
+
inFlight: z2.number(),
|
|
637
|
+
inFlightValue: z2.string(),
|
|
638
|
+
unsettled: z2.number(),
|
|
639
|
+
unsettledValue: z2.string(),
|
|
640
|
+
unattributed: z2.number(),
|
|
641
|
+
settlementsSeen: z2.number(),
|
|
642
|
+
gaps: z2.array(AllowanceGap),
|
|
643
|
+
truncated: z2.boolean()
|
|
644
|
+
});
|
|
645
|
+
var IssuedApiKey = z2.object({ key: ApiKey, secret: z2.string() });
|
|
356
646
|
var PaymentRequirement = z2.object({
|
|
357
647
|
scheme: z2.string(),
|
|
358
648
|
network: z2.string(),
|
|
@@ -417,6 +707,81 @@ function requirementDecimals(requirement) {
|
|
|
417
707
|
const decimals = requirement.extra?.["decimals"];
|
|
418
708
|
return typeof decimals === "number" && Number.isInteger(decimals) && decimals >= 0 ? decimals : 6;
|
|
419
709
|
}
|
|
710
|
+
var CAIP2_ALIASES = {
|
|
711
|
+
base: "eip155:8453",
|
|
712
|
+
"base-sepolia": "eip155:84532",
|
|
713
|
+
polygon: "eip155:137",
|
|
714
|
+
"polygon-amoy": "eip155:80002",
|
|
715
|
+
bnb: "eip155:56",
|
|
716
|
+
bsc: "eip155:56",
|
|
717
|
+
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
718
|
+
"solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
|
|
719
|
+
};
|
|
720
|
+
function caip2Of(network) {
|
|
721
|
+
const key = network.toLowerCase();
|
|
722
|
+
return CAIP2_ALIASES[key] ?? key;
|
|
723
|
+
}
|
|
724
|
+
function sameNetwork(a, b) {
|
|
725
|
+
return caip2Of(a) === caip2Of(b);
|
|
726
|
+
}
|
|
727
|
+
var PaymentRequirementsV2 = z2.object({
|
|
728
|
+
scheme: z2.string(),
|
|
729
|
+
/** CAIP-2, e.g. "eip155:84532". */
|
|
730
|
+
network: z2.string(),
|
|
731
|
+
/** Atomic units — v2's rename of v1's maxAmountRequired. */
|
|
732
|
+
amount: z2.string().regex(/^\d+$/, "atomic amount must be an integer string"),
|
|
733
|
+
asset: z2.string().min(1),
|
|
734
|
+
payTo: z2.string().min(1),
|
|
735
|
+
maxTimeoutSeconds: z2.number().optional(),
|
|
736
|
+
extra: z2.record(z2.unknown()).optional()
|
|
737
|
+
});
|
|
738
|
+
var ResourceInfoV2 = z2.object({
|
|
739
|
+
url: z2.string(),
|
|
740
|
+
description: z2.string().optional(),
|
|
741
|
+
mimeType: z2.string().optional()
|
|
742
|
+
});
|
|
743
|
+
var PaymentRequiredV2 = z2.object({
|
|
744
|
+
x402Version: z2.literal(2),
|
|
745
|
+
error: z2.string().optional(),
|
|
746
|
+
resource: ResourceInfoV2.optional(),
|
|
747
|
+
accepts: z2.array(PaymentRequirementsV2).min(1),
|
|
748
|
+
extensions: z2.record(z2.unknown()).optional()
|
|
749
|
+
});
|
|
750
|
+
function v2Requirements(requirement) {
|
|
751
|
+
return {
|
|
752
|
+
scheme: requirement.scheme,
|
|
753
|
+
network: caip2Of(requirement.network),
|
|
754
|
+
amount: requirement.maxAmountRequired,
|
|
755
|
+
asset: requirement.asset,
|
|
756
|
+
payTo: requirement.payTo,
|
|
757
|
+
...requirement.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: requirement.maxTimeoutSeconds } : {},
|
|
758
|
+
...requirement.extra ? { extra: requirement.extra } : {}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function buildPaymentRequiredV2(requirement, error) {
|
|
762
|
+
return {
|
|
763
|
+
x402Version: 2,
|
|
764
|
+
error,
|
|
765
|
+
...requirement.resource ? {
|
|
766
|
+
resource: {
|
|
767
|
+
url: requirement.resource,
|
|
768
|
+
...requirement.description ? { description: requirement.description } : {},
|
|
769
|
+
...requirement.mimeType ? { mimeType: requirement.mimeType } : {}
|
|
770
|
+
}
|
|
771
|
+
} : {},
|
|
772
|
+
accepts: [v2Requirements(requirement)],
|
|
773
|
+
extensions: {}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function encodeBase64Json(value) {
|
|
777
|
+
return Buffer.from(JSON.stringify(value)).toString("base64");
|
|
778
|
+
}
|
|
779
|
+
var V1Envelope = z2.object({
|
|
780
|
+
x402Version: z2.literal(1),
|
|
781
|
+
scheme: z2.string(),
|
|
782
|
+
network: z2.string(),
|
|
783
|
+
payload: z2.record(z2.unknown())
|
|
784
|
+
});
|
|
420
785
|
|
|
421
786
|
// src/errors.ts
|
|
422
787
|
var FacilitatorError = class extends Error {
|
|
@@ -450,6 +815,17 @@ var MockPaymentHeader = z3.object({
|
|
|
450
815
|
network: z3.string(),
|
|
451
816
|
payload: MockExactPayload
|
|
452
817
|
});
|
|
818
|
+
var MockPaymentHeaderV2 = z3.object({
|
|
819
|
+
x402Version: z3.literal(2),
|
|
820
|
+
accepted: z3.object({
|
|
821
|
+
scheme: z3.string(),
|
|
822
|
+
network: z3.string(),
|
|
823
|
+
amount: z3.string().regex(/^\d+$/, "atomic amount must be an integer string"),
|
|
824
|
+
asset: z3.string().min(1),
|
|
825
|
+
payTo: z3.string().min(1)
|
|
826
|
+
}).passthrough(),
|
|
827
|
+
payload: MockExactPayload
|
|
828
|
+
}).passthrough();
|
|
453
829
|
function encodePaymentHeader(header) {
|
|
454
830
|
return Buffer.from(JSON.stringify(header)).toString("base64");
|
|
455
831
|
}
|
|
@@ -460,11 +836,37 @@ function decodePaymentHeader(raw) {
|
|
|
460
836
|
} catch {
|
|
461
837
|
throw new FacilitatorError("malformed_payment", "X-PAYMENT is not base64-encoded JSON");
|
|
462
838
|
}
|
|
839
|
+
if (json?.x402Version === 2) {
|
|
840
|
+
const parsed2 = MockPaymentHeaderV2.safeParse(json);
|
|
841
|
+
if (!parsed2.success) {
|
|
842
|
+
throw new FacilitatorError(
|
|
843
|
+
"malformed_payment",
|
|
844
|
+
`invalid v2 payment envelope: ${parsed2.error.message}`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (parsed2.data.payload.value !== parsed2.data.accepted.amount) {
|
|
848
|
+
throw new FacilitatorError(
|
|
849
|
+
"malformed_payment",
|
|
850
|
+
`v2 envelope contradicts itself: accepted.amount ${parsed2.data.accepted.amount} vs payload value ${parsed2.data.payload.value}`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
version: 2,
|
|
855
|
+
scheme: parsed2.data.accepted.scheme,
|
|
856
|
+
network: parsed2.data.accepted.network,
|
|
857
|
+
payload: parsed2.data.payload
|
|
858
|
+
};
|
|
859
|
+
}
|
|
463
860
|
const parsed = MockPaymentHeader.safeParse(json);
|
|
464
861
|
if (!parsed.success) {
|
|
465
862
|
throw new FacilitatorError("malformed_payment", `invalid X-PAYMENT body: ${parsed.error.message}`);
|
|
466
863
|
}
|
|
467
|
-
return
|
|
864
|
+
return {
|
|
865
|
+
version: 1,
|
|
866
|
+
scheme: parsed.data.scheme,
|
|
867
|
+
network: parsed.data.network,
|
|
868
|
+
payload: parsed.data.payload
|
|
869
|
+
};
|
|
468
870
|
}
|
|
469
871
|
var SettlementResponse = z3.object({
|
|
470
872
|
success: z3.boolean(),
|
|
@@ -507,7 +909,8 @@ var MockFacilitator = class {
|
|
|
507
909
|
}
|
|
508
910
|
});
|
|
509
911
|
}
|
|
510
|
-
/** Verify
|
|
912
|
+
/** Verify a payment header (either dialect) against the requirement it
|
|
913
|
+
* claims to satisfy. */
|
|
511
914
|
verify(paymentHeader, requirement) {
|
|
512
915
|
const header = decodePaymentHeader(paymentHeader);
|
|
513
916
|
if (header.scheme.toLowerCase() !== "exact" || requirement.scheme.toLowerCase() !== "exact") {
|
|
@@ -516,7 +919,7 @@ var MockFacilitator = class {
|
|
|
516
919
|
`mock facilitator only settles "exact", got "${header.scheme}"/"${requirement.scheme}"`
|
|
517
920
|
);
|
|
518
921
|
}
|
|
519
|
-
if (header.network
|
|
922
|
+
if (!sameNetwork(header.network, requirement.network)) {
|
|
520
923
|
throw new FacilitatorError(
|
|
521
924
|
"network_mismatch",
|
|
522
925
|
`payment is on "${header.network}" but the requirement wants "${requirement.network}"`
|
|
@@ -675,6 +1078,7 @@ function sameAddress(a, b) {
|
|
|
675
1078
|
// src/vendor.ts
|
|
676
1079
|
function createMockVendor(options) {
|
|
677
1080
|
const calls = [];
|
|
1081
|
+
const advertise = options.advertise ?? "v1";
|
|
678
1082
|
const requirementFor = (url) => PaymentRequirement.parse({
|
|
679
1083
|
scheme: options.scheme ?? "exact",
|
|
680
1084
|
network: options.network ?? "base",
|
|
@@ -683,18 +1087,27 @@ function createMockVendor(options) {
|
|
|
683
1087
|
payTo: options.payTo,
|
|
684
1088
|
asset: options.asset ?? "USDC"
|
|
685
1089
|
});
|
|
686
|
-
const paymentRequired = (url, error) =>
|
|
687
|
-
|
|
688
|
-
{
|
|
689
|
-
|
|
1090
|
+
const paymentRequired = (url, error) => {
|
|
1091
|
+
const requirement = requirementFor(url);
|
|
1092
|
+
const body = advertise === "v2" ? { error } : { x402Version: 1, accepts: [requirement], error };
|
|
1093
|
+
return new Response(JSON.stringify(body), {
|
|
1094
|
+
status: 402,
|
|
1095
|
+
headers: {
|
|
1096
|
+
"content-type": "application/json",
|
|
1097
|
+
...advertise !== "v1" ? {
|
|
1098
|
+
"PAYMENT-REQUIRED": encodeBase64Json(buildPaymentRequiredV2(requirement, error))
|
|
1099
|
+
} : {}
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
};
|
|
690
1103
|
const fetch = async (input, init) => {
|
|
691
1104
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
692
1105
|
const headers = new Headers(
|
|
693
1106
|
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
694
1107
|
);
|
|
695
|
-
const payment = headers.get("X-PAYMENT");
|
|
1108
|
+
const payment = headers.get("PAYMENT-SIGNATURE") ?? headers.get("X-PAYMENT");
|
|
696
1109
|
calls.push({ url, payment });
|
|
697
|
-
if (payment === null) return paymentRequired(url, "
|
|
1110
|
+
if (payment === null) return paymentRequired(url, "payment is required");
|
|
698
1111
|
let settled;
|
|
699
1112
|
try {
|
|
700
1113
|
settled = options.facilitator.settle(payment, requirementFor(url));
|
|
@@ -704,7 +1117,13 @@ function createMockVendor(options) {
|
|
|
704
1117
|
}
|
|
705
1118
|
return new Response(JSON.stringify(options.body ?? { ok: true }), {
|
|
706
1119
|
status: 200,
|
|
707
|
-
headers: {
|
|
1120
|
+
headers: {
|
|
1121
|
+
"content-type": "application/json",
|
|
1122
|
+
// Both dialect names carry the same base64 settlement object; the
|
|
1123
|
+
// strict v2 vendor sets ONLY the v2 name.
|
|
1124
|
+
...advertise !== "v2" ? { "X-PAYMENT-RESPONSE": settled.header } : {},
|
|
1125
|
+
"PAYMENT-RESPONSE": settled.header
|
|
1126
|
+
}
|
|
708
1127
|
});
|
|
709
1128
|
};
|
|
710
1129
|
return { fetch, calls, requirementFor };
|
|
@@ -716,6 +1135,7 @@ export {
|
|
|
716
1135
|
MockIndexer,
|
|
717
1136
|
MockLedger,
|
|
718
1137
|
MockPaymentHeader,
|
|
1138
|
+
MockPaymentHeaderV2,
|
|
719
1139
|
SettlementResponse,
|
|
720
1140
|
createMockVendor,
|
|
721
1141
|
decodePaymentHeader,
|