s402 0.1.0 → 0.1.1
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/http.d.mts +12 -1
- package/dist/http.mjs +84 -3
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +22 -0
- package/dist/types.d.mts +65 -2
- package/package.json +1 -1
package/dist/http.d.mts
CHANGED
|
@@ -11,14 +11,25 @@ declare function encodeSettleResponse(response: s402SettleResponse): string;
|
|
|
11
11
|
declare function pickRequirementsFields(obj: Record<string, unknown>): s402PaymentRequirements;
|
|
12
12
|
/** Decode payment requirements from the `payment-required` header */
|
|
13
13
|
declare function decodePaymentRequired(header: string): s402PaymentRequirements;
|
|
14
|
+
/** Return a clean payload object with only known s402 payload fields. */
|
|
15
|
+
declare function pickPayloadFields(obj: Record<string, unknown>): s402PaymentPayload;
|
|
14
16
|
/** Decode payment payload from the `x-payment` header */
|
|
15
17
|
declare function decodePaymentPayload(header: string): s402PaymentPayload;
|
|
18
|
+
/** Return a clean settle response with only known s402 fields. */
|
|
19
|
+
declare function pickSettleResponseFields(obj: Record<string, unknown>): s402SettleResponse;
|
|
16
20
|
/** Decode settlement response from the `payment-response` header */
|
|
17
21
|
declare function decodeSettleResponse(header: string): s402SettleResponse;
|
|
18
22
|
/**
|
|
19
23
|
* Check that a string represents a canonical non-negative integer (valid for Sui MIST amounts).
|
|
20
24
|
* Rejects leading zeros ("007"), empty strings, negatives, decimals.
|
|
21
25
|
* Accepts "0" as the only zero representation.
|
|
26
|
+
*
|
|
27
|
+
* A-13 (Semantic gap): "0" passes validation because it's a valid u64 on-chain.
|
|
28
|
+
* However, amount="0" in payment requirements is semantically ambiguous — it could
|
|
29
|
+
* mean "free" or be a misconfiguration. The s402 wire format intentionally allows it
|
|
30
|
+
* (some schemes like prepaid use amount="0" for deposit-based flows). Resource servers
|
|
31
|
+
* that want to reject zero-amount payments should check this in their business logic,
|
|
32
|
+
* not at the protocol level.
|
|
22
33
|
*/
|
|
23
34
|
declare function isValidAmount(s: string): boolean;
|
|
24
35
|
/**
|
|
@@ -64,4 +75,4 @@ declare function detectProtocol(headers: Headers): 's402' | 'x402' | 'unknown';
|
|
|
64
75
|
*/
|
|
65
76
|
declare function extractRequirementsFromResponse(response: Response): s402PaymentRequirements | null;
|
|
66
77
|
//#endregion
|
|
67
|
-
export { decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, pickRequirementsFields, validateEscrowShape, validateMandateShape, validatePrepaidShape, validateRequirementsShape, validateStreamShape, validateSubObjects, validateUnlockShape };
|
|
78
|
+
export { decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, pickPayloadFields, pickRequirementsFields, pickSettleResponseFields, validateEscrowShape, validateMandateShape, validatePrepaidShape, validateRequirementsShape, validateStreamShape, validateSubObjects, validateUnlockShape };
|
package/dist/http.mjs
CHANGED
|
@@ -74,6 +74,50 @@ function decodePaymentRequired(header) {
|
|
|
74
74
|
validateRequirementsShape(parsed);
|
|
75
75
|
return pickRequirementsFields(parsed);
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Known top-level keys on s402PaymentPayload.
|
|
79
|
+
* Used by decodePaymentPayload to strip unknown keys at the HTTP trust boundary.
|
|
80
|
+
*/
|
|
81
|
+
const S402_PAYLOAD_TOP_KEYS = new Set([
|
|
82
|
+
"s402Version",
|
|
83
|
+
"scheme",
|
|
84
|
+
"payload"
|
|
85
|
+
]);
|
|
86
|
+
/**
|
|
87
|
+
* Known inner payload keys per scheme. All schemes share transaction + signature;
|
|
88
|
+
* unlock adds encryptionId, prepaid adds ratePerCall + maxCalls.
|
|
89
|
+
*/
|
|
90
|
+
const S402_PAYLOAD_INNER_KEYS = {
|
|
91
|
+
exact: new Set(["transaction", "signature"]),
|
|
92
|
+
stream: new Set(["transaction", "signature"]),
|
|
93
|
+
escrow: new Set(["transaction", "signature"]),
|
|
94
|
+
unlock: new Set([
|
|
95
|
+
"transaction",
|
|
96
|
+
"signature",
|
|
97
|
+
"encryptionId"
|
|
98
|
+
]),
|
|
99
|
+
prepaid: new Set([
|
|
100
|
+
"transaction",
|
|
101
|
+
"signature",
|
|
102
|
+
"ratePerCall",
|
|
103
|
+
"maxCalls"
|
|
104
|
+
])
|
|
105
|
+
};
|
|
106
|
+
/** Return a clean payload object with only known s402 payload fields. */
|
|
107
|
+
function pickPayloadFields(obj) {
|
|
108
|
+
const result = {};
|
|
109
|
+
for (const key of S402_PAYLOAD_TOP_KEYS) if (key in obj) result[key] = obj[key];
|
|
110
|
+
if (result.payload && typeof result.payload === "object" && typeof result.scheme === "string") {
|
|
111
|
+
const allowedInner = S402_PAYLOAD_INNER_KEYS[result.scheme];
|
|
112
|
+
if (allowedInner) {
|
|
113
|
+
const inner = result.payload;
|
|
114
|
+
const cleanInner = {};
|
|
115
|
+
for (const key of allowedInner) if (key in inner) cleanInner[key] = inner[key];
|
|
116
|
+
result.payload = cleanInner;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
77
121
|
/** Decode payment payload from the `x-payment` header */
|
|
78
122
|
function decodePaymentPayload(header) {
|
|
79
123
|
if (header.length > MAX_HEADER_BYTES) throw new s402Error("INVALID_PAYLOAD", `x-payment header exceeds maximum size (${header.length} > ${MAX_HEADER_BYTES})`);
|
|
@@ -84,7 +128,28 @@ function decodePaymentPayload(header) {
|
|
|
84
128
|
throw new s402Error("INVALID_PAYLOAD", `Failed to decode x-payment header: ${e instanceof Error ? e.message : "invalid base64 or JSON"}`);
|
|
85
129
|
}
|
|
86
130
|
validatePayloadShape(parsed);
|
|
87
|
-
return parsed;
|
|
131
|
+
return pickPayloadFields(parsed);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Known top-level keys on s402SettleResponse.
|
|
135
|
+
* Used by decodeSettleResponse to strip unknown keys at the HTTP trust boundary.
|
|
136
|
+
*/
|
|
137
|
+
const S402_SETTLE_RESPONSE_KEYS = new Set([
|
|
138
|
+
"success",
|
|
139
|
+
"txDigest",
|
|
140
|
+
"receiptId",
|
|
141
|
+
"finalityMs",
|
|
142
|
+
"streamId",
|
|
143
|
+
"escrowId",
|
|
144
|
+
"balanceId",
|
|
145
|
+
"error",
|
|
146
|
+
"errorCode"
|
|
147
|
+
]);
|
|
148
|
+
/** Return a clean settle response with only known s402 fields. */
|
|
149
|
+
function pickSettleResponseFields(obj) {
|
|
150
|
+
const result = {};
|
|
151
|
+
for (const key of S402_SETTLE_RESPONSE_KEYS) if (key in obj) result[key] = obj[key];
|
|
152
|
+
return result;
|
|
88
153
|
}
|
|
89
154
|
/** Decode settlement response from the `payment-response` header */
|
|
90
155
|
function decodeSettleResponse(header) {
|
|
@@ -96,7 +161,7 @@ function decodeSettleResponse(header) {
|
|
|
96
161
|
throw new s402Error("INVALID_PAYLOAD", `Failed to decode payment-response header: ${e instanceof Error ? e.message : "invalid base64 or JSON"}`);
|
|
97
162
|
}
|
|
98
163
|
validateSettleShape(parsed);
|
|
99
|
-
return parsed;
|
|
164
|
+
return pickSettleResponseFields(parsed);
|
|
100
165
|
}
|
|
101
166
|
/** Valid s402 payment scheme values */
|
|
102
167
|
const VALID_SCHEMES = new Set([
|
|
@@ -110,6 +175,13 @@ const VALID_SCHEMES = new Set([
|
|
|
110
175
|
* Check that a string represents a canonical non-negative integer (valid for Sui MIST amounts).
|
|
111
176
|
* Rejects leading zeros ("007"), empty strings, negatives, decimals.
|
|
112
177
|
* Accepts "0" as the only zero representation.
|
|
178
|
+
*
|
|
179
|
+
* A-13 (Semantic gap): "0" passes validation because it's a valid u64 on-chain.
|
|
180
|
+
* However, amount="0" in payment requirements is semantically ambiguous — it could
|
|
181
|
+
* mean "free" or be a misconfiguration. The s402 wire format intentionally allows it
|
|
182
|
+
* (some schemes like prepaid use amount="0" for deposit-based flows). Resource servers
|
|
183
|
+
* that want to reject zero-amount payments should check this in their business logic,
|
|
184
|
+
* not at the protocol level.
|
|
113
185
|
*/
|
|
114
186
|
function isValidAmount(s) {
|
|
115
187
|
return /^(0|[1-9][0-9]*)$/.test(s);
|
|
@@ -145,8 +217,11 @@ function validateStreamShape(value) {
|
|
|
145
217
|
assertPlainObject(value, "stream");
|
|
146
218
|
const obj = value;
|
|
147
219
|
assertString(obj, "ratePerSecond", "stream");
|
|
220
|
+
if (typeof obj.ratePerSecond === "string" && !isValidAmount(obj.ratePerSecond)) throw new s402Error("INVALID_PAYLOAD", `stream.ratePerSecond must be a non-negative integer string, got "${obj.ratePerSecond}"`);
|
|
148
221
|
assertString(obj, "budgetCap", "stream");
|
|
222
|
+
if (typeof obj.budgetCap === "string" && !isValidAmount(obj.budgetCap)) throw new s402Error("INVALID_PAYLOAD", `stream.budgetCap must be a non-negative integer string, got "${obj.budgetCap}"`);
|
|
149
223
|
assertString(obj, "minDeposit", "stream");
|
|
224
|
+
if (typeof obj.minDeposit === "string" && !isValidAmount(obj.minDeposit)) throw new s402Error("INVALID_PAYLOAD", `stream.minDeposit must be a non-negative integer string, got "${obj.minDeposit}"`);
|
|
150
225
|
assertOptionalString(obj, "streamSetupUrl", "stream");
|
|
151
226
|
}
|
|
152
227
|
/**
|
|
@@ -157,6 +232,7 @@ function validateEscrowShape(value) {
|
|
|
157
232
|
const obj = value;
|
|
158
233
|
assertString(obj, "seller", "escrow");
|
|
159
234
|
assertString(obj, "deadlineMs", "escrow");
|
|
235
|
+
if (typeof obj.deadlineMs === "string" && !isValidAmount(obj.deadlineMs)) throw new s402Error("INVALID_PAYLOAD", `escrow.deadlineMs must be a non-negative integer string (Unix timestamp ms), got "${obj.deadlineMs}"`);
|
|
160
236
|
assertOptionalString(obj, "arbiter", "escrow");
|
|
161
237
|
}
|
|
162
238
|
/**
|
|
@@ -176,8 +252,11 @@ function validatePrepaidShape(value) {
|
|
|
176
252
|
assertPlainObject(value, "prepaid");
|
|
177
253
|
const obj = value;
|
|
178
254
|
assertString(obj, "ratePerCall", "prepaid");
|
|
255
|
+
if (typeof obj.ratePerCall === "string" && !isValidAmount(obj.ratePerCall)) throw new s402Error("INVALID_PAYLOAD", `prepaid.ratePerCall must be a non-negative integer string, got "${obj.ratePerCall}"`);
|
|
179
256
|
assertString(obj, "minDeposit", "prepaid");
|
|
257
|
+
if (typeof obj.minDeposit === "string" && !isValidAmount(obj.minDeposit)) throw new s402Error("INVALID_PAYLOAD", `prepaid.minDeposit must be a non-negative integer string, got "${obj.minDeposit}"`);
|
|
180
258
|
assertString(obj, "withdrawalDelayMs", "prepaid");
|
|
259
|
+
if (typeof obj.withdrawalDelayMs === "string" && !isValidAmount(obj.withdrawalDelayMs)) throw new s402Error("INVALID_PAYLOAD", `prepaid.withdrawalDelayMs must be a non-negative integer string (milliseconds), got "${obj.withdrawalDelayMs}"`);
|
|
181
260
|
assertOptionalString(obj, "maxCalls", "prepaid");
|
|
182
261
|
}
|
|
183
262
|
/**
|
|
@@ -204,6 +283,7 @@ function validateRequirementsShape(obj) {
|
|
|
204
283
|
if (typeof record.amount !== "string") missing.push("amount (string)");
|
|
205
284
|
else if (!isValidAmount(record.amount)) throw new s402Error("INVALID_PAYLOAD", `Invalid amount "${record.amount}": must be a non-negative integer string`);
|
|
206
285
|
if (typeof record.payTo !== "string") missing.push("payTo (string)");
|
|
286
|
+
else if (!record.payTo.startsWith("0x")) throw new s402Error("INVALID_PAYLOAD", `payTo must be a hex address starting with "0x", got "${record.payTo.substring(0, 20)}..."`);
|
|
207
287
|
if (missing.length > 0) throw new s402Error("INVALID_PAYLOAD", `Malformed payment requirements: missing ${missing.join(", ")}`);
|
|
208
288
|
if (Array.isArray(record.accepts) && record.accepts.length === 0) throw new s402Error("INVALID_PAYLOAD", "accepts array must contain at least one scheme");
|
|
209
289
|
const accepts = record.accepts;
|
|
@@ -246,6 +326,7 @@ function validateSettleShape(obj) {
|
|
|
246
326
|
function detectProtocol(headers) {
|
|
247
327
|
const paymentRequired = headers.get(S402_HEADERS.PAYMENT_REQUIRED);
|
|
248
328
|
if (!paymentRequired) return "unknown";
|
|
329
|
+
if (paymentRequired.length > MAX_HEADER_BYTES) return "unknown";
|
|
249
330
|
try {
|
|
250
331
|
const decoded = JSON.parse(fromBase64(paymentRequired));
|
|
251
332
|
if (decoded != null && typeof decoded === "object" && "s402Version" in decoded) return "s402";
|
|
@@ -271,4 +352,4 @@ function extractRequirementsFromResponse(response) {
|
|
|
271
352
|
}
|
|
272
353
|
|
|
273
354
|
//#endregion
|
|
274
|
-
export { decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, pickRequirementsFields, validateEscrowShape, validateMandateShape, validatePrepaidShape, validateRequirementsShape, validateStreamShape, validateSubObjects, validateUnlockShape };
|
|
355
|
+
export { decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, pickPayloadFields, pickRequirementsFields, pickSettleResponseFields, validateEscrowShape, validateMandateShape, validatePrepaidShape, validateRequirementsShape, validateStreamShape, validateSubObjects, validateUnlockShape };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createS402Error, s402Error, s402ErrorCode, s402ErrorCodeType, s402ErrorInfo } from "./errors.mjs";
|
|
2
|
-
import { S402_HEADERS, S402_VERSION, s402Discovery, s402EscrowExtra, s402EscrowPayload, s402ExactPayload, s402Mandate, s402MandateRequirements, s402PaymentPayload, s402PaymentPayloadBase, s402PaymentRequirements, s402PrepaidExtra, s402PrepaidPayload, s402Scheme, s402SettleResponse, s402SettlementMode, s402StreamExtra, s402StreamPayload, s402UnlockExtra, s402UnlockPayload, s402VerifyResponse } from "./types.mjs";
|
|
2
|
+
import { S402_HEADERS, S402_VERSION, s402Discovery, s402EscrowExtra, s402EscrowPayload, s402ExactPayload, s402Mandate, s402MandateRequirements, s402PaymentPayload, s402PaymentPayloadBase, s402PaymentRequirements, s402PaymentSession, s402PrepaidExtra, s402PrepaidPayload, s402RegistryQuery, s402Scheme, s402ServiceEntry, s402SettleResponse, s402SettlementMode, s402StreamExtra, s402StreamPayload, s402UnlockExtra, s402UnlockPayload, s402VerifyResponse } from "./types.mjs";
|
|
3
3
|
import { decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, validateRequirementsShape } from "./http.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/scheme.d.ts
|
|
@@ -128,10 +128,12 @@ declare class s402Facilitator {
|
|
|
128
128
|
register(network: string, scheme: s402FacilitatorScheme): this;
|
|
129
129
|
/**
|
|
130
130
|
* Verify a payment payload by dispatching to the correct scheme.
|
|
131
|
+
* Includes expiration guard — rejects expired requirements before verification.
|
|
131
132
|
*/
|
|
132
133
|
verify(payload: s402PaymentPayload, requirements: s402PaymentRequirements): Promise<s402VerifyResponse>;
|
|
133
134
|
/**
|
|
134
135
|
* Settle a payment by dispatching to the correct scheme.
|
|
136
|
+
* Includes expiration guard — rejects expired requirements before settlement.
|
|
135
137
|
*/
|
|
136
138
|
settle(payload: s402PaymentPayload, requirements: s402PaymentRequirements): Promise<s402SettleResponse>;
|
|
137
139
|
/**
|
|
@@ -189,4 +191,4 @@ declare class s402ResourceServer {
|
|
|
189
191
|
process(payload: s402PaymentPayload, requirements: s402PaymentRequirements): Promise<s402SettleResponse>;
|
|
190
192
|
}
|
|
191
193
|
//#endregion
|
|
192
|
-
export { S402_HEADERS, S402_VERSION, createS402Error, decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, s402Client, type s402ClientScheme, type s402DirectScheme, type s402Discovery, s402Error, s402ErrorCode, type s402ErrorCodeType, type s402ErrorInfo, type s402EscrowExtra, type s402EscrowPayload, type s402ExactPayload, s402Facilitator, type s402FacilitatorScheme, type s402Mandate, type s402MandateRequirements, type s402PaymentPayload, type s402PaymentPayloadBase, type s402PaymentRequirements, type s402PrepaidExtra, type s402PrepaidPayload, s402ResourceServer, type s402RouteConfig, type s402Scheme, type s402ServerScheme, type s402SettleResponse, type s402SettlementMode, type s402StreamExtra, type s402StreamPayload, type s402UnlockExtra, type s402UnlockPayload, type s402VerifyResponse, validateRequirementsShape };
|
|
194
|
+
export { S402_HEADERS, S402_VERSION, createS402Error, decodePaymentPayload, decodePaymentRequired, decodeSettleResponse, detectProtocol, encodePaymentPayload, encodePaymentRequired, encodeSettleResponse, extractRequirementsFromResponse, isValidAmount, s402Client, type s402ClientScheme, type s402DirectScheme, type s402Discovery, s402Error, s402ErrorCode, type s402ErrorCodeType, type s402ErrorInfo, type s402EscrowExtra, type s402EscrowPayload, type s402ExactPayload, s402Facilitator, type s402FacilitatorScheme, type s402Mandate, type s402MandateRequirements, type s402PaymentPayload, type s402PaymentPayloadBase, type s402PaymentRequirements, type s402PaymentSession, type s402PrepaidExtra, type s402PrepaidPayload, type s402RegistryQuery, s402ResourceServer, type s402RouteConfig, type s402Scheme, type s402ServerScheme, type s402ServiceEntry, type s402SettleResponse, type s402SettlementMode, type s402StreamExtra, type s402StreamPayload, type s402UnlockExtra, type s402UnlockPayload, type s402VerifyResponse, validateRequirementsShape };
|
package/dist/index.mjs
CHANGED
|
@@ -138,14 +138,29 @@ var s402Facilitator = class {
|
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
140
|
* Verify a payment payload by dispatching to the correct scheme.
|
|
141
|
+
* Includes expiration guard — rejects expired requirements before verification.
|
|
141
142
|
*/
|
|
142
143
|
async verify(payload, requirements) {
|
|
144
|
+
if (typeof requirements.expiresAt === "number" && Number.isFinite(requirements.expiresAt)) {
|
|
145
|
+
if (Date.now() > requirements.expiresAt) return {
|
|
146
|
+
valid: false,
|
|
147
|
+
invalidReason: `Payment requirements expired at ${new Date(requirements.expiresAt).toISOString()}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
143
150
|
return this.resolveScheme(payload.scheme, requirements.network).verify(payload, requirements);
|
|
144
151
|
}
|
|
145
152
|
/**
|
|
146
153
|
* Settle a payment by dispatching to the correct scheme.
|
|
154
|
+
* Includes expiration guard — rejects expired requirements before settlement.
|
|
147
155
|
*/
|
|
148
156
|
async settle(payload, requirements) {
|
|
157
|
+
if (typeof requirements.expiresAt === "number" && Number.isFinite(requirements.expiresAt)) {
|
|
158
|
+
if (Date.now() > requirements.expiresAt) return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: `Payment requirements expired at ${new Date(requirements.expiresAt).toISOString()}`,
|
|
161
|
+
errorCode: "REQUIREMENTS_EXPIRED"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
149
164
|
return this.resolveScheme(payload.scheme, requirements.network).settle(payload, requirements);
|
|
150
165
|
}
|
|
151
166
|
/**
|
|
@@ -169,6 +184,13 @@ var s402Facilitator = class {
|
|
|
169
184
|
errorCode: "REQUIREMENTS_EXPIRED"
|
|
170
185
|
};
|
|
171
186
|
}
|
|
187
|
+
if (requirements.accepts && requirements.accepts.length > 0) {
|
|
188
|
+
if (!requirements.accepts.includes(payload.scheme)) return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: `Scheme "${payload.scheme}" is not accepted by these requirements. Accepted: [${requirements.accepts.join(", ")}]`,
|
|
191
|
+
errorCode: "SCHEME_NOT_SUPPORTED"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
172
194
|
const scheme = this.resolveScheme(payload.scheme, requirements.network);
|
|
173
195
|
const verifyResult = await scheme.verify(payload, requirements);
|
|
174
196
|
if (!verifyResult.valid) return {
|
package/dist/types.d.mts
CHANGED
|
@@ -46,7 +46,15 @@ interface s402PaymentRequirements {
|
|
|
46
46
|
unlock?: s402UnlockExtra;
|
|
47
47
|
/** Extra fields for prepaid scheme */
|
|
48
48
|
prepaid?: s402PrepaidExtra;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Arbitrary extension data (forward-compatible extensibility).
|
|
51
|
+
*
|
|
52
|
+
* D-10 (Trust boundary): extensions is an opaque bag — the s402 library
|
|
53
|
+
* passes it through without validation. Consumers MUST treat extension values
|
|
54
|
+
* as untrusted input. Do not use extensions for security-critical fields
|
|
55
|
+
* (use first-class typed fields instead). Scheme implementations should
|
|
56
|
+
* validate any extension keys they consume.
|
|
57
|
+
*/
|
|
50
58
|
extensions?: Record<string, unknown>;
|
|
51
59
|
}
|
|
52
60
|
/** Stream-specific requirements */
|
|
@@ -237,6 +245,61 @@ interface s402Discovery {
|
|
|
237
245
|
/** Address that receives the protocol fee */
|
|
238
246
|
protocolFeeAddress?: string;
|
|
239
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Tracks the lifecycle of a single payment exchange.
|
|
250
|
+
* Used by s402 clients (SDK fetch wrapper, MCP tools) to correlate
|
|
251
|
+
* the 402 response → payment creation → settlement result.
|
|
252
|
+
*/
|
|
253
|
+
interface s402PaymentSession {
|
|
254
|
+
/** Unique session ID (client-generated, e.g., crypto.randomUUID()) */
|
|
255
|
+
id: string;
|
|
256
|
+
/** When the session started (Date.now()) */
|
|
257
|
+
startedAt: number;
|
|
258
|
+
/** The payment requirements from the 402 response */
|
|
259
|
+
requirements: s402PaymentRequirements;
|
|
260
|
+
/** The payment payload sent to the facilitator/server (null until created) */
|
|
261
|
+
payload: s402PaymentPayload | null;
|
|
262
|
+
/** Settlement result (null until settled) */
|
|
263
|
+
result: s402SettleResponse | null;
|
|
264
|
+
/** Current session state */
|
|
265
|
+
state: 'pending' | 'paying' | 'settled' | 'failed';
|
|
266
|
+
/** Number of retry attempts */
|
|
267
|
+
retries: number;
|
|
268
|
+
/** Error message if state is 'failed' */
|
|
269
|
+
error?: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A single s402-enabled service endpoint in a registry.
|
|
273
|
+
* Supports multi-service discovery (e.g., an API gateway advertising
|
|
274
|
+
* multiple endpoints with different payment requirements).
|
|
275
|
+
*/
|
|
276
|
+
interface s402ServiceEntry {
|
|
277
|
+
/** Service name (human-readable) */
|
|
278
|
+
name: string;
|
|
279
|
+
/** Service endpoint URL */
|
|
280
|
+
url: string;
|
|
281
|
+
/** Payment schemes this service accepts */
|
|
282
|
+
accepts: s402Scheme[];
|
|
283
|
+
/** Supported coin types */
|
|
284
|
+
assets: string[];
|
|
285
|
+
/** Base price in smallest unit (MIST for SUI, micro for USDC) */
|
|
286
|
+
baseAmount: string;
|
|
287
|
+
/** Facilitator URL (if not direct settlement) */
|
|
288
|
+
facilitatorUrl?: string;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Query parameters for service registry lookups.
|
|
292
|
+
*/
|
|
293
|
+
interface s402RegistryQuery {
|
|
294
|
+
/** Filter by supported scheme */
|
|
295
|
+
scheme?: s402Scheme;
|
|
296
|
+
/** Filter by coin type */
|
|
297
|
+
asset?: string;
|
|
298
|
+
/** Filter by network */
|
|
299
|
+
network?: string;
|
|
300
|
+
/** Maximum number of results */
|
|
301
|
+
limit?: number;
|
|
302
|
+
}
|
|
240
303
|
/**
|
|
241
304
|
* HTTP headers used by s402 (same names as x402 for compatibility).
|
|
242
305
|
* All lowercase per HTTP/2 spec (RFC 9113 §8.2.1). The Headers API
|
|
@@ -249,4 +312,4 @@ declare const S402_HEADERS: {
|
|
|
249
312
|
readonly STREAM_ID: "x-stream-id";
|
|
250
313
|
};
|
|
251
314
|
//#endregion
|
|
252
|
-
export { S402_HEADERS, S402_VERSION, s402Discovery, s402EscrowExtra, s402EscrowPayload, s402ExactPayload, s402Mandate, s402MandateRequirements, s402PaymentPayload, s402PaymentPayloadBase, s402PaymentRequirements, s402PrepaidExtra, s402PrepaidPayload, s402Scheme, s402SettleResponse, s402SettlementMode, s402StreamExtra, s402StreamPayload, s402UnlockExtra, s402UnlockPayload, s402VerifyResponse };
|
|
315
|
+
export { S402_HEADERS, S402_VERSION, s402Discovery, s402EscrowExtra, s402EscrowPayload, s402ExactPayload, s402Mandate, s402MandateRequirements, s402PaymentPayload, s402PaymentPayloadBase, s402PaymentRequirements, s402PaymentSession, s402PrepaidExtra, s402PrepaidPayload, s402RegistryQuery, s402Scheme, s402ServiceEntry, s402SettleResponse, s402SettlementMode, s402StreamExtra, s402StreamPayload, s402UnlockExtra, s402UnlockPayload, s402VerifyResponse };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s402",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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": "MIT",
|