@seedhape/core 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.
Files changed (133) hide show
  1. package/dist/__tests__/acp.test.d.ts +2 -0
  2. package/dist/__tests__/acp.test.d.ts.map +1 -0
  3. package/dist/__tests__/acp.test.js +16 -0
  4. package/dist/__tests__/acp.test.js.map +1 -0
  5. package/dist/__tests__/ap2-official.test.d.ts +2 -0
  6. package/dist/__tests__/ap2-official.test.d.ts.map +1 -0
  7. package/dist/__tests__/ap2-official.test.js +49 -0
  8. package/dist/__tests__/ap2-official.test.js.map +1 -0
  9. package/dist/__tests__/mpp.test.d.ts +2 -0
  10. package/dist/__tests__/mpp.test.d.ts.map +1 -0
  11. package/dist/__tests__/mpp.test.js +42 -0
  12. package/dist/__tests__/mpp.test.js.map +1 -0
  13. package/dist/__tests__/outbound.test.d.ts +2 -0
  14. package/dist/__tests__/outbound.test.d.ts.map +1 -0
  15. package/dist/__tests__/outbound.test.js +23 -0
  16. package/dist/__tests__/outbound.test.js.map +1 -0
  17. package/dist/__tests__/protocols.test.d.ts +2 -0
  18. package/dist/__tests__/protocols.test.d.ts.map +1 -0
  19. package/dist/__tests__/protocols.test.js +16 -0
  20. package/dist/__tests__/protocols.test.js.map +1 -0
  21. package/dist/__tests__/providers.test.d.ts +2 -0
  22. package/dist/__tests__/providers.test.d.ts.map +1 -0
  23. package/dist/__tests__/providers.test.js +41 -0
  24. package/dist/__tests__/providers.test.js.map +1 -0
  25. package/dist/__tests__/reconciliation.test.d.ts +2 -0
  26. package/dist/__tests__/reconciliation.test.d.ts.map +1 -0
  27. package/dist/__tests__/reconciliation.test.js +35 -0
  28. package/dist/__tests__/reconciliation.test.js.map +1 -0
  29. package/dist/__tests__/spending-controls.test.d.ts +2 -0
  30. package/dist/__tests__/spending-controls.test.d.ts.map +1 -0
  31. package/dist/__tests__/spending-controls.test.js +47 -0
  32. package/dist/__tests__/spending-controls.test.js.map +1 -0
  33. package/dist/__tests__/tax-and-policy.test.d.ts +7 -0
  34. package/dist/__tests__/tax-and-policy.test.d.ts.map +1 -0
  35. package/dist/__tests__/tax-and-policy.test.js +389 -0
  36. package/dist/__tests__/tax-and-policy.test.js.map +1 -0
  37. package/dist/__tests__/web-bot-auth.test.d.ts +2 -0
  38. package/dist/__tests__/web-bot-auth.test.d.ts.map +1 -0
  39. package/dist/__tests__/web-bot-auth.test.js +33 -0
  40. package/dist/__tests__/web-bot-auth.test.js.map +1 -0
  41. package/dist/__tests__/x402-solana.test.d.ts +2 -0
  42. package/dist/__tests__/x402-solana.test.d.ts.map +1 -0
  43. package/dist/__tests__/x402-solana.test.js +155 -0
  44. package/dist/__tests__/x402-solana.test.js.map +1 -0
  45. package/dist/acp.d.ts +28 -0
  46. package/dist/acp.d.ts.map +1 -0
  47. package/dist/acp.js +49 -0
  48. package/dist/acp.js.map +1 -0
  49. package/dist/ap2/mandates.d.ts +319 -0
  50. package/dist/ap2/mandates.d.ts.map +1 -0
  51. package/dist/ap2/mandates.js +214 -0
  52. package/dist/ap2/mandates.js.map +1 -0
  53. package/dist/ap2/official.d.ts +156 -0
  54. package/dist/ap2/official.d.ts.map +1 -0
  55. package/dist/ap2/official.js +222 -0
  56. package/dist/ap2/official.js.map +1 -0
  57. package/dist/ap2/policy.d.ts +93 -0
  58. package/dist/ap2/policy.d.ts.map +1 -0
  59. package/dist/ap2/policy.js +216 -0
  60. package/dist/ap2/policy.js.map +1 -0
  61. package/dist/index.d.ts +22 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +22 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/india/fx.d.ts +59 -0
  66. package/dist/india/fx.d.ts.map +1 -0
  67. package/dist/india/fx.js +93 -0
  68. package/dist/india/fx.js.map +1 -0
  69. package/dist/india/vda-tax.d.ts +151 -0
  70. package/dist/india/vda-tax.d.ts.map +1 -0
  71. package/dist/india/vda-tax.js +226 -0
  72. package/dist/india/vda-tax.js.map +1 -0
  73. package/dist/mandate-builder.d.ts +102 -0
  74. package/dist/mandate-builder.d.ts.map +1 -0
  75. package/dist/mandate-builder.js +99 -0
  76. package/dist/mandate-builder.js.map +1 -0
  77. package/dist/mpp.d.ts +53 -0
  78. package/dist/mpp.d.ts.map +1 -0
  79. package/dist/mpp.js +164 -0
  80. package/dist/mpp.js.map +1 -0
  81. package/dist/orchestrator.d.ts +115 -0
  82. package/dist/orchestrator.d.ts.map +1 -0
  83. package/dist/orchestrator.js +351 -0
  84. package/dist/orchestrator.js.map +1 -0
  85. package/dist/postgres-store.d.ts +60 -0
  86. package/dist/postgres-store.d.ts.map +1 -0
  87. package/dist/postgres-store.js +441 -0
  88. package/dist/postgres-store.js.map +1 -0
  89. package/dist/protocols.d.ts +18 -0
  90. package/dist/protocols.d.ts.map +1 -0
  91. package/dist/protocols.js +24 -0
  92. package/dist/protocols.js.map +1 -0
  93. package/dist/reconciliation.d.ts +27 -0
  94. package/dist/reconciliation.d.ts.map +1 -0
  95. package/dist/reconciliation.js +38 -0
  96. package/dist/reconciliation.js.map +1 -0
  97. package/dist/store.d.ts +110 -0
  98. package/dist/store.d.ts.map +1 -0
  99. package/dist/store.js +369 -0
  100. package/dist/store.js.map +1 -0
  101. package/dist/types.d.ts +195 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +30 -0
  104. package/dist/types.js.map +1 -0
  105. package/dist/util/canonical.d.ts +9 -0
  106. package/dist/util/canonical.d.ts.map +1 -0
  107. package/dist/util/canonical.js +35 -0
  108. package/dist/util/canonical.js.map +1 -0
  109. package/dist/wallet/provider.d.ts +118 -0
  110. package/dist/wallet/provider.d.ts.map +1 -0
  111. package/dist/wallet/provider.js +160 -0
  112. package/dist/wallet/provider.js.map +1 -0
  113. package/dist/wallet/providers.d.ts +107 -0
  114. package/dist/wallet/providers.d.ts.map +1 -0
  115. package/dist/wallet/providers.js +188 -0
  116. package/dist/wallet/providers.js.map +1 -0
  117. package/dist/web-bot-auth.d.ts +54 -0
  118. package/dist/web-bot-auth.d.ts.map +1 -0
  119. package/dist/web-bot-auth.js +112 -0
  120. package/dist/web-bot-auth.js.map +1 -0
  121. package/dist/x402/client.d.ts +101 -0
  122. package/dist/x402/client.d.ts.map +1 -0
  123. package/dist/x402/client.js +291 -0
  124. package/dist/x402/client.js.map +1 -0
  125. package/dist/x402/outbound.d.ts +15 -0
  126. package/dist/x402/outbound.d.ts.map +1 -0
  127. package/dist/x402/outbound.js +59 -0
  128. package/dist/x402/outbound.js.map +1 -0
  129. package/dist/x402/types.d.ts +80 -0
  130. package/dist/x402/types.d.ts.map +1 -0
  131. package/dist/x402/types.js +2 -0
  132. package/dist/x402/types.js.map +1 -0
  133. package/package.json +29 -0
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Tests for the two subsystems where a silent bug is expensive: the tax ledger
3
+ * (wrong numbers on a filing) and the policy engine (money leaving that
4
+ * shouldn't). Run with `node --test`.
5
+ */
6
+ import { test, describe } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { accrueTds, buildVdaTaxReport, consumeFifo, InsufficientLotsError, paiseToInr, RULES_FY_2026_27_SPECIFIED_PERSON, } from "../india/vda-tax.js";
9
+ import { toInrPaise, inrToScaled } from "../india/fx.js";
10
+ import { PolicyEngine, DEFAULT_POLICY_OPTIONS } from "../ap2/policy.js";
11
+ import { buildIntentMandate, MandateValidationError } from "../mandate-builder.js";
12
+ import { hashIntentMandate, listRoot } from "../ap2/mandates.js";
13
+ import { matchesHostList } from "../ap2/policy.js";
14
+ import { USDC } from "../types.js";
15
+ const ASSET = USDC.base;
16
+ const USER = "0x1111111111111111111111111111111111111111";
17
+ const AGENT = "0x2222222222222222222222222222222222222222";
18
+ const MERCHANT = "0x3333333333333333333333333333333333333333";
19
+ function lot(id, usdc, inr, at) {
20
+ const quantity = BigInt(Math.round(Number(usdc) * 1e6));
21
+ return {
22
+ id,
23
+ userId: "u1",
24
+ quantity,
25
+ remaining: quantity,
26
+ costInrPaise: BigInt(Math.round(Number(inr) * 100)),
27
+ acquiredAt: at,
28
+ providerTxId: `tx-${id}`,
29
+ provider: "test",
30
+ };
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ describe("FIFO cost basis", () => {
34
+ test("consumes oldest lots first and apportions cost proportionally", () => {
35
+ const lots = [
36
+ lot("a", "10", "880", 1000), // ₹88.00/USDC
37
+ lot("b", "10", "900", 2000), // ₹90.00/USDC
38
+ ];
39
+ // Spend 15 USDC for ₹1,350 of value.
40
+ const d = consumeFifo(lots, 15000000n, 135000n);
41
+ assert.equal(d.consumptions.length, 2);
42
+ assert.equal(d.consumptions[0].lotId, "a");
43
+ assert.equal(d.consumptions[0].quantity, 10000000n);
44
+ assert.equal(d.consumptions[1].lotId, "b");
45
+ assert.equal(d.consumptions[1].quantity, 5000000n);
46
+ // Cost = 880 + (900 * 5/10) = 880 + 450 = ₹1,330
47
+ assert.equal(d.costInrPaise, 133000n);
48
+ assert.equal(d.gainInrPaise, 2000n); // ₹20 gain
49
+ assert.equal(lots[0].remaining, 0n);
50
+ assert.equal(lots[1].remaining, 5000000n);
51
+ });
52
+ test("recognises a loss when the rate moves against the user", () => {
53
+ const lots = [lot("a", "10", "900", 1000)];
54
+ const d = consumeFifo(lots, 10000000n, 85000n); // spent when worth ₹850
55
+ assert.equal(d.gainInrPaise, -5000n);
56
+ assert.ok(d.gainInrPaise < 0n);
57
+ });
58
+ test("throws with the exact shortfall when lots are insufficient", () => {
59
+ const lots = [lot("a", "1", "88", 1000)];
60
+ assert.throws(() => consumeFifo(lots, 2000000n, 17600n), (e) => e instanceof InsufficientLotsError && e.shortfall === 1000000n);
61
+ });
62
+ test("rounding residue never lets total cost exceed what was paid", () => {
63
+ // 3 USDC costing ₹100 exactly; consume in three 1-USDC slices.
64
+ const lots = [lot("a", "3", "100", 1000)];
65
+ let total = 0n;
66
+ for (let i = 0; i < 3; i++) {
67
+ total += consumeFifo(lots, 1000000n, 3400n).costInrPaise;
68
+ }
69
+ assert.ok(total <= 10000n, `apportioned cost ${total} exceeded ₹100`);
70
+ });
71
+ });
72
+ // ---------------------------------------------------------------------------
73
+ describe("Section 194S TDS accrual", () => {
74
+ const rules = RULES_FY_2026_27_SPECIFIED_PERSON; // ₹50,000 threshold
75
+ test("no TDS below the annual threshold", () => {
76
+ const a = accrueTds(rules, 100000n, 0n, 0n); // ₹1,000
77
+ assert.equal(a.tdsPaise, 0n);
78
+ assert.equal(a.thresholdCrossed, false);
79
+ });
80
+ test("crossing the threshold catches up on all prior consideration", () => {
81
+ // ₹49,000 already transferred, now another ₹2,000 → cumulative ₹51,000.
82
+ const a = accrueTds(rules, 200000n, 4900000n, 0n);
83
+ assert.equal(a.thresholdCrossed, true);
84
+ // Total due = 1% of ₹51,000 = ₹510 = 51,000 paise.
85
+ assert.equal(a.tdsPaise + a.catchUpPaise, 51000n);
86
+ });
87
+ test("after crossing, each transfer accrues only its own 1%", () => {
88
+ const a = accrueTds(rules, 100000n, 6000000n, 60000n);
89
+ assert.equal(a.thresholdCrossed, false);
90
+ assert.equal(a.catchUpPaise, 0n);
91
+ assert.equal(a.tdsPaise, 1000n); // 1% of ₹1,000
92
+ });
93
+ });
94
+ // ---------------------------------------------------------------------------
95
+ describe("year-end report", () => {
96
+ test("losses are reported but never reduce the taxable base", () => {
97
+ const lots = [lot("a", "100", "9000", 1000)]; // ₹90/USDC
98
+ const spends = [
99
+ // Gain: 10 USDC used when worth ₹1,000 (cost ₹900)
100
+ mkSpend("s1", 10000000n, 100000n, 2000),
101
+ // Loss: 10 USDC used when worth ₹800 (cost ₹900)
102
+ mkSpend("s2", 10000000n, 80000n, 3000),
103
+ ];
104
+ const r = buildVdaTaxReport({ rules: RULES_FY_2026_27_SPECIFIED_PERSON, spends, lots });
105
+ assert.equal(r.totalGainsPaise, 10000n); // ₹100
106
+ assert.equal(r.totalLossesPaise, 10000n); // ₹100
107
+ assert.equal(r.netPaise, 0n);
108
+ // Tax is on gains only — the loss does NOT net off.
109
+ assert.equal(r.taxOnGainsPaise, 3000n); // 30% of ₹100
110
+ assert.ok(r.warnings.some((w) => w.includes("NOT deductible")));
111
+ });
112
+ test("flags missing cost basis rather than silently assuming zero", () => {
113
+ const r = buildVdaTaxReport({
114
+ rules: RULES_FY_2026_27_SPECIFIED_PERSON,
115
+ spends: [mkSpend("s1", 5000000n, 44000n, 2000)],
116
+ lots: [],
117
+ });
118
+ assert.ok(r.warnings.some((w) => w.includes("no cost basis")));
119
+ });
120
+ });
121
+ // ---------------------------------------------------------------------------
122
+ describe("FX conversion", () => {
123
+ test("converts USDC base units to paise without float drift", () => {
124
+ const rate = inrToScaled(88.42);
125
+ // 0.25 USDC at ₹88.42 = ₹22.105 → 2210 paise (truncated)
126
+ assert.equal(toInrPaise(250000n, 6, rate), 2210n);
127
+ });
128
+ test("handles micropayments below one paisa", () => {
129
+ const rate = inrToScaled(88);
130
+ // 0.0001 USDC = ₹0.0088 → 0 paise. Must not throw or go negative.
131
+ assert.equal(toInrPaise(100n, 6, rate), 0n);
132
+ });
133
+ });
134
+ describe("Indian digit grouping", () => {
135
+ test("groups in lakhs and crores, not thousands", () => {
136
+ assert.equal(paiseToInr(1234567800n), "1,23,45,678.00");
137
+ assert.equal(paiseToInr(50000n), "500.00");
138
+ assert.equal(paiseToInr(-12345n), "-123.45");
139
+ });
140
+ });
141
+ // ---------------------------------------------------------------------------
142
+ describe("policy engine", () => {
143
+ const engine = new PolicyEngine();
144
+ function mandate(overrides = {}) {
145
+ const m = buildIntentMandate({
146
+ subject: USER,
147
+ agent: AGENT,
148
+ purpose: "Pay for weather and market data APIs",
149
+ maxPerTransaction: "0.50",
150
+ maxPerPeriod: "5.00",
151
+ period: "day",
152
+ maxTxPerPeriod: 50,
153
+ validForDays: 7,
154
+ asset: ASSET,
155
+ chainId: 8453,
156
+ ...overrides,
157
+ });
158
+ return { mandate: m, hash: hashIntentMandate(m), signature: "0xdead" };
159
+ }
160
+ const baseCtx = {
161
+ now: Math.floor(Date.now() / 1000),
162
+ history: [],
163
+ knownHosts: new Set(["api.known.in"]),
164
+ };
165
+ const candidate = {
166
+ resource: "https://api.known.in/v1/quote",
167
+ host: "api.known.in",
168
+ payTo: MERCHANT,
169
+ amount: 100000n, // 0.10 USDC
170
+ assetAddress: ASSET.address,
171
+ network: ASSET.network,
172
+ };
173
+ test("allows a payment inside every limit", () => {
174
+ const d = engine.evaluate(mandate(), candidate, baseCtx);
175
+ assert.equal(d.allowed, true);
176
+ assert.equal(d.reasons.length, 0);
177
+ });
178
+ test("blocks above the per-transaction cap with a readable reason", () => {
179
+ const d = engine.evaluate(mandate(), { ...candidate, amount: 600000n }, baseCtx);
180
+ assert.equal(d.allowed, false);
181
+ assert.ok(d.reasons.includes("PER_TX_CAP_EXCEEDED"));
182
+ assert.match(d.explanation, /per-payment cap/);
183
+ });
184
+ test("blocks once the rolling period budget is exhausted", () => {
185
+ // 10 x 0.495 = 4.95 spent; another 0.10 would reach 5.05 against a 5.00 cap.
186
+ const history = Array.from({ length: 10 }, (_, i) => mkSpend(`h${i}`, 495000n, 4300n, baseCtx.now - 100));
187
+ const d = engine.evaluate(mandate(), candidate, { ...baseCtx, history });
188
+ assert.equal(d.allowed, false);
189
+ assert.ok(d.reasons.includes("PERIOD_VALUE_CAP_EXCEEDED"));
190
+ });
191
+ test("spending exactly to the cap is allowed; one base unit more is not", () => {
192
+ // A ₹500/day cap must permit spending exactly ₹500, not ₹499.99.
193
+ const history = Array.from({ length: 10 }, (_, i) => mkSpend(`h${i}`, 490000n, 4300n, baseCtx.now - 100));
194
+ const atCap = engine.evaluate(mandate(), candidate, { ...baseCtx, history });
195
+ assert.equal(atCap.allowed, true, "exactly at the cap must be allowed");
196
+ const overCap = engine.evaluate(mandate(), { ...candidate, amount: 100001n }, { ...baseCtx, history });
197
+ assert.equal(overCap.allowed, false, "one base unit over the cap must block");
198
+ });
199
+ test("period window excludes spends older than periodSeconds", () => {
200
+ const m = mandate();
201
+ const stale = Array.from({ length: 20 }, (_, i) => mkSpend(`old${i}`, 495000n, 4300n, baseCtx.now - m.mandate.periodSeconds - 60));
202
+ const d = engine.evaluate(m, candidate, { ...baseCtx, history: stale });
203
+ assert.equal(d.allowed, true, "spends outside the rolling window must not count");
204
+ });
205
+ test("blocked and failed spends do not consume the budget", () => {
206
+ const history = Array.from({ length: 10 }, (_, i) => ({
207
+ ...mkSpend(`b${i}`, 495000n, 4300n, baseCtx.now - 100),
208
+ status: "blocked",
209
+ }));
210
+ const d = engine.evaluate(mandate(), candidate, { ...baseCtx, history });
211
+ assert.equal(d.allowed, true, "a refused payment must not eat the user's budget");
212
+ });
213
+ test("blocks a host outside the allowlist", () => {
214
+ const m = mandate({ allowedHosts: ["api.known.in"] });
215
+ const d = engine.evaluate(m, { ...candidate, host: "evil.example.com" }, baseCtx);
216
+ assert.equal(d.allowed, false);
217
+ assert.ok(d.reasons.includes("HOST_NOT_ALLOWLISTED"));
218
+ });
219
+ test("wildcard allowlist matches subdomains but not the attacker suffix trick", () => {
220
+ const m = mandate({ allowedHosts: ["*.trusted.in"] });
221
+ assert.equal(engine.evaluate(m, { ...candidate, host: "api.trusted.in" }, baseCtx).allowed, true);
222
+ assert.equal(engine.evaluate(m, { ...candidate, host: "trusted.in" }, baseCtx).allowed, true);
223
+ assert.equal(engine.evaluate(m, { ...candidate, host: "eviltrusted.in" }, baseCtx).allowed, false, "suffix match must not be a substring match");
224
+ });
225
+ test("blocks a retry loop before it drains the daily cap", () => {
226
+ const history = Array.from({ length: DEFAULT_POLICY_OPTIONS.maxRepeatsPerResource }, (_, i) => mkSpend(`r${i}`, 1000n, 10n, baseCtx.now - 5, candidate.resource));
227
+ const d = engine.evaluate(mandate(), candidate, { ...baseCtx, history });
228
+ assert.equal(d.allowed, false);
229
+ assert.ok(d.reasons.includes("RESOURCE_BURST_LIMIT"));
230
+ });
231
+ test("flags a price that jumped far above the historical median", () => {
232
+ const d = engine.evaluate(mandate(), candidate, {
233
+ ...baseCtx,
234
+ medianPriceForResource: 10000n, // usually 0.01 USDC, now asking 0.10
235
+ });
236
+ assert.equal(d.allowed, true);
237
+ assert.equal(d.requiresConfirmation, true);
238
+ assert.ok(d.reasons.includes("PRICE_ANOMALY"));
239
+ });
240
+ test("flags a first payment to an unknown host above the threshold", () => {
241
+ const d = engine.evaluate(mandate(), { ...candidate, host: "new.example.in", amount: 150000n }, baseCtx);
242
+ assert.equal(d.requiresConfirmation, true);
243
+ assert.ok(d.reasons.includes("NEW_HOST_ABOVE_THRESHOLD"));
244
+ });
245
+ test("does not flag a small first payment to an unknown host", () => {
246
+ // Trivial amounts to new hosts are the normal case for agent traffic;
247
+ // prompting on every one of them trains the user to click through.
248
+ const d = engine.evaluate(mandate(), { ...candidate, host: "new.example.in", amount: 1000n }, baseCtx);
249
+ assert.equal(d.allowed, true);
250
+ assert.equal(d.requiresConfirmation, false);
251
+ });
252
+ test("a flagged payment is still allowed, only gated", () => {
253
+ // requiresConfirmation must never silently become a hard block: the
254
+ // orchestrator decides, and a headless deployment defaults to refusing.
255
+ const d = engine.evaluate(mandate(), candidate, {
256
+ ...baseCtx,
257
+ medianPriceForResource: 10000n,
258
+ });
259
+ assert.equal(d.allowed, true);
260
+ assert.equal(d.requiresConfirmation, true);
261
+ });
262
+ test("blocks a revoked mandate", () => {
263
+ const m = { ...mandate(), revokedAt: baseCtx.now - 10 };
264
+ const d = engine.evaluate(m, candidate, baseCtx);
265
+ assert.equal(d.allowed, false);
266
+ assert.ok(d.reasons.includes("MANDATE_REVOKED"));
267
+ });
268
+ test("blocks a different asset even at a trivial amount", () => {
269
+ const d = engine.evaluate(mandate(), { ...candidate, assetAddress: "0x9999999999999999999999999999999999999999" }, baseCtx);
270
+ assert.equal(d.allowed, false);
271
+ assert.ok(d.reasons.includes("ASSET_NOT_AUTHORISED"));
272
+ });
273
+ });
274
+ describe("mandate builder guardrails", () => {
275
+ const base = {
276
+ subject: USER,
277
+ agent: AGENT,
278
+ purpose: "Pay for market data APIs",
279
+ maxPerTransaction: "0.50",
280
+ maxPerPeriod: "5.00",
281
+ period: "day",
282
+ maxTxPerPeriod: 50,
283
+ validForDays: 7,
284
+ asset: ASSET,
285
+ chainId: 8453,
286
+ };
287
+ test("rejects a mandate that never expires in practice", () => {
288
+ assert.throws(() => buildIntentMandate({ ...base, validForDays: 3650 }), MandateValidationError);
289
+ });
290
+ test("rejects per-tx caps that swallow the whole period budget", () => {
291
+ assert.throws(() => buildIntentMandate({ ...base, maxPerTransaction: "10", maxPerPeriod: "5" }), MandateValidationError);
292
+ });
293
+ test("rejects a vague purpose, since it is what the user reads when signing", () => {
294
+ assert.throws(() => buildIntentMandate({ ...base, purpose: "stuff" }), MandateValidationError);
295
+ });
296
+ test("allowlist commitment is order-independent and case-insensitive", () => {
297
+ assert.equal(listRoot(["API.Example.IN", "b.example.in"]), listRoot(["b.example.in", "api.example.in"]));
298
+ });
299
+ });
300
+ // ---------------------------------------------------------------------------
301
+ function mkSpend(id, amount, inrPaise, createdAt, resource = "https://api.known.in/v1/quote") {
302
+ return {
303
+ id,
304
+ userId: "u1",
305
+ intentHash: "0xabc",
306
+ resource,
307
+ host: new URL(resource).host,
308
+ payTo: MERCHANT,
309
+ amount,
310
+ asset: ASSET,
311
+ inrPaise,
312
+ fxRateScaled: inrToScaled(88),
313
+ status: "settled",
314
+ createdAt,
315
+ settledAt: createdAt,
316
+ };
317
+ }
318
+ // ---------------------------------------------------------------------------
319
+ describe("host matching", () => {
320
+ test("portless allowlist entry matches any port", () => {
321
+ assert.ok(matchesHostList("api.example.in:8443", ["api.example.in"]));
322
+ assert.ok(matchesHostList("api.example.in", ["api.example.in"]));
323
+ });
324
+ test("explicit port in the allowlist must match exactly", () => {
325
+ assert.ok(matchesHostList("api.example.in:8443", ["api.example.in:8443"]));
326
+ assert.equal(matchesHostList("api.example.in:9999", ["api.example.in:8443"]), false);
327
+ });
328
+ test("wildcard matches subdomains on a label boundary only", () => {
329
+ assert.ok(matchesHostList("a.example.in", ["*.example.in"]));
330
+ assert.ok(matchesHostList("deep.a.example.in", ["*.example.in"]));
331
+ assert.ok(matchesHostList("example.in", ["*.example.in"]));
332
+ assert.equal(matchesHostList("evilexample.in", ["*.example.in"]), false);
333
+ assert.equal(matchesHostList("evil-example.in", ["*.example.in"]), false);
334
+ assert.equal(matchesHostList("example.in.evil.com", ["*.example.in"]), false);
335
+ });
336
+ test("IPv6 literals survive port stripping", () => {
337
+ assert.ok(matchesHostList("[::1]:8080", ["[::1]"]));
338
+ assert.ok(matchesHostList("[::1]", ["[::1]"]));
339
+ });
340
+ test("matching is case-insensitive", () => {
341
+ assert.ok(matchesHostList("API.Example.IN", ["api.example.in"]));
342
+ });
343
+ });
344
+ describe("anomaly detection on unseen resources", () => {
345
+ const engine = new PolicyEngine();
346
+ const m = (() => {
347
+ const x = buildIntentMandate({
348
+ subject: USER, agent: AGENT, purpose: "Pay for market data APIs",
349
+ maxPerTransaction: "0.50", maxPerPeriod: "5.00", period: "day",
350
+ maxTxPerPeriod: 50, validForDays: 7, asset: ASSET, chainId: 8453,
351
+ });
352
+ return { mandate: x, hash: hashIntentMandate(x), signature: "0xdead" };
353
+ })();
354
+ const ctx = {
355
+ now: Math.floor(Date.now() / 1000),
356
+ history: [],
357
+ knownHosts: new Set(["api.known.in"]),
358
+ medianPriceOverall: 10000n, // user typically pays 0.01 USDC
359
+ };
360
+ const cand = {
361
+ resource: "https://api.known.in/v1/never-seen-before",
362
+ host: "api.known.in",
363
+ payTo: MERCHANT,
364
+ amount: 400000n, // 40x typical, but under the 0.50 per-tx cap
365
+ assetAddress: ASSET.address,
366
+ network: ASSET.network,
367
+ };
368
+ test("flags a first-time resource priced far above the user's norm", () => {
369
+ // This is the prompt-injection shape: a new URL, a big price, under the cap.
370
+ const d = engine.evaluate(m, cand, ctx);
371
+ assert.equal(d.allowed, true, "still legal under the mandate");
372
+ assert.equal(d.requiresConfirmation, true, "but must not go through silently");
373
+ assert.ok(d.reasons.includes("ABOVE_TYPICAL_SPEND"));
374
+ assert.match(d.explanation, /content it read/);
375
+ });
376
+ test("does not flag ordinary payments near the user's norm", () => {
377
+ const d = engine.evaluate(m, { ...cand, amount: 20000n }, ctx);
378
+ assert.equal(d.requiresConfirmation, false);
379
+ });
380
+ test("floor suppresses noise for users whose median is tiny", () => {
381
+ const d = engine.evaluate(m, { ...cand, amount: 30000n }, { ...ctx, medianPriceOverall: 100n });
382
+ assert.equal(d.requiresConfirmation, false, "0.03 USDC is below the anomaly floor");
383
+ });
384
+ test("no overall history means no overall anomaly signal", () => {
385
+ const d = engine.evaluate(m, cand, { ...ctx, medianPriceOverall: undefined });
386
+ assert.equal(d.reasons.includes("ABOVE_TYPICAL_SPEND"), false);
387
+ });
388
+ });
389
+ //# sourceMappingURL=tax-and-policy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-and-policy.test.js","sourceRoot":"","sources":["../../src/__tests__/tax-and-policy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-bot-auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bot-auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/web-bot-auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import assert from "node:assert/strict";
2
+ import { generateKeyPairSync } from "node:crypto";
3
+ import test from "node:test";
4
+ import { signWebBotAuth, signWebBotAuthDirectory, verifyWebBotAuth } from "../web-bot-auth.js";
5
+ test("signs and verifies a Web Bot Auth request with replay protection", async () => {
6
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
7
+ const headers = signWebBotAuth({ url: "https://api.example.test/v1/data", signatureAgent: "https://bot.example.test", keyId: "bot-key-1", privateKey, now: 1_800_000_000, nonce: "nonce-1" });
8
+ let consumed = false;
9
+ const verified = await verifyWebBotAuth({ url: "https://api.example.test/v1/data", headers, publicKey, expectedKeyId: "bot-key-1", now: 1_800_000_010, consumeNonce: () => { if (consumed)
10
+ return false; consumed = true; return true; } });
11
+ assert.equal(verified.nonce, "nonce-1");
12
+ await assert.rejects(() => verifyWebBotAuth({ url: "https://api.example.test/v1/data", headers, publicKey, now: 1_800_000_010, consumeNonce: () => false }), /already consumed/);
13
+ });
14
+ test("rejects unsafe agents, expiry, and altered request authority", async () => {
15
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
16
+ assert.throws(() => signWebBotAuth({ url: "http://api.example.test/data", signatureAgent: "https://bot.example.test", keyId: "k", privateKey }), /HTTPS/);
17
+ const headers = signWebBotAuth({ url: "https://api.example.test/data", signatureAgent: "https://bot.example.test", keyId: "k", privateKey, now: 1_800_000_000, expiresInSeconds: 1 });
18
+ await assert.rejects(() => verifyWebBotAuth({ url: "https://api.example.test/data", headers, publicKey, now: 1_800_000_001 }), /expired/);
19
+ await assert.rejects(() => verifyWebBotAuth({ url: "https://api.example.test/data", headers, publicKey, now: 1_800_000_002 }), /expired/);
20
+ let consumed = 0;
21
+ await assert.rejects(() => verifyWebBotAuth({ url: "https://other.example.test/data", headers, publicKey, now: 1_800_000_000, consumeNonce: () => { consumed += 1; return true; } }), /verification failed/);
22
+ assert.equal(consumed, 0);
23
+ const longLived = signWebBotAuth({ url: "https://api.example.test/data", signatureAgent: "https://bot.example.test", keyId: "k", privateKey, now: 1_800_000_000, expiresInSeconds: 301 });
24
+ await assert.rejects(() => verifyWebBotAuth({ url: "https://api.example.test/data", headers: longLived, publicKey }), /invalid timestamps/);
25
+ });
26
+ test("signs a Web Bot Auth key directory with the required directory tag", () => {
27
+ const { privateKey } = generateKeyPairSync("ed25519");
28
+ const headers = signWebBotAuthDirectory({ url: "https://bot.example.test/.well-known/http-message-signatures-directory", keyId: "bot-key-1", privateKey, now: 1_800_000_000, nonce: "directory-1" });
29
+ assert.match(headers["Signature-Input"], /;req\)/);
30
+ assert.match(headers["Signature-Input"], /tag="http-message-signatures-directory"/);
31
+ assert.match(headers.Signature, /^sig1=:[^:]+:$/);
32
+ });
33
+ //# sourceMappingURL=web-bot-auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bot-auth.test.js","sourceRoot":"","sources":["../../src/__tests__/web-bot-auth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=x402-solana.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x402-solana.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/x402-solana.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,155 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createServer } from "node:http";
4
+ import { generateKeyPairSigner } from "@solana/kit";
5
+ import { X402Client, PaymentDeclinedError } from "../x402/client.js";
6
+ const SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
7
+ const SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
8
+ test("reads a Solana v2 challenge from PAYMENT-REQUIRED and selects it", async () => {
9
+ const challenge = {
10
+ x402Version: 2,
11
+ resource: {
12
+ url: "http://127.0.0.1:4021/data",
13
+ description: "Solana-priced data",
14
+ mimeType: "application/json",
15
+ },
16
+ accepts: [{
17
+ scheme: "exact",
18
+ network: SOLANA_MAINNET,
19
+ amount: "1000",
20
+ asset: SOLANA_USDC,
21
+ payTo: "9MkM5Ra48KFcSv9HDQ8sb3C3r5HjouEwGQ6sp1cx1vde",
22
+ maxTimeoutSeconds: 300,
23
+ extra: { feePayer: "D6ZhtNQ5nT9ZnTHUbqXZsTx5MH2rPFiBBggX4hY1WePM" },
24
+ }],
25
+ };
26
+ let authorisedNetwork;
27
+ const client = new X402Client({
28
+ allowedNetworks: [SOLANA_MAINNET],
29
+ allowedAssets: [SOLANA_USDC],
30
+ absoluteMaxPerPayment: 10000n,
31
+ outboundPolicy: { allowPrivateNetworks: true },
32
+ fetchImpl: async () => new Response("{}", {
33
+ status: 402,
34
+ headers: {
35
+ "content-type": "application/json",
36
+ "payment-required": Buffer.from(JSON.stringify(challenge)).toString("base64"),
37
+ },
38
+ }),
39
+ authorise: async ({ requirement }) => {
40
+ authorisedNetwork = requirement.network;
41
+ return { approved: true };
42
+ },
43
+ });
44
+ await assert.rejects(() => client.fetch("http://127.0.0.1:4021/data"), (error) => error instanceof PaymentDeclinedError && error.reason === "SIGNER_UNAVAILABLE");
45
+ assert.equal(authorisedNetwork, SOLANA_MAINNET);
46
+ const quote = await client.quote("http://127.0.0.1:4021/data");
47
+ assert.equal(quote?.accepts[0].maxAmountRequired, "1000");
48
+ assert.equal(quote?.accepts[0].extra?.feePayer, challenge.accepts[0].extra.feePayer);
49
+ });
50
+ test("treats Solana asset addresses as case-sensitive", async () => {
51
+ const challenge = {
52
+ x402Version: 2,
53
+ accepts: [{
54
+ scheme: "exact",
55
+ network: SOLANA_MAINNET,
56
+ amount: "1000",
57
+ asset: SOLANA_USDC,
58
+ payTo: "9MkM5Ra48KFcSv9HDQ8sb3C3r5HjouEwGQ6sp1cx1vde",
59
+ maxTimeoutSeconds: 300,
60
+ extra: { feePayer: "D6ZhtNQ5nT9ZnTHUbqXZsTx5MH2rPFiBBggX4hY1WePM" },
61
+ }],
62
+ };
63
+ const client = new X402Client({
64
+ allowedNetworks: [SOLANA_MAINNET],
65
+ allowedAssets: [SOLANA_USDC.toLowerCase()],
66
+ absoluteMaxPerPayment: 10000n,
67
+ outboundPolicy: { allowPrivateNetworks: true },
68
+ fetchImpl: async () => new Response(JSON.stringify(challenge), { status: 402 }),
69
+ authorise: async () => ({ approved: true }),
70
+ });
71
+ await assert.rejects(() => client.fetch("http://127.0.0.1:4021/data"), (error) => error instanceof PaymentDeclinedError && error.reason === "NO_ACCEPTABLE_REQUIREMENT");
72
+ });
73
+ test("creates and submits an official exact-SVM payment payload", async () => {
74
+ const mint = Buffer.alloc(82);
75
+ mint[44] = 6; // decimals
76
+ mint[45] = 1; // initialized
77
+ const rpc = createServer((req, res) => {
78
+ const chunks = [];
79
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
80
+ req.on("end", () => {
81
+ const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
82
+ const result = request.method === "getAccountInfo"
83
+ ? {
84
+ context: { slot: 1 },
85
+ value: {
86
+ data: [mint.toString("base64"), "base64"],
87
+ executable: false,
88
+ lamports: 1,
89
+ owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
90
+ rentEpoch: 0,
91
+ space: mint.length,
92
+ },
93
+ }
94
+ : { context: { slot: 1 }, value: { blockhash: "11111111111111111111111111111111", lastValidBlockHeight: 100 } };
95
+ res.setHeader("content-type", "application/json");
96
+ res.end(JSON.stringify({ jsonrpc: "2.0", id: request.id, result }));
97
+ });
98
+ });
99
+ await new Promise((resolve) => rpc.listen(0, "127.0.0.1", resolve));
100
+ const address = rpc.address();
101
+ assert.ok(address && typeof address === "object");
102
+ try {
103
+ const signer = await generateKeyPairSigner();
104
+ const challenge = {
105
+ x402Version: 2,
106
+ resource: { url: "http://127.0.0.1:4021/data", description: "Solana data", mimeType: "application/json" },
107
+ accepts: [{
108
+ scheme: "exact",
109
+ network: SOLANA_MAINNET,
110
+ amount: "1000",
111
+ asset: SOLANA_USDC,
112
+ payTo: "9MkM5Ra48KFcSv9HDQ8sb3C3r5HjouEwGQ6sp1cx1vde",
113
+ maxTimeoutSeconds: 300,
114
+ extra: {
115
+ feePayer: "D6ZhtNQ5nT9ZnTHUbqXZsTx5MH2rPFiBBggX4hY1WePM",
116
+ recentBlockhash: "11111111111111111111111111111111",
117
+ },
118
+ }],
119
+ };
120
+ let calls = 0;
121
+ let submitted;
122
+ const settlement = { success: true, transaction: "5KtPn3C2ExampleSolanaSignature", network: SOLANA_MAINNET, payer: String(signer.address) };
123
+ const client = new X402Client({
124
+ svmSigner: signer,
125
+ svmRpcUrl: `http://127.0.0.1:${address.port}`,
126
+ allowedNetworks: [SOLANA_MAINNET],
127
+ allowedAssets: [SOLANA_USDC],
128
+ absoluteMaxPerPayment: 10000n,
129
+ outboundPolicy: { allowPrivateNetworks: true },
130
+ authorise: async () => ({ approved: true }),
131
+ fetchImpl: async (_url, init) => {
132
+ calls += 1;
133
+ if (calls === 1) {
134
+ return new Response("{}", { status: 402, headers: { "payment-required": Buffer.from(JSON.stringify(challenge)).toString("base64") } });
135
+ }
136
+ const header = new Headers(init?.headers).get("payment-signature");
137
+ assert.ok(header);
138
+ submitted = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
139
+ return new Response("paid", {
140
+ status: 200,
141
+ headers: { "payment-response": Buffer.from(JSON.stringify(settlement)).toString("base64") },
142
+ });
143
+ },
144
+ });
145
+ const result = await client.fetch(challenge.resource.url);
146
+ assert.equal(result.paid, true);
147
+ assert.equal(result.settlement?.transaction, settlement.transaction);
148
+ assert.equal((submitted?.accepted).network, SOLANA_MAINNET);
149
+ assert.equal(typeof (submitted?.payload).transaction, "string");
150
+ }
151
+ finally {
152
+ await new Promise((resolve, reject) => rpc.close((error) => error ? reject(error) : resolve()));
153
+ }
154
+ });
155
+ //# sourceMappingURL=x402-solana.test.js.map