s402 0.1.5 → 0.1.7
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/CHANGELOG.md +50 -0
- package/README.md +9 -6
- package/SECURITY.md +1 -1
- package/dist/compat.mjs +7 -0
- package/dist/errors.d.mts +1 -0
- package/dist/errors.mjs +6 -1
- package/dist/http.mjs +12 -3
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +75 -16
- package/dist/types.d.mts +33 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.6] - 2026-02-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Security audit patches** (15 true positives, H-1 through M-6, L-2):
|
|
13
|
+
- H-1: `process()` wraps `resolveScheme`/`verify`/`settle` in try/catch — unhandled rejections no longer crash server middleware; returns `{success: false}` instead
|
|
14
|
+
- H-2: In-flight dedup `Set` on `process()` — concurrent identical payloads can no longer both reach `scheme.settle()`
|
|
15
|
+
- H-3: `Promise.race()` timeouts — 5s for verify, 15s for settle — prevents hanging RPC calls from exhausting the event loop
|
|
16
|
+
- M-1: `facilitatorUrl` in x402 compat now validated via `new URL()` — rejects `javascript:`, `file://`, and other non-http(s) schemes (SSRF guard)
|
|
17
|
+
- M-2: `isValidAmount` → `isValidU64Amount` on decode — rejects amounts above u64 max at the wire boundary
|
|
18
|
+
- M-5: Settle catch returns `SETTLEMENT_FAILED` (`retryable: true`) instead of `VERIFICATION_FAILED` (`retryable: false`) — agents can now retry on transient RPC failures
|
|
19
|
+
- M-6: `payTo` validation tightened from `startsWith('0x')` to full Sui address regex `/^0x[0-9a-fA-F]{64}$/` — rejects `'0x'` alone and non-hex chars
|
|
20
|
+
- L-2: `expiresAt` guard extended to reject `<= 0` — negative timestamps and zero are now invalid at decode time
|
|
21
|
+
|
|
22
|
+
## [0.1.5] - 2026-02-19
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Author updated to SweeInc brand name
|
|
27
|
+
- Renamed `@sweepay/*` → `@sweefi/*` across all documentation
|
|
28
|
+
|
|
29
|
+
## [0.1.4] - 2026-02-18
|
|
30
|
+
|
|
31
|
+
_Version bump for npm publish after license change._
|
|
32
|
+
|
|
33
|
+
## [0.1.3] - 2026-02-18
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- License changed from MIT to Apache-2.0
|
|
38
|
+
- Documentation consolidated (removed codebase-tour, added complete guide)
|
|
39
|
+
- Updated tagline to "HTTP 402 payment protocol"
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- CI and npm version badges to README
|
|
44
|
+
|
|
45
|
+
## [0.1.2] - 2026-02-16
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- CI workflow (GitHub Actions) with tag-based npm releases
|
|
50
|
+
- Separate build job for Node 22
|
|
51
|
+
|
|
8
52
|
## [0.1.1] - 2026-02-16
|
|
9
53
|
|
|
10
54
|
### Fixed
|
|
@@ -33,4 +77,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
77
|
- Property-based fuzz testing via fast-check
|
|
34
78
|
- 207 tests, zero runtime dependencies
|
|
35
79
|
|
|
80
|
+
[0.1.6]: https://github.com/s402-protocol/core/compare/v0.1.5...v0.1.6
|
|
81
|
+
[0.1.5]: https://github.com/s402-protocol/core/compare/v0.1.4...v0.1.5
|
|
82
|
+
[0.1.4]: https://github.com/s402-protocol/core/compare/v0.1.3...v0.1.4
|
|
83
|
+
[0.1.3]: https://github.com/s402-protocol/core/compare/v0.1.2...v0.1.3
|
|
84
|
+
[0.1.2]: https://github.com/s402-protocol/core/compare/v0.1.1...v0.1.2
|
|
85
|
+
[0.1.1]: https://github.com/s402-protocol/core/compare/v0.1.0...v0.1.1
|
|
36
86
|
[0.1.0]: https://github.com/s402-protocol/core/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -45,11 +45,14 @@ s402 <-- You are here. Protocol spec. Zero runtime deps.
|
|
|
45
45
|
|-- Compat Optional x402 migration aid
|
|
46
46
|
|-- Errors Typed error codes with recovery hints
|
|
47
47
|
|
|
|
48
|
-
@sweefi/sui <-- Sui
|
|
49
|
-
@sweefi/
|
|
48
|
+
@sweefi/sui <-- Sui adapter: 40 PTB builders + SuiPaymentAdapter + createS402Client
|
|
49
|
+
@sweefi/server <-- Chain-agnostic HTTP: s402Gate middleware + wrapFetchWithS402
|
|
50
|
+
@sweefi/ui-core <-- State machine + PaymentAdapter interface
|
|
51
|
+
@sweefi/vue <-- Vue 3 plugin + useSweefiPayment() composable
|
|
52
|
+
@sweefi/react <-- React context + useSweefiPayment() hook
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
`s402` is **chain-agnostic protocol plumbing**. It defines _what_ gets sent over HTTP. The Sui-specific _how_
|
|
55
|
+
`s402` is **chain-agnostic protocol plumbing**. It defines _what_ gets sent over HTTP. The Sui-specific _how_ lives in [`@sweefi/sui`](https://www.npmjs.com/package/@sweefi/sui).
|
|
53
56
|
|
|
54
57
|
## Payment Schemes
|
|
55
58
|
|
|
@@ -158,7 +161,7 @@ const requirements: s402PaymentRequirements = {
|
|
|
158
161
|
network: 'sui:mainnet',
|
|
159
162
|
asset: '0x2::sui::SUI',
|
|
160
163
|
amount: '1000000', // 0.001 SUI in MIST
|
|
161
|
-
payTo: '
|
|
164
|
+
payTo: '0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
162
165
|
};
|
|
163
166
|
|
|
164
167
|
response.status = 402;
|
|
@@ -258,7 +261,7 @@ import type {
|
|
|
258
261
|
} from 's402';
|
|
259
262
|
```
|
|
260
263
|
|
|
261
|
-
The reference Sui implementation of all five schemes
|
|
264
|
+
The reference Sui implementation of all five schemes is available in [`@sweefi/sui`](https://www.npmjs.com/package/@sweefi/sui).
|
|
262
265
|
|
|
263
266
|
## Wire Format
|
|
264
267
|
|
|
@@ -303,7 +306,7 @@ const requirements: s402PaymentRequirements = {
|
|
|
303
306
|
network: 'sui:mainnet',
|
|
304
307
|
asset: '0x2::sui::SUI',
|
|
305
308
|
amount: '1000000',
|
|
306
|
-
payTo: '
|
|
309
|
+
payTo: '0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
307
310
|
expiresAt: Date.now() + 5 * 60 * 1000, // 5-minute window
|
|
308
311
|
};
|
|
309
312
|
```
|
package/SECURITY.md
CHANGED
|
@@ -15,7 +15,7 @@ You will receive an acknowledgment within 48 hours. We aim to provide a fix or m
|
|
|
15
15
|
|
|
16
16
|
This policy covers the `s402` npm package — the protocol types, HTTP encoding/decoding, scheme registry, and compat layer.
|
|
17
17
|
|
|
18
|
-
Security issues in downstream packages (`@sweefi/sui`, `@sweefi/
|
|
18
|
+
Security issues in downstream packages (`@sweefi/sui`, `@sweefi/server`, `@sweefi/ui-core`, etc.) should be reported to the same email.
|
|
19
19
|
|
|
20
20
|
## What qualifies
|
|
21
21
|
|
package/dist/compat.mjs
CHANGED
|
@@ -12,6 +12,13 @@ function fromX402Requirements(x402) {
|
|
|
12
12
|
const amount = x402.amount ?? x402.maxAmountRequired;
|
|
13
13
|
if (!amount) throw new s402Error("INVALID_PAYLOAD", "x402 requirements missing both \"amount\" (V2) and \"maxAmountRequired\" (V1)");
|
|
14
14
|
if (!isValidAmount(amount)) throw new s402Error("INVALID_PAYLOAD", `Invalid amount "${amount}": must be a non-negative integer string`);
|
|
15
|
+
if (x402.facilitatorUrl !== void 0) try {
|
|
16
|
+
const url = new URL(x402.facilitatorUrl);
|
|
17
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") throw new s402Error("INVALID_PAYLOAD", `facilitatorUrl must use https:// or http://, got "${url.protocol}"`);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (e instanceof s402Error) throw e;
|
|
20
|
+
throw new s402Error("INVALID_PAYLOAD", "facilitatorUrl is not a valid URL");
|
|
21
|
+
}
|
|
15
22
|
return {
|
|
16
23
|
s402Version: S402_VERSION,
|
|
17
24
|
accepts: ["exact"],
|
package/dist/errors.d.mts
CHANGED
|
@@ -22,6 +22,7 @@ declare const s402ErrorCode: {
|
|
|
22
22
|
readonly SIGNATURE_INVALID: "SIGNATURE_INVALID";
|
|
23
23
|
readonly REQUIREMENTS_EXPIRED: "REQUIREMENTS_EXPIRED";
|
|
24
24
|
readonly VERIFICATION_FAILED: "VERIFICATION_FAILED";
|
|
25
|
+
readonly SETTLEMENT_FAILED: "SETTLEMENT_FAILED";
|
|
25
26
|
};
|
|
26
27
|
type s402ErrorCodeType = (typeof s402ErrorCode)[keyof typeof s402ErrorCode];
|
|
27
28
|
interface s402ErrorInfo {
|
package/dist/errors.mjs
CHANGED
|
@@ -21,7 +21,8 @@ const s402ErrorCode = {
|
|
|
21
21
|
NETWORK_MISMATCH: "NETWORK_MISMATCH",
|
|
22
22
|
SIGNATURE_INVALID: "SIGNATURE_INVALID",
|
|
23
23
|
REQUIREMENTS_EXPIRED: "REQUIREMENTS_EXPIRED",
|
|
24
|
-
VERIFICATION_FAILED: "VERIFICATION_FAILED"
|
|
24
|
+
VERIFICATION_FAILED: "VERIFICATION_FAILED",
|
|
25
|
+
SETTLEMENT_FAILED: "SETTLEMENT_FAILED"
|
|
25
26
|
};
|
|
26
27
|
/** Error recovery hints for each error code */
|
|
27
28
|
const ERROR_HINTS = {
|
|
@@ -80,6 +81,10 @@ const ERROR_HINTS = {
|
|
|
80
81
|
VERIFICATION_FAILED: {
|
|
81
82
|
retryable: false,
|
|
82
83
|
suggestedAction: "Check payment amount and transaction structure"
|
|
84
|
+
},
|
|
85
|
+
SETTLEMENT_FAILED: {
|
|
86
|
+
retryable: true,
|
|
87
|
+
suggestedAction: "Transient RPC failure during settlement — retry in a few seconds"
|
|
83
88
|
}
|
|
84
89
|
};
|
|
85
90
|
/**
|
package/dist/http.mjs
CHANGED
|
@@ -341,10 +341,12 @@ function validateRequirementsShape(obj) {
|
|
|
341
341
|
if (typeof record.network !== "string") missing.push("network (string)");
|
|
342
342
|
if (typeof record.asset !== "string") missing.push("asset (string)");
|
|
343
343
|
if (typeof record.amount !== "string") missing.push("amount (string)");
|
|
344
|
-
else if (!
|
|
344
|
+
else if (!isValidU64Amount(record.amount)) throw new s402Error("INVALID_PAYLOAD", `Invalid amount "${record.amount}": must be a non-negative integer string within u64 range`);
|
|
345
345
|
if (typeof record.payTo !== "string") missing.push("payTo (string)");
|
|
346
|
-
else if (
|
|
346
|
+
else if (!/^0x[0-9a-fA-F]{64}$/.test(record.payTo)) throw new s402Error("INVALID_PAYLOAD", `payTo must be a 32-byte Sui address (0x + 64 hex chars), got "${record.payTo.substring(0, 20)}..."`);
|
|
347
347
|
if (missing.length > 0) throw new s402Error("INVALID_PAYLOAD", `Malformed payment requirements: missing ${missing.join(", ")}`);
|
|
348
|
+
if (/[\x00-\x1f\x7f]/.test(record.network)) throw new s402Error("INVALID_PAYLOAD", "network contains control characters");
|
|
349
|
+
if (/[\x00-\x1f\x7f]/.test(record.asset)) throw new s402Error("INVALID_PAYLOAD", "asset contains control characters");
|
|
348
350
|
if (Array.isArray(record.accepts) && record.accepts.length === 0) throw new s402Error("INVALID_PAYLOAD", "accepts array must contain at least one scheme");
|
|
349
351
|
const accepts = record.accepts;
|
|
350
352
|
for (const scheme of accepts) if (typeof scheme !== "string") throw new s402Error("INVALID_PAYLOAD", `Invalid entry in accepts array: expected string, got ${typeof scheme}`);
|
|
@@ -352,7 +354,14 @@ function validateRequirementsShape(obj) {
|
|
|
352
354
|
if (typeof record.protocolFeeBps !== "number" || !Number.isFinite(record.protocolFeeBps) || !Number.isInteger(record.protocolFeeBps) || record.protocolFeeBps < 0 || record.protocolFeeBps > 1e4) throw new s402Error("INVALID_PAYLOAD", `protocolFeeBps must be an integer between 0 and 10000, got ${record.protocolFeeBps}`);
|
|
353
355
|
}
|
|
354
356
|
if (record.expiresAt !== void 0) {
|
|
355
|
-
if (typeof record.expiresAt !== "number" || !Number.isFinite(record.expiresAt)) throw new s402Error("INVALID_PAYLOAD", `expiresAt must be a finite number (Unix timestamp ms), got ${
|
|
357
|
+
if (typeof record.expiresAt !== "number" || !Number.isFinite(record.expiresAt) || record.expiresAt <= 0) throw new s402Error("INVALID_PAYLOAD", `expiresAt must be a positive finite number (Unix timestamp ms), got ${record.expiresAt}`);
|
|
358
|
+
}
|
|
359
|
+
if (record.protocolFeeAddress !== void 0) {
|
|
360
|
+
if (typeof record.protocolFeeAddress !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(record.protocolFeeAddress)) throw new s402Error("INVALID_PAYLOAD", `protocolFeeAddress must be a 32-byte Sui address (0x + 64 hex chars), got "${String(record.protocolFeeAddress).substring(0, 20)}..."`);
|
|
361
|
+
}
|
|
362
|
+
if (record.facilitatorUrl !== void 0) {
|
|
363
|
+
if (typeof record.facilitatorUrl !== "string") throw new s402Error("INVALID_PAYLOAD", `facilitatorUrl must be a string, got ${typeof record.facilitatorUrl}`);
|
|
364
|
+
if (/[\x00-\x1f\x7f]/.test(record.facilitatorUrl)) throw new s402Error("INVALID_PAYLOAD", "facilitatorUrl contains control characters (potential header injection)");
|
|
356
365
|
}
|
|
357
366
|
validateSubObjects(record);
|
|
358
367
|
}
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -128,6 +128,7 @@ var s402ResourceServer = class {
|
|
|
128
128
|
//#region src/facilitator.ts
|
|
129
129
|
var s402Facilitator = class {
|
|
130
130
|
schemes = /* @__PURE__ */ new Map();
|
|
131
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
131
132
|
/**
|
|
132
133
|
* Register a scheme-specific facilitator for a network.
|
|
133
134
|
*/
|
|
@@ -157,7 +158,18 @@ var s402Facilitator = class {
|
|
|
157
158
|
invalidReason: `Scheme "${payload.scheme}" is not accepted by these requirements. Accepted: [${requirements.accepts.join(", ")}]`
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
|
-
|
|
161
|
+
try {
|
|
162
|
+
return this.resolveScheme(payload.scheme, requirements.network).verify(payload, requirements);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
if (e instanceof s402Error) return {
|
|
165
|
+
valid: false,
|
|
166
|
+
invalidReason: e.message
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
valid: false,
|
|
170
|
+
invalidReason: "Unexpected error resolving scheme"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
161
173
|
}
|
|
162
174
|
/**
|
|
163
175
|
* Settle a payment by dispatching to the correct scheme.
|
|
@@ -183,7 +195,20 @@ var s402Facilitator = class {
|
|
|
183
195
|
errorCode: "SCHEME_NOT_SUPPORTED"
|
|
184
196
|
};
|
|
185
197
|
}
|
|
186
|
-
|
|
198
|
+
try {
|
|
199
|
+
return this.resolveScheme(payload.scheme, requirements.network).settle(payload, requirements);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
if (e instanceof s402Error) return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: e.message,
|
|
204
|
+
errorCode: e.code
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: "Unexpected error resolving scheme",
|
|
209
|
+
errorCode: "SCHEME_NOT_SUPPORTED"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
187
212
|
}
|
|
188
213
|
/**
|
|
189
214
|
* Expiration-guarded verify + settle in one call.
|
|
@@ -213,26 +238,60 @@ var s402Facilitator = class {
|
|
|
213
238
|
errorCode: "SCHEME_NOT_SUPPORTED"
|
|
214
239
|
};
|
|
215
240
|
}
|
|
216
|
-
|
|
217
|
-
const verifyResult = await scheme.verify(payload, requirements);
|
|
218
|
-
if (!verifyResult.valid) return {
|
|
219
|
-
success: false,
|
|
220
|
-
error: verifyResult.invalidReason ?? "Payment verification failed",
|
|
221
|
-
errorCode: "VERIFICATION_FAILED"
|
|
222
|
-
};
|
|
223
|
-
if (typeof requirements.expiresAt === "number" && Date.now() > requirements.expiresAt) return {
|
|
224
|
-
success: false,
|
|
225
|
-
error: `Payment requirements expired during verification at ${new Date(requirements.expiresAt).toISOString()}`,
|
|
226
|
-
errorCode: "REQUIREMENTS_EXPIRED"
|
|
227
|
-
};
|
|
241
|
+
let scheme;
|
|
228
242
|
try {
|
|
229
|
-
|
|
243
|
+
scheme = this.resolveScheme(payload.scheme, requirements.network);
|
|
230
244
|
} catch (e) {
|
|
245
|
+
if (e instanceof s402Error) return {
|
|
246
|
+
success: false,
|
|
247
|
+
error: e.message,
|
|
248
|
+
errorCode: e.code
|
|
249
|
+
};
|
|
231
250
|
return {
|
|
232
251
|
success: false,
|
|
233
|
-
error:
|
|
252
|
+
error: "Failed to resolve payment scheme",
|
|
253
|
+
errorCode: "SCHEME_NOT_SUPPORTED"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const dedupeKey = JSON.stringify(payload);
|
|
257
|
+
if (this.inFlight.has(dedupeKey)) return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: "Duplicate payment request already in flight",
|
|
260
|
+
errorCode: "INVALID_PAYLOAD"
|
|
261
|
+
};
|
|
262
|
+
this.inFlight.add(dedupeKey);
|
|
263
|
+
try {
|
|
264
|
+
let verifyResult;
|
|
265
|
+
try {
|
|
266
|
+
verifyResult = await Promise.race([scheme.verify(payload, requirements), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Verification timed out after 5s")), 5e3))]);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
return {
|
|
269
|
+
success: false,
|
|
270
|
+
error: e instanceof Error ? e.message : "Verification threw an unexpected error",
|
|
271
|
+
errorCode: "VERIFICATION_FAILED"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (!verifyResult.valid) return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: verifyResult.invalidReason ?? "Payment verification failed",
|
|
234
277
|
errorCode: "VERIFICATION_FAILED"
|
|
235
278
|
};
|
|
279
|
+
if (typeof requirements.expiresAt === "number" && Date.now() > requirements.expiresAt) return {
|
|
280
|
+
success: false,
|
|
281
|
+
error: `Payment requirements expired during verification at ${new Date(requirements.expiresAt).toISOString()}`,
|
|
282
|
+
errorCode: "REQUIREMENTS_EXPIRED"
|
|
283
|
+
};
|
|
284
|
+
try {
|
|
285
|
+
return await Promise.race([scheme.settle(payload, requirements), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Settlement timed out after 15s")), 15e3))]);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
error: e instanceof Error ? e.message : "Settlement failed with an unexpected error",
|
|
290
|
+
errorCode: "SETTLEMENT_FAILED"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} finally {
|
|
294
|
+
this.inFlight.delete(dedupeKey);
|
|
236
295
|
}
|
|
237
296
|
}
|
|
238
297
|
/**
|
package/dist/types.d.mts
CHANGED
|
@@ -28,9 +28,27 @@ interface s402PaymentRequirements {
|
|
|
28
28
|
facilitatorUrl?: string;
|
|
29
29
|
/** AP2 mandate requirements (if agent spending authorization is needed) */
|
|
30
30
|
mandate?: s402MandateRequirements;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Protocol fee in basis points (0-10000). **Advisory only.**
|
|
33
|
+
*
|
|
34
|
+
* This field is a transparency hint for the client's UI — it lets the payer
|
|
35
|
+
* see the total cost before committing. It is NOT the source of truth for
|
|
36
|
+
* settlement math. The authoritative fee rate is owned by the Facilitator
|
|
37
|
+
* (configured in its ProtocolState or equivalent on-chain object) and
|
|
38
|
+
* enforced at the smart contract level.
|
|
39
|
+
*
|
|
40
|
+
* Resource Servers SHOULD omit this field and let the Facilitator provide
|
|
41
|
+
* it via its `/.well-known/s402-facilitator` endpoint. If included, it MUST
|
|
42
|
+
* match the Facilitator's configured rate — a mismatch is a warning sign.
|
|
43
|
+
*
|
|
44
|
+
* Trust model: Facilitator owns the fee. Resource Server cannot override it.
|
|
45
|
+
*/
|
|
32
46
|
protocolFeeBps?: number;
|
|
33
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Address that receives the protocol fee.
|
|
49
|
+
* Advisory only — authoritative value is in Facilitator's on-chain config.
|
|
50
|
+
* Defaults to the Facilitator's own address if omitted.
|
|
51
|
+
*/
|
|
34
52
|
protocolFeeAddress?: string;
|
|
35
53
|
/** Whether the server requires an on-chain receipt NFT */
|
|
36
54
|
receiptRequired?: boolean;
|
|
@@ -107,6 +125,19 @@ interface s402PrepaidExtra {
|
|
|
107
125
|
minDeposit: string;
|
|
108
126
|
/** Withdrawal delay in ms. Agent must wait this long after last claim. Min 60s, max 7d. */
|
|
109
127
|
withdrawalDelayMs: string;
|
|
128
|
+
/**
|
|
129
|
+
* Provider's Ed25519 public key (hex string, 32 bytes).
|
|
130
|
+
* When present, enables v0.2 signed receipt mode — claims enter a pending
|
|
131
|
+
* state and can be disputed with cryptographic fraud proofs.
|
|
132
|
+
* @since v0.2
|
|
133
|
+
*/
|
|
134
|
+
providerPubkey?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Dispute window in milliseconds. Min 60s (60000), max 24h (86400000).
|
|
137
|
+
* Only relevant when providerPubkey is set.
|
|
138
|
+
* @since v0.2
|
|
139
|
+
*/
|
|
140
|
+
disputeWindowMs?: string;
|
|
110
141
|
}
|
|
111
142
|
/** Mandate requirements in a 402 response — tells client what mandate is needed */
|
|
112
143
|
interface s402MandateRequirements {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s402",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "s402 — Sui-native HTTP 402 wire format. Types, HTTP encoding, and scheme registry for five payment schemes. Wire-compatible with x402. Zero runtime dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|