@devlab.group/agent-commerce 2.1.0 → 2.2.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/README.md +35 -5
- package/dist/ap2.js +3 -3
- package/dist/{chunk-LZS5BL3W.js → chunk-2DAJAJI7.js} +23 -5
- package/dist/chunk-2DAJAJI7.js.map +1 -0
- package/dist/{chunk-UHMYRV3J.js → chunk-BNUD4RBN.js} +22 -7
- package/dist/chunk-BNUD4RBN.js.map +1 -0
- package/dist/{chunk-IFGRCXME.js → chunk-REBY2IEP.js} +24 -20
- package/dist/chunk-REBY2IEP.js.map +1 -0
- package/dist/{chunk-SY6GUYSQ.js → chunk-XYHDQ22D.js} +3 -3
- package/dist/{chunk-SY6GUYSQ.js.map → chunk-XYHDQ22D.js.map} +1 -1
- package/dist/cli/ap2-HLBIUSTK.js +678 -0
- package/dist/cli/ap2-HLBIUSTK.js.map +1 -0
- package/dist/cli/chunk-2LN5PO5I.js +1352 -0
- package/dist/cli/chunk-2LN5PO5I.js.map +1 -0
- package/dist/cli/chunk-3EQBCFZY.js +53 -0
- package/dist/cli/chunk-3EQBCFZY.js.map +1 -0
- package/dist/cli/chunk-3M6U7ILB.js +8 -0
- package/dist/cli/chunk-3M6U7ILB.js.map +1 -0
- package/dist/cli/chunk-3MGAJ5FX.js +70 -0
- package/dist/cli/chunk-3MGAJ5FX.js.map +1 -0
- package/dist/cli/chunk-5K4XEAZV.js +113 -0
- package/dist/cli/chunk-5K4XEAZV.js.map +1 -0
- package/dist/cli/chunk-AA3FIEAR.js +27 -0
- package/dist/cli/chunk-AA3FIEAR.js.map +1 -0
- package/dist/cli/chunk-FHCFR2GS.js +17 -0
- package/dist/cli/chunk-FHCFR2GS.js.map +1 -0
- package/dist/cli/chunk-ILUYEQSV.js +1744 -0
- package/dist/cli/chunk-ILUYEQSV.js.map +1 -0
- package/dist/cli/chunk-KTV52MCU.js +338 -0
- package/dist/cli/chunk-KTV52MCU.js.map +1 -0
- package/dist/cli/chunk-OSTCU6ZK.js +44 -0
- package/dist/cli/chunk-OSTCU6ZK.js.map +1 -0
- package/dist/cli/chunk-OZ4LAWBK.js +256 -0
- package/dist/cli/chunk-OZ4LAWBK.js.map +1 -0
- package/dist/cli/chunk-QQ3WY5L3.js +271 -0
- package/dist/cli/chunk-QQ3WY5L3.js.map +1 -0
- package/dist/cli/chunk-QY3K6PEQ.js +8 -0
- package/dist/cli/chunk-QY3K6PEQ.js.map +1 -0
- package/dist/cli/chunk-QZ6TOMX6.js +1413 -0
- package/dist/cli/chunk-QZ6TOMX6.js.map +1 -0
- package/dist/cli/chunk-SNM3OVCY.js +625 -0
- package/dist/cli/chunk-SNM3OVCY.js.map +1 -0
- package/dist/cli/chunk-SPAYKVUP.js +118 -0
- package/dist/cli/chunk-SPAYKVUP.js.map +1 -0
- package/dist/cli/chunk-TPC4EJLT.js +11 -0
- package/dist/cli/chunk-TPC4EJLT.js.map +1 -0
- package/dist/cli/config-VYAJGZXE.js +11 -0
- package/dist/cli/config-VYAJGZXE.js.map +1 -0
- package/dist/cli/index.js +150 -3627
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-HFD7ZNCQ.js +559 -0
- package/dist/cli/mcp-HFD7ZNCQ.js.map +1 -0
- package/dist/cli/near-5AY3HWYF.js +58 -0
- package/dist/cli/near-5AY3HWYF.js.map +1 -0
- package/dist/cli/provider-JV5HCQGR.js +396 -0
- package/dist/cli/provider-JV5HCQGR.js.map +1 -0
- package/dist/cli/run-WV44B62Y.js +7301 -0
- package/dist/cli/run-WV44B62Y.js.map +1 -0
- package/dist/cli/svm-3MMYFPC7.js +33 -0
- package/dist/cli/svm-3MMYFPC7.js.map +1 -0
- package/dist/cli/x402-NADFV5CD.js +96 -0
- package/dist/cli/x402-NADFV5CD.js.map +1 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +82 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +6 -16
- package/dist/mcp.js.map +1 -1
- package/dist/mpp.js +3 -3
- package/dist/{networks-olfflCI_.d.ts → networks-DTWRlj-K.d.ts} +2 -2
- package/dist/x402.d.ts +7 -2
- package/dist/x402.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-IFGRCXME.js.map +0 -1
- package/dist/chunk-LZS5BL3W.js.map +0 -1
- package/dist/chunk-UHMYRV3J.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,2854 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import '
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
for (var name in all)
|
|
24
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// src/config/filename.ts
|
|
28
|
-
var DEFAULT_CONFIG_FILENAME;
|
|
29
|
-
var init_filename = __esm({
|
|
30
|
-
"src/config/filename.ts"() {
|
|
31
|
-
DEFAULT_CONFIG_FILENAME = "config.yaml";
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// src/core/domain/common.ts
|
|
36
|
-
var PROTOCOL_NAME_KEYS, PROTOCOL_NAMES, PAYMENT_METHOD_NAME_KEYS, PAYMENT_METHOD_NAMES;
|
|
37
|
-
var init_common = __esm({
|
|
38
|
-
"src/core/domain/common.ts"() {
|
|
39
|
-
PROTOCOL_NAME_KEYS = {
|
|
40
|
-
http: true,
|
|
41
|
-
mcp: true,
|
|
42
|
-
a2a: true,
|
|
43
|
-
acp: true
|
|
44
|
-
};
|
|
45
|
-
PROTOCOL_NAMES = Object.keys(
|
|
46
|
-
PROTOCOL_NAME_KEYS
|
|
47
|
-
);
|
|
48
|
-
PAYMENT_METHOD_NAME_KEYS = { x402: true, mpp: true };
|
|
49
|
-
PAYMENT_METHOD_NAMES = Object.keys(
|
|
50
|
-
PAYMENT_METHOD_NAME_KEYS
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// src/core/domain/event.ts
|
|
56
|
-
var init_event = __esm({
|
|
57
|
-
"src/core/domain/event.ts"() {
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// src/core/domain/resource.ts
|
|
62
|
-
var init_resource = __esm({
|
|
63
|
-
"src/core/domain/resource.ts"() {
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// src/core/errors/codes.ts
|
|
68
|
-
var COMMERCE_ERROR_HTTP_STATUS, RETRYABLE_ERROR_CODES;
|
|
69
|
-
var init_codes = __esm({
|
|
70
|
-
"src/core/errors/codes.ts"() {
|
|
71
|
-
COMMERCE_ERROR_HTTP_STATUS = {
|
|
72
|
-
CONFIG_INVALID: 500,
|
|
73
|
-
RESOURCE_NOT_FOUND: 404,
|
|
74
|
-
INPUT_INVALID: 400,
|
|
75
|
-
PAYMENT_REQUIRED: 402,
|
|
76
|
-
PAYMENT_INVALID: 402,
|
|
77
|
-
PAYMENT_REPLAYED: 409,
|
|
78
|
-
PAYMENT_PROVIDER_UNAVAILABLE: 503,
|
|
79
|
-
PAYMENT_SETTLEMENT_FAILED: 502,
|
|
80
|
-
// 403, not 402: a 402 tells a client to pay and retry, which cannot fix a
|
|
81
|
-
// missing or rejected mandate, and a client that auto-pays on 402 would be
|
|
82
|
-
// charged for a request that was never going to be delivered
|
|
83
|
-
AUTHORIZATION_REQUIRED: 403,
|
|
84
|
-
AUTHORIZATION_INVALID: 403,
|
|
85
|
-
AUTHORIZATION_REPLAYED: 409,
|
|
86
|
-
AUTHORIZATION_PROVIDER_UNAVAILABLE: 503,
|
|
87
|
-
BACKEND_TIMEOUT: 504,
|
|
88
|
-
BACKEND_ERROR: 502,
|
|
89
|
-
PROTOCOL_UNSUPPORTED: 501,
|
|
90
|
-
GATEWAY_BUSY: 503,
|
|
91
|
-
STORAGE_ERROR: 500,
|
|
92
|
-
INTERNAL_ERROR: 500
|
|
93
|
-
};
|
|
94
|
-
RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
95
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
96
|
-
// The verifier or replay store was unreachable, so the mandate got no
|
|
97
|
-
// verdict. The same proof can verify once the outage clears.
|
|
98
|
-
"AUTHORIZATION_PROVIDER_UNAVAILABLE",
|
|
99
|
-
"BACKEND_TIMEOUT",
|
|
100
|
-
// Load shedding is transient: the caller should back off and try again.
|
|
101
|
-
// Clients act on this flag, and leaving it out would make a momentary
|
|
102
|
-
// throttle look like a permanent failure.
|
|
103
|
-
"GATEWAY_BUSY"
|
|
104
|
-
]);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// src/core/errors/commerce-error.ts
|
|
109
|
-
function isCommerceError(value) {
|
|
110
|
-
return value instanceof CommerceError;
|
|
111
|
-
}
|
|
112
|
-
function toCommerceError(value, fallbackCode = "INTERNAL_ERROR", fallbackMessage = "Unexpected internal error") {
|
|
113
|
-
if (isCommerceError(value)) return value;
|
|
114
|
-
return new CommerceError(fallbackCode, fallbackMessage, { cause: value });
|
|
115
|
-
}
|
|
116
|
-
var CommerceError;
|
|
117
|
-
var init_commerce_error = __esm({
|
|
118
|
-
"src/core/errors/commerce-error.ts"() {
|
|
119
|
-
init_codes();
|
|
120
|
-
CommerceError = class _CommerceError extends Error {
|
|
121
|
-
code;
|
|
122
|
-
httpStatus;
|
|
123
|
-
retryable;
|
|
124
|
-
details;
|
|
125
|
-
requestId;
|
|
126
|
-
resourceId;
|
|
127
|
-
constructor(code, message, options = {}) {
|
|
128
|
-
super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
129
|
-
this.name = "CommerceError";
|
|
130
|
-
this.code = code;
|
|
131
|
-
this.httpStatus = options.httpStatus ?? COMMERCE_ERROR_HTTP_STATUS[code];
|
|
132
|
-
this.retryable = RETRYABLE_ERROR_CODES.has(code);
|
|
133
|
-
if (options.details !== void 0) this.details = options.details;
|
|
134
|
-
if (options.requestId !== void 0) this.requestId = options.requestId;
|
|
135
|
-
if (options.resourceId !== void 0) this.resourceId = options.resourceId;
|
|
136
|
-
Error.captureStackTrace?.(this, _CommerceError);
|
|
137
|
-
}
|
|
138
|
-
/** Non-secret, client-safe representation. `cause` is never included */
|
|
139
|
-
toInfo() {
|
|
140
|
-
return {
|
|
141
|
-
code: this.code,
|
|
142
|
-
message: this.message,
|
|
143
|
-
httpStatus: this.httpStatus,
|
|
144
|
-
retryable: this.retryable,
|
|
145
|
-
...this.requestId !== void 0 ? { requestId: this.requestId } : {},
|
|
146
|
-
...this.resourceId !== void 0 ? { resourceId: this.resourceId } : {},
|
|
147
|
-
...this.details !== void 0 ? { details: this.details } : {}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// src/core/errors/index.ts
|
|
155
|
-
var init_errors = __esm({
|
|
156
|
-
"src/core/errors/index.ts"() {
|
|
157
|
-
init_codes();
|
|
158
|
-
init_commerce_error();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// src/core/is-record.ts
|
|
163
|
-
function isRecord(value) {
|
|
164
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
165
|
-
}
|
|
166
|
-
var init_is_record = __esm({
|
|
167
|
-
"src/core/is-record.ts"() {
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// src/core/domain/wire.ts
|
|
172
|
-
var PAYMENT_INPUT_FIELD, AUTHORIZATION_INPUT_FIELD, RESERVED_INPUT_FIELDS;
|
|
173
|
-
var init_wire = __esm({
|
|
174
|
-
"src/core/domain/wire.ts"() {
|
|
175
|
-
init_errors();
|
|
176
|
-
init_is_record();
|
|
177
|
-
PAYMENT_INPUT_FIELD = "_payment";
|
|
178
|
-
AUTHORIZATION_INPUT_FIELD = "_authorization";
|
|
179
|
-
RESERVED_INPUT_FIELDS = [
|
|
180
|
-
PAYMENT_INPUT_FIELD,
|
|
181
|
-
AUTHORIZATION_INPUT_FIELD
|
|
182
|
-
];
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// src/core/interfaces/logger.ts
|
|
187
|
-
var NOOP_LOGGER;
|
|
188
|
-
var init_logger = __esm({
|
|
189
|
-
"src/core/interfaces/logger.ts"() {
|
|
190
|
-
NOOP_LOGGER = {
|
|
191
|
-
debug: () => {
|
|
192
|
-
},
|
|
193
|
-
info: () => {
|
|
194
|
-
},
|
|
195
|
-
warn: () => {
|
|
196
|
-
},
|
|
197
|
-
error: () => {
|
|
198
|
-
},
|
|
199
|
-
child: () => NOOP_LOGGER
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// src/core/interfaces/protocol-adapter.ts
|
|
205
|
-
var init_protocol_adapter = __esm({
|
|
206
|
-
"src/core/interfaces/protocol-adapter.ts"() {
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// src/core/interfaces/runtime.ts
|
|
211
|
-
var systemClock;
|
|
212
|
-
var init_runtime = __esm({
|
|
213
|
-
"src/core/interfaces/runtime.ts"() {
|
|
214
|
-
systemClock = {
|
|
215
|
-
now: () => /* @__PURE__ */ new Date(),
|
|
216
|
-
nowIso: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
217
|
-
monotonicMs: () => performance.now()
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// src/core/public-types.ts
|
|
223
|
-
var init_public_types = __esm({
|
|
224
|
-
"src/core/public-types.ts"() {
|
|
225
|
-
init_common();
|
|
226
|
-
init_event();
|
|
227
|
-
init_resource();
|
|
228
|
-
init_wire();
|
|
229
|
-
init_errors();
|
|
230
|
-
init_logger();
|
|
231
|
-
init_protocol_adapter();
|
|
232
|
-
init_runtime();
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// src/core/index.ts
|
|
237
|
-
var init_core = __esm({
|
|
238
|
-
"src/core/index.ts"() {
|
|
239
|
-
init_public_types();
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// src/core/execution/backend-request.ts
|
|
244
|
-
function extractPathParameterNames(url) {
|
|
245
|
-
return [...url.matchAll(PATH_PARAM_PATTERN)].map((match) => match[1]);
|
|
246
|
-
}
|
|
247
|
-
function findUnparsedBraceToken(url) {
|
|
248
|
-
const residue = url.replace(PATH_PARAM_PATTERN, "");
|
|
249
|
-
const index = residue.search(/[{}]/);
|
|
250
|
-
if (index === -1) return void 0;
|
|
251
|
-
const token = /\{[^{}]*\}?|\}/.exec(residue.slice(index));
|
|
252
|
-
return token?.[0] ?? residue[index];
|
|
253
|
-
}
|
|
254
|
-
var PATH_PARAM_PATTERN;
|
|
255
|
-
var init_backend_request = __esm({
|
|
256
|
-
"src/core/execution/backend-request.ts"() {
|
|
257
|
-
init_errors();
|
|
258
|
-
init_is_record();
|
|
259
|
-
PATH_PARAM_PATTERN = /\{([a-zA-Z0-9_.-]+)\}/g;
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
var init_backend_http = __esm({
|
|
263
|
-
"src/core/execution/backend-http.ts"() {
|
|
264
|
-
init_resource();
|
|
265
|
-
init_errors();
|
|
266
|
-
init_logger();
|
|
267
|
-
init_is_record();
|
|
268
|
-
init_backend_request();
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// src/core/execution/settlement-order.ts
|
|
273
|
-
var init_settlement_order = __esm({
|
|
274
|
-
"src/core/execution/settlement-order.ts"() {
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
function typeList(raw) {
|
|
278
|
-
if (typeof raw === "string") return [raw];
|
|
279
|
-
if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string")) {
|
|
280
|
-
return raw;
|
|
281
|
-
}
|
|
282
|
-
return void 0;
|
|
283
|
-
}
|
|
284
|
-
function isObjectSchemaNode(schema) {
|
|
285
|
-
const types = typeList(schema["type"]);
|
|
286
|
-
if (types !== void 0) return types.includes("object");
|
|
287
|
-
return Object.hasOwn(schema, "properties") || Object.hasOwn(schema, "required");
|
|
288
|
-
}
|
|
289
|
-
var init_validation = __esm({
|
|
290
|
-
"src/core/execution/validation.ts"() {
|
|
291
|
-
init_is_record();
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// src/core/execution/pipeline/authorization.ts
|
|
296
|
-
var init_authorization = __esm({
|
|
297
|
-
"src/core/execution/pipeline/authorization.ts"() {
|
|
298
|
-
init_errors();
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// src/core/execution/pipeline/challenge.ts
|
|
303
|
-
var init_challenge = __esm({
|
|
304
|
-
"src/core/execution/pipeline/challenge.ts"() {
|
|
305
|
-
init_errors();
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// src/core/execution/pipeline/admission.ts
|
|
310
|
-
var init_admission = __esm({
|
|
311
|
-
"src/core/execution/pipeline/admission.ts"() {
|
|
312
|
-
init_errors();
|
|
313
|
-
init_settlement_order();
|
|
314
|
-
init_authorization();
|
|
315
|
-
init_challenge();
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// src/core/execution/pipeline/context.ts
|
|
320
|
-
var init_context = __esm({
|
|
321
|
-
"src/core/execution/pipeline/context.ts"() {
|
|
322
|
-
init_errors();
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// src/core/execution/pipeline/settlement.ts
|
|
327
|
-
var init_settlement = __esm({
|
|
328
|
-
"src/core/execution/pipeline/settlement.ts"() {
|
|
329
|
-
init_errors();
|
|
330
|
-
init_challenge();
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
var init_pipeline = __esm({
|
|
334
|
-
"src/core/execution/pipeline/index.ts"() {
|
|
335
|
-
init_wire();
|
|
336
|
-
init_errors();
|
|
337
|
-
init_is_record();
|
|
338
|
-
init_backend_request();
|
|
339
|
-
init_settlement_order();
|
|
340
|
-
init_validation();
|
|
341
|
-
init_admission();
|
|
342
|
-
init_authorization();
|
|
343
|
-
init_context();
|
|
344
|
-
init_settlement();
|
|
345
|
-
[...RESERVED_INPUT_FIELDS, "__proto__"];
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// src/core/execution/registry.ts
|
|
350
|
-
var init_registry = __esm({
|
|
351
|
-
"src/core/execution/registry.ts"() {
|
|
352
|
-
init_errors();
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// src/core/execution/store-event-sink.ts
|
|
357
|
-
var init_store_event_sink = __esm({
|
|
358
|
-
"src/core/execution/store-event-sink.ts"() {
|
|
359
|
-
init_errors();
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// src/core/execution/index.ts
|
|
364
|
-
var init_execution = __esm({
|
|
365
|
-
"src/core/execution/index.ts"() {
|
|
366
|
-
init_backend_http();
|
|
367
|
-
init_backend_request();
|
|
368
|
-
init_pipeline();
|
|
369
|
-
init_registry();
|
|
370
|
-
init_store_event_sink();
|
|
371
|
-
init_validation();
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// src/config/env.ts
|
|
376
|
-
function substituteEnv(value, env, path2 = "$") {
|
|
377
|
-
if (typeof value === "string") {
|
|
378
|
-
return substituteString(value, env, path2);
|
|
379
|
-
}
|
|
380
|
-
if (Array.isArray(value)) {
|
|
381
|
-
return value.map((entry, index) => substituteEnv(entry, env, `${path2}[${index}]`));
|
|
382
|
-
}
|
|
383
|
-
if (isRecord(value)) {
|
|
384
|
-
const result = {};
|
|
385
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
386
|
-
result[key] = substituteEnv(entry, env, `${path2}.${key}`);
|
|
387
|
-
}
|
|
388
|
-
return result;
|
|
389
|
-
}
|
|
390
|
-
return value;
|
|
391
|
-
}
|
|
392
|
-
function substituteString(value, env, path2) {
|
|
393
|
-
assertTemplateIsSupported(value, path2);
|
|
394
|
-
return substituteOnce(value, env, path2);
|
|
395
|
-
}
|
|
396
|
-
function assertTemplateIsSupported(value, path2) {
|
|
397
|
-
if (NESTED_PLACEHOLDER.test(value)) {
|
|
398
|
-
throw new CommerceError(
|
|
399
|
-
"CONFIG_INVALID",
|
|
400
|
-
`Configuration value at "${path2}" nests placeholders (e.g. "\${A:-\${B}}"), which is not supported: the inner one is not resolved and its closing brace ends up in the value. Use a plain default, or set the variable.`,
|
|
401
|
-
{ details: { path: path2 } }
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
for (const [token] of value.matchAll(BRACE_TOKEN)) {
|
|
405
|
-
if (SUPPORTED_TOKEN.test(token)) continue;
|
|
406
|
-
throw new CommerceError(
|
|
407
|
-
"CONFIG_INVALID",
|
|
408
|
-
`Configuration value at "${path2}" contains "${token}", which is not a supported placeholder. Only "\${VAR}" and "\${VAR:-default}" are recognized; shell forms such as "\${VAR-default}", "\${VAR:=default}" and "\${VAR:?message}" would load as literal text.`,
|
|
409
|
-
{ details: { path: path2, token } }
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function substituteOnce(value, env, path2) {
|
|
414
|
-
return value.replace(
|
|
415
|
-
PLACEHOLDER_PATTERN,
|
|
416
|
-
(_match, name, hasDefault, def) => {
|
|
417
|
-
const envValue = env[name];
|
|
418
|
-
if (hasDefault !== void 0) {
|
|
419
|
-
if (envValue === void 0 || envValue === "") return def ?? "";
|
|
420
|
-
return envValue;
|
|
421
|
-
}
|
|
422
|
-
if (envValue === void 0) {
|
|
423
|
-
throw new CommerceError(
|
|
424
|
-
"CONFIG_INVALID",
|
|
425
|
-
`Unresolved environment variable "\${${name}}" referenced at config path "${path2}"`,
|
|
426
|
-
{ details: { variable: name, path: path2 } }
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
return envValue;
|
|
430
|
-
}
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
var PLACEHOLDER_PATTERN, BRACE_TOKEN, SUPPORTED_TOKEN, NESTED_PLACEHOLDER;
|
|
434
|
-
var init_env = __esm({
|
|
435
|
-
"src/config/env.ts"() {
|
|
436
|
-
init_core();
|
|
437
|
-
init_is_record();
|
|
438
|
-
PLACEHOLDER_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g;
|
|
439
|
-
BRACE_TOKEN = /\$\{[^{}]*\}/g;
|
|
440
|
-
SUPPORTED_TOKEN = /^\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}$/;
|
|
441
|
-
NESTED_PLACEHOLDER = /\$\{[^{}]*\$\{/;
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// src/protocols/acp/constants.ts
|
|
446
|
-
var ACP_SPEC_VERSION, ACP_API_VERSION, ACP_WELL_KNOWN_PATH, ACP_CHECKOUT_OPERATIONS, ACP_OPERATION_INPUT_KEYS, ACP_OPERATION_OPTIONAL_INPUT_KEYS, ACP_SESSION_ID_INPUT_KEY;
|
|
447
|
-
var init_constants = __esm({
|
|
448
|
-
"src/protocols/acp/constants.ts"() {
|
|
449
|
-
ACP_SPEC_VERSION = "2026-04-17";
|
|
450
|
-
ACP_API_VERSION = "2026-04-17";
|
|
451
|
-
ACP_WELL_KNOWN_PATH = "/.well-known/acp.json";
|
|
452
|
-
ACP_CHECKOUT_OPERATIONS = [
|
|
453
|
-
"createCheckoutSession",
|
|
454
|
-
"updateCheckoutSession",
|
|
455
|
-
"getCheckoutSession",
|
|
456
|
-
"completeCheckoutSession",
|
|
457
|
-
"cancelCheckoutSession"
|
|
458
|
-
];
|
|
459
|
-
ACP_OPERATION_INPUT_KEYS = {
|
|
460
|
-
createCheckoutSession: ["body"],
|
|
461
|
-
updateCheckoutSession: ["path", "body"],
|
|
462
|
-
getCheckoutSession: ["path"],
|
|
463
|
-
completeCheckoutSession: ["path", "body"],
|
|
464
|
-
cancelCheckoutSession: ["path"]
|
|
465
|
-
};
|
|
466
|
-
ACP_OPERATION_OPTIONAL_INPUT_KEYS = {
|
|
467
|
-
cancelCheckoutSession: ["body"]
|
|
468
|
-
};
|
|
469
|
-
ACP_SESSION_ID_INPUT_KEY = "checkout_session_id";
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// src/payments/x402/addresses.ts
|
|
474
|
-
function normalizeAddress(network, value) {
|
|
475
|
-
switch (network.family) {
|
|
476
|
-
case "evm":
|
|
477
|
-
return isEvmAddress(value) ? value : void 0;
|
|
478
|
-
case "svm":
|
|
479
|
-
return SOLANA_ADDRESS.test(value) && base58ByteLength(value) === 32 ? value : void 0;
|
|
480
|
-
case "near":
|
|
481
|
-
return value.length >= 2 && value.length <= 64 && NEAR_ACCOUNT_ID.test(value) ? value : void 0;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
function isBurnAddress(network, wireAddress) {
|
|
485
|
-
switch (network.family) {
|
|
486
|
-
case "evm":
|
|
487
|
-
return isEvmZeroAddress(wireAddress);
|
|
488
|
-
case "svm":
|
|
489
|
-
return wireAddress === SOLANA_ZERO;
|
|
490
|
-
case "near":
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
function sameAddress(network, a, b) {
|
|
495
|
-
return network.family === "evm" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
496
|
-
}
|
|
497
|
-
function base58ByteLength(value) {
|
|
498
|
-
let n = 0n;
|
|
499
|
-
for (const char of value) n = n * 58n + BigInt(BASE58_ALPHABET.indexOf(char));
|
|
500
|
-
const leadingZeros = value.length - value.replace(/^1+/, "").length;
|
|
501
|
-
return leadingZeros + (n === 0n ? 0 : Math.ceil(n.toString(16).length / 2));
|
|
502
|
-
}
|
|
503
|
-
var EVM_ADDRESS, EVM_ZERO, isEvmAddress, isEvmZeroAddress, BASE58_ALPHABET, SOLANA_ADDRESS, SOLANA_ZERO, NEAR_ACCOUNT_ID;
|
|
504
|
-
var init_addresses = __esm({
|
|
505
|
-
"src/payments/x402/addresses.ts"() {
|
|
506
|
-
EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
507
|
-
EVM_ZERO = /^0x0{40}$/i;
|
|
508
|
-
isEvmAddress = (value) => typeof value === "string" && EVM_ADDRESS.test(value);
|
|
509
|
-
isEvmZeroAddress = (value) => EVM_ZERO.test(value);
|
|
510
|
-
BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
511
|
-
SOLANA_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
512
|
-
SOLANA_ZERO = "11111111111111111111111111111111";
|
|
513
|
-
NEAR_ACCOUNT_ID = /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/;
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// src/config/schema/values.ts
|
|
518
|
-
function toNumber(value, path2, bounds = {}) {
|
|
519
|
-
if (typeof value === "string" && !/^\s*\d+\s*$/.test(value)) {
|
|
520
|
-
throw new CommerceError(
|
|
521
|
-
"CONFIG_INVALID",
|
|
522
|
-
`Configuration value at "${path2}" must be an integer written in decimal digits (got ${value === "" ? "an empty string" : `"${value}"`})`,
|
|
523
|
-
{ details: { path: path2 } }
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
527
|
-
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
528
|
-
throw new CommerceError(
|
|
529
|
-
"CONFIG_INVALID",
|
|
530
|
-
`Configuration value at "${path2}" must be an integer`,
|
|
531
|
-
{
|
|
532
|
-
details: { path: path2 }
|
|
533
|
-
}
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
if (bounds.min !== void 0 && n < bounds.min) {
|
|
537
|
-
throw new CommerceError(
|
|
538
|
-
"CONFIG_INVALID",
|
|
539
|
-
`Configuration value at "${path2}" must be >= ${bounds.min}`,
|
|
540
|
-
{
|
|
541
|
-
details: { path: path2 }
|
|
542
|
-
}
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
if (bounds.max !== void 0 && n > bounds.max) {
|
|
546
|
-
throw new CommerceError(
|
|
547
|
-
"CONFIG_INVALID",
|
|
548
|
-
`Configuration value at "${path2}" must be <= ${bounds.max}`,
|
|
549
|
-
{
|
|
550
|
-
details: { path: path2 }
|
|
551
|
-
}
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
return n;
|
|
555
|
-
}
|
|
556
|
-
function toBoolean(value, path2) {
|
|
557
|
-
if (typeof value === "boolean") return value;
|
|
558
|
-
const normalized = value.trim().toLowerCase();
|
|
559
|
-
if (normalized === "true") return true;
|
|
560
|
-
if (normalized === "false") return false;
|
|
561
|
-
throw new CommerceError(
|
|
562
|
-
"CONFIG_INVALID",
|
|
563
|
-
`Configuration value at "${path2}" must be a boolean ("true" or "false")`,
|
|
564
|
-
{ details: { path: path2 } }
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
function validateAddress(path2, value) {
|
|
568
|
-
if (!isEvmAddress(value)) {
|
|
569
|
-
throw new CommerceError(
|
|
570
|
-
"CONFIG_INVALID",
|
|
571
|
-
`Configuration value at "${path2}" is not a plausible address (expected "0x" followed by 40 hex characters)`,
|
|
572
|
-
{ details: { path: path2 } }
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
if (isEvmZeroAddress(value)) {
|
|
576
|
-
throw new CommerceError(
|
|
577
|
-
"CONFIG_INVALID",
|
|
578
|
-
`Configuration value at "${path2}" must not be the zero address`,
|
|
579
|
-
{
|
|
580
|
-
details: { path: path2 }
|
|
581
|
-
}
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
var init_values = __esm({
|
|
586
|
-
"src/config/schema/values.ts"() {
|
|
587
|
-
init_core();
|
|
588
|
-
init_addresses();
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// src/config/schema/acp.ts
|
|
593
|
-
function acpInvalid(path2, message, extra = {}) {
|
|
594
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2, ...extra } });
|
|
595
|
-
}
|
|
596
|
-
function normalizeAcp(raw) {
|
|
597
|
-
const mountPath = raw?.mountPath ?? DEFAULT_ACP_MOUNT_PATH;
|
|
598
|
-
if (raw === void 0 || !toBoolean(raw.enabled, "protocols.acp.enabled")) {
|
|
599
|
-
return { enabled: false, mountPath };
|
|
600
|
-
}
|
|
601
|
-
if (raw.auth === void 0) {
|
|
602
|
-
throw acpInvalid(
|
|
603
|
-
"protocols.acp.auth",
|
|
604
|
-
'protocols.acp.auth is required when ACP is enabled - every ACP checkout endpoint is authenticated, and "bearer" is the only scheme supported in this release'
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
if (raw.idempotency === void 0) {
|
|
608
|
-
throw acpInvalid(
|
|
609
|
-
"protocols.acp.idempotency",
|
|
610
|
-
"protocols.acp.idempotency is required when ACP is enabled - it names the SQLite file holding checkout idempotency records, and ACP makes the Idempotency-Key mandatory on every checkout POST"
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
const discovery = raw.discovery;
|
|
614
|
-
return {
|
|
615
|
-
enabled: true,
|
|
616
|
-
mountPath,
|
|
617
|
-
auth: raw.auth,
|
|
618
|
-
idempotency: {
|
|
619
|
-
path: raw.idempotency.path,
|
|
620
|
-
retentionHours: toNumber(
|
|
621
|
-
raw.idempotency.retentionHours ?? ACP_RETENTION_HOURS,
|
|
622
|
-
"protocols.acp.idempotency.retentionHours",
|
|
623
|
-
{ min: ACP_RETENTION_HOURS }
|
|
624
|
-
),
|
|
625
|
-
merchantIdempotent: raw.idempotency.merchantIdempotent ?? false
|
|
626
|
-
},
|
|
627
|
-
checkout: { operations: normalizeAcpOperations(raw.checkout) },
|
|
628
|
-
// Built key by key so absent metadata stays absent rather than `undefined`
|
|
629
|
-
...discovery !== void 0 ? {
|
|
630
|
-
discovery: {
|
|
631
|
-
...discovery.documentationUrl !== void 0 ? { documentationUrl: discovery.documentationUrl } : {},
|
|
632
|
-
...discovery.supportedCurrencies !== void 0 ? { supportedCurrencies: discovery.supportedCurrencies } : {},
|
|
633
|
-
...discovery.supportedLocales !== void 0 ? { supportedLocales: discovery.supportedLocales } : {},
|
|
634
|
-
...discovery.interventionTypes !== void 0 ? { interventionTypes: discovery.interventionTypes } : {}
|
|
635
|
-
}
|
|
636
|
-
} : {}
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
function normalizeAcpOperations(checkout) {
|
|
640
|
-
const configured = checkout?.operations ?? {};
|
|
641
|
-
for (const key of Object.keys(configured)) {
|
|
642
|
-
if (!ACP_CHECKOUT_OPERATIONS.includes(key)) {
|
|
643
|
-
throw acpInvalid(
|
|
644
|
-
`protocols.acp.checkout.operations.${key}`,
|
|
645
|
-
`protocols.acp.checkout.operations names unknown ACP checkout operation "${key}". Supported: ${ACP_CHECKOUT_OPERATIONS.join(", ")}.`
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
const operations = {};
|
|
650
|
-
const claimedBy = /* @__PURE__ */ new Map();
|
|
651
|
-
for (const operation of ACP_CHECKOUT_OPERATIONS) {
|
|
652
|
-
const path2 = `protocols.acp.checkout.operations.${operation}`;
|
|
653
|
-
const resourceId = configured[operation];
|
|
654
|
-
if (resourceId === void 0) {
|
|
655
|
-
throw acpInvalid(
|
|
656
|
-
path2,
|
|
657
|
-
`${path2} is required when ACP is enabled - all five checkout operations must be mapped, since ACP discovery advertises the checkout service as a whole`
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
const claimed = claimedBy.get(resourceId);
|
|
661
|
-
if (claimed !== void 0) {
|
|
662
|
-
throw acpInvalid(
|
|
663
|
-
path2,
|
|
664
|
-
`${path2} maps resource "${resourceId}", which is already mapped to "${claimed}" - each ACP checkout operation needs its own resource`,
|
|
665
|
-
{ resourceId }
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
claimedBy.set(resourceId, operation);
|
|
669
|
-
operations[operation] = resourceId;
|
|
670
|
-
}
|
|
671
|
-
return operations;
|
|
672
|
-
}
|
|
673
|
-
function validateAcpCheckoutMapping(acp, resources) {
|
|
674
|
-
const byId = new Map(resources.map((resource) => [resource.id, resource]));
|
|
675
|
-
for (const operation of ACP_CHECKOUT_OPERATIONS) {
|
|
676
|
-
const resourceId = acp.checkout.operations[operation];
|
|
677
|
-
const path2 = `protocols.acp.checkout.operations.${operation}`;
|
|
678
|
-
const resource = byId.get(resourceId);
|
|
679
|
-
if (resource === void 0) {
|
|
680
|
-
throw acpInvalid(
|
|
681
|
-
path2,
|
|
682
|
-
`${path2} maps resource "${resourceId}", which is not defined under "resources"`,
|
|
683
|
-
{ resourceId }
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
if (!resource.exposedVia.includes("acp")) {
|
|
687
|
-
throw acpInvalid(
|
|
688
|
-
path2,
|
|
689
|
-
`Resource "${resourceId}" implements ACP operation "${operation}" but does not list "acp" in its expose - the mapping says which resource serves the operation, expose says ACP may invoke it, and both are required`,
|
|
690
|
-
{ resourceId }
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
if (resource.pricing.type !== "free" || resource.paymentMethods.length > 0) {
|
|
694
|
-
throw acpInvalid(
|
|
695
|
-
path2,
|
|
696
|
-
`Resource "${resourceId}" implements ACP operation "${operation}" and must use pricing.type "free" with no "payments" - ACP checkout carries the merchant's own purchase payment, so the gateway does not also charge for the invocation`,
|
|
697
|
-
{ resourceId }
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
validateAcpOperationInput(path2, operation, resource);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
function validateAcpOperationInput(path2, operation, resource) {
|
|
704
|
-
const schema = resource.inputSchema;
|
|
705
|
-
if (schema === void 0) return;
|
|
706
|
-
const keys = ACP_OPERATION_INPUT_KEYS[operation];
|
|
707
|
-
const optionalKeys = ACP_OPERATION_OPTIONAL_INPUT_KEYS[operation] ?? [];
|
|
708
|
-
const sent = [...keys, ...optionalKeys];
|
|
709
|
-
const topLevel = closedObjectProperties(schema);
|
|
710
|
-
if (topLevel !== void 0) {
|
|
711
|
-
for (const key of sent) {
|
|
712
|
-
if (!Object.hasOwn(topLevel, key)) {
|
|
713
|
-
const when = keys.includes(key) ? "always sends" : "sends when the caller supplies it";
|
|
714
|
-
throw acpInvalid(
|
|
715
|
-
path2,
|
|
716
|
-
`Resource "${resource.id}" implements ACP operation "${operation}" but its input schema sets additionalProperties: false without declaring "${key}", which the adapter ${when} for this operation`,
|
|
717
|
-
{ resourceId: resource.id }
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
const declared = isRecord(schema["properties"]) ? schema["properties"] : {};
|
|
723
|
-
const pathKeys = closedObjectProperties(declared["path"]);
|
|
724
|
-
if (sent.includes("path") && pathKeys !== void 0 && !Object.hasOwn(pathKeys, ACP_SESSION_ID_INPUT_KEY)) {
|
|
725
|
-
throw acpInvalid(
|
|
726
|
-
path2,
|
|
727
|
-
`Resource "${resource.id}" implements ACP operation "${operation}" but its "path" schema sets additionalProperties: false without declaring "${ACP_SESSION_ID_INPUT_KEY}", which the adapter always sends inside "path"`,
|
|
728
|
-
{ resourceId: resource.id }
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
const bodyKeys = closedObjectProperties(declared["body"]);
|
|
732
|
-
if (sent.includes("body") && bodyKeys !== void 0 && Object.keys(bodyKeys).length === 0) {
|
|
733
|
-
throw acpInvalid(
|
|
734
|
-
path2,
|
|
735
|
-
`Resource "${resource.id}" implements ACP operation "${operation}" but its "body" schema accepts no keys, so it refuses every ACP document. Config load closes an object schema that omits additionalProperties: declare body as { type: object, additionalProperties: true }`,
|
|
736
|
-
{ resourceId: resource.id }
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
const required = schema["required"];
|
|
740
|
-
if (Array.isArray(required)) {
|
|
741
|
-
for (const entry of required) {
|
|
742
|
-
if (typeof entry === "string" && !keys.includes(entry)) {
|
|
743
|
-
throw acpInvalid(
|
|
744
|
-
path2,
|
|
745
|
-
`Resource "${resource.id}" implements ACP operation "${operation}" but its input schema requires "${entry}", which that operation does not always supply (it always sends: ${keys.join(", ")})`,
|
|
746
|
-
{ resourceId: resource.id }
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
function closedObjectProperties(schema) {
|
|
753
|
-
if (!isRecord(schema) || schema["additionalProperties"] !== false) return void 0;
|
|
754
|
-
return isRecord(schema["properties"]) ? schema["properties"] : {};
|
|
755
|
-
}
|
|
756
|
-
var DEFAULT_ACP_MOUNT_PATH, ACP_RETENTION_HOURS;
|
|
757
|
-
var init_acp = __esm({
|
|
758
|
-
"src/config/schema/acp.ts"() {
|
|
759
|
-
init_core();
|
|
760
|
-
init_is_record();
|
|
761
|
-
init_constants();
|
|
762
|
-
init_values();
|
|
763
|
-
DEFAULT_ACP_MOUNT_PATH = "/acp";
|
|
764
|
-
ACP_RETENTION_HOURS = 24;
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
// src/authorization/ap2/constants.ts
|
|
769
|
-
var AP2_SPEC_VERSION, AP2_MODES, AP2_SIGNING_ALGORITHM, AP2_KEY_TYPE, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, AP2_JWK_MEMBERS, AP2_CHECKOUT_MANDATE_VCT, AP2_CHECKOUT_PROFILE, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_MAX_CLOCK_SKEW_SECONDS;
|
|
770
|
-
var init_constants2 = __esm({
|
|
771
|
-
"src/authorization/ap2/constants.ts"() {
|
|
772
|
-
AP2_SPEC_VERSION = "0.2.0";
|
|
773
|
-
AP2_MODES = ["direct"];
|
|
774
|
-
AP2_SIGNING_ALGORITHM = "ES256";
|
|
775
|
-
AP2_KEY_TYPE = "EC";
|
|
776
|
-
AP2_JWK_CURVE = "P-256";
|
|
777
|
-
AP2_JWK_COORDINATE_BYTES = 32;
|
|
778
|
-
AP2_JWK_MEMBERS = ["kty", "crv", "x", "y", "kid", "alg", "use"];
|
|
779
|
-
AP2_CHECKOUT_MANDATE_VCT = "mandate.checkout.1";
|
|
780
|
-
AP2_CHECKOUT_PROFILE = "agent-commerce/ap2/checkout/v1";
|
|
781
|
-
AP2_DEFAULT_CLOCK_SKEW_SECONDS = 60;
|
|
782
|
-
AP2_MAX_CLOCK_SKEW_SECONDS = 300;
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
// src/config/schema/ap2.ts
|
|
787
|
-
function ap2Invalid(path2, message, extra = {}) {
|
|
788
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2, ...extra } });
|
|
789
|
-
}
|
|
790
|
-
function normalizeAp2(raw) {
|
|
791
|
-
if (raw === void 0) return void 0;
|
|
792
|
-
if (!toBoolean(raw.enabled, "authorization.ap2.enabled")) return { enabled: false };
|
|
793
|
-
const specVersion = raw.specVersion ?? AP2_SPEC_VERSION;
|
|
794
|
-
if (specVersion !== AP2_SPEC_VERSION) {
|
|
795
|
-
throw ap2Invalid(
|
|
796
|
-
"authorization.ap2.specVersion",
|
|
797
|
-
`authorization.ap2.specVersion "${specVersion}" is not supported - this gateway verifies against the tagged AP2 release ${AP2_SPEC_VERSION} only`
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
const mode = raw.mode ?? AP2_MODES[0];
|
|
801
|
-
if (!AP2_MODES.includes(mode)) {
|
|
802
|
-
throw ap2Invalid(
|
|
803
|
-
"authorization.ap2.mode",
|
|
804
|
-
`authorization.ap2.mode "${mode}" is not supported. Supported: ${AP2_MODES.join(", ")}. Autonomous mode needs open mandates, agent key binding and constraint evaluation, none of which this release implements.`
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
if (raw.replay === void 0) {
|
|
808
|
-
throw ap2Invalid(
|
|
809
|
-
"authorization.ap2.replay",
|
|
810
|
-
"authorization.ap2.replay is required when AP2 is enabled - it names the SQLite file recording which mandates have been spent, and without it a verified mandate could authorize a second settlement"
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
const mandateIssuers = normalizeIssuers(
|
|
814
|
-
raw.trust?.mandateIssuers,
|
|
815
|
-
"authorization.ap2.trust.mandateIssuers"
|
|
816
|
-
);
|
|
817
|
-
const checkoutIssuers = normalizeIssuers(
|
|
818
|
-
raw.trust?.checkoutIssuers,
|
|
819
|
-
"authorization.ap2.trust.checkoutIssuers"
|
|
820
|
-
);
|
|
821
|
-
return {
|
|
822
|
-
enabled: true,
|
|
823
|
-
specVersion: AP2_SPEC_VERSION,
|
|
824
|
-
mode,
|
|
825
|
-
trust: { mandateIssuers, checkoutIssuers },
|
|
826
|
-
clockSkewSeconds: toNumber(
|
|
827
|
-
raw.clockSkewSeconds ?? AP2_DEFAULT_CLOCK_SKEW_SECONDS,
|
|
828
|
-
"authorization.ap2.clockSkewSeconds",
|
|
829
|
-
{ min: 0, max: AP2_MAX_CLOCK_SKEW_SECONDS }
|
|
830
|
-
),
|
|
831
|
-
requireMandateAudience: toBoolean(
|
|
832
|
-
raw.requireMandateAudience ?? false,
|
|
833
|
-
"authorization.ap2.requireMandateAudience"
|
|
834
|
-
),
|
|
835
|
-
requireMandateExpiry: toBoolean(
|
|
836
|
-
raw.requireMandateExpiry ?? false,
|
|
837
|
-
"authorization.ap2.requireMandateExpiry"
|
|
838
|
-
),
|
|
839
|
-
replay: { path: raw.replay.path }
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
function normalizeIssuers(raw, path2) {
|
|
843
|
-
if (raw === void 0 || raw.length === 0) {
|
|
844
|
-
throw ap2Invalid(
|
|
845
|
-
path2,
|
|
846
|
-
`${path2} is required when AP2 is enabled and must name at least one issuer - trust is operator-configured and static, so an empty list means no mandate can ever verify`
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
const seen = /* @__PURE__ */ new Set();
|
|
850
|
-
return raw.map((entry, index) => {
|
|
851
|
-
const entryPath = `${path2}.${index}`;
|
|
852
|
-
if (seen.has(entry.issuer)) {
|
|
853
|
-
throw ap2Invalid(
|
|
854
|
-
`${entryPath}.issuer`,
|
|
855
|
-
`${entryPath}.issuer "${entry.issuer}" is listed twice - key lookup resolves by issuer, so a second entry for the same one is silently unreachable. Merge the keys into one entry instead (which is also how a rotation overlaps an old and a new key).`,
|
|
856
|
-
{ issuer: entry.issuer }
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
seen.add(entry.issuer);
|
|
860
|
-
const kids = /* @__PURE__ */ new Set();
|
|
861
|
-
const keys = entry.keys.map((key, keyIndex) => {
|
|
862
|
-
const keyPath = `${entryPath}.keys.${keyIndex}`;
|
|
863
|
-
if (kids.has(key.kid)) {
|
|
864
|
-
throw ap2Invalid(
|
|
865
|
-
`${keyPath}.kid`,
|
|
866
|
-
`${keyPath}.kid "${key.kid}" is listed twice for issuer "${entry.issuer}" - a kid selects exactly one key, so which of the two verifies a signature would be undefined`,
|
|
867
|
-
{ issuer: entry.issuer, kid: key.kid }
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
kids.add(key.kid);
|
|
871
|
-
return { kid: key.kid, jwk: validateJwk(key.jwk, key.kid, keyPath) };
|
|
872
|
-
});
|
|
873
|
-
return { issuer: entry.issuer, audience: entry.audience, keys };
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
function validateJwk(jwk, kid, path2) {
|
|
877
|
-
const fail = (detail) => {
|
|
878
|
-
throw ap2Invalid(`${path2}.jwk`, `${path2}.jwk (kid "${kid}") ${detail}`, { kid });
|
|
879
|
-
};
|
|
880
|
-
for (const member of Object.keys(jwk)) {
|
|
881
|
-
if (AP2_JWK_MEMBERS.includes(member)) continue;
|
|
882
|
-
if (member === "d" || member === "k") {
|
|
883
|
-
fail(
|
|
884
|
-
`carries private key material ("${member}"). The gateway verifies signatures and never produces them; publish only the public half. Treat the pasted key as compromised and rotate it.`
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
fail(
|
|
888
|
-
`has unsupported member "${member}". Allowed: ${AP2_JWK_MEMBERS.join(", ")}. Members naming a URL are refused on purpose - keys are configured inline and never fetched.`
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
const value = (member) => {
|
|
892
|
-
const raw = jwk[member];
|
|
893
|
-
if (typeof raw !== "string" || raw.length === 0) {
|
|
894
|
-
fail(`must give "${member}" as a non-empty string`);
|
|
895
|
-
}
|
|
896
|
-
return raw;
|
|
897
|
-
};
|
|
898
|
-
if (value("kty") !== AP2_KEY_TYPE) {
|
|
899
|
-
fail(
|
|
900
|
-
`must have kty "${AP2_KEY_TYPE}" (got "${value("kty")}") - ${AP2_SIGNING_ALGORITHM} is the only accepted algorithm`
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
if (value("crv") !== AP2_JWK_CURVE) {
|
|
904
|
-
fail(
|
|
905
|
-
`must be on curve "${AP2_JWK_CURVE}" (got "${value("crv")}") - ${AP2_SIGNING_ALGORITHM} is the only accepted algorithm`
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
for (const coordinate of ["x", "y"]) {
|
|
909
|
-
const encoded = value(coordinate);
|
|
910
|
-
if (!/^[A-Za-z0-9_-]+$/.test(encoded)) {
|
|
911
|
-
fail(`coordinate "${coordinate}" is not base64url (no padding, no "+" or "/")`);
|
|
912
|
-
}
|
|
913
|
-
if (Buffer.from(encoded, "base64url").length !== AP2_JWK_COORDINATE_BYTES) {
|
|
914
|
-
fail(
|
|
915
|
-
`coordinate "${coordinate}" decodes to ${Buffer.from(encoded, "base64url").length} bytes; a ${AP2_JWK_CURVE} coordinate is ${AP2_JWK_COORDINATE_BYTES}`
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
if (jwk["alg"] !== void 0 && value("alg") !== AP2_SIGNING_ALGORITHM) {
|
|
920
|
-
fail(`declares alg "${value("alg")}"; only ${AP2_SIGNING_ALGORITHM} is accepted`);
|
|
921
|
-
}
|
|
922
|
-
if (jwk["use"] !== void 0 && value("use") !== "sig") {
|
|
923
|
-
fail(`declares use "${value("use")}"; a verification key must be "sig"`);
|
|
924
|
-
}
|
|
925
|
-
if (jwk["kid"] !== void 0 && value("kid") !== kid) {
|
|
926
|
-
fail(`declares kid "${value("kid")}", which disagrees with the configured kid "${kid}"`);
|
|
927
|
-
}
|
|
928
|
-
const normalized = {};
|
|
929
|
-
for (const member of AP2_JWK_MEMBERS) {
|
|
930
|
-
if (jwk[member] !== void 0) normalized[member] = value(member);
|
|
931
|
-
}
|
|
932
|
-
return normalized;
|
|
933
|
-
}
|
|
934
|
-
function validateReplayStoreIsolated(replayPath, receiptsPath, acp) {
|
|
935
|
-
if (replayPath === ":memory:") return;
|
|
936
|
-
const others = [["storage.receipts.path", receiptsPath]];
|
|
937
|
-
if (acp.enabled) others.push(["protocols.acp.idempotency.path", acp.idempotency.path]);
|
|
938
|
-
for (const [otherPath, other] of others) {
|
|
939
|
-
if (replayPath !== other) continue;
|
|
940
|
-
throw ap2Invalid(
|
|
941
|
-
"authorization.ap2.replay.path",
|
|
942
|
-
`authorization.ap2.replay.path is the same file as ${otherPath} ("${replayPath}") - the AP2 replay store keeps its own schema and must not share a database with another store`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
function normalizeResourceAuthorization(id, entry, pricing, ap2) {
|
|
947
|
-
const required = entry.authorization?.required;
|
|
948
|
-
if (required === void 0) return void 0;
|
|
949
|
-
const path2 = `resources.${id}.authorization.required`;
|
|
950
|
-
const methods = [];
|
|
951
|
-
for (const method of required) {
|
|
952
|
-
if (!SUPPORTED_AUTHORIZATION_METHODS.has(method)) {
|
|
953
|
-
throw ap2Invalid(
|
|
954
|
-
path2,
|
|
955
|
-
`Resource "${id}" requires unsupported authorization method "${method}". Supported: ${[...SUPPORTED_AUTHORIZATION_METHODS].join(", ")}.`,
|
|
956
|
-
{ resourceId: id, method }
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
if (methods.includes(method)) {
|
|
960
|
-
throw ap2Invalid(path2, `Resource "${id}" lists authorization method "${method}" twice`, {
|
|
961
|
-
resourceId: id,
|
|
962
|
-
method
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
if (method === "ap2" && (ap2 === void 0 || !ap2.enabled)) {
|
|
966
|
-
throw ap2Invalid(
|
|
967
|
-
path2,
|
|
968
|
-
`Resource "${id}" requires authorization method "ap2", which is not configured or not enabled under authorization.ap2`,
|
|
969
|
-
{ resourceId: id, method }
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
methods.push(method);
|
|
973
|
-
}
|
|
974
|
-
if (pricing.type !== "fixed") {
|
|
975
|
-
throw ap2Invalid(
|
|
976
|
-
path2,
|
|
977
|
-
`Resource "${id}" requires authorization but its pricing is "${pricing.type}" - authorization proves a purchase was approved and never replaces payment, so it applies to fixed-price paid resources only in this release`,
|
|
978
|
-
{ resourceId: id }
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
return { required: methods };
|
|
982
|
-
}
|
|
983
|
-
var SUPPORTED_AUTHORIZATION_METHODS;
|
|
984
|
-
var init_ap2 = __esm({
|
|
985
|
-
"src/config/schema/ap2.ts"() {
|
|
986
|
-
init_constants2();
|
|
987
|
-
init_core();
|
|
988
|
-
init_values();
|
|
989
|
-
SUPPORTED_AUTHORIZATION_METHODS = /* @__PURE__ */ new Set(["ap2"]);
|
|
990
|
-
}
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
// src/payments/mpp/constants.ts
|
|
994
|
-
function isMppNetwork(value) {
|
|
995
|
-
return MPP_NETWORKS.includes(value);
|
|
996
|
-
}
|
|
997
|
-
var MPP_SPEC_COMMIT, MPP_SPEC_DRAFTS, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPPX_VERSION, MPP_PROFILE, MPP_NETWORKS, MPP_DEFAULT_NETWORK, MPP_DEFAULT_PAYMENT_FLOW;
|
|
998
|
-
var init_constants3 = __esm({
|
|
999
|
-
"src/payments/mpp/constants.ts"() {
|
|
1000
|
-
MPP_SPEC_COMMIT = "806fdb8b8c92cda5c84b8660558921b6bbeef7e0";
|
|
1001
|
-
MPP_SPEC_DRAFTS = {
|
|
1002
|
-
core: "draft-httpauth-payment-01",
|
|
1003
|
-
intent: "draft-payment-intent-charge-00",
|
|
1004
|
-
method: "draft-evm-charge-00"
|
|
1005
|
-
};
|
|
1006
|
-
MPP_MIN_CHALLENGE_SECRET_LENGTH = 32;
|
|
1007
|
-
MPPX_VERSION = "0.13.1";
|
|
1008
|
-
MPP_PROFILE = {
|
|
1009
|
-
intent: "charge",
|
|
1010
|
-
method: "evm",
|
|
1011
|
-
credentialType: "authorization",
|
|
1012
|
-
assetSymbol: "USDC",
|
|
1013
|
-
assetDecimals: 6
|
|
1014
|
-
};
|
|
1015
|
-
MPP_NETWORKS = ["eip155:31337", "eip155:84532", "eip155:8453"];
|
|
1016
|
-
MPP_DEFAULT_NETWORK = "eip155:84532";
|
|
1017
|
-
MPP_DEFAULT_PAYMENT_FLOW = "upfront";
|
|
1018
|
-
`mpp/${MPP_SPEC_DRAFTS.core} intent=${MPP_PROFILE.intent} method=${MPP_PROFILE.method}`;
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
// src/payments/x402/local-chain/accounts.ts
|
|
1023
|
-
var ANVIL_WELL_KNOWN_ACCOUNTS;
|
|
1024
|
-
var init_accounts = __esm({
|
|
1025
|
-
"src/payments/x402/local-chain/accounts.ts"() {
|
|
1026
|
-
ANVIL_WELL_KNOWN_ACCOUNTS = [
|
|
1027
|
-
{
|
|
1028
|
-
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
|
1029
|
-
privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
|
1033
|
-
privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
|
1034
|
-
},
|
|
1035
|
-
{
|
|
1036
|
-
address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
|
|
1037
|
-
privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
|
|
1038
|
-
}
|
|
1039
|
-
];
|
|
1040
|
-
ANVIL_WELL_KNOWN_ACCOUNTS[0];
|
|
1041
|
-
ANVIL_WELL_KNOWN_ACCOUNTS[1];
|
|
1042
|
-
ANVIL_WELL_KNOWN_ACCOUNTS[2];
|
|
1043
|
-
}
|
|
1044
|
-
});
|
|
1045
|
-
|
|
1046
|
-
// src/payments/x402/dev-key-guard.ts
|
|
1047
|
-
function isWellKnownDevAddress(address) {
|
|
1048
|
-
return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
|
|
1049
|
-
}
|
|
1050
|
-
function isLikelyLocalOrPrivateHost(hostname) {
|
|
1051
|
-
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1052
|
-
if (h === "localhost" || h === "::1") return true;
|
|
1053
|
-
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
1054
|
-
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
1055
|
-
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
1056
|
-
if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
1057
|
-
if (!h.includes(".") && !h.includes(":")) return true;
|
|
1058
|
-
return false;
|
|
1059
|
-
}
|
|
1060
|
-
var WELL_KNOWN_DEV_ADDRESSES;
|
|
1061
|
-
var init_dev_key_guard = __esm({
|
|
1062
|
-
"src/payments/x402/dev-key-guard.ts"() {
|
|
1063
|
-
init_core();
|
|
1064
|
-
init_accounts();
|
|
1065
|
-
new Set(
|
|
1066
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
|
|
1067
|
-
);
|
|
1068
|
-
WELL_KNOWN_DEV_ADDRESSES = new Set(
|
|
1069
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
|
|
1070
|
-
);
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
// src/payments/x402/networks.ts
|
|
1075
|
-
function invalid(message, path2) {
|
|
1076
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
|
|
1077
|
-
}
|
|
1078
|
-
function findX402Network(id, custom) {
|
|
1079
|
-
const builtIn = BY_ID.get(id);
|
|
1080
|
-
if (builtIn) return builtIn;
|
|
1081
|
-
const declared = custom?.[id];
|
|
1082
|
-
const chainId = Number(EVM_NETWORK_ID.exec(id)?.[1]);
|
|
1083
|
-
if (!declared || !Number.isSafeInteger(chainId)) return void 0;
|
|
1084
|
-
return {
|
|
1085
|
-
family: "evm",
|
|
1086
|
-
id,
|
|
1087
|
-
chainId,
|
|
1088
|
-
displayName: declared.displayName,
|
|
1089
|
-
kind: declared.kind
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
function requireX402Network(id, path2, custom) {
|
|
1093
|
-
const network = findX402Network(id, custom);
|
|
1094
|
-
if (!network) {
|
|
1095
|
-
throw new CommerceError(
|
|
1096
|
-
"CONFIG_INVALID",
|
|
1097
|
-
`${path2}: "${id}" is not a known network. Built-in CAIP-2 identifiers: ${PROFILES.map((p) => p.id).join(", ")}. Declare another EVM network under payments.x402.networks with its kind and displayName.`,
|
|
1098
|
-
{ details: { path: path2, network: id } }
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
return network;
|
|
1102
|
-
}
|
|
1103
|
-
function validateCustomNetworks(custom, path2) {
|
|
1104
|
-
for (const id of Object.keys(custom)) {
|
|
1105
|
-
const at = `${path2}.${id}`;
|
|
1106
|
-
if (BY_ID.has(id)) {
|
|
1107
|
-
throw invalid(
|
|
1108
|
-
`${at}: "${id}" is a built-in network and cannot be redeclared; remove this entry`,
|
|
1109
|
-
at
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1112
|
-
if (!findX402Network(id, custom)) {
|
|
1113
|
-
throw invalid(
|
|
1114
|
-
`${at}: "${id}" is not an EVM CAIP-2 identifier ("eip155:" followed by a positive chain id below 2^53). Only EVM networks can be declared.`,
|
|
1115
|
-
at
|
|
1116
|
-
);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
function resolveDeploymentMode(profile) {
|
|
1121
|
-
return profile.local ? "local" : profile.kind;
|
|
1122
|
-
}
|
|
1123
|
-
function describeDeploymentMode(mode) {
|
|
1124
|
-
return mode === "mainnet" ? LIVE_MAINNET_BANNER : mode.toUpperCase();
|
|
1125
|
-
}
|
|
1126
|
-
var LOCAL_CHAIN_ID, usdc, PROFILES, BY_ID, EVM_NETWORK_ID, LIVE_MAINNET_BANNER;
|
|
1127
|
-
var init_networks = __esm({
|
|
1128
|
-
"src/payments/x402/networks.ts"() {
|
|
1129
|
-
init_core();
|
|
1130
|
-
LOCAL_CHAIN_ID = 31337;
|
|
1131
|
-
usdc = (address, name) => ({ symbol: "USDC", address, name, version: "2", decimals: 6 });
|
|
1132
|
-
PROFILES = [
|
|
1133
|
-
{
|
|
1134
|
-
family: "evm",
|
|
1135
|
-
id: `eip155:${LOCAL_CHAIN_ID}`,
|
|
1136
|
-
chainId: LOCAL_CHAIN_ID,
|
|
1137
|
-
displayName: "Local Anvil",
|
|
1138
|
-
kind: "testnet",
|
|
1139
|
-
local: true
|
|
1140
|
-
},
|
|
1141
|
-
{
|
|
1142
|
-
family: "evm",
|
|
1143
|
-
id: "eip155:84532",
|
|
1144
|
-
chainId: 84532,
|
|
1145
|
-
displayName: "Base Sepolia",
|
|
1146
|
-
kind: "testnet",
|
|
1147
|
-
canonicalAsset: usdc("0x036CbD53842c5426634e7929541eC2318f3dCF7e", "USDC")
|
|
1148
|
-
},
|
|
1149
|
-
{
|
|
1150
|
-
family: "evm",
|
|
1151
|
-
id: "eip155:8453",
|
|
1152
|
-
chainId: 8453,
|
|
1153
|
-
displayName: "Base",
|
|
1154
|
-
kind: "mainnet",
|
|
1155
|
-
canonicalAsset: usdc("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "USD Coin")
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
family: "evm",
|
|
1159
|
-
id: "eip155:1",
|
|
1160
|
-
chainId: 1,
|
|
1161
|
-
displayName: "Ethereum",
|
|
1162
|
-
kind: "mainnet",
|
|
1163
|
-
canonicalAsset: usdc("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "USD Coin")
|
|
1164
|
-
},
|
|
1165
|
-
{
|
|
1166
|
-
family: "evm",
|
|
1167
|
-
id: "eip155:137",
|
|
1168
|
-
chainId: 137,
|
|
1169
|
-
displayName: "Polygon",
|
|
1170
|
-
kind: "mainnet",
|
|
1171
|
-
canonicalAsset: usdc("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", "USD Coin")
|
|
1172
|
-
},
|
|
1173
|
-
{
|
|
1174
|
-
family: "evm",
|
|
1175
|
-
id: "eip155:42161",
|
|
1176
|
-
chainId: 42161,
|
|
1177
|
-
displayName: "Arbitrum One",
|
|
1178
|
-
kind: "mainnet",
|
|
1179
|
-
canonicalAsset: usdc("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "USD Coin")
|
|
1180
|
-
},
|
|
1181
|
-
{
|
|
1182
|
-
family: "evm",
|
|
1183
|
-
id: "eip155:421614",
|
|
1184
|
-
chainId: 421614,
|
|
1185
|
-
displayName: "Arbitrum Sepolia",
|
|
1186
|
-
kind: "testnet",
|
|
1187
|
-
canonicalAsset: usdc("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "USD Coin")
|
|
1188
|
-
},
|
|
1189
|
-
{
|
|
1190
|
-
family: "evm",
|
|
1191
|
-
id: "eip155:43114",
|
|
1192
|
-
chainId: 43114,
|
|
1193
|
-
displayName: "Avalanche C-Chain",
|
|
1194
|
-
kind: "mainnet",
|
|
1195
|
-
canonicalAsset: usdc("0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", "USD Coin")
|
|
1196
|
-
},
|
|
1197
|
-
{
|
|
1198
|
-
family: "svm",
|
|
1199
|
-
id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
1200
|
-
displayName: "Solana",
|
|
1201
|
-
kind: "mainnet",
|
|
1202
|
-
canonicalAsset: {
|
|
1203
|
-
symbol: "USDC",
|
|
1204
|
-
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1205
|
-
decimals: 6
|
|
1206
|
-
}
|
|
1207
|
-
},
|
|
1208
|
-
{
|
|
1209
|
-
family: "svm",
|
|
1210
|
-
id: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
1211
|
-
displayName: "Solana Devnet",
|
|
1212
|
-
kind: "testnet",
|
|
1213
|
-
canonicalAsset: {
|
|
1214
|
-
symbol: "USDC",
|
|
1215
|
-
address: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
1216
|
-
decimals: 6
|
|
1217
|
-
}
|
|
1218
|
-
},
|
|
1219
|
-
// NEAR's USDC contracts are implicit accounts: 64 lowercase hex characters
|
|
1220
|
-
{
|
|
1221
|
-
family: "near",
|
|
1222
|
-
id: "near:mainnet",
|
|
1223
|
-
displayName: "NEAR",
|
|
1224
|
-
kind: "mainnet",
|
|
1225
|
-
canonicalAsset: {
|
|
1226
|
-
symbol: "USDC",
|
|
1227
|
-
address: "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
|
|
1228
|
-
decimals: 6
|
|
1229
|
-
}
|
|
1230
|
-
},
|
|
1231
|
-
{
|
|
1232
|
-
family: "near",
|
|
1233
|
-
id: "near:testnet",
|
|
1234
|
-
displayName: "NEAR Testnet",
|
|
1235
|
-
kind: "testnet",
|
|
1236
|
-
canonicalAsset: {
|
|
1237
|
-
symbol: "USDC",
|
|
1238
|
-
address: "3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af",
|
|
1239
|
-
decimals: 6
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
];
|
|
1243
|
-
BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
|
|
1244
|
-
PROFILES.filter(
|
|
1245
|
-
(p) => p.family === "evm"
|
|
1246
|
-
).map((p) => p.id);
|
|
1247
|
-
EVM_NETWORK_ID = /^eip155:([1-9]\d{0,15})$/;
|
|
1248
|
-
LIVE_MAINNET_BANNER = "LIVE MAINNET MODE - REAL FUNDS";
|
|
1249
|
-
}
|
|
1250
|
-
});
|
|
1251
|
-
|
|
1252
|
-
// src/payments/x402/guardrails.ts
|
|
1253
|
-
function invalid2(message, path2) {
|
|
1254
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
|
|
1255
|
-
}
|
|
1256
|
-
function resolveX402Deployment(input) {
|
|
1257
|
-
const at = input.configPath ?? "payments.x402";
|
|
1258
|
-
const payTo = input.payToField ?? "payTo";
|
|
1259
|
-
const profile = requireX402Network(input.network, `${at}.network`, input.customNetworks);
|
|
1260
|
-
const mode = resolveDeploymentMode(profile);
|
|
1261
|
-
if (profile.kind === "mainnet" && input.facilitator.mode === "local") {
|
|
1262
|
-
throw invalid2(
|
|
1263
|
-
`${at}: network "${profile.id}" (${profile.displayName}) is a mainnet and cannot be served by facilitator.mode "local". A mainnet deployment must settle through a remote facilitator.`,
|
|
1264
|
-
`${at}.facilitator.mode`
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
if (input.facilitator.mode === "local" && mode !== "local") {
|
|
1268
|
-
throw invalid2(
|
|
1269
|
-
profile.id === BASE_SEPOLIA ? `${at}: the local Anvil chain is eip155:${LOCAL_CHAIN_ID}, Anvil's default chain id; "${BASE_SEPOLIA}" is public Base Sepolia. Set ${at}.network to eip155:${LOCAL_CHAIN_ID}, start Anvil with its default chain id and redeploy the local contracts (npm run chain:start, npm run chain:deploy).` : `${at}: facilitator.mode "local" serves the local Anvil chain (eip155:${LOCAL_CHAIN_ID}) only, not "${profile.id}" (${profile.displayName}). Use a remote facilitator.`,
|
|
1270
|
-
`${at}.facilitator.mode`
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
if (mode === "mainnet" && input.allowMainnet !== true) {
|
|
1274
|
-
throw invalid2(
|
|
1275
|
-
`${at}: network "${profile.id}" (${profile.displayName}) settles real funds. Set ${at}.allowMainnet: true to acknowledge this explicitly - it is never the default.`,
|
|
1276
|
-
`${at}.allowMainnet`
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
if (input.facilitator.mode === "remote") {
|
|
1280
|
-
assertFacilitatorUrlIsSafe(input.facilitator.url, mode, at);
|
|
1281
|
-
if (input.facilitator.auth.type === "cdp" && !isCdpFacilitatorUrl(input.facilitator.url)) {
|
|
1282
|
-
throw invalid2(
|
|
1283
|
-
`${at}: facilitator.auth.type "cdp" signs requests for ${CDP_FACILITATOR_URL} only, so facilitator ${describeOrigin(input.facilitator.url)} cannot accept its credential. Set facilitator.url to ${CDP_FACILITATOR_URL}, or use auth.type "bearer" or "none".`,
|
|
1284
|
-
`${at}.facilitator.url`
|
|
1285
|
-
);
|
|
1286
|
-
}
|
|
1287
|
-
if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
|
|
1288
|
-
throw invalid2(
|
|
1289
|
-
`${at}: no credential is configured for facilitator ${describeOrigin(input.facilitator.url)} on mainnet. Set ${at}.allowUnauthenticatedFacilitator: true to accept anonymous access and the facilitator's limits, or configure facilitator.auth.`,
|
|
1290
|
-
`${at}.allowUnauthenticatedFacilitator`
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
for (const [field, value] of credentialFields(input.facilitator.auth)) {
|
|
1294
|
-
if (value.trim() === "") {
|
|
1295
|
-
throw invalid2(
|
|
1296
|
-
`${at}: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
|
|
1297
|
-
`${at}.facilitator.auth.${field}`
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
if (isBurnAddress(profile, input.payTo)) {
|
|
1303
|
-
throw invalid2(
|
|
1304
|
-
`${at}: "${payTo}" is the zero address. Every payment settled there is destroyed.`,
|
|
1305
|
-
`${at}.${payTo}`
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
|
|
1309
|
-
throw invalid2(
|
|
1310
|
-
`${at}: "${payTo}" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set ${payTo} to your own merchant wallet.`,
|
|
1311
|
-
`${at}.${payTo}`
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
const canonical = profile.canonicalAsset;
|
|
1315
|
-
if (canonical) {
|
|
1316
|
-
if (!sameAddress(profile, input.asset, canonical.address)) {
|
|
1317
|
-
throw invalid2(
|
|
1318
|
-
`${at}: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). A built-in network settles only in its ${canonical.symbol}.`,
|
|
1319
|
-
`${at}.asset`
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
const domain = "name" in canonical ? canonical : void 0;
|
|
1323
|
-
if (domain && input.assetName !== void 0 && input.assetName !== domain.name) {
|
|
1324
|
-
throw invalid2(
|
|
1325
|
-
`${at}: assetName "${input.assetName}" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected "${domain.name}"). Every payment would be refused after the buyer signed.`,
|
|
1326
|
-
`${at}.assetName`
|
|
1327
|
-
);
|
|
1328
|
-
}
|
|
1329
|
-
if (domain && input.assetVersion !== void 0 && input.assetVersion !== domain.version) {
|
|
1330
|
-
throw invalid2(
|
|
1331
|
-
`${at}: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${domain.version}").`,
|
|
1332
|
-
`${at}.assetVersion`
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
return { profile, mode };
|
|
1337
|
-
}
|
|
1338
|
-
function assertFacilitatorUrlIsSafe(url, mode, at) {
|
|
1339
|
-
let parsed;
|
|
1340
|
-
try {
|
|
1341
|
-
parsed = new URL(url);
|
|
1342
|
-
} catch (cause) {
|
|
1343
|
-
throw new CommerceError("CONFIG_INVALID", `${at}: facilitator.url is not a valid URL`, {
|
|
1344
|
-
cause,
|
|
1345
|
-
details: { path: `${at}.facilitator.url` }
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
if (parsed.protocol === "https:") return;
|
|
1349
|
-
if (parsed.protocol !== "http:") {
|
|
1350
|
-
throw invalid2(
|
|
1351
|
-
`${at}: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
|
|
1352
|
-
`${at}.facilitator.url`
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1355
|
-
if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
|
|
1356
|
-
throw invalid2(
|
|
1357
|
-
`${at}: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorizations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
|
|
1358
|
-
`${at}.facilitator.url`
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1361
|
-
function isCdpFacilitatorUrl(url) {
|
|
1362
|
-
try {
|
|
1363
|
-
const parsed = new URL(url);
|
|
1364
|
-
const expected = new URL(CDP_FACILITATOR_URL);
|
|
1365
|
-
return parsed.origin === expected.origin && parsed.pathname.replace(/\/+$/, "") === expected.pathname && parsed.search === "";
|
|
1366
|
-
} catch {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
function describeOrigin(url) {
|
|
1371
|
-
try {
|
|
1372
|
-
return new URL(url).origin;
|
|
1373
|
-
} catch {
|
|
1374
|
-
return "[unparseable facilitator.url]";
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
function credentialFields(auth) {
|
|
1378
|
-
switch (auth.type) {
|
|
1379
|
-
case "bearer":
|
|
1380
|
-
return [["token", auth.token]];
|
|
1381
|
-
case "cdp":
|
|
1382
|
-
return [
|
|
1383
|
-
["apiKeyId", auth.apiKeyId],
|
|
1384
|
-
["apiKeySecret", auth.apiKeySecret]
|
|
1385
|
-
];
|
|
1386
|
-
default:
|
|
1387
|
-
return [];
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
var BASE_SEPOLIA, CDP_FACILITATOR_URL;
|
|
1391
|
-
var init_guardrails = __esm({
|
|
1392
|
-
"src/payments/x402/guardrails.ts"() {
|
|
1393
|
-
init_core();
|
|
1394
|
-
init_addresses();
|
|
1395
|
-
init_dev_key_guard();
|
|
1396
|
-
init_networks();
|
|
1397
|
-
BASE_SEPOLIA = "eip155:84532";
|
|
1398
|
-
CDP_FACILITATOR_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
// src/payments/x402/options.ts
|
|
1403
|
-
function invalid3(message, path2) {
|
|
1404
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
|
|
1405
|
-
}
|
|
1406
|
-
function resolveX402Option(input, path2, custom) {
|
|
1407
|
-
if (input.scheme !== void 0 && input.scheme !== "exact") {
|
|
1408
|
-
throw invalid3(
|
|
1409
|
-
`${path2}.scheme: "${input.scheme}" is not supported; the only x402 scheme is "exact"`,
|
|
1410
|
-
`${path2}.scheme`
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
const network = requireX402Network(input.network, `${path2}.network`, custom);
|
|
1414
|
-
const address = (field, value) => {
|
|
1415
|
-
const wire = normalizeAddress(network, value);
|
|
1416
|
-
if (wire === void 0) {
|
|
1417
|
-
throw invalid3(
|
|
1418
|
-
// The value is not echoed: a swapped variable could put a key here
|
|
1419
|
-
`${path2}.${field} is not a plausible address on ${network.displayName} (expected ${ADDRESS_SHAPES[network.family]})`,
|
|
1420
|
-
`${path2}.${field}`
|
|
1421
|
-
);
|
|
1422
|
-
}
|
|
1423
|
-
return wire;
|
|
1424
|
-
};
|
|
1425
|
-
const payTo = address("payTo", input.payTo);
|
|
1426
|
-
if (isBurnAddress(network, payTo)) {
|
|
1427
|
-
throw invalid3(
|
|
1428
|
-
`${path2}.payTo is the zero address of ${network.displayName}. Every payment settled there is destroyed.`,
|
|
1429
|
-
`${path2}.payTo`
|
|
1430
|
-
);
|
|
1431
|
-
}
|
|
1432
|
-
const method = input.assetTransferMethod ?? "eip3009";
|
|
1433
|
-
if (method !== "eip3009" && method !== "permit2") {
|
|
1434
|
-
throw invalid3(
|
|
1435
|
-
`${path2}.assetTransferMethod: "${method}" is not supported; use "eip3009" or "permit2"`,
|
|
1436
|
-
`${path2}.assetTransferMethod`
|
|
1437
|
-
);
|
|
1438
|
-
}
|
|
1439
|
-
if (network.family !== "evm" && input.assetTransferMethod !== void 0) {
|
|
1440
|
-
throw invalid3(
|
|
1441
|
-
`${path2}.assetTransferMethod applies to EVM networks only, not to ${network.displayName}`,
|
|
1442
|
-
`${path2}.assetTransferMethod`
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
if (network.family !== "evm") {
|
|
1446
|
-
for (const field of ["assetName", "assetVersion"]) {
|
|
1447
|
-
if (input[field] !== void 0) {
|
|
1448
|
-
throw invalid3(
|
|
1449
|
-
`${path2}.${field}: an EIP-712 domain applies to EVM tokens only, not to ${network.displayName}`,
|
|
1450
|
-
`${path2}.${field}`
|
|
1451
|
-
);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
const canonical = network.canonicalAsset;
|
|
1456
|
-
const domain = canonical && "name" in canonical ? canonical : void 0;
|
|
1457
|
-
const asset = input.asset !== void 0 ? address("asset", input.asset) : void 0;
|
|
1458
|
-
if (asset !== void 0 && isBurnAddress(network, asset)) {
|
|
1459
|
-
throw invalid3(`${path2}.asset must not be the zero address`, `${path2}.asset`);
|
|
1460
|
-
}
|
|
1461
|
-
if (canonical && (asset === void 0 || sameAddress(network, asset, canonical.address))) {
|
|
1462
|
-
const expected = {
|
|
1463
|
-
assetName: domain?.name,
|
|
1464
|
-
assetVersion: domain?.version,
|
|
1465
|
-
assetDecimals: canonical.decimals
|
|
1466
|
-
};
|
|
1467
|
-
for (const field of ["assetName", "assetVersion", "assetDecimals"]) {
|
|
1468
|
-
const given = input[field];
|
|
1469
|
-
if (given !== void 0 && given !== expected[field]) {
|
|
1470
|
-
throw invalid3(
|
|
1471
|
-
`${path2}.${field} does not match ${canonical.symbol} on ${network.displayName}, which reports ${FIELD_MEANINGS[field]} ${JSON.stringify(expected[field])}. Remove ${field}, or set asset to the token it describes.`,
|
|
1472
|
-
`${path2}.${field}`
|
|
1473
|
-
);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
return {
|
|
1477
|
-
scheme: "exact",
|
|
1478
|
-
network,
|
|
1479
|
-
payTo,
|
|
1480
|
-
asset: asset ?? canonical.address,
|
|
1481
|
-
...domain ? { assetName: domain.name, assetVersion: domain.version } : {},
|
|
1482
|
-
assetDecimals: canonical.decimals,
|
|
1483
|
-
defaultAssetSymbol: canonical.symbol,
|
|
1484
|
-
assetTransferMethod: method
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
const required = network.family === "evm" && method === "eip3009" ? ["asset", "assetName", "assetVersion", "assetDecimals"] : ["asset", "assetDecimals"];
|
|
1488
|
-
const missing = required.filter((field) => input[field] === void 0);
|
|
1489
|
-
if (asset === void 0 || missing.length > 0) {
|
|
1490
|
-
throw invalid3(
|
|
1491
|
-
canonical ? `${path2}: a token other than ${canonical.symbol} on ${network.displayName} needs ${missing.join(", ")}` : `${path2}: ${network.displayName} has no default asset, so the option needs ${missing.join(", ")}`,
|
|
1492
|
-
`${path2}.${missing[0] ?? "asset"}`
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
return {
|
|
1496
|
-
scheme: "exact",
|
|
1497
|
-
network,
|
|
1498
|
-
payTo,
|
|
1499
|
-
asset,
|
|
1500
|
-
...input.assetName !== void 0 ? { assetName: input.assetName } : {},
|
|
1501
|
-
...input.assetVersion !== void 0 ? { assetVersion: input.assetVersion } : {},
|
|
1502
|
-
assetDecimals: input.assetDecimals,
|
|
1503
|
-
assetTransferMethod: method
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
var FIELD_MEANINGS, ADDRESS_SHAPES;
|
|
1507
|
-
var init_options = __esm({
|
|
1508
|
-
"src/payments/x402/options.ts"() {
|
|
1509
|
-
init_core();
|
|
1510
|
-
init_addresses();
|
|
1511
|
-
init_networks();
|
|
1512
|
-
FIELD_MEANINGS = {
|
|
1513
|
-
assetName: "EIP-712 domain name",
|
|
1514
|
-
assetVersion: "EIP-712 domain version",
|
|
1515
|
-
assetDecimals: "decimals"
|
|
1516
|
-
};
|
|
1517
|
-
ADDRESS_SHAPES = {
|
|
1518
|
-
evm: '"0x" followed by 40 hex characters',
|
|
1519
|
-
svm: "a base58 public key of 32 bytes",
|
|
1520
|
-
near: 'a NEAR account ID: 2 to 64 lowercase characters, digits and single "-", "_" or "." separators'
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
});
|
|
1524
|
-
|
|
1525
|
-
// src/payments/x402/payment-flow.ts
|
|
1526
|
-
var X402_PAYMENT_FLOWS, X402_DEFAULT_PAYMENT_FLOW;
|
|
1527
|
-
var init_payment_flow = __esm({
|
|
1528
|
-
"src/payments/x402/payment-flow.ts"() {
|
|
1529
|
-
X402_PAYMENT_FLOWS = ["authorization", "upfront"];
|
|
1530
|
-
X402_DEFAULT_PAYMENT_FLOW = "authorization";
|
|
1531
|
-
}
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
// src/config/schema/x402.ts
|
|
1535
|
-
function x402Invalid(message, path2) {
|
|
1536
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
|
|
1537
|
-
}
|
|
1538
|
-
function x402OptionInput(raw, path2) {
|
|
1539
|
-
return {
|
|
1540
|
-
...raw.scheme !== void 0 ? { scheme: raw.scheme } : {},
|
|
1541
|
-
network: raw.network,
|
|
1542
|
-
payTo: raw.payTo,
|
|
1543
|
-
...raw.asset !== void 0 ? { asset: raw.asset } : {},
|
|
1544
|
-
...raw.assetName !== void 0 ? { assetName: raw.assetName } : {},
|
|
1545
|
-
...raw.assetVersion !== void 0 ? { assetVersion: raw.assetVersion } : {},
|
|
1546
|
-
...raw.assetTransferMethod !== void 0 ? { assetTransferMethod: raw.assetTransferMethod } : {},
|
|
1547
|
-
...raw.assetDecimals !== void 0 ? {
|
|
1548
|
-
assetDecimals: toNumber(raw.assetDecimals, `${path2}.assetDecimals`, { min: 0, max: 36 })
|
|
1549
|
-
} : {}
|
|
1550
|
-
};
|
|
1551
|
-
}
|
|
1552
|
-
function normalizeX402(raw, resources) {
|
|
1553
|
-
const at = "payments.x402";
|
|
1554
|
-
const direct = DIRECT_OPTION_FIELDS.filter((field) => raw[field] !== void 0);
|
|
1555
|
-
if (raw.options !== void 0 && direct.length > 0) {
|
|
1556
|
-
throw x402Invalid(
|
|
1557
|
-
`${at}: give the option either under ${at}.options or directly (${direct.join(", ")}), not both`,
|
|
1558
|
-
`${at}.options`
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
if (raw.options?.length === 0) {
|
|
1562
|
-
throw x402Invalid(`${at}.options is empty; list at least one option`, `${at}.options`);
|
|
1563
|
-
}
|
|
1564
|
-
if (raw.options === void 0) {
|
|
1565
|
-
for (const field of ["network", "payTo"]) {
|
|
1566
|
-
if (raw[field] === void 0) {
|
|
1567
|
-
throw x402Invalid(
|
|
1568
|
-
`${at}.${field} is required, unless the options are listed under ${at}.options`,
|
|
1569
|
-
`${at}.${field}`
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
const settings = raw.networks ?? {};
|
|
1575
|
-
const declared = {};
|
|
1576
|
-
for (const [id, entry] of Object.entries(settings)) {
|
|
1577
|
-
if (entry.kind === void 0 && entry.displayName === void 0) continue;
|
|
1578
|
-
if (entry.kind === void 0 || entry.displayName === void 0) {
|
|
1579
|
-
const path2 = `${at}.networks.${id}`;
|
|
1580
|
-
throw x402Invalid(`${path2}: declaring a network needs both kind and displayName`, path2);
|
|
1581
|
-
}
|
|
1582
|
-
declared[id] = { kind: entry.kind, displayName: entry.displayName };
|
|
1583
|
-
}
|
|
1584
|
-
const networks = Object.keys(declared).length > 0 ? declared : void 0;
|
|
1585
|
-
if (networks !== void 0) validateCustomNetworks(networks, `${at}.networks`);
|
|
1586
|
-
for (const id of Object.keys(settings)) {
|
|
1587
|
-
if (!findX402Network(id, networks)) {
|
|
1588
|
-
const path2 = `${at}.networks.${id}`;
|
|
1589
|
-
throw x402Invalid(
|
|
1590
|
-
`${path2}: "${id}" is not a known network; declare it with kind and displayName`,
|
|
1591
|
-
path2
|
|
1592
|
-
);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
const facilitator = normalizeFacilitator(raw.facilitator);
|
|
1596
|
-
const allowMainnet = raw.allowMainnet !== void 0 ? toBoolean(raw.allowMainnet, `${at}.allowMainnet`) : void 0;
|
|
1597
|
-
const allowUnauthenticatedFacilitator = raw.allowUnauthenticatedFacilitator !== void 0 ? toBoolean(raw.allowUnauthenticatedFacilitator, `${at}.allowUnauthenticatedFacilitator`) : void 0;
|
|
1598
|
-
const rpcOwners = /* @__PURE__ */ new Map();
|
|
1599
|
-
let firstMode;
|
|
1600
|
-
const resolveList = (entries) => {
|
|
1601
|
-
const resolved = entries.map(
|
|
1602
|
-
([path2, entry]) => resolveX402Option(x402OptionInput(entry, path2), path2, networks)
|
|
1603
|
-
);
|
|
1604
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1605
|
-
const options2 = resolved.map((option, i) => {
|
|
1606
|
-
const path2 = entries[i]?.[0];
|
|
1607
|
-
const network = option.network.id;
|
|
1608
|
-
const rpcUrl = settings[network]?.rpcUrl ?? raw.rpcUrl;
|
|
1609
|
-
if (rpcUrl === void 0) {
|
|
1610
|
-
throw x402Invalid(
|
|
1611
|
-
`${path2}.network: no RPC endpoint for ${network}; set ${at}.rpcUrl or ${at}.networks.${network}.rpcUrl`,
|
|
1612
|
-
`${at}.rpcUrl`
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
const endpoint = new URL(rpcUrl).href;
|
|
1616
|
-
const owner = rpcOwners.get(endpoint);
|
|
1617
|
-
if (owner !== void 0 && owner !== network) {
|
|
1618
|
-
throw x402Invalid(
|
|
1619
|
-
`${at}: ${owner} and ${network} would use the same RPC endpoint; give each network its own ${at}.networks.<id>.rpcUrl`,
|
|
1620
|
-
`${at}.networks`
|
|
1621
|
-
);
|
|
1622
|
-
}
|
|
1623
|
-
rpcOwners.set(endpoint, network);
|
|
1624
|
-
const comparable = (value) => option.network.family === "evm" ? value.toLowerCase() : value;
|
|
1625
|
-
const key = `${network}|${comparable(option.asset)}|${comparable(option.payTo)}`;
|
|
1626
|
-
if (seen.has(key)) {
|
|
1627
|
-
throw x402Invalid(
|
|
1628
|
-
`${path2}: repeats an earlier option, the same asset to the same payTo on ${network}`,
|
|
1629
|
-
path2
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
seen.add(key);
|
|
1633
|
-
const { mode } = resolveX402Deployment({
|
|
1634
|
-
network,
|
|
1635
|
-
payTo: option.payTo,
|
|
1636
|
-
asset: option.asset,
|
|
1637
|
-
...option.assetName !== void 0 ? { assetName: option.assetName } : {},
|
|
1638
|
-
...option.assetVersion !== void 0 ? { assetVersion: option.assetVersion } : {},
|
|
1639
|
-
facilitator,
|
|
1640
|
-
...allowMainnet !== void 0 ? { allowMainnet } : {},
|
|
1641
|
-
...allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator } : {},
|
|
1642
|
-
...networks !== void 0 ? { customNetworks: networks } : {}
|
|
1643
|
-
});
|
|
1644
|
-
if (firstMode !== void 0 && mode !== firstMode) {
|
|
1645
|
-
throw x402Invalid(
|
|
1646
|
-
`${path2}.network: ${network} is a ${mode} network, but the first option is on a ${firstMode} one; every option must be on the same kind of network`,
|
|
1647
|
-
`${path2}.network`
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
firstMode = mode;
|
|
1651
|
-
return {
|
|
1652
|
-
network,
|
|
1653
|
-
rpcUrl,
|
|
1654
|
-
asset: option.asset,
|
|
1655
|
-
...option.assetName !== void 0 ? { assetName: option.assetName } : {},
|
|
1656
|
-
...option.assetVersion !== void 0 ? { assetVersion: option.assetVersion } : {},
|
|
1657
|
-
assetDecimals: option.assetDecimals,
|
|
1658
|
-
payTo: option.payTo,
|
|
1659
|
-
...option.assetTransferMethod === "permit2" ? { assetTransferMethod: "permit2" } : {}
|
|
1660
|
-
};
|
|
1661
|
-
});
|
|
1662
|
-
return {
|
|
1663
|
-
options: options2,
|
|
1664
|
-
defaultAssetSymbols: [
|
|
1665
|
-
...new Set(resolved.flatMap((option) => option.defaultAssetSymbol ?? []))
|
|
1666
|
-
]
|
|
1667
|
-
};
|
|
1668
|
-
};
|
|
1669
|
-
const rail = resolveList(
|
|
1670
|
-
raw.options !== void 0 ? raw.options.map((option, i) => [`${at}.options[${i}]`, option]) : [[at, raw]]
|
|
1671
|
-
);
|
|
1672
|
-
const resourceTerms = {};
|
|
1673
|
-
for (const [id, entry] of Object.entries(resources)) {
|
|
1674
|
-
const accepts = entry.x402?.accepts;
|
|
1675
|
-
if (accepts === void 0) continue;
|
|
1676
|
-
const path2 = `resources.${id}.x402.accepts`;
|
|
1677
|
-
if (accepts.length === 0) {
|
|
1678
|
-
throw x402Invalid(`${path2} is empty; list at least one option`, path2);
|
|
1679
|
-
}
|
|
1680
|
-
resourceTerms[id] = resolveList(accepts.map((option, i) => [`${path2}[${i}]`, option]));
|
|
1681
|
-
}
|
|
1682
|
-
const options = rail.options;
|
|
1683
|
-
const first = options[0];
|
|
1684
|
-
const resourceOptions = Object.fromEntries(
|
|
1685
|
-
Object.entries(resourceTerms).map(([id, terms]) => [id, terms.options])
|
|
1686
|
-
);
|
|
1687
|
-
const config = {
|
|
1688
|
-
enabled: toBoolean(raw.enabled, `${at}.enabled`),
|
|
1689
|
-
...first,
|
|
1690
|
-
maxTimeoutSeconds: toNumber(raw.maxTimeoutSeconds, `${at}.maxTimeoutSeconds`, { min: 1 }),
|
|
1691
|
-
paymentFlow: raw.paymentFlow ?? X402_DEFAULT_PAYMENT_FLOW,
|
|
1692
|
-
facilitator,
|
|
1693
|
-
...allowMainnet !== void 0 ? { allowMainnet } : {},
|
|
1694
|
-
...allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator } : {},
|
|
1695
|
-
...networks !== void 0 ? { networks } : {},
|
|
1696
|
-
options,
|
|
1697
|
-
...Object.keys(resourceOptions).length > 0 ? { resourceOptions } : {}
|
|
1698
|
-
};
|
|
1699
|
-
return { config, ...rail, resources: resourceTerms };
|
|
1700
|
-
}
|
|
1701
|
-
function normalizeFacilitator(raw) {
|
|
1702
|
-
return raw.mode === "local" ? { mode: "local", signerPrivateKey: raw.signerPrivateKey } : { mode: "remote", url: raw.url, auth: raw.auth ?? { type: "none" } };
|
|
1703
|
-
}
|
|
1704
|
-
var DIRECT_OPTION_FIELDS;
|
|
1705
|
-
var init_x402 = __esm({
|
|
1706
|
-
"src/config/schema/x402.ts"() {
|
|
1707
|
-
init_core();
|
|
1708
|
-
init_guardrails();
|
|
1709
|
-
init_networks();
|
|
1710
|
-
init_options();
|
|
1711
|
-
init_payment_flow();
|
|
1712
|
-
init_values();
|
|
1713
|
-
DIRECT_OPTION_FIELDS = [
|
|
1714
|
-
"network",
|
|
1715
|
-
"payTo",
|
|
1716
|
-
"asset",
|
|
1717
|
-
"assetName",
|
|
1718
|
-
"assetVersion",
|
|
1719
|
-
"assetDecimals",
|
|
1720
|
-
"assetTransferMethod"
|
|
1721
|
-
];
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
|
-
|
|
1725
|
-
// src/config/schema/mpp.ts
|
|
1726
|
-
function normalizeMpp(raw) {
|
|
1727
|
-
if (raw === void 0) return void 0;
|
|
1728
|
-
const network = raw.network ?? MPP_DEFAULT_NETWORK;
|
|
1729
|
-
if (!isMppNetwork(network)) {
|
|
1730
|
-
throw new CommerceError(
|
|
1731
|
-
"CONFIG_INVALID",
|
|
1732
|
-
`payments.mpp.network must be one of ${MPP_NETWORKS.join(", ")}`,
|
|
1733
|
-
{ details: { path: "payments.mpp.network" } }
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
const mpp = {
|
|
1737
|
-
enabled: toBoolean(raw.enabled, "payments.mpp.enabled"),
|
|
1738
|
-
network,
|
|
1739
|
-
rpcUrl: raw.rpcUrl,
|
|
1740
|
-
asset: raw.asset,
|
|
1741
|
-
assetName: raw.assetName,
|
|
1742
|
-
assetVersion: raw.assetVersion,
|
|
1743
|
-
recipient: raw.recipient,
|
|
1744
|
-
realm: raw.realm,
|
|
1745
|
-
challengeSecret: raw.challengeSecret,
|
|
1746
|
-
...raw.challengeTtlSeconds !== void 0 ? {
|
|
1747
|
-
challengeTtlSeconds: toNumber(
|
|
1748
|
-
raw.challengeTtlSeconds,
|
|
1749
|
-
"payments.mpp.challengeTtlSeconds",
|
|
1750
|
-
{ min: 1 }
|
|
1751
|
-
)
|
|
1752
|
-
} : {},
|
|
1753
|
-
paymentFlow: raw.paymentFlow ?? MPP_DEFAULT_PAYMENT_FLOW,
|
|
1754
|
-
facilitator: normalizeFacilitator(raw.facilitator),
|
|
1755
|
-
...raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(raw.allowMainnet, "payments.mpp.allowMainnet") } : {},
|
|
1756
|
-
...raw.allowUnauthenticatedFacilitator !== void 0 ? {
|
|
1757
|
-
allowUnauthenticatedFacilitator: toBoolean(
|
|
1758
|
-
raw.allowUnauthenticatedFacilitator,
|
|
1759
|
-
"payments.mpp.allowUnauthenticatedFacilitator"
|
|
1760
|
-
)
|
|
1761
|
-
} : {}
|
|
1762
|
-
};
|
|
1763
|
-
validateAddress("payments.mpp.recipient", mpp.recipient);
|
|
1764
|
-
validateAddress("payments.mpp.asset", mpp.asset);
|
|
1765
|
-
if (/[\r\n]/.test(mpp.realm)) {
|
|
1766
|
-
throw new CommerceError("CONFIG_INVALID", "payments.mpp.realm must be a single line", {
|
|
1767
|
-
details: { path: "payments.mpp.realm" }
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
if (mpp.challengeSecret.length < MPP_MIN_CHALLENGE_SECRET_LENGTH) {
|
|
1771
|
-
throw new CommerceError(
|
|
1772
|
-
"CONFIG_INVALID",
|
|
1773
|
-
`payments.mpp.challengeSecret must have length at least ${MPP_MIN_CHALLENGE_SECRET_LENGTH}`,
|
|
1774
|
-
{ details: { path: "payments.mpp.challengeSecret" } }
|
|
1775
|
-
);
|
|
1776
|
-
}
|
|
1777
|
-
resolveX402Deployment({
|
|
1778
|
-
network: mpp.network,
|
|
1779
|
-
payTo: mpp.recipient,
|
|
1780
|
-
asset: mpp.asset,
|
|
1781
|
-
assetName: mpp.assetName,
|
|
1782
|
-
assetVersion: mpp.assetVersion,
|
|
1783
|
-
facilitator: mpp.facilitator,
|
|
1784
|
-
...mpp.allowMainnet !== void 0 ? { allowMainnet: mpp.allowMainnet } : {},
|
|
1785
|
-
...mpp.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: mpp.allowUnauthenticatedFacilitator } : {},
|
|
1786
|
-
configPath: "payments.mpp",
|
|
1787
|
-
payToField: "recipient"
|
|
1788
|
-
});
|
|
1789
|
-
return mpp;
|
|
1790
|
-
}
|
|
1791
|
-
var init_mpp = __esm({
|
|
1792
|
-
"src/config/schema/mpp.ts"() {
|
|
1793
|
-
init_core();
|
|
1794
|
-
init_constants3();
|
|
1795
|
-
init_guardrails();
|
|
1796
|
-
init_values();
|
|
1797
|
-
init_x402();
|
|
1798
|
-
}
|
|
1799
|
-
});
|
|
1800
|
-
function isHttpUrl(value) {
|
|
1801
|
-
try {
|
|
1802
|
-
const { protocol } = new URL(value);
|
|
1803
|
-
return protocol === "http:" || protocol === "https:";
|
|
1804
|
-
} catch {
|
|
1805
|
-
return false;
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
var SUPPORTED_CONFIG_VERSION, NumberOrString, BooleanOrString, RpcUrl, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, AcpAuthSchema, AcpIdempotencySchema, AcpCheckoutSchema, AcpDiscoverySchema, ProtocolsSchema, BackendMethodSchema, BackendInputBindingsSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, X402OptionSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402NetworkSettingsSchema, X402Schema, MppSchema, PaymentsSchema, Ap2KeySchema, Ap2IssuerSchema, Ap2Schema, AuthorizationSchema, RawConfigSchema;
|
|
1809
|
-
var init_raw = __esm({
|
|
1810
|
-
"src/config/schema/raw.ts"() {
|
|
1811
|
-
init_payment_flow();
|
|
1812
|
-
init_constants();
|
|
1813
|
-
SUPPORTED_CONFIG_VERSION = 1;
|
|
1814
|
-
NumberOrString = z.union([z.number(), z.string()]);
|
|
1815
|
-
BooleanOrString = z.union([z.boolean(), z.string()]);
|
|
1816
|
-
RpcUrl = z.string().min(1).refine(isHttpUrl, { message: "must be an http:// or https:// URL" });
|
|
1817
|
-
MerchantSchema = z.object({
|
|
1818
|
-
id: z.string().min(1),
|
|
1819
|
-
name: z.string().min(1),
|
|
1820
|
-
publicBaseUrl: z.string().min(1)
|
|
1821
|
-
}).strict();
|
|
1822
|
-
ServerSchema = z.object({
|
|
1823
|
-
port: NumberOrString,
|
|
1824
|
-
host: z.string().min(1),
|
|
1825
|
-
// Shared secret gating /api/receipts and /api/events. Without it both
|
|
1826
|
-
// routes 404.
|
|
1827
|
-
adminToken: z.string().min(1).optional(),
|
|
1828
|
-
// Browser origins allowed to call the gateway; none by default. A request
|
|
1829
|
-
// with any other `Origin` gets 403 on every route. Entries match the
|
|
1830
|
-
// `Origin` header exactly (ignoring case), so `"*"` and a trailing slash
|
|
1831
|
-
// would match nothing: a silent lockout that an operator might "fix" by
|
|
1832
|
-
// disabling the check. Both are refused with a pointer.
|
|
1833
|
-
allowedOrigins: z.array(
|
|
1834
|
-
z.string().min(1).refine((origin) => origin !== "*", {
|
|
1835
|
-
message: `wildcard "*" is not supported - allowedOrigins entries are matched literally against the browser's Origin header, so "*" would match nothing. List each scheme://host[:port] explicitly.`
|
|
1836
|
-
}).refine((origin) => !origin.endsWith("/"), {
|
|
1837
|
-
message: 'must not end with "/" - a browser Origin header never has a trailing slash, so this entry would match nothing. Use e.g. "http://localhost:5173".'
|
|
1838
|
-
})
|
|
1839
|
-
).optional()
|
|
1840
|
-
}).strict();
|
|
1841
|
-
StorageSchema = z.object({
|
|
1842
|
-
receipts: z.object({
|
|
1843
|
-
driver: z.literal("sqlite"),
|
|
1844
|
-
path: z.string().min(1)
|
|
1845
|
-
}).strict()
|
|
1846
|
-
}).strict();
|
|
1847
|
-
RESERVED_GATEWAY_PATHS = [
|
|
1848
|
-
"/health",
|
|
1849
|
-
"/ready",
|
|
1850
|
-
"/.well-known/agent-commerce",
|
|
1851
|
-
// Fixed by the A2A specification and served by the adapter
|
|
1852
|
-
"/.well-known/agent-card.json",
|
|
1853
|
-
// Fixed by the ACP specification
|
|
1854
|
-
ACP_WELL_KNOWN_PATH,
|
|
1855
|
-
"/api/resources",
|
|
1856
|
-
"/api/resources/:id/invoke",
|
|
1857
|
-
"/api/receipts",
|
|
1858
|
-
"/api/events"
|
|
1859
|
-
];
|
|
1860
|
-
MountPathSchema = z.string().min(1).refine((value) => value.startsWith("/"), {
|
|
1861
|
-
message: 'must start with "/" - it is an absolute gateway path, e.g. "/mcp".'
|
|
1862
|
-
}).refine((value) => !/[:*?\s]/.test(value), {
|
|
1863
|
-
message: 'must not contain ":", "*", "?" or whitespace - the mount is a literal path prefix, not a Fastify route pattern, and registers its own wildcard.'
|
|
1864
|
-
}).refine(
|
|
1865
|
-
(value) => {
|
|
1866
|
-
const base = value.replace(/\/+$/, "");
|
|
1867
|
-
return !RESERVED_GATEWAY_PATHS.some(
|
|
1868
|
-
(reserved) => reserved === base || reserved.startsWith(`${base}/`)
|
|
1869
|
-
);
|
|
1870
|
-
},
|
|
1871
|
-
{
|
|
1872
|
-
message: `must not collide with a route the gateway already serves (${RESERVED_GATEWAY_PATHS.join(", ")}); pick a dedicated prefix such as "/mcp".`
|
|
1873
|
-
}
|
|
1874
|
-
);
|
|
1875
|
-
AcpAuthSchema = z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict();
|
|
1876
|
-
AcpIdempotencySchema = z.object({
|
|
1877
|
-
path: z.string().min(1),
|
|
1878
|
-
retentionHours: NumberOrString.optional(),
|
|
1879
|
-
merchantIdempotent: z.boolean().optional()
|
|
1880
|
-
}).strict();
|
|
1881
|
-
AcpCheckoutSchema = z.object({ operations: z.record(z.string().min(1)) }).strict();
|
|
1882
|
-
AcpDiscoverySchema = z.object({
|
|
1883
|
-
documentationUrl: z.string().min(1).optional(),
|
|
1884
|
-
supportedCurrencies: z.array(z.string().min(1)).optional(),
|
|
1885
|
-
supportedLocales: z.array(z.string().min(1)).optional(),
|
|
1886
|
-
interventionTypes: z.array(z.string().min(1)).optional()
|
|
1887
|
-
}).strict();
|
|
1888
|
-
ProtocolsSchema = z.object({
|
|
1889
|
-
http: z.object({ enabled: BooleanOrString }).strict(),
|
|
1890
|
-
mcp: z.object({
|
|
1891
|
-
enabled: BooleanOrString,
|
|
1892
|
-
mountPath: MountPathSchema
|
|
1893
|
-
}).strict(),
|
|
1894
|
-
// Optional; A2A is off unless configured
|
|
1895
|
-
a2a: z.object({
|
|
1896
|
-
enabled: BooleanOrString,
|
|
1897
|
-
mountPath: MountPathSchema.optional()
|
|
1898
|
-
}).strict().optional(),
|
|
1899
|
-
// Optional and off by default. The business pass requires the sub-blocks
|
|
1900
|
-
// only when ACP is enabled, so a disabled placeholder stays valid and an
|
|
1901
|
-
// enabled block gets a message naming what it lacks.
|
|
1902
|
-
acp: z.object({
|
|
1903
|
-
enabled: BooleanOrString,
|
|
1904
|
-
mountPath: MountPathSchema.optional(),
|
|
1905
|
-
auth: AcpAuthSchema.optional(),
|
|
1906
|
-
idempotency: AcpIdempotencySchema.optional(),
|
|
1907
|
-
checkout: AcpCheckoutSchema.optional(),
|
|
1908
|
-
discovery: AcpDiscoverySchema.optional()
|
|
1909
|
-
}).strict().optional()
|
|
1910
|
-
}).strict();
|
|
1911
|
-
BackendMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
1912
|
-
BackendInputBindingsSchema = z.object({
|
|
1913
|
-
path: z.string().min(1).optional(),
|
|
1914
|
-
query: z.string().min(1).optional(),
|
|
1915
|
-
body: z.string().min(1).optional()
|
|
1916
|
-
}).strict();
|
|
1917
|
-
BackendHandlerSchema = z.object({
|
|
1918
|
-
type: z.literal("http"),
|
|
1919
|
-
method: BackendMethodSchema,
|
|
1920
|
-
url: z.string().min(1),
|
|
1921
|
-
headers: z.record(z.string(), z.string()).optional(),
|
|
1922
|
-
timeoutMs: NumberOrString.optional(),
|
|
1923
|
-
inputBindings: BackendInputBindingsSchema.optional()
|
|
1924
|
-
}).strict();
|
|
1925
|
-
PricingSchema = z.discriminatedUnion("type", [
|
|
1926
|
-
z.object({ type: z.literal("free") }).strict(),
|
|
1927
|
-
z.object({
|
|
1928
|
-
type: z.literal("fixed"),
|
|
1929
|
-
amount: z.string().min(1),
|
|
1930
|
-
currency: z.string().min(1)
|
|
1931
|
-
}).strict(),
|
|
1932
|
-
z.object({
|
|
1933
|
-
type: z.literal("dynamic"),
|
|
1934
|
-
resolver: z.string().min(1)
|
|
1935
|
-
}).strict()
|
|
1936
|
-
]);
|
|
1937
|
-
JsonSchemaValueSchema = z.record(z.string(), z.unknown());
|
|
1938
|
-
X402OptionSchema = z.object({
|
|
1939
|
-
scheme: z.string().min(1).optional(),
|
|
1940
|
-
network: z.string().min(1),
|
|
1941
|
-
payTo: z.string().min(1),
|
|
1942
|
-
asset: z.string().min(1).optional(),
|
|
1943
|
-
assetName: z.string().min(1).optional(),
|
|
1944
|
-
assetVersion: z.string().min(1).optional(),
|
|
1945
|
-
assetDecimals: NumberOrString.optional(),
|
|
1946
|
-
assetTransferMethod: z.string().min(1).optional()
|
|
1947
|
-
}).strict();
|
|
1948
|
-
ResourceEntrySchema = z.object({
|
|
1949
|
-
name: z.string().min(1),
|
|
1950
|
-
description: z.string().optional(),
|
|
1951
|
-
input: JsonSchemaValueSchema.optional(),
|
|
1952
|
-
output: JsonSchemaValueSchema.optional(),
|
|
1953
|
-
backend: BackendHandlerSchema,
|
|
1954
|
-
pricing: PricingSchema,
|
|
1955
|
-
expose: z.array(z.string().min(1)).min(1),
|
|
1956
|
-
payments: z.array(z.string().min(1)).optional(),
|
|
1957
|
-
paymentFlow: z.enum(X402_PAYMENT_FLOWS).optional(),
|
|
1958
|
-
// This resource's own x402 offers, in place of `payments.x402` options
|
|
1959
|
-
x402: z.object({ accepts: z.array(X402OptionSchema) }).strict().optional(),
|
|
1960
|
-
authorization: z.object({ required: z.array(z.string().min(1)).min(1) }).strict().optional()
|
|
1961
|
-
}).strict();
|
|
1962
|
-
ResourcesMapSchema = z.record(z.string().min(1), ResourceEntrySchema);
|
|
1963
|
-
FacilitatorAuthSchema = z.discriminatedUnion("type", [
|
|
1964
|
-
z.object({ type: z.literal("none") }).strict(),
|
|
1965
|
-
z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict(),
|
|
1966
|
-
// Coinbase Developer Platform. Needs the optional peer `@coinbase/x402`,
|
|
1967
|
-
// which signs a fresh JWT per request.
|
|
1968
|
-
z.object({
|
|
1969
|
-
type: z.literal("cdp"),
|
|
1970
|
-
apiKeyId: z.string().min(1),
|
|
1971
|
-
apiKeySecret: z.string().min(1)
|
|
1972
|
-
}).strict()
|
|
1973
|
-
]);
|
|
1974
|
-
FacilitatorSchema = z.discriminatedUnion("mode", [
|
|
1975
|
-
z.object({
|
|
1976
|
-
mode: z.literal("local"),
|
|
1977
|
-
signerPrivateKey: z.string().min(1)
|
|
1978
|
-
}).strict(),
|
|
1979
|
-
z.object({
|
|
1980
|
-
mode: z.literal("remote"),
|
|
1981
|
-
url: z.string().min(1),
|
|
1982
|
-
// Absent auth sends no credential and normalizes to `{ type: 'none' }`,
|
|
1983
|
-
// which on mainnet requires `allowUnauthenticatedFacilitator`
|
|
1984
|
-
auth: FacilitatorAuthSchema.optional()
|
|
1985
|
-
}).strict()
|
|
1986
|
-
]);
|
|
1987
|
-
X402NetworkSettingsSchema = z.object({
|
|
1988
|
-
kind: z.enum(["testnet", "mainnet"]).optional(),
|
|
1989
|
-
displayName: z.string().min(1).optional(),
|
|
1990
|
-
rpcUrl: RpcUrl.optional()
|
|
1991
|
-
}).strict();
|
|
1992
|
-
X402Schema = z.object({
|
|
1993
|
-
enabled: BooleanOrString,
|
|
1994
|
-
// Accept direct fields or an `options` array, never both
|
|
1995
|
-
network: z.string().min(1).optional(),
|
|
1996
|
-
payTo: z.string().min(1).optional(),
|
|
1997
|
-
asset: z.string().min(1).optional(),
|
|
1998
|
-
assetName: z.string().min(1).optional(),
|
|
1999
|
-
assetVersion: z.string().min(1).optional(),
|
|
2000
|
-
assetDecimals: NumberOrString.optional(),
|
|
2001
|
-
assetTransferMethod: z.string().min(1).optional(),
|
|
2002
|
-
options: z.array(X402OptionSchema).optional(),
|
|
2003
|
-
networks: z.record(z.string().min(1), X402NetworkSettingsSchema).optional(),
|
|
2004
|
-
// The RPC for every network without its own `networks.<id>.rpcUrl`
|
|
2005
|
-
rpcUrl: RpcUrl.optional(),
|
|
2006
|
-
maxTimeoutSeconds: NumberOrString,
|
|
2007
|
-
// `authorization` (the x402 default) settles after the backend call
|
|
2008
|
-
paymentFlow: z.enum(X402_PAYMENT_FLOWS).optional(),
|
|
2009
|
-
facilitator: FacilitatorSchema,
|
|
2010
|
-
// Real funds. Never defaulted; see src/payments/x402/guardrails.ts
|
|
2011
|
-
allowMainnet: BooleanOrString.optional(),
|
|
2012
|
-
// Accepts credential-free facilitator access on mainnet. Never defaulted
|
|
2013
|
-
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
2014
|
-
}).strict();
|
|
2015
|
-
MppSchema = z.object({
|
|
2016
|
-
enabled: BooleanOrString,
|
|
2017
|
-
network: z.string().min(1).optional(),
|
|
2018
|
-
rpcUrl: RpcUrl,
|
|
2019
|
-
asset: z.string().min(1),
|
|
2020
|
-
assetName: z.string().min(1),
|
|
2021
|
-
assetVersion: z.string().min(1),
|
|
2022
|
-
recipient: z.string().min(1),
|
|
2023
|
-
realm: z.string().min(1),
|
|
2024
|
-
challengeSecret: z.string().min(1),
|
|
2025
|
-
challengeTtlSeconds: NumberOrString.optional(),
|
|
2026
|
-
// Same values as `payments.x402.paymentFlow`; the default is `upfront`
|
|
2027
|
-
paymentFlow: z.enum(X402_PAYMENT_FLOWS).optional(),
|
|
2028
|
-
facilitator: FacilitatorSchema,
|
|
2029
|
-
// Real funds. Never defaulted; see src/payments/x402/guardrails.ts
|
|
2030
|
-
allowMainnet: BooleanOrString.optional(),
|
|
2031
|
-
// Accepts credential-free facilitator access on mainnet. Never defaulted
|
|
2032
|
-
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
2033
|
-
}).strict();
|
|
2034
|
-
PaymentsSchema = z.object({
|
|
2035
|
-
x402: X402Schema.optional(),
|
|
2036
|
-
mpp: MppSchema.optional()
|
|
2037
|
-
}).strict();
|
|
2038
|
-
Ap2KeySchema = z.object({
|
|
2039
|
-
kid: z.string().min(1),
|
|
2040
|
-
jwk: z.record(z.string(), z.unknown())
|
|
2041
|
-
}).strict();
|
|
2042
|
-
Ap2IssuerSchema = z.object({
|
|
2043
|
-
issuer: z.string().min(1),
|
|
2044
|
-
// Required, not defaulted: without it a mandate minted for another
|
|
2045
|
-
// merchant would verify here
|
|
2046
|
-
audience: z.string().min(1),
|
|
2047
|
-
keys: z.array(Ap2KeySchema).min(1)
|
|
2048
|
-
}).strict();
|
|
2049
|
-
Ap2Schema = z.object({
|
|
2050
|
-
enabled: BooleanOrString,
|
|
2051
|
-
specVersion: z.string().min(1).optional(),
|
|
2052
|
-
mode: z.string().min(1).optional(),
|
|
2053
|
-
trust: z.object({
|
|
2054
|
-
mandateIssuers: z.array(Ap2IssuerSchema).optional(),
|
|
2055
|
-
checkoutIssuers: z.array(Ap2IssuerSchema).optional()
|
|
2056
|
-
}).strict().optional(),
|
|
2057
|
-
clockSkewSeconds: NumberOrString.optional(),
|
|
2058
|
-
requireMandateAudience: BooleanOrString.optional(),
|
|
2059
|
-
requireMandateExpiry: BooleanOrString.optional(),
|
|
2060
|
-
replay: z.object({ path: z.string().min(1) }).strict().optional()
|
|
2061
|
-
}).strict();
|
|
2062
|
-
AuthorizationSchema = z.object({ ap2: Ap2Schema.optional() }).strict();
|
|
2063
|
-
RawConfigSchema = z.object({
|
|
2064
|
-
version: z.literal(SUPPORTED_CONFIG_VERSION),
|
|
2065
|
-
merchant: MerchantSchema,
|
|
2066
|
-
server: ServerSchema,
|
|
2067
|
-
storage: StorageSchema,
|
|
2068
|
-
protocols: ProtocolsSchema,
|
|
2069
|
-
resources: ResourcesMapSchema,
|
|
2070
|
-
payments: PaymentsSchema,
|
|
2071
|
-
authorization: AuthorizationSchema.optional()
|
|
2072
|
-
}).strict();
|
|
2073
|
-
}
|
|
2074
|
-
});
|
|
2075
|
-
|
|
2076
|
-
// src/protocols/mcp/constants.ts
|
|
2077
|
-
var MCP_TOOL_NAME_CHARS, MCP_TOOL_NAME_MAX_LENGTH, MCP_TOOL_NAME_PATTERN;
|
|
2078
|
-
var init_constants4 = __esm({
|
|
2079
|
-
"src/protocols/mcp/constants.ts"() {
|
|
2080
|
-
MCP_TOOL_NAME_CHARS = "A-Za-z0-9._-";
|
|
2081
|
-
MCP_TOOL_NAME_MAX_LENGTH = 128;
|
|
2082
|
-
MCP_TOOL_NAME_PATTERN = new RegExp(
|
|
2083
|
-
`^[${MCP_TOOL_NAME_CHARS}]{1,${MCP_TOOL_NAME_MAX_LENGTH}}$`
|
|
2084
|
-
);
|
|
2085
|
-
}
|
|
2086
|
-
});
|
|
2087
|
-
|
|
2088
|
-
// src/config/schema/backend.ts
|
|
2089
|
-
function validateInputBindings(id, backend, input) {
|
|
2090
|
-
const legacy = { schema: input, where: "its input schema" };
|
|
2091
|
-
const bindings = backend.inputBindings;
|
|
2092
|
-
const templated = extractPathParameterNames(backend.url).length > 0;
|
|
2093
|
-
if (bindings === void 0) return legacy;
|
|
2094
|
-
const path2 = `resources.${id}.backend.inputBindings`;
|
|
2095
|
-
const fail = (message, details = {}) => {
|
|
2096
|
-
throw new CommerceError("CONFIG_INVALID", `Resource "${id}" ${message}`, {
|
|
2097
|
-
details: { path: path2, resourceId: id, ...details }
|
|
2098
|
-
});
|
|
2099
|
-
};
|
|
2100
|
-
const entries = Object.entries(bindings).filter(
|
|
2101
|
-
(entry) => entry[1] !== void 0
|
|
2102
|
-
);
|
|
2103
|
-
if (entries.length === 0) {
|
|
2104
|
-
fail("has an empty backend.inputBindings - remove the block to use the default mapping");
|
|
2105
|
-
}
|
|
2106
|
-
if (backend.method === "GET" || backend.method === "DELETE") {
|
|
2107
|
-
if (bindings.body !== void 0) {
|
|
2108
|
-
fail(
|
|
2109
|
-
`binds a request body on a ${backend.method}, which sends none - the value would be silently dropped`
|
|
2110
|
-
);
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
if (templated && bindings.path === void 0) {
|
|
2114
|
-
fail(
|
|
2115
|
-
'has backend.url path parameters but no "path" binding - in explicit binding mode nothing else supplies them, so every call would fail to reach the backend'
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2118
|
-
const seen = /* @__PURE__ */ new Map();
|
|
2119
|
-
const properties = isRecord(input?.["properties"]) ? input["properties"] : {};
|
|
2120
|
-
const required = new Set(
|
|
2121
|
-
Array.isArray(input?.["required"]) ? input["required"].filter((value) => typeof value === "string") : []
|
|
2122
|
-
);
|
|
2123
|
-
for (const [location, property] of entries) {
|
|
2124
|
-
if (RESERVED_INPUT_FIELDS.includes(property)) {
|
|
2125
|
-
fail(`binds "${location}" to "${property}", which is reserved by the gateway`, { location });
|
|
2126
|
-
}
|
|
2127
|
-
const other = seen.get(property);
|
|
2128
|
-
if (other !== void 0) {
|
|
2129
|
-
fail(`binds both "${other}" and "${location}" to the input property "${property}"`, {
|
|
2130
|
-
location,
|
|
2131
|
-
property
|
|
2132
|
-
});
|
|
2133
|
-
}
|
|
2134
|
-
seen.set(property, location);
|
|
2135
|
-
if (!Object.hasOwn(properties, property)) {
|
|
2136
|
-
fail(
|
|
2137
|
-
`binds "${location}" to input property "${property}", which the input schema does not declare - the schema is closed, so a caller could never supply it`,
|
|
2138
|
-
{ location, property }
|
|
2139
|
-
);
|
|
2140
|
-
}
|
|
2141
|
-
const declared = properties[property];
|
|
2142
|
-
if (location !== "body" && isRecord(declared) && !isObjectSchemaNode(declared)) {
|
|
2143
|
-
fail(
|
|
2144
|
-
`binds "${location}" to input property "${property}", which is not an object schema - ${location} parameters are read as an object of name/value pairs`,
|
|
2145
|
-
{ location, property }
|
|
2146
|
-
);
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
if (templated && bindings.path !== void 0 && !required.has(bindings.path)) {
|
|
2150
|
-
fail(
|
|
2151
|
-
`binds path parameters to input property "${bindings.path}" without listing it in the input schema's "required" - a caller that omits it cannot supply any path parameter, so the request could never be built`,
|
|
2152
|
-
{ property: bindings.path }
|
|
2153
|
-
);
|
|
2154
|
-
}
|
|
2155
|
-
if (bindings.path === void 0) return { schema: void 0, where: "its input schema" };
|
|
2156
|
-
const group = properties[bindings.path];
|
|
2157
|
-
return {
|
|
2158
|
-
schema: isRecord(group) ? group : void 0,
|
|
2159
|
-
where: `input.properties.${bindings.path}`
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
|
-
function validatePathParametersDeclared(id, url, input, where) {
|
|
2163
|
-
const stray = findUnparsedBraceToken(url);
|
|
2164
|
-
if (stray !== void 0) {
|
|
2165
|
-
throw new CommerceError(
|
|
2166
|
-
"CONFIG_INVALID",
|
|
2167
|
-
`Resource "${id}" has backend.url containing "${stray}", which is not a valid path parameter (allowed characters: A-Z a-z 0-9 _ . -). It would reach the backend as literal text, so a paid resource would take payment for a request the backend cannot serve`,
|
|
2168
|
-
{ details: { path: `resources.${id}.backend.url`, resourceId: id, token: stray } }
|
|
2169
|
-
);
|
|
2170
|
-
}
|
|
2171
|
-
const params = extractPathParameterNames(url);
|
|
2172
|
-
if (params.length === 0) return;
|
|
2173
|
-
const firstBrace = url.indexOf("{");
|
|
2174
|
-
if (firstBrace !== -1) {
|
|
2175
|
-
const prefix = url.slice(0, firstBrace);
|
|
2176
|
-
let origin;
|
|
2177
|
-
try {
|
|
2178
|
-
const parsedPrefix = new URL(prefix);
|
|
2179
|
-
origin = parsedPrefix.hostname === "" ? void 0 : parsedPrefix.origin;
|
|
2180
|
-
} catch {
|
|
2181
|
-
}
|
|
2182
|
-
if (origin === void 0 || !prefix.startsWith(`${origin}/`)) {
|
|
2183
|
-
throw new CommerceError(
|
|
2184
|
-
"CONFIG_INVALID",
|
|
2185
|
-
`Resource "${id}" has a backend.url parameter before the end of the host. A caller-supplied value would choose which host the gateway calls, which is request forgery with the gateway's own network position. Parameters are supported inside the path and query only`,
|
|
2186
|
-
{ details: { path: `resources.${id}.backend.url`, resourceId: id } }
|
|
2187
|
-
);
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
const propertiesRaw = input?.["properties"];
|
|
2191
|
-
const properties = isRecord(propertiesRaw) ? propertiesRaw : {};
|
|
2192
|
-
const requiredRaw = input?.["required"];
|
|
2193
|
-
const required = new Set(
|
|
2194
|
-
Array.isArray(requiredRaw) ? requiredRaw.filter((entry) => typeof entry === "string") : []
|
|
2195
|
-
);
|
|
2196
|
-
for (const param of params) {
|
|
2197
|
-
const path2 = `resources.${id}.backend.url`;
|
|
2198
|
-
if (!Object.hasOwn(properties, param)) {
|
|
2199
|
-
throw new CommerceError(
|
|
2200
|
-
"CONFIG_INVALID",
|
|
2201
|
-
`Resource "${id}" has backend.url path parameter "{${param}}" which is not declared in ${where} - the caller has no way to supply it, so every call would be refused`,
|
|
2202
|
-
{ details: { path: path2, resourceId: id, param } }
|
|
2203
|
-
);
|
|
2204
|
-
}
|
|
2205
|
-
if (!required.has(param)) {
|
|
2206
|
-
throw new CommerceError(
|
|
2207
|
-
"CONFIG_INVALID",
|
|
2208
|
-
`Resource "${id}" has backend.url path parameter "{${param}}" declared in ${where} but not listed in its "required" - a call that omits it would be refused`,
|
|
2209
|
-
{ details: { path: path2, resourceId: id, param } }
|
|
2210
|
-
);
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
function validateBackendUrl(id, url) {
|
|
2215
|
-
let parsed;
|
|
2216
|
-
try {
|
|
2217
|
-
parsed = new URL(url);
|
|
2218
|
-
} catch {
|
|
2219
|
-
throw new CommerceError(
|
|
2220
|
-
"CONFIG_INVALID",
|
|
2221
|
-
`Resource "${id}" has an invalid backend.url: must be an absolute http:// or https:// URL`,
|
|
2222
|
-
{ details: { path: `resources.${id}.backend.url`, resourceId: id } }
|
|
2223
|
-
);
|
|
2224
|
-
}
|
|
2225
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2226
|
-
throw new CommerceError(
|
|
2227
|
-
"CONFIG_INVALID",
|
|
2228
|
-
`Resource "${id}" has a backend.url with scheme "${parsed.protocol}": must be http:// or https://`,
|
|
2229
|
-
{ details: { path: `resources.${id}.backend.url`, resourceId: id } }
|
|
2230
|
-
);
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
function validateBackendHeaders(id, headers) {
|
|
2234
|
-
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
2235
|
-
try {
|
|
2236
|
-
new Headers([[name, value]]);
|
|
2237
|
-
} catch {
|
|
2238
|
-
throw new CommerceError(
|
|
2239
|
-
"CONFIG_INVALID",
|
|
2240
|
-
`Resource "${id}" has backend header "${name}" with an illegal name or value (a name must be an HTTP token; a value must not contain NUL, a character above U+00FF, or a line break except at either end, which fetch trims)`,
|
|
2241
|
-
{ details: { path: `resources.${id}.backend.headers.${name}`, resourceId: id } }
|
|
2242
|
-
);
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
var init_backend = __esm({
|
|
2247
|
-
"src/config/schema/backend.ts"() {
|
|
2248
|
-
init_core();
|
|
2249
|
-
init_execution();
|
|
2250
|
-
init_is_record();
|
|
2251
|
-
}
|
|
2252
|
-
});
|
|
2253
|
-
|
|
2254
|
-
// src/config/schema/resource-schema.ts
|
|
2255
|
-
function defaultClosedObjectSchema(schema) {
|
|
2256
|
-
const isObjectSchema = isObjectSchemaNode(schema);
|
|
2257
|
-
const result = { ...schema };
|
|
2258
|
-
if (isObjectSchema) {
|
|
2259
|
-
const properties = schema["properties"];
|
|
2260
|
-
if (isRecord(properties)) {
|
|
2261
|
-
result["properties"] = Object.fromEntries(
|
|
2262
|
-
Object.entries(properties).map(([key, sub]) => [
|
|
2263
|
-
key,
|
|
2264
|
-
isRecord(sub) ? defaultClosedObjectSchema(sub) : sub
|
|
2265
|
-
])
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2268
|
-
if (!Object.hasOwn(schema, "additionalProperties")) {
|
|
2269
|
-
result["additionalProperties"] = false;
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
const items = schema["items"];
|
|
2273
|
-
if (isRecord(items)) {
|
|
2274
|
-
result["items"] = defaultClosedObjectSchema(items);
|
|
2275
|
-
}
|
|
2276
|
-
const additional = schema["additionalProperties"];
|
|
2277
|
-
if (isRecord(additional)) {
|
|
2278
|
-
result["additionalProperties"] = defaultClosedObjectSchema(additional);
|
|
2279
|
-
}
|
|
2280
|
-
return result;
|
|
2281
|
-
}
|
|
2282
|
-
function excludesObjectType(schema) {
|
|
2283
|
-
const type = schema["type"];
|
|
2284
|
-
if (type === void 0 || type === "object") return false;
|
|
2285
|
-
if (Array.isArray(type)) return !type.includes("object");
|
|
2286
|
-
return true;
|
|
2287
|
-
}
|
|
2288
|
-
function validateResourceSchemaKeywords(id, path2, schema) {
|
|
2289
|
-
for (const keyword of UNSUPPORTED_SCHEMA_KEYWORDS) {
|
|
2290
|
-
if (Object.hasOwn(schema, keyword)) {
|
|
2291
|
-
console.warn(
|
|
2292
|
-
`[agent-commerce] resource "${id}" ${path2} uses JSON Schema keyword "${keyword}", which this gateway does not enforce (see src/core/execution/validation.ts for the supported subset). Remove it or treat it as documentation only.`
|
|
2293
|
-
);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
if ((Object.hasOwn(schema, "properties") || Object.hasOwn(schema, "required")) && excludesObjectType(schema)) {
|
|
2297
|
-
throw new CommerceError(
|
|
2298
|
-
"CONFIG_INVALID",
|
|
2299
|
-
`Resource "${id}" ${path2} declares "properties" and/or "required" but its "type" (${JSON.stringify(schema["type"])}) does not include "object" - the validator will never route a value through either check under that type, so this schema claims to constrain input it does not actually enforce. Remove "properties"/"required", or include "object" in "type".`,
|
|
2300
|
-
{ details: { path: `resources.${id}.${path2}`, resourceId: id } }
|
|
2301
|
-
);
|
|
2302
|
-
}
|
|
2303
|
-
const requiredRaw = schema["required"];
|
|
2304
|
-
const closed = !Object.hasOwn(schema, "additionalProperties") ? true : schema["additionalProperties"] === false;
|
|
2305
|
-
if (Array.isArray(requiredRaw) && closed && isObjectSchemaNode(schema)) {
|
|
2306
|
-
const declared = isRecord(schema["properties"]) ? schema["properties"] : {};
|
|
2307
|
-
const undeclared = requiredRaw.filter(
|
|
2308
|
-
(name) => typeof name === "string" && !Object.hasOwn(declared, name)
|
|
2309
|
-
);
|
|
2310
|
-
if (undeclared.length > 0) {
|
|
2311
|
-
throw new CommerceError(
|
|
2312
|
-
"CONFIG_INVALID",
|
|
2313
|
-
`Resource "${id}" ${path2} lists ${undeclared.map((n) => `"${n}"`).join(", ")} in "required" but does not declare ${undeclared.length === 1 ? "it" : "them"} in "properties". The schema is closed (additionalProperties: false), so ${undeclared.length === 1 ? "that name is" : "those names are"} rejected as an unknown key and no input can ever satisfy this schema. Declare ${undeclared.length === 1 ? "it" : "them"} in "properties".`,
|
|
2314
|
-
{ details: { path: `resources.${id}.${path2}`, resourceId: id, undeclared } }
|
|
2315
|
-
);
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
const properties = schema["properties"];
|
|
2319
|
-
if (isRecord(properties)) {
|
|
2320
|
-
for (const [key, sub] of Object.entries(properties)) {
|
|
2321
|
-
if (isRecord(sub)) validateResourceSchemaKeywords(id, `${path2}.properties.${key}`, sub);
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
const additional = schema["additionalProperties"];
|
|
2325
|
-
if (isRecord(additional)) {
|
|
2326
|
-
validateResourceSchemaKeywords(id, `${path2}.additionalProperties`, additional);
|
|
2327
|
-
}
|
|
2328
|
-
const items = schema["items"];
|
|
2329
|
-
if (isRecord(items)) {
|
|
2330
|
-
validateResourceSchemaKeywords(id, `${path2}.items`, items);
|
|
2331
|
-
} else if (Array.isArray(items)) {
|
|
2332
|
-
console.warn(
|
|
2333
|
-
`[agent-commerce] resource "${id}" ${path2}.items is a tuple (an array of schemas), which this gateway does not enforce - only a single schema applied to every array element is supported (see src/core/execution/validation.ts). Each position's schema is unenforced; treat it as documentation only.`
|
|
2334
|
-
);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
var EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS;
|
|
2338
|
-
var init_resource_schema = __esm({
|
|
2339
|
-
"src/config/schema/resource-schema.ts"() {
|
|
2340
|
-
init_core();
|
|
2341
|
-
init_execution();
|
|
2342
|
-
init_is_record();
|
|
2343
|
-
EMPTY_CLOSED_OBJECT_SCHEMA = {
|
|
2344
|
-
type: "object",
|
|
2345
|
-
properties: {},
|
|
2346
|
-
additionalProperties: false
|
|
2347
|
-
};
|
|
2348
|
-
UNSUPPORTED_SCHEMA_KEYWORDS = [
|
|
2349
|
-
"pattern",
|
|
2350
|
-
"minLength",
|
|
2351
|
-
"maxLength",
|
|
2352
|
-
"format",
|
|
2353
|
-
"minimum",
|
|
2354
|
-
"maximum",
|
|
2355
|
-
"exclusiveMinimum",
|
|
2356
|
-
"exclusiveMaximum",
|
|
2357
|
-
"multipleOf",
|
|
2358
|
-
"oneOf",
|
|
2359
|
-
"anyOf",
|
|
2360
|
-
"allOf",
|
|
2361
|
-
"not",
|
|
2362
|
-
"$ref",
|
|
2363
|
-
"const",
|
|
2364
|
-
"patternProperties",
|
|
2365
|
-
"minItems",
|
|
2366
|
-
"maxItems",
|
|
2367
|
-
"uniqueItems"
|
|
2368
|
-
];
|
|
2369
|
-
}
|
|
2370
|
-
});
|
|
2371
|
-
|
|
2372
|
-
// src/config/schema/resources.ts
|
|
2373
|
-
function normalizeResource(id, entry, protocols, x402, mpp, ap2) {
|
|
2374
|
-
if (entry.input !== void 0) validateResourceSchemaKeywords(id, "input", entry.input);
|
|
2375
|
-
const inputProperties = entry.input?.["properties"];
|
|
2376
|
-
if (inputProperties && typeof inputProperties === "object") {
|
|
2377
|
-
for (const reserved of RESERVED_INPUT_FIELDS) {
|
|
2378
|
-
if (!(reserved in inputProperties)) continue;
|
|
2379
|
-
throw new CommerceError(
|
|
2380
|
-
"CONFIG_INVALID",
|
|
2381
|
-
`Resource "${id}" declares an input property "${reserved}", which is reserved by the gateway`,
|
|
2382
|
-
{
|
|
2383
|
-
details: {
|
|
2384
|
-
path: `resources.${id}.input.properties.${reserved}`,
|
|
2385
|
-
resourceId: id
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
);
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
for (const protocol of entry.expose) {
|
|
2392
|
-
if (!SUPPORTED_PROTOCOLS.has(protocol)) {
|
|
2393
|
-
const hint = protocol === "ucp" ? " (UCP is planned, not supported in this release)" : "";
|
|
2394
|
-
throw new CommerceError(
|
|
2395
|
-
"CONFIG_INVALID",
|
|
2396
|
-
`Resource "${id}" exposes unsupported protocol "${protocol}"${hint}. Supported: ${PROTOCOL_NAMES.join(", ")}.`,
|
|
2397
|
-
{ details: { path: `resources.${id}.expose`, resourceId: id, protocol } }
|
|
2398
|
-
);
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
if (entry.expose.includes("mcp")) {
|
|
2402
|
-
if (!protocols.mcp.enabled) {
|
|
2403
|
-
throw new CommerceError(
|
|
2404
|
-
"CONFIG_INVALID",
|
|
2405
|
-
`Resource "${id}" is exposed via "mcp" but protocols.mcp.enabled is false`,
|
|
2406
|
-
{ details: { path: `resources.${id}.expose`, resourceId: id } }
|
|
2407
|
-
);
|
|
2408
|
-
}
|
|
2409
|
-
if (!MCP_TOOL_NAME_PATTERN.test(id)) {
|
|
2410
|
-
throw new CommerceError(
|
|
2411
|
-
"CONFIG_INVALID",
|
|
2412
|
-
`Resource "${id}" is exposed via "mcp" but its id is not a legal MCP tool name (allowed: A-Z, a-z, 0-9, "_", "-", ".", 1-128 chars)`,
|
|
2413
|
-
{ details: { path: `resources.${id}`, resourceId: id } }
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
if (entry.expose.includes("a2a") && !protocols.a2a.enabled) {
|
|
2418
|
-
throw new CommerceError(
|
|
2419
|
-
"CONFIG_INVALID",
|
|
2420
|
-
`Resource "${id}" is exposed via "a2a" but protocols.a2a.enabled is false`,
|
|
2421
|
-
{ details: { path: `resources.${id}.expose`, resourceId: id } }
|
|
2422
|
-
);
|
|
2423
|
-
}
|
|
2424
|
-
if (entry.expose.includes("acp") && !protocols.acp.enabled) {
|
|
2425
|
-
throw new CommerceError(
|
|
2426
|
-
"CONFIG_INVALID",
|
|
2427
|
-
`Resource "${id}" is exposed via "acp" but protocols.acp.enabled is false`,
|
|
2428
|
-
{ details: { path: `resources.${id}.expose`, resourceId: id } }
|
|
2429
|
-
);
|
|
2430
|
-
}
|
|
2431
|
-
if (entry.expose.includes("http") && !protocols.http.enabled) {
|
|
2432
|
-
throw new CommerceError(
|
|
2433
|
-
"CONFIG_INVALID",
|
|
2434
|
-
`Resource "${id}" is exposed via "http" but protocols.http.enabled is false`,
|
|
2435
|
-
{ details: { path: `resources.${id}.expose`, resourceId: id } }
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2438
|
-
validateBackendUrl(id, entry.backend.url);
|
|
2439
|
-
validateBackendHeaders(id, entry.backend.headers);
|
|
2440
|
-
const pathScope = validateInputBindings(id, entry.backend, entry.input);
|
|
2441
|
-
validatePathParametersDeclared(id, entry.backend.url, pathScope.schema, pathScope.where);
|
|
2442
|
-
if (entry.pricing.type === "dynamic") {
|
|
2443
|
-
throw new CommerceError(
|
|
2444
|
-
"CONFIG_INVALID",
|
|
2445
|
-
`Resource "${id}" uses pricing.type "dynamic", which is not supported in this release`,
|
|
2446
|
-
{ details: { path: `resources.${id}.pricing.type`, resourceId: id } }
|
|
2447
|
-
);
|
|
2448
|
-
}
|
|
2449
|
-
const paymentMethods = entry.payments ?? [];
|
|
2450
|
-
if (entry.x402 !== void 0) {
|
|
2451
|
-
const at = `resources.${id}.x402`;
|
|
2452
|
-
if (x402 === void 0) {
|
|
2453
|
-
throw new CommerceError(
|
|
2454
|
-
"CONFIG_INVALID",
|
|
2455
|
-
`Resource "${id}" sets x402.accepts, but payments.x402 is not configured`,
|
|
2456
|
-
{ details: { path: at, resourceId: id } }
|
|
2457
|
-
);
|
|
2458
|
-
}
|
|
2459
|
-
if (entry.pricing.type !== "fixed" || !paymentMethods.includes("x402")) {
|
|
2460
|
-
throw new CommerceError(
|
|
2461
|
-
"CONFIG_INVALID",
|
|
2462
|
-
`Resource "${id}" sets x402.accepts, which requires fixed pricing and "x402" in payments`,
|
|
2463
|
-
{ details: { path: at, resourceId: id } }
|
|
2464
|
-
);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
const offers = x402 !== void 0 ? x402.resources[id] ?? x402 : void 0;
|
|
2468
|
-
if (entry.pricing.type === "fixed") {
|
|
2469
|
-
const decimals = paymentMethods.includes("x402") ? offers?.options.map((option) => option.assetDecimals) : void 0;
|
|
2470
|
-
validatePricingAmount(id, entry.pricing.amount, decimals && Math.min(...decimals));
|
|
2471
|
-
}
|
|
2472
|
-
if (entry.pricing.type === "fixed") {
|
|
2473
|
-
if (paymentMethods.length === 0) {
|
|
2474
|
-
throw new CommerceError(
|
|
2475
|
-
"CONFIG_INVALID",
|
|
2476
|
-
`Resource "${id}" has fixed pricing but declares no "payments" (a paid resource must name at least one payment method)`,
|
|
2477
|
-
{ details: { path: `resources.${id}.payments`, resourceId: id } }
|
|
2478
|
-
);
|
|
2479
|
-
}
|
|
2480
|
-
for (const method of paymentMethods) {
|
|
2481
|
-
if (!SUPPORTED_PAYMENT_METHODS.has(method)) {
|
|
2482
|
-
throw new CommerceError(
|
|
2483
|
-
"CONFIG_INVALID",
|
|
2484
|
-
`Resource "${id}" names unsupported payment method "${method}". Supported: ${PAYMENT_METHOD_NAMES.join(", ")}.`,
|
|
2485
|
-
{ details: { path: `resources.${id}.payments`, resourceId: id, method } }
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
const enabledRails = [
|
|
2490
|
-
...x402?.config.enabled ? ["x402"] : [],
|
|
2491
|
-
...mpp?.enabled ? ["mpp"] : []
|
|
2492
|
-
];
|
|
2493
|
-
if (!paymentMethods.some((method) => enabledRails.includes(method))) {
|
|
2494
|
-
throw new CommerceError(
|
|
2495
|
-
"CONFIG_INVALID",
|
|
2496
|
-
`Resource "${id}" names payment method(s) "${paymentMethods.join(", ")}", none of which is configured and enabled under "payments"`,
|
|
2497
|
-
{ details: { path: `resources.${id}.payments`, resourceId: id, paymentMethods } }
|
|
2498
|
-
);
|
|
2499
|
-
}
|
|
2500
|
-
const symbols = offers?.defaultAssetSymbols ?? [];
|
|
2501
|
-
const currency = entry.pricing.currency;
|
|
2502
|
-
if (x402?.config.enabled && symbols.length > 0 && paymentMethods.includes("x402") && currency !== "USD" && !(symbols.length === 1 && currency === symbols[0])) {
|
|
2503
|
-
const tokens = symbols.join(" and ");
|
|
2504
|
-
throw new CommerceError(
|
|
2505
|
-
"CONFIG_INVALID",
|
|
2506
|
-
`Resource "${id}" is priced in ${currency}, but x402 settles in ${tokens}, the default asset of its network. Price it in ${symbols.length === 1 ? `${tokens} or ` : ""}USD.`,
|
|
2507
|
-
{ details: { path: `resources.${id}.pricing.currency`, resourceId: id } }
|
|
2508
|
-
);
|
|
2509
|
-
}
|
|
2510
|
-
if (mpp?.enabled && paymentMethods.includes("mpp")) {
|
|
2511
|
-
if (entry.pricing.currency !== MPP_PROFILE.assetSymbol) {
|
|
2512
|
-
throw new CommerceError(
|
|
2513
|
-
"CONFIG_INVALID",
|
|
2514
|
-
`Resource "${id}" is priced in ${entry.pricing.currency}, but MPP charges ${MPP_PROFILE.assetSymbol}`,
|
|
2515
|
-
{ details: { path: `resources.${id}.pricing.currency`, resourceId: id } }
|
|
2516
|
-
);
|
|
2517
|
-
}
|
|
2518
|
-
validatePricingAmount(id, entry.pricing.amount, MPP_PROFILE.assetDecimals);
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
if (entry.paymentFlow !== void 0 && (entry.pricing.type !== "fixed" || !paymentMethods.some((method) => method === "x402" || method === "mpp"))) {
|
|
2522
|
-
throw new CommerceError(
|
|
2523
|
-
"CONFIG_INVALID",
|
|
2524
|
-
`Resource "${id}" sets paymentFlow, which requires fixed pricing and "x402" or "mpp" in payments`,
|
|
2525
|
-
{ details: { path: `resources.${id}.paymentFlow`, resourceId: id } }
|
|
2526
|
-
);
|
|
2527
|
-
}
|
|
2528
|
-
const pricing = entry.pricing.type === "free" ? { type: "free" } : { type: "fixed", amount: entry.pricing.amount, currency: entry.pricing.currency };
|
|
2529
|
-
const authorization = normalizeResourceAuthorization(id, entry, pricing, ap2);
|
|
2530
|
-
return {
|
|
2531
|
-
id,
|
|
2532
|
-
name: entry.name,
|
|
2533
|
-
...entry.description !== void 0 ? { description: entry.description } : {},
|
|
2534
|
-
inputSchema: entry.input !== void 0 ? defaultClosedObjectSchema(entry.input) : EMPTY_CLOSED_OBJECT_SCHEMA,
|
|
2535
|
-
...entry.output !== void 0 ? { outputSchema: entry.output } : {},
|
|
2536
|
-
handler: {
|
|
2537
|
-
type: "http",
|
|
2538
|
-
method: entry.backend.method,
|
|
2539
|
-
url: entry.backend.url,
|
|
2540
|
-
...entry.backend.headers !== void 0 ? { headers: entry.backend.headers } : {},
|
|
2541
|
-
...entry.backend.timeoutMs !== void 0 ? {
|
|
2542
|
-
timeoutMs: toNumber(entry.backend.timeoutMs, `resources.${id}.backend.timeoutMs`, {
|
|
2543
|
-
min: 1
|
|
2544
|
-
})
|
|
2545
|
-
} : {},
|
|
2546
|
-
...entry.backend.inputBindings !== void 0 ? { inputBindings: pickDefined(entry.backend.inputBindings) } : {}
|
|
2547
|
-
},
|
|
2548
|
-
pricing,
|
|
2549
|
-
exposedVia: entry.expose,
|
|
2550
|
-
paymentMethods,
|
|
2551
|
-
...authorization !== void 0 ? { authorization } : {}
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
function validatePricingAmount(id, amount, decimals) {
|
|
2555
|
-
const path2 = `resources.${id}.pricing.amount`;
|
|
2556
|
-
if (!PRICING_AMOUNT_PATTERN.test(amount)) {
|
|
2557
|
-
throw new CommerceError(
|
|
2558
|
-
"CONFIG_INVALID",
|
|
2559
|
-
`Resource "${id}" has pricing.amount "${amount}", which is not a plain positive decimal (no currency symbol, no thousands separator, no exponent - e.g. "0.01")`,
|
|
2560
|
-
{ details: { path: path2, resourceId: id } }
|
|
2561
|
-
);
|
|
2562
|
-
}
|
|
2563
|
-
if (ZERO_AMOUNT_PATTERN.test(amount)) {
|
|
2564
|
-
throw new CommerceError(
|
|
2565
|
-
"CONFIG_INVALID",
|
|
2566
|
-
`Resource "${id}" has pricing.amount "0" - a paid resource cannot cost zero; use "pricing: { type: free }" instead`,
|
|
2567
|
-
{ details: { path: path2, resourceId: id } }
|
|
2568
|
-
);
|
|
2569
|
-
}
|
|
2570
|
-
if (decimals !== void 0) {
|
|
2571
|
-
const fractionalDigits = amount.includes(".") ? amount.split(".")[1]?.length ?? 0 : 0;
|
|
2572
|
-
if (fractionalDigits > decimals) {
|
|
2573
|
-
throw new CommerceError(
|
|
2574
|
-
"CONFIG_INVALID",
|
|
2575
|
-
`Resource "${id}" has pricing.amount "${amount}" with more precision (${fractionalDigits} fractional digits) than the configured asset supports (${decimals} decimals)`,
|
|
2576
|
-
{ details: { path: path2, resourceId: id } }
|
|
2577
|
-
);
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
function pickDefined(value) {
|
|
2582
|
-
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
2583
|
-
}
|
|
2584
|
-
var SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
2585
|
-
var init_resources = __esm({
|
|
2586
|
-
"src/config/schema/resources.ts"() {
|
|
2587
|
-
init_core();
|
|
2588
|
-
init_constants3();
|
|
2589
|
-
init_constants4();
|
|
2590
|
-
init_ap2();
|
|
2591
|
-
init_backend();
|
|
2592
|
-
init_resource_schema();
|
|
2593
|
-
init_values();
|
|
2594
|
-
SUPPORTED_PROTOCOLS = new Set(PROTOCOL_NAMES);
|
|
2595
|
-
SUPPORTED_PAYMENT_METHODS = new Set(PAYMENT_METHOD_NAMES);
|
|
2596
|
-
PRICING_AMOUNT_PATTERN = /^\d+(?:\.\d+)?$/;
|
|
2597
|
-
ZERO_AMOUNT_PATTERN = /^0(?:\.0+)?$/;
|
|
2598
|
-
}
|
|
2599
|
-
});
|
|
2600
|
-
|
|
2601
|
-
// src/config/schema/normalize.ts
|
|
2602
|
-
function normalize(raw) {
|
|
2603
|
-
const protocols = {
|
|
2604
|
-
http: { enabled: toBoolean(raw.protocols.http.enabled, "protocols.http.enabled") },
|
|
2605
|
-
mcp: {
|
|
2606
|
-
enabled: toBoolean(raw.protocols.mcp.enabled, "protocols.mcp.enabled"),
|
|
2607
|
-
mountPath: raw.protocols.mcp.mountPath
|
|
2608
|
-
},
|
|
2609
|
-
a2a: {
|
|
2610
|
-
enabled: toBoolean(raw.protocols.a2a?.enabled ?? false, "protocols.a2a.enabled"),
|
|
2611
|
-
mountPath: raw.protocols.a2a?.mountPath ?? DEFAULT_A2A_MOUNT_PATH
|
|
2612
|
-
},
|
|
2613
|
-
acp: normalizeAcp(raw.protocols.acp)
|
|
2614
|
-
};
|
|
2615
|
-
validateMountPaths(protocols);
|
|
2616
|
-
const x402Settings = raw.payments.x402 !== void 0 ? normalizeX402(raw.payments.x402, raw.resources) : void 0;
|
|
2617
|
-
const x402 = x402Settings?.config;
|
|
2618
|
-
const mpp = normalizeMpp(raw.payments.mpp);
|
|
2619
|
-
const ap2 = normalizeAp2(raw.authorization?.ap2);
|
|
2620
|
-
if (ap2?.enabled) {
|
|
2621
|
-
validateReplayStoreIsolated(ap2.replay.path, raw.storage.receipts.path, protocols.acp);
|
|
2622
|
-
}
|
|
2623
|
-
const resources = Object.entries(raw.resources).map(
|
|
2624
|
-
([id, entry]) => normalizeResource(id, entry, protocols, x402Settings, mpp, ap2)
|
|
2625
|
-
);
|
|
2626
|
-
const flowOverrides = (rail) => {
|
|
2627
|
-
const flows = Object.fromEntries(
|
|
2628
|
-
resources.flatMap((resource) => {
|
|
2629
|
-
const flow = raw.resources[resource.id]?.paymentFlow;
|
|
2630
|
-
return flow !== void 0 && resource.paymentMethods.includes(rail) ? [[resource.id, flow]] : [];
|
|
2631
|
-
})
|
|
2632
|
-
);
|
|
2633
|
-
return Object.keys(flows).length > 0 ? { resourcePaymentFlows: flows } : {};
|
|
2634
|
-
};
|
|
2635
|
-
const x402WithFlows = x402 !== void 0 ? { ...x402, ...flowOverrides("x402") } : x402;
|
|
2636
|
-
const mppWithFlows = mpp !== void 0 ? { ...mpp, ...flowOverrides("mpp") } : mpp;
|
|
2637
|
-
if (protocols.acp.enabled) validateAcpCheckoutMapping(protocols.acp, resources);
|
|
2638
|
-
if (protocols.a2a.enabled && !resources.some((r) => r.exposedVia.includes("a2a"))) {
|
|
2639
|
-
throw new CommerceError(
|
|
2640
|
-
"CONFIG_INVALID",
|
|
2641
|
-
'protocols.a2a.enabled is true but no resource lists "a2a" in its expose',
|
|
2642
|
-
{ details: { path: "protocols.a2a.enabled" } }
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
return {
|
|
2646
|
-
version: SUPPORTED_CONFIG_VERSION,
|
|
2647
|
-
merchant: {
|
|
2648
|
-
id: raw.merchant.id,
|
|
2649
|
-
name: raw.merchant.name,
|
|
2650
|
-
publicBaseUrl: raw.merchant.publicBaseUrl
|
|
2651
|
-
},
|
|
2652
|
-
// Port 0 lets the OS pick a free port, for tests and throwaway instances
|
|
2653
|
-
server: {
|
|
2654
|
-
port: toNumber(raw.server.port, "server.port", { min: 0, max: 65535 }),
|
|
2655
|
-
host: raw.server.host,
|
|
2656
|
-
...raw.server.adminToken !== void 0 ? { adminToken: raw.server.adminToken } : {},
|
|
2657
|
-
allowedOrigins: raw.server.allowedOrigins ?? []
|
|
2658
|
-
},
|
|
2659
|
-
storage: { receipts: { driver: "sqlite", path: raw.storage.receipts.path } },
|
|
2660
|
-
protocols,
|
|
2661
|
-
resources,
|
|
2662
|
-
payments: {
|
|
2663
|
-
...x402WithFlows !== void 0 ? { x402: x402WithFlows } : {},
|
|
2664
|
-
...mppWithFlows !== void 0 ? { mpp: mppWithFlows } : {}
|
|
2665
|
-
},
|
|
2666
|
-
...ap2 !== void 0 ? { authorization: { ap2 } } : {}
|
|
2667
|
-
};
|
|
2668
|
-
}
|
|
2669
|
-
function validateMountPaths(protocols) {
|
|
2670
|
-
const mounts = [
|
|
2671
|
-
["mcp", protocols.mcp],
|
|
2672
|
-
["a2a", protocols.a2a],
|
|
2673
|
-
["acp", protocols.acp]
|
|
2674
|
-
].filter(([, p]) => p.enabled);
|
|
2675
|
-
for (const [index, [nameA, a]] of mounts.entries()) {
|
|
2676
|
-
for (const [nameB, b] of mounts.slice(index + 1)) {
|
|
2677
|
-
const baseA = a.mountPath.replace(/\/+$/, "");
|
|
2678
|
-
const baseB = b.mountPath.replace(/\/+$/, "");
|
|
2679
|
-
if (baseA === baseB || baseA.startsWith(`${baseB}/`) || baseB.startsWith(`${baseA}/`)) {
|
|
2680
|
-
throw new CommerceError(
|
|
2681
|
-
"CONFIG_INVALID",
|
|
2682
|
-
`protocols.${nameA}.mountPath ("${a.mountPath}") collides with protocols.${nameB}.mountPath ("${b.mountPath}") - each mount registers a wildcard, so overlapping prefixes cannot both be served`,
|
|
2683
|
-
{ details: { path: `protocols.${nameA}.mountPath` } }
|
|
2684
|
-
);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
var DEFAULT_A2A_MOUNT_PATH;
|
|
2690
|
-
var init_normalize = __esm({
|
|
2691
|
-
"src/config/schema/normalize.ts"() {
|
|
2692
|
-
init_core();
|
|
2693
|
-
init_acp();
|
|
2694
|
-
init_ap2();
|
|
2695
|
-
init_mpp();
|
|
2696
|
-
init_raw();
|
|
2697
|
-
init_resources();
|
|
2698
|
-
init_values();
|
|
2699
|
-
init_x402();
|
|
2700
|
-
DEFAULT_A2A_MOUNT_PATH = "/a2a";
|
|
2701
|
-
}
|
|
2702
|
-
});
|
|
2703
|
-
|
|
2704
|
-
// src/config/schema/index.ts
|
|
2705
|
-
function parseConfig(raw, env) {
|
|
2706
|
-
if (!isRecord(raw)) {
|
|
2707
|
-
throw new CommerceError(
|
|
2708
|
-
"CONFIG_INVALID",
|
|
2709
|
-
'Configuration root must be a mapping (object) at "$"',
|
|
2710
|
-
{
|
|
2711
|
-
details: { path: "$" }
|
|
2712
|
-
}
|
|
2713
|
-
);
|
|
2714
|
-
}
|
|
2715
|
-
checkVersion(raw);
|
|
2716
|
-
const substituted = substituteEnv(raw, env);
|
|
2717
|
-
const parsed = parseWithZod(RawConfigSchema, substituted);
|
|
2718
|
-
return normalize(parsed);
|
|
2719
|
-
}
|
|
2720
|
-
function checkVersion(raw) {
|
|
2721
|
-
if (!("version" in raw)) {
|
|
2722
|
-
throw new CommerceError("CONFIG_INVALID", 'Configuration is missing required field "version"', {
|
|
2723
|
-
details: { path: "version" }
|
|
2724
|
-
});
|
|
2725
|
-
}
|
|
2726
|
-
const version = raw["version"];
|
|
2727
|
-
if (version !== SUPPORTED_CONFIG_VERSION) {
|
|
2728
|
-
const hint = typeof version === "string" ? ", written as a number without quotes" : "";
|
|
2729
|
-
throw new CommerceError(
|
|
2730
|
-
"CONFIG_INVALID",
|
|
2731
|
-
`Unsupported config version "${String(version)}": this gateway only supports version ${SUPPORTED_CONFIG_VERSION}${hint}`,
|
|
2732
|
-
{ details: { path: "version", supported: SUPPORTED_CONFIG_VERSION } }
|
|
2733
|
-
);
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
function parseWithZod(schema, value) {
|
|
2737
|
-
const result = schema.safeParse(value);
|
|
2738
|
-
if (!result.success) {
|
|
2739
|
-
throw zodErrorToConfigError(result.error);
|
|
2740
|
-
}
|
|
2741
|
-
return result.data;
|
|
2742
|
-
}
|
|
2743
|
-
function zodErrorToConfigError(error) {
|
|
2744
|
-
const issues = error.issues.map(describeIssue);
|
|
2745
|
-
const first = issues[0];
|
|
2746
|
-
const message = first ? `Configuration invalid at "${first.path}": ${first.message}` : "Configuration invalid";
|
|
2747
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { issues } });
|
|
2748
|
-
}
|
|
2749
|
-
function describeIssue(issue) {
|
|
2750
|
-
const path2 = issue.path.length > 0 ? issue.path.join(".") : "$";
|
|
2751
|
-
return { path: path2, message: issue.message, code: issue.code };
|
|
2752
|
-
}
|
|
2753
|
-
var init_schema = __esm({
|
|
2754
|
-
"src/config/schema/index.ts"() {
|
|
2755
|
-
init_core();
|
|
2756
|
-
init_is_record();
|
|
2757
|
-
init_env();
|
|
2758
|
-
init_normalize();
|
|
2759
|
-
init_raw();
|
|
2760
|
-
}
|
|
2761
|
-
});
|
|
2762
|
-
async function loadConfig(options = {}) {
|
|
2763
|
-
const env = options.env ?? process.env;
|
|
2764
|
-
const cwd = options.cwd ?? process.cwd();
|
|
2765
|
-
const configPath = resolveConfigPath(options.path, env, cwd);
|
|
2766
|
-
let text2;
|
|
2767
|
-
try {
|
|
2768
|
-
text2 = await promises.readFile(configPath, "utf8");
|
|
2769
|
-
} catch (error) {
|
|
2770
|
-
throw new CommerceError("CONFIG_INVALID", `Could not read configuration file "${configPath}"`, {
|
|
2771
|
-
details: { path: configPath },
|
|
2772
|
-
cause: error
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
let raw;
|
|
2776
|
-
try {
|
|
2777
|
-
raw = parse(text2);
|
|
2778
|
-
} catch (error) {
|
|
2779
|
-
const { reason, details } = describeYamlError(error);
|
|
2780
|
-
throw new CommerceError(
|
|
2781
|
-
"CONFIG_INVALID",
|
|
2782
|
-
`Configuration file "${configPath}" is not valid YAML: ${reason}`,
|
|
2783
|
-
{
|
|
2784
|
-
details: { path: configPath, ...details },
|
|
2785
|
-
// Kept for programmatic callers. Never print it: its `.message` quotes
|
|
2786
|
-
// the source line (see describeYamlError).
|
|
2787
|
-
cause: error
|
|
2788
|
-
}
|
|
2789
|
-
);
|
|
2790
|
-
}
|
|
2791
|
-
return parseConfig(raw, env);
|
|
2792
|
-
}
|
|
2793
|
-
function describeYamlError(error) {
|
|
2794
|
-
if (!(error instanceof YAMLError)) {
|
|
2795
|
-
return { reason: "unknown YAML parse error", details: {} };
|
|
2796
|
-
}
|
|
2797
|
-
const start = error.linePos?.[0];
|
|
2798
|
-
const where = start === void 0 ? "" : ` at line ${String(start.line)}, column ${String(start.col)}`;
|
|
2799
|
-
const explanation = YAML_ERROR_EXPLANATIONS[error.code];
|
|
2800
|
-
const what = explanation === void 0 ? error.code : `${error.code} (${explanation})`;
|
|
2801
|
-
return {
|
|
2802
|
-
reason: `${what}${where}`,
|
|
2803
|
-
details: {
|
|
2804
|
-
yamlErrorCode: error.code,
|
|
2805
|
-
...start === void 0 ? {} : { line: start.line, column: start.col }
|
|
2806
|
-
}
|
|
2807
|
-
};
|
|
2808
|
-
}
|
|
2809
|
-
function resolveConfigPath(explicitPath, env, cwd) {
|
|
2810
|
-
if (explicitPath) return path.resolve(cwd, explicitPath);
|
|
2811
|
-
const fromEnv = env[CONFIG_PATH_ENV_VAR];
|
|
2812
|
-
if (fromEnv) return path.resolve(cwd, fromEnv);
|
|
2813
|
-
return path.resolve(cwd, DEFAULT_CONFIG_FILENAME);
|
|
2814
|
-
}
|
|
2815
|
-
var CONFIG_PATH_ENV_VAR, YAML_ERROR_EXPLANATIONS;
|
|
2816
|
-
var init_load = __esm({
|
|
2817
|
-
"src/config/load.ts"() {
|
|
2818
|
-
init_core();
|
|
2819
|
-
init_filename();
|
|
2820
|
-
init_schema();
|
|
2821
|
-
CONFIG_PATH_ENV_VAR = "AGENT_COMMERCE_CONFIG";
|
|
2822
|
-
YAML_ERROR_EXPLANATIONS = {
|
|
2823
|
-
DUPLICATE_KEY: "map keys must be unique",
|
|
2824
|
-
BAD_INDENT: "bad indentation",
|
|
2825
|
-
TAB_AS_INDENT: "tabs cannot be used for indentation",
|
|
2826
|
-
MISSING_CHAR: "a required character is missing",
|
|
2827
|
-
BLOCK_AS_IMPLICIT_KEY: "a block value cannot be used as a key (missing quotes, or a stray colon)",
|
|
2828
|
-
MULTILINE_IMPLICIT_KEY: "a key spans multiple lines (missing quotes, or a stray colon)",
|
|
2829
|
-
UNEXPECTED_TOKEN: "unexpected token"
|
|
2830
|
-
};
|
|
2831
|
-
}
|
|
2832
|
-
});
|
|
2833
|
-
|
|
2834
|
-
// src/config/index.ts
|
|
2835
|
-
var config_exports = {};
|
|
2836
|
-
__export(config_exports, {
|
|
2837
|
-
loadConfig: () => loadConfig,
|
|
2838
|
-
parseConfig: () => parseConfig,
|
|
2839
|
-
substituteEnv: () => substituteEnv
|
|
2840
|
-
});
|
|
2841
|
-
var init_config = __esm({
|
|
2842
|
-
"src/config/index.ts"() {
|
|
2843
|
-
init_env();
|
|
2844
|
-
init_load();
|
|
2845
|
-
init_schema();
|
|
2846
|
-
}
|
|
2847
|
-
});
|
|
2848
|
-
|
|
2849
|
-
// src/cli/program.ts
|
|
2850
|
-
init_filename();
|
|
2851
|
-
init_core();
|
|
2
|
+
import { createSqliteReceiptStore, A2A_SPEC_VERSION, A2A_PROTOCOL_VERSION, A2A_PROTOCOL_BINDING, A2A_AGENT_CARD_PATH, A2A_UNSUPPORTED, ACP_UNSUPPORTED } from './chunk-SNM3OVCY.js';
|
|
3
|
+
import { DEFAULT_CONFIG_FILENAME, extractPathParameterNames, ACP_SPEC_VERSION, ACP_API_VERSION, ACP_WELL_KNOWN_PATH, findUnparsedBraceToken } from './chunk-2LN5PO5I.js';
|
|
4
|
+
import { MPP_PROFILE, MPP_SPEC_DRAFTS, MPPX_VERSION, MPP_SPEC_COMMIT, MPP_DEFAULT_PAYMENT_FLOW } from './chunk-AA3FIEAR.js';
|
|
5
|
+
import { ANVIL_WELL_KNOWN_ACCOUNTS, describeDeploymentMode, X402_DEFAULT_PAYMENT_FLOW, findX402Network, isEvmAddress, LOCAL_CHAIN_ID, resolveDeploymentMode, sameAddress } from './chunk-OZ4LAWBK.js';
|
|
6
|
+
import { AP2_UNSUPPORTED } from './chunk-3EQBCFZY.js';
|
|
7
|
+
import './chunk-3MGAJ5FX.js';
|
|
8
|
+
import { AP2_SPEC_VERSION, AP2_CHECKOUT_MANDATE_VCT, AP2_CHECKOUT_PROFILE } from './chunk-FHCFR2GS.js';
|
|
9
|
+
import { MCP_TOOL_NAME_MAX_LENGTH, MCP_TOOL_NAME_CHARS } from './chunk-TPC4EJLT.js';
|
|
10
|
+
import { PACKAGE_VERSION } from './chunk-QY3K6PEQ.js';
|
|
11
|
+
import { PROTOCOL_NAMES, isCommerceError, CommerceError } from './chunk-QQ3WY5L3.js';
|
|
12
|
+
import { isRecord } from './chunk-3M6U7ILB.js';
|
|
13
|
+
import { CommanderError, Command } from 'commander';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, accessSync, constants } from 'fs';
|
|
15
|
+
import { dirname, resolve, join, basename, extname, win32 } from 'path';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { stringify, parse, Document } from 'yaml';
|
|
18
|
+
import picocolors from 'picocolors';
|
|
19
|
+
import { writeFile, stat, mkdir, rename, rm, open } from 'fs/promises';
|
|
20
|
+
import { isDeepStrictEqual } from 'util';
|
|
21
|
+
import { validate } from '@scalar/openapi-parser';
|
|
22
|
+
import * as clack from '@clack/prompts';
|
|
2852
23
|
|
|
2853
24
|
// src/cli/commands/demo.ts
|
|
2854
25
|
function defaultDemoSteps() {
|
|
@@ -2904,555 +75,10 @@ async function execCommand(command, args) {
|
|
|
2904
75
|
});
|
|
2905
76
|
}
|
|
2906
77
|
|
|
2907
|
-
// src/cli/commands/doctor/index.ts
|
|
2908
|
-
init_core();
|
|
2909
|
-
init_execution();
|
|
2910
|
-
|
|
2911
|
-
// src/storage/receipts/store.ts
|
|
2912
|
-
init_core();
|
|
2913
|
-
var PACKAGE_VERSION = "2.1.0" ;
|
|
2914
|
-
|
|
2915
|
-
// src/storage/sqlite.ts
|
|
2916
|
-
init_core();
|
|
2917
|
-
function openSqliteDatabase(options) {
|
|
2918
|
-
const { path: path2, label, logger } = options;
|
|
2919
|
-
const isFileBacked = path2 !== ":memory:";
|
|
2920
|
-
if (isFileBacked) {
|
|
2921
|
-
mkdirSync(dirname(path2), { recursive: true, mode: 448 });
|
|
2922
|
-
}
|
|
2923
|
-
if (isFileBacked && existsSync(path2)) {
|
|
2924
|
-
try {
|
|
2925
|
-
accessSync(path2, constants.W_OK);
|
|
2926
|
-
} catch {
|
|
2927
|
-
throw new CommerceError(
|
|
2928
|
-
"STORAGE_ERROR",
|
|
2929
|
-
`${label} "${path2}" exists but is not writable by this process (uid ${typeof process.getuid === "function" ? process.getuid() : "unknown"}). If this is a container that recently changed user, an existing volume may still be owned by the previous one - recreate it (docker compose down -v) or fix its ownership.`,
|
|
2930
|
-
{ details: { path: path2 } }
|
|
2931
|
-
);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
if (isFileBacked) {
|
|
2935
|
-
try {
|
|
2936
|
-
closeSync(openSync(path2, "a", 384));
|
|
2937
|
-
} catch {
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
const db = new Database(path2);
|
|
2941
|
-
db.pragma("journal_mode = WAL");
|
|
2942
|
-
db.pragma("foreign_keys = ON");
|
|
2943
|
-
if (isFileBacked) restrictDatabasePermissions(path2, label, logger);
|
|
2944
|
-
return db;
|
|
2945
|
-
}
|
|
2946
|
-
function restrictDatabasePermissions(path2, label, logger) {
|
|
2947
|
-
for (const file of [path2, `${path2}-wal`, `${path2}-shm`]) {
|
|
2948
|
-
try {
|
|
2949
|
-
chmodSync(file, 384);
|
|
2950
|
-
} catch (error) {
|
|
2951
|
-
const code = error.code;
|
|
2952
|
-
if (code === "ENOENT") continue;
|
|
2953
|
-
logger.warn(
|
|
2954
|
-
{ file, label, error: code ?? "unknown" },
|
|
2955
|
-
"could not restrict database permissions to owner-only"
|
|
2956
|
-
);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
// src/storage/receipts/redact.ts
|
|
2962
|
-
var SECRET_KEY_PATTERN = /private[-_ ]?key|secret|password|mnemonic|seed[-_ ]?phrase|api[-_ ]?key|authoriz(e|ation)|^auth$|auth[-_]?header|payment[-_ ]?proof|^proof$|x-payment|signature|signed[-_ ]?message|token|bearer|credential|cookie|session|jwt/i;
|
|
2963
|
-
var REDACTED = "[REDACTED]";
|
|
2964
|
-
function isSecretKey(key) {
|
|
2965
|
-
return SECRET_KEY_PATTERN.test(key);
|
|
2966
|
-
}
|
|
2967
|
-
function redact(value) {
|
|
2968
|
-
return redactValue(value);
|
|
2969
|
-
}
|
|
2970
|
-
function redactValue(value) {
|
|
2971
|
-
if (Array.isArray(value)) {
|
|
2972
|
-
return value.map(redactValue);
|
|
2973
|
-
}
|
|
2974
|
-
if (value !== null && typeof value === "object") {
|
|
2975
|
-
const out = {};
|
|
2976
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
2977
|
-
out[key] = isSecretKey(key) ? REDACTED : redactValue(inner);
|
|
2978
|
-
}
|
|
2979
|
-
return out;
|
|
2980
|
-
}
|
|
2981
|
-
return value;
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
// src/storage/receipts/rows.ts
|
|
2985
|
-
function receiptToRow(receipt) {
|
|
2986
|
-
return {
|
|
2987
|
-
id: receipt.id,
|
|
2988
|
-
request_id: receipt.requestId,
|
|
2989
|
-
resource_id: receipt.resourceId,
|
|
2990
|
-
payment_json: receipt.payment !== void 0 ? JSON.stringify(redact(receipt.payment)) : null,
|
|
2991
|
-
delivered_at: receipt.deliveredAt,
|
|
2992
|
-
backend_status: receipt.backendStatus,
|
|
2993
|
-
duration_ms: receipt.durationMs ?? null,
|
|
2994
|
-
protocol: receipt.protocol ?? null,
|
|
2995
|
-
metadata_json: receipt.metadata !== void 0 ? JSON.stringify(redact(receipt.metadata)) : null,
|
|
2996
|
-
authorization_json: receipt.authorization !== void 0 ? JSON.stringify(redact(receipt.authorization)) : null
|
|
2997
|
-
};
|
|
2998
|
-
}
|
|
2999
|
-
function rowToReceipt(row) {
|
|
3000
|
-
const payment = row.payment_json !== null ? JSON.parse(row.payment_json) : void 0;
|
|
3001
|
-
const metadata = row.metadata_json !== null ? JSON.parse(row.metadata_json) : void 0;
|
|
3002
|
-
const authorization = row.authorization_json !== null ? JSON.parse(row.authorization_json) : void 0;
|
|
3003
|
-
return {
|
|
3004
|
-
id: row.id,
|
|
3005
|
-
requestId: row.request_id,
|
|
3006
|
-
resourceId: row.resource_id,
|
|
3007
|
-
deliveredAt: row.delivered_at,
|
|
3008
|
-
backendStatus: row.backend_status,
|
|
3009
|
-
...payment !== void 0 ? { payment } : {},
|
|
3010
|
-
...row.duration_ms !== null ? { durationMs: row.duration_ms } : {},
|
|
3011
|
-
...row.protocol !== null ? { protocol: row.protocol } : {},
|
|
3012
|
-
...metadata !== void 0 ? { metadata } : {},
|
|
3013
|
-
...authorization !== void 0 ? { authorization } : {}
|
|
3014
|
-
};
|
|
3015
|
-
}
|
|
3016
|
-
function eventToRow(event) {
|
|
3017
|
-
return {
|
|
3018
|
-
id: event.id,
|
|
3019
|
-
type: event.type,
|
|
3020
|
-
request_id: event.requestId,
|
|
3021
|
-
resource_id: event.resourceId ?? null,
|
|
3022
|
-
at: event.at,
|
|
3023
|
-
adapter: event.adapter ?? null,
|
|
3024
|
-
payment_provider: event.paymentProvider ?? null,
|
|
3025
|
-
duration_ms: event.durationMs ?? null,
|
|
3026
|
-
status: event.status ?? null,
|
|
3027
|
-
data_json: event.data !== void 0 ? JSON.stringify(redact(event.data)) : null
|
|
3028
|
-
};
|
|
3029
|
-
}
|
|
3030
|
-
function rowToEvent(row) {
|
|
3031
|
-
const data = row.data_json !== null ? JSON.parse(row.data_json) : void 0;
|
|
3032
|
-
return {
|
|
3033
|
-
id: row.id,
|
|
3034
|
-
type: row.type,
|
|
3035
|
-
requestId: row.request_id,
|
|
3036
|
-
at: row.at,
|
|
3037
|
-
...row.resource_id !== null ? { resourceId: row.resource_id } : {},
|
|
3038
|
-
...row.adapter !== null ? { adapter: row.adapter } : {},
|
|
3039
|
-
...row.payment_provider !== null ? { paymentProvider: row.payment_provider } : {},
|
|
3040
|
-
...row.duration_ms !== null ? { durationMs: row.duration_ms } : {},
|
|
3041
|
-
...row.status !== null ? { status: row.status } : {},
|
|
3042
|
-
...data !== void 0 ? { data } : {}
|
|
3043
|
-
};
|
|
3044
|
-
}
|
|
3045
|
-
function rowToPaymentAttempt(row) {
|
|
3046
|
-
return {
|
|
3047
|
-
id: row.id,
|
|
3048
|
-
requestId: row.request_id,
|
|
3049
|
-
resourceId: row.resource_id,
|
|
3050
|
-
provider: row.provider,
|
|
3051
|
-
replayKey: row.replay_key,
|
|
3052
|
-
status: row.status,
|
|
3053
|
-
amount: row.amount,
|
|
3054
|
-
currency: row.currency,
|
|
3055
|
-
...row.payer !== null ? { payer: row.payer } : {},
|
|
3056
|
-
...row.payee !== null ? { payee: row.payee } : {},
|
|
3057
|
-
...row.external_reference !== null ? { externalReference: row.external_reference } : {},
|
|
3058
|
-
...row.rejection_reason !== null ? { rejectionReason: row.rejection_reason } : {},
|
|
3059
|
-
createdAt: row.created_at,
|
|
3060
|
-
updatedAt: row.updated_at
|
|
3061
|
-
};
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
// src/storage/receipts/schema.ts
|
|
3065
|
-
var MIGRATIONS = [
|
|
3066
|
-
{
|
|
3067
|
-
version: 1,
|
|
3068
|
-
up(db) {
|
|
3069
|
-
db.exec(`
|
|
3070
|
-
CREATE TABLE IF NOT EXISTS receipts (
|
|
3071
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3072
|
-
id TEXT NOT NULL UNIQUE,
|
|
3073
|
-
request_id TEXT NOT NULL,
|
|
3074
|
-
resource_id TEXT NOT NULL,
|
|
3075
|
-
payment_json TEXT,
|
|
3076
|
-
delivered_at TEXT NOT NULL,
|
|
3077
|
-
backend_status INTEGER NOT NULL,
|
|
3078
|
-
duration_ms INTEGER,
|
|
3079
|
-
protocol TEXT,
|
|
3080
|
-
metadata_json TEXT
|
|
3081
|
-
);
|
|
3082
|
-
CREATE INDEX IF NOT EXISTS idx_receipts_request_id ON receipts(request_id);
|
|
3083
|
-
CREATE INDEX IF NOT EXISTS idx_receipts_delivered_at ON receipts(delivered_at);
|
|
3084
|
-
|
|
3085
|
-
CREATE TABLE IF NOT EXISTS events (
|
|
3086
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3087
|
-
id TEXT NOT NULL UNIQUE,
|
|
3088
|
-
type TEXT NOT NULL,
|
|
3089
|
-
request_id TEXT NOT NULL,
|
|
3090
|
-
resource_id TEXT,
|
|
3091
|
-
at TEXT NOT NULL,
|
|
3092
|
-
adapter TEXT,
|
|
3093
|
-
payment_provider TEXT,
|
|
3094
|
-
duration_ms INTEGER,
|
|
3095
|
-
status TEXT,
|
|
3096
|
-
data_json TEXT
|
|
3097
|
-
);
|
|
3098
|
-
CREATE INDEX IF NOT EXISTS idx_events_request_id ON events(request_id);
|
|
3099
|
-
CREATE INDEX IF NOT EXISTS idx_events_at ON events(at);
|
|
3100
|
-
|
|
3101
|
-
CREATE TABLE IF NOT EXISTS payment_attempts (
|
|
3102
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3103
|
-
id TEXT NOT NULL UNIQUE,
|
|
3104
|
-
request_id TEXT NOT NULL,
|
|
3105
|
-
resource_id TEXT NOT NULL,
|
|
3106
|
-
provider TEXT NOT NULL,
|
|
3107
|
-
replay_key TEXT NOT NULL UNIQUE,
|
|
3108
|
-
status TEXT NOT NULL,
|
|
3109
|
-
amount TEXT NOT NULL,
|
|
3110
|
-
currency TEXT NOT NULL,
|
|
3111
|
-
payer TEXT,
|
|
3112
|
-
payee TEXT,
|
|
3113
|
-
external_reference TEXT,
|
|
3114
|
-
rejection_reason TEXT,
|
|
3115
|
-
created_at TEXT NOT NULL,
|
|
3116
|
-
updated_at TEXT NOT NULL
|
|
3117
|
-
);
|
|
3118
|
-
CREATE INDEX IF NOT EXISTS idx_payment_attempts_request_id ON payment_attempts(request_id);
|
|
3119
|
-
CREATE INDEX IF NOT EXISTS idx_payment_attempts_created_at ON payment_attempts(created_at);
|
|
3120
|
-
`);
|
|
3121
|
-
}
|
|
3122
|
-
},
|
|
3123
|
-
{
|
|
3124
|
-
version: 2,
|
|
3125
|
-
up(db) {
|
|
3126
|
-
db.exec(`ALTER TABLE receipts ADD COLUMN authorization_json TEXT;`);
|
|
3127
|
-
}
|
|
3128
|
-
},
|
|
3129
|
-
{
|
|
3130
|
-
version: 3,
|
|
3131
|
-
up(db) {
|
|
3132
|
-
db.exec(`
|
|
3133
|
-
CREATE TABLE IF NOT EXISTS payment_attempt_history (
|
|
3134
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3135
|
-
id TEXT NOT NULL,
|
|
3136
|
-
request_id TEXT NOT NULL,
|
|
3137
|
-
resource_id TEXT NOT NULL,
|
|
3138
|
-
provider TEXT NOT NULL,
|
|
3139
|
-
replay_key TEXT NOT NULL,
|
|
3140
|
-
status TEXT NOT NULL,
|
|
3141
|
-
amount TEXT NOT NULL,
|
|
3142
|
-
currency TEXT NOT NULL,
|
|
3143
|
-
payer TEXT,
|
|
3144
|
-
payee TEXT,
|
|
3145
|
-
external_reference TEXT,
|
|
3146
|
-
rejection_reason TEXT,
|
|
3147
|
-
created_at TEXT NOT NULL,
|
|
3148
|
-
updated_at TEXT NOT NULL,
|
|
3149
|
-
superseded_at TEXT NOT NULL
|
|
3150
|
-
);
|
|
3151
|
-
CREATE INDEX IF NOT EXISTS idx_payment_attempt_history_replay_key ON payment_attempt_history(replay_key);
|
|
3152
|
-
CREATE INDEX IF NOT EXISTS idx_payment_attempt_history_request_id ON payment_attempt_history(request_id);
|
|
3153
|
-
`);
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
];
|
|
3157
|
-
var SCHEMA_VERSION = MIGRATIONS.at(-1)?.version ?? 0;
|
|
3158
|
-
function migrate(db) {
|
|
3159
|
-
const current = db.pragma("user_version", { simple: true });
|
|
3160
|
-
for (const migration of MIGRATIONS) {
|
|
3161
|
-
if (migration.version <= current) continue;
|
|
3162
|
-
const apply = db.transaction(() => {
|
|
3163
|
-
migration.up(db);
|
|
3164
|
-
db.pragma(`user_version = ${migration.version}`);
|
|
3165
|
-
});
|
|
3166
|
-
apply();
|
|
3167
|
-
}
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
// src/storage/receipts/store.ts
|
|
3171
|
-
var DEFAULT_LIST_LIMIT = 50;
|
|
3172
|
-
var MAX_LIST_LIMIT = 500;
|
|
3173
|
-
function clampListLimit(limit) {
|
|
3174
|
-
const base = limit !== void 0 && Number.isFinite(limit) ? limit : DEFAULT_LIST_LIMIT;
|
|
3175
|
-
return Math.min(Math.max(Math.trunc(base), 1), MAX_LIST_LIMIT);
|
|
3176
|
-
}
|
|
3177
|
-
var defaultIds = {
|
|
3178
|
-
next: (prefix) => prefix !== void 0 ? `${prefix}_${randomUUID()}` : randomUUID()
|
|
3179
|
-
};
|
|
3180
|
-
function isUniqueConstraintOn(err, column) {
|
|
3181
|
-
return err instanceof Error && "code" in err && err.code === "SQLITE_CONSTRAINT_UNIQUE" && err.message.includes(column);
|
|
3182
|
-
}
|
|
3183
|
-
function createSqliteReceiptStore(options) {
|
|
3184
|
-
const logger = options.logger ?? NOOP_LOGGER;
|
|
3185
|
-
const clock = options.clock ?? systemClock;
|
|
3186
|
-
const ids = options.ids ?? defaultIds;
|
|
3187
|
-
const path2 = options.path;
|
|
3188
|
-
const isFileBacked = path2 !== ":memory:";
|
|
3189
|
-
const db = openSqliteDatabase({ path: path2, label: "Receipt database", logger });
|
|
3190
|
-
migrate(db);
|
|
3191
|
-
const insertReceiptStmt = db.prepare(
|
|
3192
|
-
`INSERT INTO receipts (id, request_id, resource_id, payment_json, delivered_at, backend_status, duration_ms, protocol, metadata_json, authorization_json)
|
|
3193
|
-
VALUES (@id, @request_id, @resource_id, @payment_json, @delivered_at, @backend_status, @duration_ms, @protocol, @metadata_json, @authorization_json)`
|
|
3194
|
-
);
|
|
3195
|
-
const getReceiptStmt = db.prepare("SELECT * FROM receipts WHERE id = ?");
|
|
3196
|
-
const listReceiptsStmt = db.prepare(
|
|
3197
|
-
"SELECT * FROM receipts ORDER BY delivered_at DESC, seq DESC LIMIT ?"
|
|
3198
|
-
);
|
|
3199
|
-
const listReceiptsByRequestStmt = db.prepare(
|
|
3200
|
-
"SELECT * FROM receipts WHERE request_id = ? ORDER BY delivered_at DESC, seq DESC LIMIT ?"
|
|
3201
|
-
);
|
|
3202
|
-
const countReceiptsStmt = db.prepare(
|
|
3203
|
-
"SELECT COUNT(*) AS count FROM receipts"
|
|
3204
|
-
);
|
|
3205
|
-
const countUndeliveredReceiptsStmt = db.prepare(
|
|
3206
|
-
"SELECT COUNT(*) AS count FROM receipts WHERE backend_status NOT BETWEEN 200 AND 299"
|
|
3207
|
-
);
|
|
3208
|
-
const insertEventStmt = db.prepare(
|
|
3209
|
-
`INSERT INTO events (id, type, request_id, resource_id, at, adapter, payment_provider, duration_ms, status, data_json)
|
|
3210
|
-
VALUES (@id, @type, @request_id, @resource_id, @at, @adapter, @payment_provider, @duration_ms, @status, @data_json)`
|
|
3211
|
-
);
|
|
3212
|
-
const listEventsStmt = db.prepare(
|
|
3213
|
-
"SELECT * FROM events ORDER BY at DESC, seq DESC LIMIT ?"
|
|
3214
|
-
);
|
|
3215
|
-
const listEventsByRequestStmt = db.prepare(
|
|
3216
|
-
"SELECT * FROM events WHERE request_id = ? ORDER BY at DESC, seq DESC LIMIT ?"
|
|
3217
|
-
);
|
|
3218
|
-
const insertPaymentAttemptStmt = db.prepare(
|
|
3219
|
-
`INSERT INTO payment_attempts (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at)
|
|
3220
|
-
VALUES (@id, @request_id, @resource_id, @provider, @replay_key, @status, @amount, @currency, @payer, @payee, @external_reference, @rejection_reason, @created_at, @updated_at)
|
|
3221
|
-
RETURNING *`
|
|
3222
|
-
);
|
|
3223
|
-
const archiveRejectedAttemptStmt = db.prepare(
|
|
3224
|
-
`INSERT INTO payment_attempt_history (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, superseded_at)
|
|
3225
|
-
SELECT id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, @created_at
|
|
3226
|
-
FROM payment_attempts
|
|
3227
|
-
WHERE replay_key = @replay_key AND provider = @provider AND status = 'rejected'`
|
|
3228
|
-
);
|
|
3229
|
-
const reclaimPaymentAttemptStmt = db.prepare(
|
|
3230
|
-
`UPDATE payment_attempts SET id = @id, request_id = @request_id, resource_id = @resource_id,
|
|
3231
|
-
status = 'reserved', amount = @amount, currency = @currency,
|
|
3232
|
-
payer = @payer, payee = @payee, external_reference = NULL, rejection_reason = NULL,
|
|
3233
|
-
created_at = @created_at, updated_at = @updated_at
|
|
3234
|
-
WHERE replay_key = @replay_key AND provider = @provider AND status = 'rejected'
|
|
3235
|
-
RETURNING *`
|
|
3236
|
-
);
|
|
3237
|
-
const updatePaymentAttemptStmt = db.prepare(
|
|
3238
|
-
`UPDATE payment_attempts SET status = @status,
|
|
3239
|
-
external_reference = COALESCE(@external_reference, external_reference),
|
|
3240
|
-
rejection_reason = COALESCE(@rejection_reason, rejection_reason),
|
|
3241
|
-
updated_at = @updated_at
|
|
3242
|
-
WHERE replay_key = @replay_key`
|
|
3243
|
-
);
|
|
3244
|
-
const listPaymentAttemptsStmt = db.prepare(
|
|
3245
|
-
"SELECT * FROM payment_attempts ORDER BY created_at DESC, seq DESC LIMIT ?"
|
|
3246
|
-
);
|
|
3247
|
-
const listPaymentAttemptsByRequestStmt = db.prepare(
|
|
3248
|
-
"SELECT * FROM payment_attempts WHERE request_id = ? ORDER BY created_at DESC, seq DESC LIMIT ?"
|
|
3249
|
-
);
|
|
3250
|
-
const paymentAttemptStatusStmt = db.prepare(
|
|
3251
|
-
"SELECT status FROM payment_attempts WHERE replay_key = ?"
|
|
3252
|
-
);
|
|
3253
|
-
const reclaimRejectedAttempt = db.transaction(
|
|
3254
|
-
(params) => {
|
|
3255
|
-
archiveRejectedAttemptStmt.run(params);
|
|
3256
|
-
return reclaimPaymentAttemptStmt.get(params);
|
|
3257
|
-
}
|
|
3258
|
-
);
|
|
3259
|
-
let closed = false;
|
|
3260
|
-
const descriptor = {
|
|
3261
|
-
name: "sqlite-receipt-store",
|
|
3262
|
-
kind: "storage",
|
|
3263
|
-
implementationVersion: PACKAGE_VERSION,
|
|
3264
|
-
supportedSpec: `sqlite-schema-v${SCHEMA_VERSION}`,
|
|
3265
|
-
capabilities: ["receipts", "events", "payment-attempts"],
|
|
3266
|
-
status: "stable"
|
|
3267
|
-
};
|
|
3268
|
-
const store = {
|
|
3269
|
-
async init() {
|
|
3270
|
-
const version = db.pragma("user_version", { simple: true });
|
|
3271
|
-
if (version !== SCHEMA_VERSION) {
|
|
3272
|
-
throw new CommerceError(
|
|
3273
|
-
"STORAGE_ERROR",
|
|
3274
|
-
`Receipt store schema version mismatch (found ${version}, expected ${SCHEMA_VERSION})`
|
|
3275
|
-
);
|
|
3276
|
-
}
|
|
3277
|
-
},
|
|
3278
|
-
async appendEvent(event) {
|
|
3279
|
-
try {
|
|
3280
|
-
insertEventStmt.run(eventToRow(event));
|
|
3281
|
-
} catch (err) {
|
|
3282
|
-
logger.warn(
|
|
3283
|
-
{
|
|
3284
|
-
err: err instanceof Error ? err.message : String(err),
|
|
3285
|
-
eventId: event.id,
|
|
3286
|
-
requestId: event.requestId
|
|
3287
|
-
},
|
|
3288
|
-
"receipt-store: failed to persist commerce event"
|
|
3289
|
-
);
|
|
3290
|
-
}
|
|
3291
|
-
},
|
|
3292
|
-
async reservePaymentAttempt(reservation) {
|
|
3293
|
-
const now = clock.nowIso();
|
|
3294
|
-
const params = {
|
|
3295
|
-
id: ids.next("attempt"),
|
|
3296
|
-
request_id: reservation.requestId,
|
|
3297
|
-
resource_id: reservation.resourceId,
|
|
3298
|
-
provider: reservation.provider,
|
|
3299
|
-
replay_key: reservation.replayKey,
|
|
3300
|
-
amount: reservation.amount,
|
|
3301
|
-
currency: reservation.currency,
|
|
3302
|
-
payer: reservation.payer ?? null,
|
|
3303
|
-
payee: reservation.payee ?? null,
|
|
3304
|
-
created_at: now,
|
|
3305
|
-
updated_at: now
|
|
3306
|
-
};
|
|
3307
|
-
let row;
|
|
3308
|
-
try {
|
|
3309
|
-
row = (reservation.reclaimRejected === true ? reclaimRejectedAttempt.immediate(params) : void 0) ?? // RETURNING on a successful insert always yields the inserted row
|
|
3310
|
-
insertPaymentAttemptStmt.get({
|
|
3311
|
-
...params,
|
|
3312
|
-
status: "reserved",
|
|
3313
|
-
external_reference: null,
|
|
3314
|
-
rejection_reason: null
|
|
3315
|
-
});
|
|
3316
|
-
} catch (err) {
|
|
3317
|
-
if (isUniqueConstraintOn(err, "payment_attempts.replay_key")) {
|
|
3318
|
-
const existing = paymentAttemptStatusStmt.get(reservation.replayKey);
|
|
3319
|
-
throw new CommerceError(
|
|
3320
|
-
"PAYMENT_REPLAYED",
|
|
3321
|
-
`Payment authorization has already been used (replayKey=${reservation.replayKey})`,
|
|
3322
|
-
{
|
|
3323
|
-
requestId: reservation.requestId,
|
|
3324
|
-
resourceId: reservation.resourceId,
|
|
3325
|
-
...existing !== void 0 ? { details: { attemptStatus: existing.status } } : {}
|
|
3326
|
-
}
|
|
3327
|
-
);
|
|
3328
|
-
}
|
|
3329
|
-
throw toCommerceError(err, "STORAGE_ERROR", "Failed to reserve payment attempt");
|
|
3330
|
-
}
|
|
3331
|
-
return rowToPaymentAttempt(row);
|
|
3332
|
-
},
|
|
3333
|
-
async updatePaymentAttempt(update) {
|
|
3334
|
-
try {
|
|
3335
|
-
const result = updatePaymentAttemptStmt.run({
|
|
3336
|
-
replay_key: update.replayKey,
|
|
3337
|
-
status: update.status,
|
|
3338
|
-
external_reference: update.externalReference ?? null,
|
|
3339
|
-
rejection_reason: update.rejectionReason ?? null,
|
|
3340
|
-
updated_at: clock.nowIso()
|
|
3341
|
-
});
|
|
3342
|
-
if (result.changes === 0) {
|
|
3343
|
-
throw new CommerceError(
|
|
3344
|
-
"STORAGE_ERROR",
|
|
3345
|
-
`No payment attempt found for replayKey=${update.replayKey}`
|
|
3346
|
-
);
|
|
3347
|
-
}
|
|
3348
|
-
} catch (err) {
|
|
3349
|
-
throw toCommerceError(err, "STORAGE_ERROR", "Failed to update payment attempt");
|
|
3350
|
-
}
|
|
3351
|
-
},
|
|
3352
|
-
async saveReceipt(receipt) {
|
|
3353
|
-
try {
|
|
3354
|
-
insertReceiptStmt.run(receiptToRow(receipt));
|
|
3355
|
-
} catch (err) {
|
|
3356
|
-
throw toCommerceError(err, "STORAGE_ERROR", "Failed to save receipt");
|
|
3357
|
-
}
|
|
3358
|
-
},
|
|
3359
|
-
async getReceipt(id) {
|
|
3360
|
-
const row = getReceiptStmt.get(id);
|
|
3361
|
-
return row !== void 0 ? rowToReceipt(row) : void 0;
|
|
3362
|
-
},
|
|
3363
|
-
async listReceipts(listOptions = {}) {
|
|
3364
|
-
const limit = clampListLimit(listOptions.limit);
|
|
3365
|
-
const rows = listOptions.requestId !== void 0 ? listReceiptsByRequestStmt.all(listOptions.requestId, limit) : listReceiptsStmt.all(limit);
|
|
3366
|
-
return rows.map(rowToReceipt);
|
|
3367
|
-
},
|
|
3368
|
-
// Exact total, not bounded by the listReceipts clamp (see ReceiptStore.countReceipts)
|
|
3369
|
-
async countReceipts() {
|
|
3370
|
-
return countReceiptsStmt.get()?.count ?? 0;
|
|
3371
|
-
},
|
|
3372
|
-
// Exact count of non-2xx receipts (see ReceiptStore.countUndeliveredReceipts)
|
|
3373
|
-
async countUndeliveredReceipts() {
|
|
3374
|
-
return countUndeliveredReceiptsStmt.get()?.count ?? 0;
|
|
3375
|
-
},
|
|
3376
|
-
async listEvents(listOptions = {}) {
|
|
3377
|
-
const limit = clampListLimit(listOptions.limit);
|
|
3378
|
-
const rows = listOptions.requestId !== void 0 ? listEventsByRequestStmt.all(listOptions.requestId, limit) : listEventsStmt.all(limit);
|
|
3379
|
-
return rows.map(rowToEvent);
|
|
3380
|
-
},
|
|
3381
|
-
async listPaymentAttempts(listOptions = {}) {
|
|
3382
|
-
const limit = clampListLimit(listOptions.limit);
|
|
3383
|
-
const rows = listOptions.requestId !== void 0 ? listPaymentAttemptsByRequestStmt.all(listOptions.requestId, limit) : listPaymentAttemptsStmt.all(limit);
|
|
3384
|
-
return rows.map(rowToPaymentAttempt);
|
|
3385
|
-
},
|
|
3386
|
-
descriptor,
|
|
3387
|
-
async health() {
|
|
3388
|
-
const startedAt = clock.monotonicMs();
|
|
3389
|
-
if (closed) {
|
|
3390
|
-
return { status: "fail", detail: "store-unavailable", checkedAt: clock.nowIso() };
|
|
3391
|
-
}
|
|
3392
|
-
if (isFileBacked) {
|
|
3393
|
-
try {
|
|
3394
|
-
accessSync(path2, constants.W_OK);
|
|
3395
|
-
} catch (err) {
|
|
3396
|
-
logger.warn(
|
|
3397
|
-
{ err: err instanceof Error ? err.message : String(err) },
|
|
3398
|
-
"receipt-store: health check failed (store not writable)"
|
|
3399
|
-
);
|
|
3400
|
-
return {
|
|
3401
|
-
status: "fail",
|
|
3402
|
-
detail: "store-unwritable",
|
|
3403
|
-
checkedAt: clock.nowIso(),
|
|
3404
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
3405
|
-
};
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
try {
|
|
3409
|
-
const version = db.pragma("user_version", { simple: true });
|
|
3410
|
-
if (version !== SCHEMA_VERSION) {
|
|
3411
|
-
logger.warn(
|
|
3412
|
-
{ found: version, expected: SCHEMA_VERSION },
|
|
3413
|
-
"receipt-store: health check failed (schema version mismatch)"
|
|
3414
|
-
);
|
|
3415
|
-
return {
|
|
3416
|
-
status: "fail",
|
|
3417
|
-
detail: "store-schema-mismatch",
|
|
3418
|
-
checkedAt: clock.nowIso(),
|
|
3419
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
3420
|
-
};
|
|
3421
|
-
}
|
|
3422
|
-
return {
|
|
3423
|
-
status: "pass",
|
|
3424
|
-
detail: `sqlite schema v${SCHEMA_VERSION} writable`,
|
|
3425
|
-
checkedAt: clock.nowIso(),
|
|
3426
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
3427
|
-
};
|
|
3428
|
-
} catch (err) {
|
|
3429
|
-
logger.warn(
|
|
3430
|
-
{ err: err instanceof Error ? err.message : String(err) },
|
|
3431
|
-
"receipt-store: health check failed (store unavailable)"
|
|
3432
|
-
);
|
|
3433
|
-
return {
|
|
3434
|
-
status: "fail",
|
|
3435
|
-
detail: "store-unavailable",
|
|
3436
|
-
checkedAt: clock.nowIso(),
|
|
3437
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
3438
|
-
};
|
|
3439
|
-
}
|
|
3440
|
-
},
|
|
3441
|
-
async close() {
|
|
3442
|
-
if (!closed) {
|
|
3443
|
-
closed = true;
|
|
3444
|
-
db.close();
|
|
3445
|
-
}
|
|
3446
|
-
}
|
|
3447
|
-
};
|
|
3448
|
-
return store;
|
|
3449
|
-
}
|
|
3450
|
-
|
|
3451
78
|
// src/cli/lib/config-client.ts
|
|
3452
|
-
init_core();
|
|
3453
79
|
async function importConfig() {
|
|
3454
80
|
try {
|
|
3455
|
-
return await
|
|
81
|
+
return await import('./config-VYAJGZXE.js');
|
|
3456
82
|
} catch (err) {
|
|
3457
83
|
throw new CommerceError(
|
|
3458
84
|
"INTERNAL_ERROR",
|
|
@@ -3486,9 +112,6 @@ async function fetchJson(fetchImpl, url, timeoutMs = 1500) {
|
|
|
3486
112
|
};
|
|
3487
113
|
}
|
|
3488
114
|
}
|
|
3489
|
-
|
|
3490
|
-
// src/payments/x402/local-chain/manifest.ts
|
|
3491
|
-
init_core();
|
|
3492
115
|
var LOCAL_CHAIN_MANIFEST_PATH = ".deploy/local.json";
|
|
3493
116
|
var nonEmpty = z.string().min(1, "must be a non-empty string");
|
|
3494
117
|
var keyedAccount = z.object({ address: z.string(), privateKey: z.string(), note: z.string() });
|
|
@@ -3506,18 +129,18 @@ var MANIFEST_SCHEMA = z.object({
|
|
|
3506
129
|
buyerInitialBalance: nonEmpty
|
|
3507
130
|
});
|
|
3508
131
|
function readLocalChainManifest(cwd = process.cwd()) {
|
|
3509
|
-
const
|
|
3510
|
-
if (!existsSync(
|
|
132
|
+
const path = join(cwd, LOCAL_CHAIN_MANIFEST_PATH);
|
|
133
|
+
if (!existsSync(path)) {
|
|
3511
134
|
throw new CommerceError(
|
|
3512
135
|
"CONFIG_INVALID",
|
|
3513
|
-
`Local chain manifest not found at "${
|
|
136
|
+
`Local chain manifest not found at "${path}". Run "npm run chain:start" then "npm run chain:deploy" first.`
|
|
3514
137
|
);
|
|
3515
138
|
}
|
|
3516
139
|
let raw;
|
|
3517
140
|
try {
|
|
3518
|
-
raw = readFileSync(
|
|
141
|
+
raw = readFileSync(path, "utf8");
|
|
3519
142
|
} catch (cause) {
|
|
3520
|
-
throw new CommerceError("CONFIG_INVALID", `Could not read local chain manifest at "${
|
|
143
|
+
throw new CommerceError("CONFIG_INVALID", `Could not read local chain manifest at "${path}".`, {
|
|
3521
144
|
cause
|
|
3522
145
|
});
|
|
3523
146
|
}
|
|
@@ -3527,7 +150,7 @@ function readLocalChainManifest(cwd = process.cwd()) {
|
|
|
3527
150
|
} catch (cause) {
|
|
3528
151
|
throw new CommerceError(
|
|
3529
152
|
"CONFIG_INVALID",
|
|
3530
|
-
`Local chain manifest at "${
|
|
153
|
+
`Local chain manifest at "${path}" is not valid JSON. Re-run "npm run chain:deploy" to regenerate it.`,
|
|
3531
154
|
{ cause }
|
|
3532
155
|
);
|
|
3533
156
|
}
|
|
@@ -3537,7 +160,7 @@ function readLocalChainManifest(cwd = process.cwd()) {
|
|
|
3537
160
|
const where = issue?.path.length ? `${issue.path.join(".")}: ` : "";
|
|
3538
161
|
throw new CommerceError(
|
|
3539
162
|
"CONFIG_INVALID",
|
|
3540
|
-
`Local chain manifest at "${
|
|
163
|
+
`Local chain manifest at "${path}" is invalid: ${where}${issue?.message ?? "unexpected shape"}. Re-run "npm run chain:deploy".`
|
|
3541
164
|
);
|
|
3542
165
|
}
|
|
3543
166
|
return result.data;
|
|
@@ -3572,6 +195,20 @@ function fillEnvFromLocalChainManifest(baseEnv, cwd = process.cwd()) {
|
|
|
3572
195
|
}
|
|
3573
196
|
return { env, filled, manifestFound: true };
|
|
3574
197
|
}
|
|
198
|
+
function hasChainDeployScript(cwd = process.cwd()) {
|
|
199
|
+
try {
|
|
200
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
201
|
+
return pkg.name === "@devlab.group/agent-commerce" && typeof pkg.scripts?.["chain:deploy"] === "string";
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function unresolvedVariableHint(variable, manifestFound, chainDeploy) {
|
|
207
|
+
if (chainDeploy && !manifestFound && MANIFEST_FILLABLE_ENV_VAR_NAMES.has(variable)) {
|
|
208
|
+
return `"npm run chain:deploy" writes ${LOCAL_CHAIN_MANIFEST_PATH}, which validate and doctor read to fill local X402_* and MERCHANT_WALLET placeholders`;
|
|
209
|
+
}
|
|
210
|
+
return `set ${variable} in the environment, or in a file passed with --env-file`;
|
|
211
|
+
}
|
|
3575
212
|
|
|
3576
213
|
// package.json
|
|
3577
214
|
var package_default = {
|
|
@@ -3636,62 +273,34 @@ function readVersionReport() {
|
|
|
3636
273
|
});
|
|
3637
274
|
return { cliVersion: PACKAGE_VERSION, pinned };
|
|
3638
275
|
}
|
|
3639
|
-
|
|
3640
|
-
// src/cli/commands/doctor/ap2.ts
|
|
3641
|
-
init_constants2();
|
|
3642
|
-
|
|
3643
|
-
// src/authorization/ap2/descriptor.ts
|
|
3644
|
-
init_constants2();
|
|
3645
|
-
var AP2_UNSUPPORTED = [
|
|
3646
|
-
// Mandate kinds. Open mandates are refused: see AP2_CHECKOUT_MANDATE_VCT
|
|
3647
|
-
"autonomous mode",
|
|
3648
|
-
"open checkout mandates (mandate.checkout.open.1)",
|
|
3649
|
-
"payment mandates (mandate.payment.1)",
|
|
3650
|
-
"spending constraint evaluation",
|
|
3651
|
-
"cnf-bound agent keys",
|
|
3652
|
-
// Delegation models other than a trusted agent provider
|
|
3653
|
-
"user credential delegation (OpenID4VP)",
|
|
3654
|
-
"delegation chains and key-binding JWTs",
|
|
3655
|
-
// Key handling. Every key is written into config by an operator
|
|
3656
|
-
"JWKS and any key discovery by URL (jku, x5u)",
|
|
3657
|
-
"issuer metadata fetching",
|
|
3658
|
-
"key rotation without a config change",
|
|
3659
|
-
// Algorithms
|
|
3660
|
-
"signature algorithms other than ES256",
|
|
3661
|
-
"digest algorithms other than sha-256",
|
|
3662
|
-
// Roles this gateway does not play
|
|
3663
|
-
"mandate issuance",
|
|
3664
|
-
"checkout receipts",
|
|
3665
|
-
"AP2 over the ACP checkout adapter"
|
|
3666
|
-
];
|
|
3667
|
-
function isWritableFile(path2) {
|
|
276
|
+
function isWritableFile(path) {
|
|
3668
277
|
try {
|
|
3669
|
-
accessSync(
|
|
278
|
+
accessSync(path, constants.W_OK);
|
|
3670
279
|
return true;
|
|
3671
280
|
} catch {
|
|
3672
281
|
return false;
|
|
3673
282
|
}
|
|
3674
283
|
}
|
|
3675
|
-
function storeFileCheck(name,
|
|
3676
|
-
if (
|
|
284
|
+
function storeFileCheck(name, path, risk, suffix = "") {
|
|
285
|
+
if (path === ":memory:") {
|
|
3677
286
|
return {
|
|
3678
287
|
name,
|
|
3679
288
|
status: "WARN",
|
|
3680
289
|
detail: `in-memory store: ${risk.inMemory}; use a file path in production${suffix}`
|
|
3681
290
|
};
|
|
3682
291
|
}
|
|
3683
|
-
if (existsSync(
|
|
3684
|
-
return isWritableFile(
|
|
292
|
+
if (existsSync(path)) {
|
|
293
|
+
return isWritableFile(path) ? { name, status: "PASS", detail: `writable at "${path}"${suffix}` } : {
|
|
3685
294
|
name,
|
|
3686
295
|
status: "FAIL",
|
|
3687
|
-
detail: `"${
|
|
296
|
+
detail: `"${path}" exists but is not writable by this user, so ${risk.unwritable}${suffix}`
|
|
3688
297
|
};
|
|
3689
298
|
}
|
|
3690
|
-
return { name, status: "WARN", detail: `${missingStoreDetail(
|
|
299
|
+
return { name, status: "WARN", detail: `${missingStoreDetail(path)}${suffix}` };
|
|
3691
300
|
}
|
|
3692
|
-
function missingStoreDetail(
|
|
3693
|
-
const directory = dirname(
|
|
3694
|
-
return directory !== "." && !existsSync(directory) ? `no store at "${
|
|
301
|
+
function missingStoreDetail(path) {
|
|
302
|
+
const directory = dirname(path);
|
|
303
|
+
return directory !== "." && !existsSync(directory) ? `no store at "${path}" and its directory does not exist. Both are created when the gateway starts, provided the path is writable` : `no store at "${path}" yet. It is created the first time the gateway starts`;
|
|
3695
304
|
}
|
|
3696
305
|
async function storageChecks(config, createStore) {
|
|
3697
306
|
const checks = [];
|
|
@@ -3795,17 +404,6 @@ function ap2Checks(config) {
|
|
|
3795
404
|
}
|
|
3796
405
|
return checks;
|
|
3797
406
|
}
|
|
3798
|
-
|
|
3799
|
-
// src/cli/commands/doctor/payments.ts
|
|
3800
|
-
init_is_record();
|
|
3801
|
-
init_constants3();
|
|
3802
|
-
init_addresses();
|
|
3803
|
-
init_networks();
|
|
3804
|
-
init_payment_flow();
|
|
3805
|
-
|
|
3806
|
-
// src/cli/lib/init-config.ts
|
|
3807
|
-
init_accounts();
|
|
3808
|
-
init_networks();
|
|
3809
407
|
var LOCAL_DEV_MERCHANT_ADDRESS = ANVIL_WELL_KNOWN_ACCOUNTS[1].address;
|
|
3810
408
|
var LOCAL_DEV_FACILITATOR_PRIVATE_KEY = ANVIL_WELL_KNOWN_ACCOUNTS[0].privateKey;
|
|
3811
409
|
var PLACEHOLDER_ASSET_ADDRESS = "0x000000000000000000000000000000000000dEaD";
|
|
@@ -4046,8 +644,7 @@ function findRailMismatch(configured, live, payToField = "payTo", customNetworks
|
|
|
4046
644
|
if (liveOverrides !== localOverrides)
|
|
4047
645
|
differ("resourcePaymentFlows", liveOverrides, localOverrides);
|
|
4048
646
|
}
|
|
4049
|
-
|
|
4050
|
-
return `${diffs.join("; ")}; the gateway may be running against an older deployment - restart it or re-run chain:deploy`;
|
|
647
|
+
return diffs.length === 0 ? void 0 : diffs.join("; ");
|
|
4051
648
|
}
|
|
4052
649
|
function describeSettlement(network, facilitator, customNetworks) {
|
|
4053
650
|
const profile = findX402Network(network, customNetworks);
|
|
@@ -4106,8 +703,9 @@ function offerHealthCheck(live, named) {
|
|
|
4106
703
|
detail: `${problems.join("; ")}. The gateway logs each reason at debug level`
|
|
4107
704
|
};
|
|
4108
705
|
}
|
|
4109
|
-
function paymentChecks(config, wellKnown, gatewayUrl) {
|
|
706
|
+
function paymentChecks(config, wellKnown, gatewayUrl, chainDeploy = false) {
|
|
4110
707
|
const checks = [];
|
|
708
|
+
const stale = `; the gateway may be running against an older deployment - restart it${chainDeploy ? " or re-run chain:deploy" : ""}`;
|
|
4111
709
|
const x402 = config?.payments.x402;
|
|
4112
710
|
if (x402 === void 0 || !x402.enabled) {
|
|
4113
711
|
checks.push({ name: "Payments", status: "INFO", detail: "x402 not configured" });
|
|
@@ -4128,7 +726,7 @@ function paymentChecks(config, wellKnown, gatewayUrl) {
|
|
|
4128
726
|
checks.push({
|
|
4129
727
|
name: "Payments",
|
|
4130
728
|
status: "WARN",
|
|
4131
|
-
detail: `${summary}, but
|
|
729
|
+
detail: `${summary}, but at least one offer uses the init placeholder ${maskMiddle(PLACEHOLDER_ASSET_ADDRESS)}. Set that offer's asset to its token contract address${chainDeploy ? ' (${X402_ASSET} is filled from .deploy/local.json, which "npm run chain:deploy" writes)' : ""}`
|
|
4132
730
|
});
|
|
4133
731
|
} else if (live === "disabled") {
|
|
4134
732
|
checks.push({
|
|
@@ -4155,7 +753,7 @@ function paymentChecks(config, wellKnown, gatewayUrl) {
|
|
|
4155
753
|
};
|
|
4156
754
|
const mismatch = findRailMismatch(configured, live, "payTo", x402.networks);
|
|
4157
755
|
checks.push(
|
|
4158
|
-
mismatch === void 0 ? { name: "Payments", status: "PASS", detail: summary } : { name: "Payments", status: "FAIL", detail: mismatch }
|
|
756
|
+
mismatch === void 0 ? { name: "Payments", status: "PASS", detail: summary } : { name: "Payments", status: "FAIL", detail: `${mismatch}${stale}` }
|
|
4159
757
|
);
|
|
4160
758
|
const offerHealth = offerHealthCheck(live, named);
|
|
4161
759
|
if (offerHealth !== void 0) checks.push(offerHealth);
|
|
@@ -4202,7 +800,7 @@ function paymentChecks(config, wellKnown, gatewayUrl) {
|
|
|
4202
800
|
} else {
|
|
4203
801
|
const mismatch = findRailMismatch(configured, live, "recipient");
|
|
4204
802
|
checks.push(
|
|
4205
|
-
mismatch === void 0 ? { name: "Payments (MPP)", status: "PASS", detail: summary } : { name: "Payments (MPP)", status: "FAIL", detail: mismatch }
|
|
803
|
+
mismatch === void 0 ? { name: "Payments (MPP)", status: "PASS", detail: summary } : { name: "Payments (MPP)", status: "FAIL", detail: `${mismatch}${stale}` }
|
|
4206
804
|
);
|
|
4207
805
|
}
|
|
4208
806
|
if (mode === "mainnet") {
|
|
@@ -4216,85 +814,6 @@ function paymentChecks(config, wellKnown, gatewayUrl) {
|
|
|
4216
814
|
return checks;
|
|
4217
815
|
}
|
|
4218
816
|
|
|
4219
|
-
// src/protocols/a2a/constants.ts
|
|
4220
|
-
var A2A_SPEC_VERSION = "1.0.1";
|
|
4221
|
-
var A2A_PROTOCOL_VERSION = "1.0";
|
|
4222
|
-
var A2A_PROTOCOL_BINDING = "JSONRPC";
|
|
4223
|
-
var A2A_AGENT_CARD_PATH = "/.well-known/agent-card.json";
|
|
4224
|
-
var A2A_PUSH_CONFIG_METHODS = [
|
|
4225
|
-
"CreateTaskPushNotificationConfig",
|
|
4226
|
-
"GetTaskPushNotificationConfig",
|
|
4227
|
-
"ListTaskPushNotificationConfigs",
|
|
4228
|
-
"DeleteTaskPushNotificationConfig"
|
|
4229
|
-
];
|
|
4230
|
-
var A2A_UNSUPPORTED_METHODS = [
|
|
4231
|
-
"SendStreamingMessage",
|
|
4232
|
-
"GetTask",
|
|
4233
|
-
"ListTasks",
|
|
4234
|
-
"CancelTask",
|
|
4235
|
-
"SubscribeToTask",
|
|
4236
|
-
...A2A_PUSH_CONFIG_METHODS,
|
|
4237
|
-
"GetExtendedAgentCard"
|
|
4238
|
-
];
|
|
4239
|
-
|
|
4240
|
-
// src/protocols/a2a/descriptor.ts
|
|
4241
|
-
var A2A_UNSUPPORTED = [
|
|
4242
|
-
// Methods, as the protocol names them; the transport refuses the same list
|
|
4243
|
-
...A2A_UNSUPPORTED_METHODS,
|
|
4244
|
-
// Transports other than the one binding served
|
|
4245
|
-
"HTTP+JSON/REST binding",
|
|
4246
|
-
"gRPC binding",
|
|
4247
|
-
// Behaviors
|
|
4248
|
-
"SSE",
|
|
4249
|
-
"long-running task persistence",
|
|
4250
|
-
"task resumption other than an x402 extension payment",
|
|
4251
|
-
"push notifications",
|
|
4252
|
-
"multi-turn conversational continuation",
|
|
4253
|
-
"authenticated extended agent cards",
|
|
4254
|
-
"A2A authentication schemes",
|
|
4255
|
-
"artifact types beyond Agent Commerce outcome data",
|
|
4256
|
-
// Pending x402 payments live in process memory
|
|
4257
|
-
"x402 extension payments across gateway restarts or instances"
|
|
4258
|
-
];
|
|
4259
|
-
|
|
4260
|
-
// src/cli/commands/doctor/protocols.ts
|
|
4261
|
-
init_constants();
|
|
4262
|
-
|
|
4263
|
-
// src/protocols/acp/descriptor.ts
|
|
4264
|
-
init_constants();
|
|
4265
|
-
[
|
|
4266
|
-
"rest transport",
|
|
4267
|
-
"well-known discovery",
|
|
4268
|
-
"bearer authentication",
|
|
4269
|
-
"API-Version enforcement",
|
|
4270
|
-
// Forwarded as received: the gateway verifies neither signature header
|
|
4271
|
-
"request header forwarding to the merchant",
|
|
4272
|
-
"checkout service",
|
|
4273
|
-
...ACP_CHECKOUT_OPERATIONS
|
|
4274
|
-
];
|
|
4275
|
-
var ACP_UNSUPPORTED = [
|
|
4276
|
-
// Services this seller does not implement, named as ACP names them. The
|
|
4277
|
-
// discovery document advertises `checkout` alone, and these are the reason.
|
|
4278
|
-
"carts service",
|
|
4279
|
-
"feed service",
|
|
4280
|
-
"standalone orders service",
|
|
4281
|
-
"delegate_payment",
|
|
4282
|
-
"delegate_authentication",
|
|
4283
|
-
// Bindings and delivery directions
|
|
4284
|
-
"ACP MCP transport binding",
|
|
4285
|
-
"webhooks",
|
|
4286
|
-
"outbound merchant-to-agent delivery",
|
|
4287
|
-
"ACP client role",
|
|
4288
|
-
// Request authentication beyond the bearer token
|
|
4289
|
-
"Signature header verification",
|
|
4290
|
-
"Timestamp replay-window verification",
|
|
4291
|
-
// Extensions, versioning and payment handlers
|
|
4292
|
-
"discount extension",
|
|
4293
|
-
"general ACP extension framework",
|
|
4294
|
-
"multiple ACP API versions",
|
|
4295
|
-
"seller-backed payment handler integration"
|
|
4296
|
-
];
|
|
4297
|
-
|
|
4298
817
|
// src/cli/commands/doctor/protocols.ts
|
|
4299
818
|
function acpMappingCheck(config, operations) {
|
|
4300
819
|
const byId = new Map(config.resources.map((resource) => [resource.id, resource]));
|
|
@@ -4434,15 +953,16 @@ function substitutePathParams(url) {
|
|
|
4434
953
|
}
|
|
4435
954
|
async function runDoctor(options, deps = {}) {
|
|
4436
955
|
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
4437
|
-
const
|
|
4438
|
-
const createStore = deps.createStore ?? ((
|
|
956
|
+
const loadConfig = deps.loadConfig ?? loadConfigDynamic;
|
|
957
|
+
const createStore = deps.createStore ?? ((path) => createSqliteReceiptStore({ path }));
|
|
4439
958
|
const fillEnvFromManifest = deps.fillEnvFromManifest ?? fillEnvFromLocalChainManifest;
|
|
959
|
+
const chainDeploy = (deps.hasChainDeployScript ?? hasChainDeployScript)();
|
|
4440
960
|
const checks = [];
|
|
4441
961
|
let config;
|
|
4442
962
|
const { env, filled, manifestFound } = fillEnvFromManifest(process.env);
|
|
4443
963
|
const filledSuffix = filled.length > 0 ? ` (using local chain manifest ${LOCAL_CHAIN_MANIFEST_PATH} for ${filled.join(", ")})` : "";
|
|
4444
964
|
try {
|
|
4445
|
-
config = await
|
|
965
|
+
config = await loadConfig({
|
|
4446
966
|
...options.configPath !== void 0 ? { path: options.configPath } : {},
|
|
4447
967
|
env
|
|
4448
968
|
});
|
|
@@ -4453,7 +973,7 @@ async function runDoctor(options, deps = {}) {
|
|
|
4453
973
|
});
|
|
4454
974
|
} catch (err) {
|
|
4455
975
|
const variable = isCommerceError(err) ? err.details?.["variable"] : void 0;
|
|
4456
|
-
const hint =
|
|
976
|
+
const hint = typeof variable === "string" ? `. Hint: ${unresolvedVariableHint(variable, manifestFound, chainDeploy)}` : "";
|
|
4457
977
|
checks.push({
|
|
4458
978
|
name: "Config",
|
|
4459
979
|
status: "FAIL",
|
|
@@ -4505,7 +1025,7 @@ async function runDoctor(options, deps = {}) {
|
|
|
4505
1025
|
) : void 0;
|
|
4506
1026
|
checks.push(...protocolChecks(config, wellKnown));
|
|
4507
1027
|
checks.push(...ap2Checks(config));
|
|
4508
|
-
checks.push(...paymentChecks(config, wellKnown, gatewayUrl));
|
|
1028
|
+
checks.push(...paymentChecks(config, wellKnown, gatewayUrl, chainDeploy));
|
|
4509
1029
|
checks.push(...await storageChecks(config, createStore));
|
|
4510
1030
|
if (wellKnown?.ok) {
|
|
4511
1031
|
checks.push({
|
|
@@ -4527,17 +1047,7 @@ async function runDoctor(options, deps = {}) {
|
|
|
4527
1047
|
return { checks, score: { passed, total: scored.length }, exitCode };
|
|
4528
1048
|
}
|
|
4529
1049
|
|
|
4530
|
-
// src/cli/commands/import-openapi.ts
|
|
4531
|
-
init_core();
|
|
4532
|
-
|
|
4533
|
-
// src/openapi/discover.ts
|
|
4534
|
-
init_errors();
|
|
4535
|
-
init_execution();
|
|
4536
|
-
init_is_record();
|
|
4537
|
-
init_constants4();
|
|
4538
|
-
|
|
4539
1050
|
// src/openapi/refs.ts
|
|
4540
|
-
init_errors();
|
|
4541
1051
|
var MAX_REF_DEPTH = 100;
|
|
4542
1052
|
function isRefNode(value) {
|
|
4543
1053
|
return typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
@@ -4637,14 +1147,14 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4637
1147
|
}
|
|
4638
1148
|
const rootServers = document["servers"];
|
|
4639
1149
|
const rootSecurity = document["security"];
|
|
4640
|
-
for (const [
|
|
4641
|
-
if (
|
|
4642
|
-
if (!
|
|
1150
|
+
for (const [path, rawPathItem] of Object.entries(paths)) {
|
|
1151
|
+
if (path.startsWith("x-")) continue;
|
|
1152
|
+
if (!path.startsWith("/")) {
|
|
4643
1153
|
diagnostics.push({
|
|
4644
1154
|
severity: "warning",
|
|
4645
1155
|
code: "invalid-path-key",
|
|
4646
|
-
operation:
|
|
4647
|
-
message: `Skipped "${
|
|
1156
|
+
operation: path,
|
|
1157
|
+
message: `Skipped "${path}": a Paths key must start with "/"`
|
|
4648
1158
|
});
|
|
4649
1159
|
continue;
|
|
4650
1160
|
}
|
|
@@ -4654,8 +1164,8 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4654
1164
|
diagnostics.push({
|
|
4655
1165
|
severity: "warning",
|
|
4656
1166
|
code: "unsupported-method",
|
|
4657
|
-
operation: `${name.toUpperCase()} ${
|
|
4658
|
-
message: `Skipped ${name.toUpperCase()} ${
|
|
1167
|
+
operation: `${name.toUpperCase()} ${path}`,
|
|
1168
|
+
message: `Skipped ${name.toUpperCase()} ${path}: only GET, POST, PUT, PATCH and DELETE are supported`
|
|
4659
1169
|
});
|
|
4660
1170
|
};
|
|
4661
1171
|
for (const [key, rawOperation] of Object.entries(pathItem)) {
|
|
@@ -4672,9 +1182,9 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4672
1182
|
}
|
|
4673
1183
|
const operation = dereference(document, rawOperation).value;
|
|
4674
1184
|
if (!isRecord(operation)) continue;
|
|
4675
|
-
const where = `${method} ${
|
|
1185
|
+
const where = `${method} ${path}`;
|
|
4676
1186
|
const operationId = typeof operation["operationId"] === "string" ? operation["operationId"] : void 0;
|
|
4677
|
-
const resourceId = toResourceId(operationId, method,
|
|
1187
|
+
const resourceId = toResourceId(operationId, method, path);
|
|
4678
1188
|
const previous = claimed.get(resourceId);
|
|
4679
1189
|
if (previous !== void 0) {
|
|
4680
1190
|
throw new CommerceError(
|
|
@@ -4692,7 +1202,7 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4692
1202
|
diagnostics
|
|
4693
1203
|
);
|
|
4694
1204
|
if (server === void 0) continue;
|
|
4695
|
-
const backendUrl = `${server.replace(/\/+$/, "")}${
|
|
1205
|
+
const backendUrl = `${server.replace(/\/+$/, "")}${path}`;
|
|
4696
1206
|
const stray = findUnparsedBraceToken(backendUrl);
|
|
4697
1207
|
if (stray !== void 0) {
|
|
4698
1208
|
diagnostics.push({
|
|
@@ -4709,7 +1219,7 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4709
1219
|
operations.push({
|
|
4710
1220
|
resourceId,
|
|
4711
1221
|
method,
|
|
4712
|
-
path
|
|
1222
|
+
path,
|
|
4713
1223
|
backendUrl,
|
|
4714
1224
|
...operationId !== void 0 ? { operationId } : {},
|
|
4715
1225
|
tags: toArray(operation["tags"]).filter((tag) => typeof tag === "string"),
|
|
@@ -4729,10 +1239,10 @@ function discoverOperations(loaded, options = {}) {
|
|
|
4729
1239
|
}
|
|
4730
1240
|
return { operations, diagnostics };
|
|
4731
1241
|
}
|
|
4732
|
-
function toResourceId(operationId, method,
|
|
1242
|
+
function toResourceId(operationId, method, path) {
|
|
4733
1243
|
const fromOperationId = operationId === void 0 ? "" : normalizeId(operationId);
|
|
4734
1244
|
if (fromOperationId !== "") return fromOperationId;
|
|
4735
|
-
return normalizeId(`${method.toLowerCase()}_${
|
|
1245
|
+
return normalizeId(`${method.toLowerCase()}_${path}`);
|
|
4736
1246
|
}
|
|
4737
1247
|
function normalizeId(value) {
|
|
4738
1248
|
return value.replace(ID_DISALLOWED, "_").replace(/_+/g, "_").replace(/^[_.-]+|[_.-]+$/g, "").slice(0, MCP_TOOL_NAME_MAX_LENGTH);
|
|
@@ -4836,17 +1346,6 @@ function assertAbsoluteHttpUrl(value, label) {
|
|
|
4836
1346
|
function toArray(value) {
|
|
4837
1347
|
return Array.isArray(value) ? value : [];
|
|
4838
1348
|
}
|
|
4839
|
-
|
|
4840
|
-
// src/openapi/draft.ts
|
|
4841
|
-
init_is_record();
|
|
4842
|
-
|
|
4843
|
-
// src/openapi/request.ts
|
|
4844
|
-
init_execution();
|
|
4845
|
-
init_is_record();
|
|
4846
|
-
|
|
4847
|
-
// src/openapi/schema.ts
|
|
4848
|
-
init_errors();
|
|
4849
|
-
init_is_record();
|
|
4850
1349
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
4851
1350
|
"object",
|
|
4852
1351
|
"string",
|
|
@@ -4869,7 +1368,7 @@ var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
4869
1368
|
"definitions"
|
|
4870
1369
|
]);
|
|
4871
1370
|
function isPrimitiveSchema(schema) {
|
|
4872
|
-
const types =
|
|
1371
|
+
const types = typeList(schema["type"]);
|
|
4873
1372
|
if (types === void 0) return false;
|
|
4874
1373
|
return types.every((type) => type !== "object" && type !== "array" && SUPPORTED_TYPES.has(type));
|
|
4875
1374
|
}
|
|
@@ -4953,7 +1452,7 @@ function isCarriedKeyword(keyword) {
|
|
|
4953
1452
|
return keyword === "type" || keyword === "properties" || keyword === "required" || keyword === "additionalProperties" || keyword === "enum" || keyword === "items" || keyword === "nullable" || keyword === "$ref" || METADATA_KEYWORDS.includes(keyword);
|
|
4954
1453
|
}
|
|
4955
1454
|
function resolveTypes(schema) {
|
|
4956
|
-
const declared =
|
|
1455
|
+
const declared = typeList(schema["type"]);
|
|
4957
1456
|
if (declared === void 0) return void 0;
|
|
4958
1457
|
const known = declared.filter((type) => SUPPORTED_TYPES.has(type));
|
|
4959
1458
|
if (known.length === 0) {
|
|
@@ -4962,7 +1461,7 @@ function resolveTypes(schema) {
|
|
|
4962
1461
|
if (schema["nullable"] === true && !known.includes("null")) known.push("null");
|
|
4963
1462
|
return known;
|
|
4964
1463
|
}
|
|
4965
|
-
function
|
|
1464
|
+
function typeList(raw) {
|
|
4966
1465
|
if (typeof raw === "string") return [raw];
|
|
4967
1466
|
if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string")) {
|
|
4968
1467
|
return raw;
|
|
@@ -4980,7 +1479,7 @@ function mergeAllOf(document, schema, stack, dropped) {
|
|
|
4980
1479
|
const parts = [...branches, ...Object.keys(siblings).length > 0 ? [siblings] : []];
|
|
4981
1480
|
for (const branch of parts) {
|
|
4982
1481
|
const converted = convertNode(document, branch, stack, dropped);
|
|
4983
|
-
const types =
|
|
1482
|
+
const types = typeList(converted["type"]);
|
|
4984
1483
|
if (types !== void 0 && !types.includes("object")) {
|
|
4985
1484
|
throw new UnsupportedSchema("allOf mixes object and non-object schemas");
|
|
4986
1485
|
}
|
|
@@ -5413,13 +1912,10 @@ function renderResourcesYaml(result) {
|
|
|
5413
1912
|
});
|
|
5414
1913
|
return doc.toString({ lineWidth: 0 });
|
|
5415
1914
|
}
|
|
5416
|
-
|
|
5417
|
-
// src/openapi/load.ts
|
|
5418
|
-
init_errors();
|
|
5419
1915
|
var MAX_SOURCE_BYTES = 10 * 1024 * 1024;
|
|
5420
1916
|
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
5421
1917
|
var SUPPORTED_VERSIONS = /* @__PURE__ */ new Set(["3.0", "3.1", "3.2"]);
|
|
5422
|
-
function
|
|
1918
|
+
function invalid(message, details, cause) {
|
|
5423
1919
|
return new CommerceError("CONFIG_INVALID", message, {
|
|
5424
1920
|
details,
|
|
5425
1921
|
...cause !== void 0 ? { cause } : {}
|
|
@@ -5440,10 +1936,10 @@ async function readCapped(sourcePath, details) {
|
|
|
5440
1936
|
await handle.close();
|
|
5441
1937
|
}
|
|
5442
1938
|
} catch (error) {
|
|
5443
|
-
throw
|
|
1939
|
+
throw invalid(`OpenAPI document "${sourcePath}" could not be read`, details, error);
|
|
5444
1940
|
}
|
|
5445
1941
|
if (length > MAX_SOURCE_BYTES) {
|
|
5446
|
-
throw
|
|
1942
|
+
throw invalid(`OpenAPI document "${sourcePath}" is over the ${MAX_SOURCE_BYTES}-byte limit`, {
|
|
5447
1943
|
...details,
|
|
5448
1944
|
maxBytes: MAX_SOURCE_BYTES
|
|
5449
1945
|
});
|
|
@@ -5456,40 +1952,40 @@ async function loadOpenApiDocument(sourcePath) {
|
|
|
5456
1952
|
try {
|
|
5457
1953
|
stats = await stat(sourcePath);
|
|
5458
1954
|
} catch (error) {
|
|
5459
|
-
throw
|
|
1955
|
+
throw invalid(`OpenAPI document "${sourcePath}" could not be read`, details, error);
|
|
5460
1956
|
}
|
|
5461
1957
|
if (stats.isDirectory()) {
|
|
5462
|
-
throw
|
|
1958
|
+
throw invalid(`"${sourcePath}" is a directory, not an OpenAPI document`, details);
|
|
5463
1959
|
}
|
|
5464
1960
|
if (stats.size > MAX_SOURCE_BYTES) {
|
|
5465
|
-
throw
|
|
1961
|
+
throw invalid(
|
|
5466
1962
|
`OpenAPI document "${sourcePath}" is ${stats.size} bytes, over the ${MAX_SOURCE_BYTES}-byte limit`,
|
|
5467
1963
|
{ ...details, size: stats.size, maxBytes: MAX_SOURCE_BYTES }
|
|
5468
1964
|
);
|
|
5469
1965
|
}
|
|
5470
1966
|
const extension = extname(sourcePath).toLowerCase();
|
|
5471
1967
|
if (!SUPPORTED_EXTENSIONS.has(extension)) {
|
|
5472
|
-
throw
|
|
1968
|
+
throw invalid(
|
|
5473
1969
|
`OpenAPI document "${sourcePath}" has an unsupported extension "${extension || "(none)"}". Supported: .yaml, .yml, .json`,
|
|
5474
1970
|
details
|
|
5475
1971
|
);
|
|
5476
1972
|
}
|
|
5477
1973
|
const source = await readCapped(sourcePath, details);
|
|
5478
1974
|
if (source.trim() === "") {
|
|
5479
|
-
throw
|
|
1975
|
+
throw invalid(`OpenAPI document "${sourcePath}" is empty`, details);
|
|
5480
1976
|
}
|
|
5481
1977
|
let parsed;
|
|
5482
1978
|
try {
|
|
5483
1979
|
parsed = extension === ".json" ? JSON.parse(source) : parse(source);
|
|
5484
1980
|
} catch (error) {
|
|
5485
|
-
throw
|
|
1981
|
+
throw invalid(
|
|
5486
1982
|
`OpenAPI document "${sourcePath}" is not valid ${extension === ".json" ? "JSON" : "YAML"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
5487
1983
|
details,
|
|
5488
1984
|
error
|
|
5489
1985
|
);
|
|
5490
1986
|
}
|
|
5491
1987
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5492
|
-
throw
|
|
1988
|
+
throw invalid(`OpenAPI document "${sourcePath}" is not an object`, details);
|
|
5493
1989
|
}
|
|
5494
1990
|
const document = parsed;
|
|
5495
1991
|
const version = readVersion(document, sourcePath);
|
|
@@ -5497,7 +1993,7 @@ async function loadOpenApiDocument(sourcePath) {
|
|
|
5497
1993
|
const result = await validate(document);
|
|
5498
1994
|
if (!result.valid) {
|
|
5499
1995
|
const first = result.errors?.[0];
|
|
5500
|
-
throw
|
|
1996
|
+
throw invalid(
|
|
5501
1997
|
`OpenAPI document "${sourcePath}" is not a valid OpenAPI ${version} description: ${first ? `${first.message}${first.path ? ` (at ${first.path})` : ""}` : "unknown validation error"}`,
|
|
5502
1998
|
{ ...details, errors: result.errors?.slice(0, 10) ?? [] }
|
|
5503
1999
|
);
|
|
@@ -5507,18 +2003,18 @@ async function loadOpenApiDocument(sourcePath) {
|
|
|
5507
2003
|
function readVersion(document, sourcePath) {
|
|
5508
2004
|
const details = { sourcePath };
|
|
5509
2005
|
if (typeof document["swagger"] === "string") {
|
|
5510
|
-
throw
|
|
2006
|
+
throw invalid(
|
|
5511
2007
|
`"${sourcePath}" is a Swagger ${document["swagger"]} document. Only OpenAPI 3.0, 3.1 and 3.2 are supported - convert it first`,
|
|
5512
2008
|
details
|
|
5513
2009
|
);
|
|
5514
2010
|
}
|
|
5515
2011
|
const declared = document["openapi"];
|
|
5516
2012
|
if (typeof declared !== "string") {
|
|
5517
|
-
throw
|
|
2013
|
+
throw invalid(`"${sourcePath}" has no "openapi" version field`, details);
|
|
5518
2014
|
}
|
|
5519
2015
|
const featureVersion = /^(\d+\.\d+)(?:\.|$)/.exec(declared)?.[1];
|
|
5520
2016
|
if (featureVersion === void 0 || !SUPPORTED_VERSIONS.has(featureVersion)) {
|
|
5521
|
-
throw
|
|
2017
|
+
throw invalid(
|
|
5522
2018
|
`OpenAPI version "${declared}" is not supported. Supported: 3.0.x, 3.1.x, 3.2.x`,
|
|
5523
2019
|
{ ...details, version: declared }
|
|
5524
2020
|
);
|
|
@@ -5540,13 +2036,13 @@ function rejectUnsupportedReferences(document, sourcePath) {
|
|
|
5540
2036
|
for (const [key, value] of Object.entries(node)) {
|
|
5541
2037
|
if (key === "$ref" && typeof value === "string") {
|
|
5542
2038
|
if (!value.startsWith("#")) {
|
|
5543
|
-
throw
|
|
2039
|
+
throw invalid(
|
|
5544
2040
|
`OpenAPI document "${sourcePath}" contains an external reference "${value}". Only internal references (#/...) are supported; the importer performs no network or filesystem lookups`,
|
|
5545
2041
|
{ sourcePath, ref: value }
|
|
5546
2042
|
);
|
|
5547
2043
|
}
|
|
5548
2044
|
if (!isPercentDecodable(value)) {
|
|
5549
|
-
throw
|
|
2045
|
+
throw invalid(
|
|
5550
2046
|
`OpenAPI document "${sourcePath}" contains a reference "${value}" with a malformed percent-escape`,
|
|
5551
2047
|
{ sourcePath, ref: value }
|
|
5552
2048
|
);
|
|
@@ -5717,23 +2213,16 @@ function describe(error) {
|
|
|
5717
2213
|
return error instanceof Error ? error.message : String(error);
|
|
5718
2214
|
}
|
|
5719
2215
|
|
|
5720
|
-
// src/cli/commands/init.ts
|
|
5721
|
-
init_filename();
|
|
5722
|
-
init_addresses();
|
|
5723
|
-
|
|
5724
2216
|
// src/cli/commands/validate.ts
|
|
5725
|
-
|
|
5726
|
-
function formatConfigError(err, manifestFound = false) {
|
|
2217
|
+
function formatConfigError(err, manifestFound = false, chainDeploy = hasChainDeployScript()) {
|
|
5727
2218
|
if (isCommerceError(err)) {
|
|
5728
2219
|
const lines = [`FAIL ${err.code}: ${err.message}`];
|
|
5729
2220
|
if (err.details !== void 0) {
|
|
5730
2221
|
lines.push(` details: ${JSON.stringify(err.details)}`);
|
|
5731
2222
|
}
|
|
5732
2223
|
const variable = err.details?.["variable"];
|
|
5733
|
-
if (
|
|
5734
|
-
lines.push(
|
|
5735
|
-
` hint: "npm run chain:deploy" writes ${LOCAL_CHAIN_MANIFEST_PATH}, which validate/doctor read automatically to fill local X402_*/MERCHANT_WALLET placeholders.`
|
|
5736
|
-
);
|
|
2224
|
+
if (typeof variable === "string") {
|
|
2225
|
+
lines.push(` hint: ${unresolvedVariableHint(variable, manifestFound, chainDeploy)}`);
|
|
5737
2226
|
}
|
|
5738
2227
|
return lines.join("\n");
|
|
5739
2228
|
}
|
|
@@ -5743,14 +2232,14 @@ function formatConfigError(err, manifestFound = false) {
|
|
|
5743
2232
|
return `FAIL ${String(err)}`;
|
|
5744
2233
|
}
|
|
5745
2234
|
async function runValidate(options, io, deps = {}) {
|
|
5746
|
-
const
|
|
2235
|
+
const loadConfig = deps.loadConfig ?? loadConfigDynamic;
|
|
5747
2236
|
const fillEnvFromManifest = deps.fillEnvFromManifest ?? fillEnvFromLocalChainManifest;
|
|
5748
2237
|
const { env, filled, manifestFound } = fillEnvFromManifest(process.env);
|
|
5749
2238
|
if (filled.length > 0) {
|
|
5750
2239
|
io.stdout(`using local chain manifest ${LOCAL_CHAIN_MANIFEST_PATH} for ${filled.join(", ")}`);
|
|
5751
2240
|
}
|
|
5752
2241
|
try {
|
|
5753
|
-
const config = await
|
|
2242
|
+
const config = await loadConfig({
|
|
5754
2243
|
...options.configPath !== void 0 ? { path: options.configPath } : {},
|
|
5755
2244
|
env
|
|
5756
2245
|
});
|
|
@@ -5767,7 +2256,9 @@ async function runValidate(options, io, deps = {}) {
|
|
|
5767
2256
|
io.stdout(` authorization: ap2=${onOff(config.authorization?.ap2.enabled)}`);
|
|
5768
2257
|
return 0;
|
|
5769
2258
|
} catch (err) {
|
|
5770
|
-
io.stderr(
|
|
2259
|
+
io.stderr(
|
|
2260
|
+
formatConfigError(err, manifestFound, (deps.hasChainDeployScript ?? hasChainDeployScript)())
|
|
2261
|
+
);
|
|
5771
2262
|
return 1;
|
|
5772
2263
|
}
|
|
5773
2264
|
}
|
|
@@ -5849,11 +2340,11 @@ async function collectAnswersInteractive() {
|
|
|
5849
2340
|
}
|
|
5850
2341
|
async function runInit(options, io, deps = {}) {
|
|
5851
2342
|
const outputPath = options.outputPath ?? DEFAULT_CONFIG_FILENAME;
|
|
5852
|
-
const
|
|
2343
|
+
const loadConfig = deps.loadConfig ?? loadConfigDynamic;
|
|
5853
2344
|
const parseConfigInMemory = deps.parseConfig ?? parseConfigDynamic;
|
|
5854
2345
|
const fileExists = deps.fileExists ?? existsSync;
|
|
5855
|
-
const writeFileImpl = deps.writeFile ?? (async (
|
|
5856
|
-
await writeFile(
|
|
2346
|
+
const writeFileImpl = deps.writeFile ?? (async (path, content) => {
|
|
2347
|
+
await writeFile(path, content, "utf8");
|
|
5857
2348
|
});
|
|
5858
2349
|
if (fileExists(outputPath) && options.force !== true) {
|
|
5859
2350
|
io.stderr(`FAIL ${outputPath} already exists. Re-run with --force to overwrite.`);
|
|
@@ -5878,7 +2369,7 @@ async function runInit(options, io, deps = {}) {
|
|
|
5878
2369
|
io.stdout(`PASS Wrote ${outputPath}`);
|
|
5879
2370
|
io.stdout(`PASS ${outputPath} is valid: ${config.resources.length} resource(s)`);
|
|
5880
2371
|
try {
|
|
5881
|
-
await
|
|
2372
|
+
await loadConfig({ path: outputPath });
|
|
5882
2373
|
} catch (err) {
|
|
5883
2374
|
io.stderr(formatConfigError(err));
|
|
5884
2375
|
io.stderr(
|
|
@@ -5886,11 +2377,17 @@ async function runInit(options, io, deps = {}) {
|
|
|
5886
2377
|
);
|
|
5887
2378
|
return 1;
|
|
5888
2379
|
}
|
|
2380
|
+
const peers = [
|
|
2381
|
+
...config.payments.x402?.enabled === true ? ["x402"] : [],
|
|
2382
|
+
...config.protocols.mcp.enabled ? ["MCP"] : []
|
|
2383
|
+
];
|
|
2384
|
+
const startNote = peers.length > 0 ? `needs the ${peers.join(" and ")} peers` : "needs no optional peers";
|
|
5889
2385
|
io.stdout("");
|
|
5890
2386
|
io.stdout("Next steps:");
|
|
5891
2387
|
io.stdout(` agent-commerce validate --config ${outputPath}`);
|
|
5892
|
-
io.stdout(
|
|
5893
|
-
io.stdout(` agent-commerce doctor --config ${outputPath}`);
|
|
2388
|
+
io.stdout(` agent-commerce start --config ${outputPath} # ${startNote}`);
|
|
2389
|
+
io.stdout(` agent-commerce doctor --config ${outputPath} # from a second terminal`);
|
|
2390
|
+
io.stdout(" docker compose up # or the whole local demo stack, from a repo clone");
|
|
5894
2391
|
return 0;
|
|
5895
2392
|
}
|
|
5896
2393
|
|
|
@@ -5922,23 +2419,45 @@ var processIo = {
|
|
|
5922
2419
|
};
|
|
5923
2420
|
|
|
5924
2421
|
// src/cli/program.ts
|
|
2422
|
+
var ENV_FILE_HELP = "load environment variables from this file; variables already set win";
|
|
5925
2423
|
function buildProgram(io = processIo) {
|
|
5926
2424
|
const program = new Command();
|
|
5927
|
-
program.name("agent-commerce").description("CLI for the Agent Commerce Gateway: init, import, validate, doctor, demo.").version(readVersionReport().cliVersion, "-v, --version", "Print the CLI version.").exitOverride().configureOutput({
|
|
2425
|
+
program.name("agent-commerce").description("CLI for the Agent Commerce Gateway: init, import, validate, start, doctor, demo.").version(readVersionReport().cliVersion, "-v, --version", "Print the CLI version.").exitOverride().configureOutput({
|
|
5928
2426
|
writeOut: (str) => io.stdout(str.replace(/\n+$/, "")),
|
|
5929
2427
|
writeErr: (str) => io.stderr(str.replace(/\n+$/, ""))
|
|
2428
|
+
}).hook("preAction", (_program, command) => {
|
|
2429
|
+
const { envFile } = command.opts();
|
|
2430
|
+
if (envFile === void 0) return;
|
|
2431
|
+
try {
|
|
2432
|
+
process.loadEnvFile(envFile);
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
command.error(
|
|
2435
|
+
`error: cannot read --env-file "${envFile}": ${error instanceof Error ? error.message : String(error)}`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
5930
2438
|
});
|
|
5931
2439
|
program.command("version").description("Print the CLI version and pinned protocol/SDK versions.").action(() => {
|
|
5932
2440
|
process.exitCode = runVersion(io);
|
|
5933
2441
|
});
|
|
5934
|
-
program.command("validate").description(`Validate ${DEFAULT_CONFIG_FILENAME} and exit non-zero if it is invalid.`).option("--config <path>", `path to ${DEFAULT_CONFIG_FILENAME}`).action(async (opts) => {
|
|
2442
|
+
program.command("validate").description(`Validate ${DEFAULT_CONFIG_FILENAME} and exit non-zero if it is invalid.`).option("--config <path>", `path to ${DEFAULT_CONFIG_FILENAME}`).option("--env-file <path>", ENV_FILE_HELP).action(async (opts) => {
|
|
5935
2443
|
const code = await runValidate(
|
|
5936
2444
|
opts.config !== void 0 ? { configPath: opts.config } : {},
|
|
5937
2445
|
io
|
|
5938
2446
|
);
|
|
5939
2447
|
process.exitCode = code;
|
|
5940
2448
|
});
|
|
5941
|
-
program.command("
|
|
2449
|
+
program.command("start").description(
|
|
2450
|
+
`Start the gateway from ${DEFAULT_CONFIG_FILENAME} and serve until SIGTERM or SIGINT.`
|
|
2451
|
+
).option("--config <path>", `path to ${DEFAULT_CONFIG_FILENAME}`).option("--env-file <path>", ENV_FILE_HELP).action(async (opts) => {
|
|
2452
|
+
try {
|
|
2453
|
+
const { runGateway } = await import('./run-WV44B62Y.js');
|
|
2454
|
+
await runGateway(opts.config !== void 0 ? { configPath: opts.config } : {});
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
io.stderr(formatConfigError(error, false, false));
|
|
2457
|
+
process.exit(1);
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
program.command("doctor").description("Diagnose config, gateway, backend, protocol and payment health.").option("--config <path>", `path to ${DEFAULT_CONFIG_FILENAME}`).option("--env-file <path>", ENV_FILE_HELP).option(
|
|
5942
2461
|
"--gateway <url>",
|
|
5943
2462
|
"gateway base URL (default: derived from config, else http://localhost:8080)"
|
|
5944
2463
|
).option("--json", "emit machine-readable JSON instead of a formatted report", false).action(async (opts) => {
|
|
@@ -5993,14 +2512,18 @@ function collect(value, previous) {
|
|
|
5993
2512
|
}
|
|
5994
2513
|
|
|
5995
2514
|
// src/cli/index.ts
|
|
2515
|
+
var serving = false;
|
|
5996
2516
|
for (const stream of [process.stdout, process.stderr]) {
|
|
5997
2517
|
stream.on("error", (err) => {
|
|
5998
|
-
if (err.code
|
|
5999
|
-
|
|
2518
|
+
if (err.code !== "EPIPE") throw err;
|
|
2519
|
+
if (!serving) process.exit(0);
|
|
6000
2520
|
});
|
|
6001
2521
|
}
|
|
6002
2522
|
async function main() {
|
|
6003
2523
|
const program = buildProgram();
|
|
2524
|
+
program.hook("preAction", (_program, command) => {
|
|
2525
|
+
serving = command.name() === "start";
|
|
2526
|
+
});
|
|
6004
2527
|
try {
|
|
6005
2528
|
await program.parseAsync(process.argv);
|
|
6006
2529
|
} catch (err) {
|