@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.
- package/dist/__tests__/acp.test.d.ts +2 -0
- package/dist/__tests__/acp.test.d.ts.map +1 -0
- package/dist/__tests__/acp.test.js +16 -0
- package/dist/__tests__/acp.test.js.map +1 -0
- package/dist/__tests__/ap2-official.test.d.ts +2 -0
- package/dist/__tests__/ap2-official.test.d.ts.map +1 -0
- package/dist/__tests__/ap2-official.test.js +49 -0
- package/dist/__tests__/ap2-official.test.js.map +1 -0
- package/dist/__tests__/mpp.test.d.ts +2 -0
- package/dist/__tests__/mpp.test.d.ts.map +1 -0
- package/dist/__tests__/mpp.test.js +42 -0
- package/dist/__tests__/mpp.test.js.map +1 -0
- package/dist/__tests__/outbound.test.d.ts +2 -0
- package/dist/__tests__/outbound.test.d.ts.map +1 -0
- package/dist/__tests__/outbound.test.js +23 -0
- package/dist/__tests__/outbound.test.js.map +1 -0
- package/dist/__tests__/protocols.test.d.ts +2 -0
- package/dist/__tests__/protocols.test.d.ts.map +1 -0
- package/dist/__tests__/protocols.test.js +16 -0
- package/dist/__tests__/protocols.test.js.map +1 -0
- package/dist/__tests__/providers.test.d.ts +2 -0
- package/dist/__tests__/providers.test.d.ts.map +1 -0
- package/dist/__tests__/providers.test.js +41 -0
- package/dist/__tests__/providers.test.js.map +1 -0
- package/dist/__tests__/reconciliation.test.d.ts +2 -0
- package/dist/__tests__/reconciliation.test.d.ts.map +1 -0
- package/dist/__tests__/reconciliation.test.js +35 -0
- package/dist/__tests__/reconciliation.test.js.map +1 -0
- package/dist/__tests__/spending-controls.test.d.ts +2 -0
- package/dist/__tests__/spending-controls.test.d.ts.map +1 -0
- package/dist/__tests__/spending-controls.test.js +47 -0
- package/dist/__tests__/spending-controls.test.js.map +1 -0
- package/dist/__tests__/tax-and-policy.test.d.ts +7 -0
- package/dist/__tests__/tax-and-policy.test.d.ts.map +1 -0
- package/dist/__tests__/tax-and-policy.test.js +389 -0
- package/dist/__tests__/tax-and-policy.test.js.map +1 -0
- package/dist/__tests__/web-bot-auth.test.d.ts +2 -0
- package/dist/__tests__/web-bot-auth.test.d.ts.map +1 -0
- package/dist/__tests__/web-bot-auth.test.js +33 -0
- package/dist/__tests__/web-bot-auth.test.js.map +1 -0
- package/dist/__tests__/x402-solana.test.d.ts +2 -0
- package/dist/__tests__/x402-solana.test.d.ts.map +1 -0
- package/dist/__tests__/x402-solana.test.js +155 -0
- package/dist/__tests__/x402-solana.test.js.map +1 -0
- package/dist/acp.d.ts +28 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +49 -0
- package/dist/acp.js.map +1 -0
- package/dist/ap2/mandates.d.ts +319 -0
- package/dist/ap2/mandates.d.ts.map +1 -0
- package/dist/ap2/mandates.js +214 -0
- package/dist/ap2/mandates.js.map +1 -0
- package/dist/ap2/official.d.ts +156 -0
- package/dist/ap2/official.d.ts.map +1 -0
- package/dist/ap2/official.js +222 -0
- package/dist/ap2/official.js.map +1 -0
- package/dist/ap2/policy.d.ts +93 -0
- package/dist/ap2/policy.d.ts.map +1 -0
- package/dist/ap2/policy.js +216 -0
- package/dist/ap2/policy.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/india/fx.d.ts +59 -0
- package/dist/india/fx.d.ts.map +1 -0
- package/dist/india/fx.js +93 -0
- package/dist/india/fx.js.map +1 -0
- package/dist/india/vda-tax.d.ts +151 -0
- package/dist/india/vda-tax.d.ts.map +1 -0
- package/dist/india/vda-tax.js +226 -0
- package/dist/india/vda-tax.js.map +1 -0
- package/dist/mandate-builder.d.ts +102 -0
- package/dist/mandate-builder.d.ts.map +1 -0
- package/dist/mandate-builder.js +99 -0
- package/dist/mandate-builder.js.map +1 -0
- package/dist/mpp.d.ts +53 -0
- package/dist/mpp.d.ts.map +1 -0
- package/dist/mpp.js +164 -0
- package/dist/mpp.js.map +1 -0
- package/dist/orchestrator.d.ts +115 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +351 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/postgres-store.d.ts +60 -0
- package/dist/postgres-store.d.ts.map +1 -0
- package/dist/postgres-store.js +441 -0
- package/dist/postgres-store.js.map +1 -0
- package/dist/protocols.d.ts +18 -0
- package/dist/protocols.d.ts.map +1 -0
- package/dist/protocols.js +24 -0
- package/dist/protocols.js.map +1 -0
- package/dist/reconciliation.d.ts +27 -0
- package/dist/reconciliation.d.ts.map +1 -0
- package/dist/reconciliation.js +38 -0
- package/dist/reconciliation.js.map +1 -0
- package/dist/store.d.ts +110 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +369 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -0
- package/dist/util/canonical.d.ts +9 -0
- package/dist/util/canonical.d.ts.map +1 -0
- package/dist/util/canonical.js +35 -0
- package/dist/util/canonical.js.map +1 -0
- package/dist/wallet/provider.d.ts +118 -0
- package/dist/wallet/provider.d.ts.map +1 -0
- package/dist/wallet/provider.js +160 -0
- package/dist/wallet/provider.js.map +1 -0
- package/dist/wallet/providers.d.ts +107 -0
- package/dist/wallet/providers.d.ts.map +1 -0
- package/dist/wallet/providers.js +188 -0
- package/dist/wallet/providers.js.map +1 -0
- package/dist/web-bot-auth.d.ts +54 -0
- package/dist/web-bot-auth.d.ts.map +1 -0
- package/dist/web-bot-auth.js +112 -0
- package/dist/web-bot-auth.js.map +1 -0
- package/dist/x402/client.d.ts +101 -0
- package/dist/x402/client.d.ts.map +1 -0
- package/dist/x402/client.js +291 -0
- package/dist/x402/client.js.map +1 -0
- package/dist/x402/outbound.d.ts +15 -0
- package/dist/x402/outbound.d.ts.map +1 -0
- package/dist/x402/outbound.js +59 -0
- package/dist/x402/outbound.js.map +1 -0
- package/dist/x402/types.d.ts +80 -0
- package/dist/x402/types.d.ts.map +1 -0
- package/dist/x402/types.js +2 -0
- package/dist/x402/types.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|