@routedock/routedock 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +113 -12
  2. package/dist/{MppSessionHandler-1EL8QBAn.d.cts → MppSessionHandler-3QfCHI5x.d.ts} +40 -2
  3. package/dist/{MppSessionHandler-Bq4S-JTE.d.ts → MppSessionHandler-xu4Kxu6N.d.cts} +40 -2
  4. package/dist/{ProviderRegistry-CA_TdREG.d.ts → ProviderRegistry-D9KqyuH-.d.cts} +16 -36
  5. package/dist/{ProviderRegistry-D-0rqK0N.d.cts → ProviderRegistry-DVi2uzgk.d.ts} +16 -36
  6. package/dist/{RouteDockClient-go9aopfF.d.cts → RouteDockClient-6rmgkbYb.d.cts} +124 -142
  7. package/dist/{RouteDockClient-Bh86bVly.d.ts → RouteDockClient-CnAkxwrW.d.ts} +124 -142
  8. package/dist/{SessionReconciler-Bw-PoBVl.d.ts → SessionReconciler-Cce9MXO1.d.ts} +10 -3
  9. package/dist/{SessionReconciler-Cz2nuOlJ.d.cts → SessionReconciler-Dpe9qXdD.d.cts} +10 -3
  10. package/dist/{SessionStore-BaYiegP0.d.ts → SessionStore-DkLBxFQu.d.cts} +28 -3
  11. package/dist/{SessionStore-B4-Hi4H8.d.cts → SessionStore-_ZkoBzFY.d.ts} +28 -3
  12. package/dist/SpendStore-BUclV02i.d.ts +73 -0
  13. package/dist/SpendStore-s_dfcemj.d.cts +73 -0
  14. package/dist/client.cjs +767 -487
  15. package/dist/client.d.cts +80 -10
  16. package/dist/client.d.ts +80 -10
  17. package/dist/client.js +733 -468
  18. package/dist/index.cjs +1295 -590
  19. package/dist/index.d.cts +13 -10
  20. package/dist/index.d.ts +13 -10
  21. package/dist/index.js +1247 -559
  22. package/dist/logger-DVa7VT4c.d.cts +41 -0
  23. package/dist/logger-DVa7VT4c.d.ts +41 -0
  24. package/dist/network.cjs +57 -0
  25. package/dist/network.d.cts +72 -0
  26. package/dist/network.d.ts +72 -0
  27. package/dist/network.js +49 -0
  28. package/dist/node.cjs +166 -0
  29. package/dist/node.d.cts +77 -0
  30. package/dist/node.d.ts +77 -0
  31. package/dist/node.js +159 -0
  32. package/dist/provider/fastify.cjs +847 -62
  33. package/dist/provider/fastify.d.cts +10 -3
  34. package/dist/provider/fastify.d.ts +10 -3
  35. package/dist/provider/fastify.js +848 -63
  36. package/dist/provider/hono.cjs +936 -60
  37. package/dist/provider/hono.d.cts +23 -8
  38. package/dist/provider/hono.d.ts +23 -8
  39. package/dist/provider/hono.js +937 -61
  40. package/dist/provider.cjs +894 -70
  41. package/dist/provider.d.cts +9 -8
  42. package/dist/provider.d.ts +9 -8
  43. package/dist/provider.js +895 -72
  44. package/dist/react.cjs +736 -436
  45. package/dist/react.d.cts +7 -5
  46. package/dist/react.d.ts +7 -5
  47. package/dist/react.js +725 -425
  48. package/dist/{routedockMiddleware-57gUaEMf.d.ts → routedockMiddleware-Cq9HOJEa.d.ts} +9 -2
  49. package/dist/{routedockMiddleware-DlyLrxp7.d.cts → routedockMiddleware-CxsWkSKa.d.cts} +9 -2
  50. package/dist/schema.cjs +7 -0
  51. package/dist/schema.js +7 -0
  52. package/dist/{sign-AKH67sft.d.cts → sign-Bq08SeFq.d.ts} +2 -2
  53. package/dist/{sign-BEq-OsSq.d.ts → sign-DqGE0Plv.d.cts} +2 -2
  54. package/dist/testing.d.cts +4 -3
  55. package/dist/testing.d.ts +4 -3
  56. package/dist/{logger-D8_Q2ytp.d.cts → types-DXaS5Wip.d.cts} +85 -47
  57. package/dist/{logger-D8_Q2ytp.d.ts → types-DXaS5Wip.d.ts} +85 -47
  58. package/package.json +50 -4
@@ -1,19 +1,20 @@
1
1
  'use strict';
2
2
 
3
3
  var stellarSdk = require('@stellar/stellar-sdk');
4
+ var nulthSdk = require('@routedock/nulth-sdk');
5
+ var jsonSchema = require('@cfworker/json-schema');
4
6
  var facilitator = require('@x402/stellar/exact/facilitator');
5
7
  var server$1 = require('@x402/stellar/exact/server');
6
8
  var server = require('@x402/core/server');
7
9
  var stellar = require('@x402/stellar');
8
10
  var http = require('@x402/core/http');
9
- var nulthSdk = require('@routedock/nulth-sdk');
10
11
  var server$4 = require('@stellar/mpp/charge/server');
11
12
  var server$2 = require('mppx/server');
12
13
  var server$3 = require('@stellar/mpp/channel/server');
13
14
  var mppx = require('mppx');
14
- var crypto$1 = require('crypto');
15
+ var sha2_js = require('@noble/hashes/sha2.js');
15
16
 
16
- // src/provider/x402Handler.ts
17
+ // src/provider/internal/assertProviderConfig.ts
17
18
 
18
19
  // src/errors.ts
19
20
  var RouteDockError = class extends Error {
@@ -36,13 +37,618 @@ var RouteDockManifestError = class extends RouteDockError {
36
37
  this.name = "RouteDockManifestError";
37
38
  }
38
39
  };
40
+ var usdcToUnits = nulthSdk.usdcToStroops;
41
+
42
+ // src/schemas/routedock.schema.json
43
+ var routedock_schema_default = {
44
+ $schema: "http://json-schema.org/draft-07/schema#",
45
+ $id: "https://routedock.xyz/schemas/routedock.schema.json",
46
+ title: "RouteDock Manifest",
47
+ description: "Discovery manifest served at /.well-known/routedock.json on every RouteDock provider endpoint",
48
+ type: "object",
49
+ required: [
50
+ "routedock",
51
+ "name",
52
+ "description",
53
+ "modes",
54
+ "network",
55
+ "asset",
56
+ "asset_contract",
57
+ "payee",
58
+ "pricing",
59
+ "endpoints",
60
+ "tags"
61
+ ],
62
+ additionalProperties: false,
63
+ properties: {
64
+ routedock: {
65
+ type: "string",
66
+ pattern: "^[0-9]+\\.[0-9]+$",
67
+ description: "Manifest schema version, e.g. '1.0', '1.1', '2.0'."
68
+ },
69
+ min_client_version: {
70
+ type: "string",
71
+ pattern: "^[0-9]+\\.[0-9]+$",
72
+ description: "Minimum SDK client version (major.minor) required to use this manifest. Clients below this version must refuse to pay."
73
+ },
74
+ deprecated_modes: {
75
+ type: "array",
76
+ items: {
77
+ $ref: "#/definitions/PaymentMode"
78
+ },
79
+ uniqueItems: true,
80
+ description: "Payment modes still listed in 'modes' for backwards compatibility but deprecated for new integrations."
81
+ },
82
+ sunset_at: {
83
+ type: "string",
84
+ format: "date-time",
85
+ description: "ISO 8601 timestamp after which this manifest version is no longer supported by the provider."
86
+ },
87
+ name: {
88
+ type: "string",
89
+ minLength: 1,
90
+ maxLength: 120,
91
+ description: "Human-readable provider name"
92
+ },
93
+ description: {
94
+ type: "string",
95
+ minLength: 1,
96
+ maxLength: 500,
97
+ description: "Human-readable description of the provider's data or service"
98
+ },
99
+ modes: {
100
+ type: "array",
101
+ minItems: 1,
102
+ uniqueItems: true,
103
+ items: {
104
+ $ref: "#/definitions/PaymentMode"
105
+ },
106
+ description: "Payment modes supported by this provider"
107
+ },
108
+ network: {
109
+ type: "string",
110
+ enum: ["testnet", "mainnet"],
111
+ description: "Stellar network this provider operates on"
112
+ },
113
+ asset: {
114
+ type: "string",
115
+ description: "Asset ticker symbol (e.g. 'USDC')"
116
+ },
117
+ asset_contract: {
118
+ type: "string",
119
+ pattern: "^C[A-Z2-7]{55}$",
120
+ description: "Stellar Asset Contract (SAC) address for the payment asset"
121
+ },
122
+ assets: {
123
+ type: "array",
124
+ minItems: 1,
125
+ items: {
126
+ $ref: "#/definitions/AssetConfig"
127
+ },
128
+ description: "Payment assets accepted by this provider. Each asset can be scoped to specific modes and endpoints. When present, assets[0] must match the root-level asset and asset_contract."
129
+ },
130
+ payee: {
131
+ type: "string",
132
+ pattern: "^G[A-Z2-7]{55}$",
133
+ description: "Stellar address (G...) that receives payments"
134
+ },
135
+ pricing: {
136
+ type: "object",
137
+ description: "Pricing configuration per supported payment mode",
138
+ additionalProperties: false,
139
+ properties: {
140
+ x402: {
141
+ $ref: "#/definitions/PricingConfig"
142
+ },
143
+ "mpp-charge": {
144
+ $ref: "#/definitions/PricingConfig"
145
+ },
146
+ "mpp-session": {
147
+ $ref: "#/definitions/SessionPricingConfig"
148
+ },
149
+ "mpp-session-ws": {
150
+ $ref: "#/definitions/SessionPricingConfig",
151
+ description: "WebSocket transport variant of mpp-session: channel opened and first voucher negotiated over HTTP, then the connection upgrades to WebSocket for push-based streaming."
152
+ }
153
+ },
154
+ minProperties: 1
155
+ },
156
+ endpoints: {
157
+ type: "object",
158
+ description: "Map of endpoint name to endpoint descriptors",
159
+ additionalProperties: {
160
+ $ref: "#/definitions/EndpointDescriptor"
161
+ },
162
+ minProperties: 1
163
+ },
164
+ sla: {
165
+ $ref: "#/definitions/SLAConfig"
166
+ },
167
+ tags: {
168
+ type: "array",
169
+ items: {
170
+ type: "string",
171
+ minLength: 1,
172
+ maxLength: 64
173
+ },
174
+ uniqueItems: true,
175
+ description: "Capability tags indexed with trigram search in the provider registry"
176
+ },
177
+ vault: {
178
+ $ref: "#/definitions/VaultMode"
179
+ },
180
+ nulth_account: {
181
+ type: "string",
182
+ description: "Optional Nulth smart account address associated with this provider"
183
+ },
184
+ capabilities: {
185
+ type: "object",
186
+ description: "Optional protocol features this provider supports",
187
+ properties: {
188
+ streaming: {
189
+ type: "array",
190
+ items: {
191
+ type: "string",
192
+ enum: ["sse", "websocket"]
193
+ },
194
+ description: "Supported streaming transports"
195
+ },
196
+ webhooks: {
197
+ type: "boolean",
198
+ description: "Whether webhook callbacks are supported"
199
+ },
200
+ idempotency_keys: {
201
+ type: "boolean",
202
+ description: "Whether idempotency keys are supported"
203
+ },
204
+ content_types: {
205
+ type: "array",
206
+ items: {
207
+ type: "string"
208
+ },
209
+ description: "Supported content types for request/response"
210
+ }
211
+ },
212
+ additionalProperties: false
213
+ },
214
+ regions: {
215
+ type: "array",
216
+ items: {
217
+ type: "string",
218
+ minLength: 2,
219
+ maxLength: 10
220
+ },
221
+ uniqueItems: true,
222
+ description: "IATA airport codes or UN/LOCODE identifiers for regions where this provider has infrastructure (e.g. ['IAD', 'AMS']). Used by agents to select the nearest provider."
223
+ },
224
+ latency_hints: {
225
+ type: "object",
226
+ additionalProperties: {
227
+ type: "number",
228
+ minimum: 0
229
+ },
230
+ description: "Provider-declared p50 latency in milliseconds per region code. Keys must be a subset of 'regions'. Example: { 'IAD': 14, 'AMS': 22 }."
231
+ },
232
+ signature: {
233
+ type: "string",
234
+ description: "Base64 Ed25519 signature (payee keypair) over the SHA-256 hash of this manifest with the signature field omitted. Clients must verify before trusting any routing field."
235
+ },
236
+ signature_version: {
237
+ type: "string",
238
+ const: "2",
239
+ description: "Manifest signature format. Version 2 recursively includes every nested manifest field in the signed digest."
240
+ },
241
+ categories: {
242
+ type: "array",
243
+ items: {
244
+ type: "string",
245
+ minLength: 1,
246
+ maxLength: 128
247
+ },
248
+ uniqueItems: true,
249
+ description: "Optional hierarchical categories from a published taxonomy (e.g. data/price/crypto)"
250
+ }
251
+ },
252
+ definitions: {
253
+ VaultMode: {
254
+ type: "string",
255
+ enum: ["local-key", "agent-vault", "nulth"],
256
+ description: "Agent vault / custody mode for payments from this provider"
257
+ },
258
+ PaymentMode: {
259
+ type: "string",
260
+ enum: ["x402", "mpp-charge", "mpp-session", "mpp-session-ws"],
261
+ description: "A supported RouteDock payment mode"
262
+ },
263
+ AssetConfig: {
264
+ type: "object",
265
+ description: "Payment asset configuration with optional mode and endpoint scoping",
266
+ required: ["asset", "asset_contract"],
267
+ additionalProperties: false,
268
+ properties: {
269
+ asset: {
270
+ type: "string",
271
+ description: "Asset ticker symbol (e.g. 'USDC' or 'XLM')"
272
+ },
273
+ asset_contract: {
274
+ type: "string",
275
+ description: "Stellar Asset Contract (SAC) address for the payment asset"
276
+ },
277
+ modes: {
278
+ type: "array",
279
+ items: {
280
+ $ref: "#/definitions/PaymentMode"
281
+ },
282
+ uniqueItems: true,
283
+ description: "Optional: restrict this asset to specific payment modes. If omitted, available for all modes."
284
+ },
285
+ endpoints: {
286
+ type: "array",
287
+ items: {
288
+ type: "string"
289
+ },
290
+ uniqueItems: true,
291
+ description: "Optional: restrict this asset to specific endpoints (by name). If omitted, available for all endpoints."
292
+ }
293
+ }
294
+ },
295
+ PricingConfig: {
296
+ type: "object",
297
+ description: "Per-request pricing for x402 or mpp-charge modes",
298
+ required: ["amount", "per"],
299
+ additionalProperties: false,
300
+ properties: {
301
+ amount: {
302
+ type: "string",
303
+ pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
304
+ description: "Cost per request in the payment asset (decimal string, e.g. '0.001')"
305
+ },
306
+ per: {
307
+ type: "string",
308
+ const: "request",
309
+ description: "Billing unit \u2014 always 'request' for non-session modes"
310
+ },
311
+ facilitator: {
312
+ type: "string",
313
+ description: "x402 facilitator URL (required for x402 mode). OpenZeppelin Channels endpoint."
314
+ },
315
+ payee: {
316
+ type: "string",
317
+ pattern: "^G[A-Z2-7]{55}$",
318
+ description: "Optional per-mode payee override (Stellar G... address). Directs settlements for this mode to a separate account for treasury separation. Falls back to the top-level 'payee' when omitted."
319
+ }
320
+ }
321
+ },
322
+ SessionPricingConfig: {
323
+ type: "object",
324
+ description: "Per-voucher pricing for mpp-session (one-way-channel) mode",
325
+ required: ["rate", "per", "channel_factory", "min_deposit", "refund_waiting_period_ledgers"],
326
+ additionalProperties: false,
327
+ properties: {
328
+ rate: {
329
+ type: "string",
330
+ pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
331
+ description: "Cost per voucher in the payment asset (decimal string, e.g. '0.0001')"
332
+ },
333
+ per: {
334
+ type: "string",
335
+ const: "voucher",
336
+ description: "Billing unit \u2014 always 'voucher' for session mode"
337
+ },
338
+ channel_factory: {
339
+ type: "string",
340
+ pattern: "^C[A-Z2-7]{55}$",
341
+ description: "Stable Soroban factory address (C...) used to deploy a per-session channel"
342
+ },
343
+ min_deposit: {
344
+ type: "string",
345
+ pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
346
+ description: "Minimum initial deposit to open a channel (decimal string)"
347
+ },
348
+ refund_waiting_period_ledgers: {
349
+ type: "integer",
350
+ minimum: 1,
351
+ description: "Ledgers the server has to settle before the funder can reclaim. Default: 17280 (~24h). Do not reduce in production."
352
+ }
353
+ }
354
+ },
355
+ SLAConfig: {
356
+ type: "object",
357
+ description: "Service Level Agreement for the provider",
358
+ required: ["uptime_30d_percent", "p95_latency_ms"],
359
+ additionalProperties: false,
360
+ properties: {
361
+ uptime_30d_percent: {
362
+ type: "number",
363
+ description: "Uptime percentage over the last 30 days"
364
+ },
365
+ p95_latency_ms: {
366
+ type: "number",
367
+ description: "P95 latency in milliseconds"
368
+ },
369
+ maintenance_windows: {
370
+ type: "array",
371
+ items: {
372
+ type: "object",
373
+ required: ["cron", "duration_minutes"],
374
+ additionalProperties: false,
375
+ properties: {
376
+ cron: {
377
+ type: "string",
378
+ description: "Cron expression for the maintenance window"
379
+ },
380
+ duration_minutes: {
381
+ type: "number",
382
+ description: "Duration of the maintenance window in minutes"
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ },
389
+ EndpointDescriptor: {
390
+ type: "object",
391
+ description: "Descriptor for a provider endpoint",
392
+ required: ["method", "path"],
393
+ additionalProperties: false,
394
+ properties: {
395
+ method: {
396
+ type: "string",
397
+ description: "HTTP method for the endpoint"
398
+ },
399
+ path: {
400
+ type: "string",
401
+ description: "Path for the endpoint"
402
+ },
403
+ deprecated: {
404
+ type: "boolean",
405
+ description: "Whether this endpoint is retained only for backwards compatibility"
406
+ },
407
+ sunset_at: {
408
+ type: "string",
409
+ format: "date-time",
410
+ description: "ISO 8601 timestamp after which callers should stop using this endpoint"
411
+ },
412
+ headers: {
413
+ type: "object",
414
+ additionalProperties: {
415
+ type: "string"
416
+ },
417
+ description: "Expected request headers"
418
+ },
419
+ request_schema: {
420
+ description: "JSON schema for the request body"
421
+ },
422
+ response_schema: {
423
+ description: "JSON schema for the response body"
424
+ },
425
+ rate_limit: {
426
+ type: "object",
427
+ required: ["requests", "window_seconds"],
428
+ additionalProperties: false,
429
+ properties: {
430
+ requests: {
431
+ type: "number"
432
+ },
433
+ window_seconds: {
434
+ type: "number"
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+ }
441
+ };
442
+
443
+ // src/manifest/validate.ts
444
+ var validator = new jsonSchema.Validator(routedock_schema_default, "7");
445
+ function validateManifestSchema(manifest, source) {
446
+ const result = validator.validate(manifest);
447
+ if (!result.valid) {
448
+ const msgs = result.errors.map((err) => err.error).join("; ");
449
+ throw new RouteDockManifestError(`Invalid manifest${""}: ${msgs}`);
450
+ }
451
+ }
452
+ function assertManifestValid(manifest, baseUrl) {
453
+ const context = "";
454
+ if (manifest.latency_hints !== void 0) {
455
+ if (!Array.isArray(manifest.regions) || manifest.regions.length === 0) {
456
+ throw new RouteDockManifestError(
457
+ `Invalid manifest${context}: latency_hints defined without declaring regions`
458
+ );
459
+ }
460
+ const regionSet = new Set(manifest.regions);
461
+ for (const region of Object.keys(manifest.latency_hints)) {
462
+ if (!regionSet.has(region)) {
463
+ throw new RouteDockManifestError(
464
+ `Invalid manifest${context}: latency_hints key '${region}' is not declared in regions (${manifest.regions.join(", ")})`
465
+ );
466
+ }
467
+ }
468
+ }
469
+ if (manifest.assets !== void 0) {
470
+ if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
471
+ throw new RouteDockManifestError(
472
+ `Invalid manifest${context}: assets must be a non-empty array when defined`
473
+ );
474
+ }
475
+ for (let i = 0; i < manifest.assets.length; i++) {
476
+ const a = manifest.assets[i];
477
+ if (!a || typeof a.asset !== "string" || typeof a.asset_contract !== "string") {
478
+ throw new RouteDockManifestError(
479
+ `Invalid manifest${context}: assets[${i}] must define asset and asset_contract strings`
480
+ );
481
+ }
482
+ }
483
+ const first = manifest.assets[0];
484
+ if (first.asset !== manifest.asset || first.asset_contract !== manifest.asset_contract) {
485
+ throw new RouteDockManifestError(
486
+ `Invalid manifest${context}: assets[0] (${first.asset}:${first.asset_contract}) must match root asset fields (${manifest.asset}:${manifest.asset_contract})`
487
+ );
488
+ }
489
+ }
490
+ assertPricingMatchesModes(manifest, context);
491
+ assertDeprecatedModesListed(manifest, context);
492
+ assertPricesParse(manifest, context);
493
+ }
494
+ function assertPricingMatchesModes(manifest, context) {
495
+ const pricing = manifest.pricing ?? {};
496
+ for (const mode of manifest.modes ?? []) {
497
+ if (pricing[mode] === void 0 || pricing[mode] === null) {
498
+ throw new RouteDockManifestError(
499
+ `Invalid manifest${context}: mode '${mode}' is listed in modes but pricing.${mode} is missing`
500
+ );
501
+ }
502
+ }
503
+ }
504
+ function assertDeprecatedModesListed(manifest, context) {
505
+ if (manifest.deprecated_modes === void 0) return;
506
+ const modes = new Set(manifest.modes ?? []);
507
+ for (const mode of manifest.deprecated_modes) {
508
+ if (!modes.has(mode)) {
509
+ throw new RouteDockManifestError(
510
+ `Invalid manifest${context}: deprecated_modes entry '${mode}' is not listed in modes`
511
+ );
512
+ }
513
+ }
514
+ }
515
+ var PRICE_FIELDS = {
516
+ x402: ["amount"],
517
+ "mpp-charge": ["amount"],
518
+ "mpp-session": ["rate", "min_deposit"],
519
+ "mpp-session-ws": ["rate", "min_deposit"]
520
+ };
521
+ function assertPricesParse(manifest, context) {
522
+ const pricing = manifest.pricing ?? {};
523
+ for (const [mode, fields] of Object.entries(PRICE_FIELDS)) {
524
+ const entry = pricing[mode];
525
+ if (entry === void 0 || entry === null) continue;
526
+ for (const field of fields) {
527
+ const value = entry[field];
528
+ try {
529
+ if (typeof value !== "string") {
530
+ throw new RangeError(`expected a decimal string, got ${typeof value}`);
531
+ }
532
+ nulthSdk.usdcToStroops(value);
533
+ } catch (err) {
534
+ const detail = err instanceof Error ? err.message : String(err);
535
+ throw new RouteDockManifestError(
536
+ `Invalid manifest${context}: pricing.${mode}.${field} ${JSON.stringify(value)} is not a valid amount (${detail})`,
537
+ { cause: err }
538
+ );
539
+ }
540
+ }
541
+ }
542
+ }
543
+
544
+ // src/provider/internal/assertProviderConfig.ts
545
+ var FLAT_MODES = ["x402", "mpp-charge"];
546
+ var SESSION_MODES = ["mpp-session", "mpp-session-ws"];
547
+ function sameAmount(adapter, label, optionValue, manifestValue) {
548
+ const parse = (where, value) => {
549
+ try {
550
+ return nulthSdk.usdcToStroops(value);
551
+ } catch {
552
+ throw new RouteDockManifestError(
553
+ `${adapter}: ${label} has an unparseable ${where} amount "${value}"`
554
+ );
555
+ }
556
+ };
557
+ return parse("option", optionValue) === parse("manifest", manifestValue);
558
+ }
559
+ function assertProviderConfig(opts, adapterName) {
560
+ const { manifest } = opts;
561
+ if (manifest === null || typeof manifest !== "object") {
562
+ throw new RouteDockManifestError(`${adapterName}: opts.manifest is required and must be an object`);
563
+ }
564
+ try {
565
+ validateManifestSchema(manifest);
566
+ assertManifestValid(manifest);
567
+ } catch (err) {
568
+ if (err instanceof RouteDockManifestError) {
569
+ throw new RouteDockManifestError(`${adapterName}: ${err.message}`, { cause: err });
570
+ }
571
+ throw err;
572
+ }
573
+ const served = new Set(opts.modes.filter((mode) => Boolean(opts.pricing[mode])));
574
+ const advertised = new Set(manifest.modes);
575
+ const notAdvertised = [...served].filter((mode) => !advertised.has(mode));
576
+ const notServed = [...advertised].filter((mode) => !served.has(mode));
577
+ if (notAdvertised.length > 0 || notServed.length > 0) {
578
+ const parts = [];
579
+ if (notAdvertised.length > 0) parts.push(`served but not in manifest.modes: ${notAdvertised.join(", ")}`);
580
+ if (notServed.length > 0) parts.push(`in manifest.modes but not served (missing from modes or pricing): ${notServed.join(", ")}`);
581
+ throw new RouteDockManifestError(`${adapterName}: modes do not match manifest.modes (${parts.join("; ")})`);
582
+ }
583
+ for (const mode of FLAT_MODES) {
584
+ if (!served.has(mode)) continue;
585
+ const optionValue = opts.pricing[mode];
586
+ const manifestValue = manifest.pricing[mode]?.amount;
587
+ if (manifestValue === void 0) {
588
+ throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode}.amount is missing but ${mode} is served`);
589
+ }
590
+ if (!sameAmount(adapterName, `pricing.${mode}`, optionValue, manifestValue)) {
591
+ throw new RouteDockManifestError(
592
+ `${adapterName}: pricing.${mode} "${optionValue}" does not match manifest.pricing.${mode}.amount "${manifestValue}"`
593
+ );
594
+ }
595
+ }
596
+ for (const mode of SESSION_MODES) {
597
+ if (!served.has(mode)) continue;
598
+ const optionValue = opts.pricing[mode];
599
+ const manifestValue = manifest.pricing[mode];
600
+ if (!manifestValue) {
601
+ throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode} is missing but ${mode} is served`);
602
+ }
603
+ if (!sameAmount(adapterName, `pricing.${mode}.rate`, optionValue.rate, manifestValue.rate)) {
604
+ throw new RouteDockManifestError(
605
+ `${adapterName}: pricing.${mode}.rate "${optionValue.rate}" does not match manifest.pricing.${mode}.rate "${manifestValue.rate}"`
606
+ );
607
+ }
608
+ if (optionValue.channelFactory !== manifestValue.channel_factory) {
609
+ throw new RouteDockManifestError(
610
+ `${adapterName}: pricing.${mode}.channelFactory "${optionValue.channelFactory}" does not match manifest.pricing.${mode}.channel_factory "${manifestValue.channel_factory}"`
611
+ );
612
+ }
613
+ }
614
+ if (opts.assetContract !== void 0 && opts.assetContract !== manifest.asset_contract) {
615
+ throw new RouteDockManifestError(
616
+ `${adapterName}: assetContract "${opts.assetContract}" does not match manifest.asset_contract "${manifest.asset_contract}"`
617
+ );
618
+ }
619
+ if (opts.asset !== void 0 && opts.asset !== manifest.asset) {
620
+ throw new RouteDockManifestError(
621
+ `${adapterName}: asset "${opts.asset}" does not match manifest.asset "${manifest.asset}"`
622
+ );
623
+ }
624
+ if (opts.network !== manifest.network) {
625
+ throw new RouteDockManifestError(
626
+ `${adapterName}: network "${opts.network}" does not match manifest.network "${manifest.network}"`
627
+ );
628
+ }
629
+ let signerPublicKey;
630
+ try {
631
+ signerPublicKey = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey).publicKey();
632
+ } catch (err) {
633
+ throw new RouteDockManifestError(`${adapterName}: payeeSecretKey is not a valid Stellar secret key`, { cause: err });
634
+ }
635
+ if (signerPublicKey !== manifest.payee) {
636
+ throw new RouteDockManifestError(
637
+ `${adapterName}: payeeSecretKey belongs to ${signerPublicKey}, not manifest.payee ${manifest.payee}; clients would reject the signed manifest`
638
+ );
639
+ }
640
+ if (opts.payee !== manifest.payee) {
641
+ throw new RouteDockManifestError(
642
+ `${adapterName}: payee "${opts.payee}" does not match manifest.payee "${manifest.payee}"`
643
+ );
644
+ }
645
+ }
39
646
 
40
647
  // src/internal/payee.ts
41
648
  function resolvePayee(manifest, mode) {
42
649
  const override = manifest.pricing[mode]?.payee;
43
650
  return override ?? manifest.payee;
44
651
  }
45
- var usdcToUnits = nulthSdk.usdcToStroops;
46
652
  function extractPayerAddress(key) {
47
653
  if (typeof key !== "string") return null;
48
654
  return stellarSdk.StrKey.isValidEd25519PublicKey(key) || stellarSdk.StrKey.isValidMed25519PublicKey(key) ? key : null;
@@ -139,6 +745,18 @@ function resolveLogger(logger, fallback = consoleLogger) {
139
745
  // src/provider/SeenTxStore.ts
140
746
  var SETTLEMENT_REPLAY_WINDOW_MS = 6e4;
141
747
  var MAX_SETTLEMENT_REPLAYS = 1;
748
+ async function recordSettlement(store, key, record, ctx) {
749
+ try {
750
+ await store.set(key, record);
751
+ } catch (err) {
752
+ ctx.logger("error", `${ctx.label} seenTxStore.set failed after settlement`, { error: err });
753
+ try {
754
+ ctx.onCallbackError?.(err, "seenTxStore.set");
755
+ } catch (cbErr) {
756
+ ctx.logger("error", `${ctx.label} onCallbackError threw`, { error: cbErr });
757
+ }
758
+ }
759
+ }
142
760
  async function checkSettlementReplay(store, key, now = Date.now()) {
143
761
  const record = await store.get(key);
144
762
  if (!record) return { kind: "miss" };
@@ -206,7 +824,7 @@ async function paymentIdempotencyKey(getHeader, scope) {
206
824
  );
207
825
  }
208
826
 
209
- // src/provider/settleResult.ts
827
+ // src/internal/settleResult.ts
210
828
  function readSettleResult(result) {
211
829
  const settle = result;
212
830
  const failed = (reason) => reason ? { ok: false, reason } : { ok: false };
@@ -356,10 +974,10 @@ function createX402Handler(opts) {
356
974
  requirements
357
975
  );
358
976
  if (!verifyResult.isValid) {
359
- res.status(401).json({
360
- error: "Payment verification failed",
361
- reason: verifyResult.invalidReason
362
- });
977
+ await respondPaymentRequired(
978
+ "Payment verification failed",
979
+ verifyResult.invalidReason
980
+ );
363
981
  return;
364
982
  }
365
983
  const settleResult = await localFacilitator.settle(
@@ -385,7 +1003,12 @@ function createX402Handler(opts) {
385
1003
  if (typeof paymentResponse === "string") {
386
1004
  headers["X-Payment-Response"] = paymentResponse;
387
1005
  }
388
- await seenTxStore.set(idempotencyKey, { txHash, headers, createdAt: Date.now() });
1006
+ await recordSettlement(
1007
+ seenTxStore,
1008
+ idempotencyKey,
1009
+ { txHash, headers, createdAt: Date.now() },
1010
+ { logger, label: "[x402]", onCallbackError: opts.onCallbackError }
1011
+ );
389
1012
  }
390
1013
  if (txHash && opts.onSettled) {
391
1014
  Promise.resolve().then(() => opts.onSettled(txHash, opts.amount, "x402", payerAddress)).catch((err) => {
@@ -404,6 +1027,51 @@ function createX402Handler(opts) {
404
1027
  }
405
1028
  };
406
1029
  }
1030
+
1031
+ // src/internal/encoding.ts
1032
+ var encoder = new TextEncoder();
1033
+ var decoder = new TextDecoder();
1034
+ function utf8ToBytes(text) {
1035
+ return encoder.encode(text);
1036
+ }
1037
+ function bytesToUtf8(bytes) {
1038
+ return decoder.decode(bytes);
1039
+ }
1040
+ function bytesToBase64(bytes) {
1041
+ const chunk = 32768;
1042
+ let binary = "";
1043
+ for (let i = 0; i < bytes.length; i += chunk) {
1044
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
1045
+ }
1046
+ return btoa(binary);
1047
+ }
1048
+ function base64ToBytes(b64) {
1049
+ const end = b64.indexOf("=");
1050
+ let clean = (end === -1 ? b64 : b64.slice(0, end)).replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
1051
+ if (clean.length % 4 === 1) clean = clean.slice(0, -1);
1052
+ const binary = atob(clean);
1053
+ const bytes = new Uint8Array(binary.length);
1054
+ for (let i = 0; i < binary.length; i++) {
1055
+ bytes[i] = binary.charCodeAt(i);
1056
+ }
1057
+ return bytes;
1058
+ }
1059
+ function base64ToUtf8(b64) {
1060
+ return bytesToUtf8(base64ToBytes(b64));
1061
+ }
1062
+ function hexToBytes(hex) {
1063
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1064
+ if (clean.length % 2 !== 0) {
1065
+ throw new Error("hexToBytes: hex string must have an even length");
1066
+ }
1067
+ const bytes = new Uint8Array(clean.length / 2);
1068
+ for (let i = 0; i < bytes.length; i++) {
1069
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1070
+ }
1071
+ return bytes;
1072
+ }
1073
+
1074
+ // src/provider/MppChargeHandler.ts
407
1075
  var MPP_NETWORK = {
408
1076
  testnet: "stellar:testnet",
409
1077
  mainnet: "stellar:pubnet"
@@ -450,7 +1118,7 @@ function createMppChargeHandler(opts) {
450
1118
  const credPart = authHeader.replace(/^Payment\s+/, "").split(",").find((p) => p.trim().startsWith("credential="));
451
1119
  if (credPart) {
452
1120
  const b64 = credPart.split("=").slice(1).join("=").replace(/^"|"$/g, "");
453
- const credJson = Buffer.from(b64, "base64").toString("utf8");
1121
+ const credJson = base64ToUtf8(b64);
454
1122
  const cred = JSON.parse(credJson);
455
1123
  const key = cred.sender ?? cred.payload?.sender ?? cred.payload?.from;
456
1124
  payerAddress = extractPayerAddress(key);
@@ -512,18 +1180,19 @@ function createMppChargeHandler(opts) {
512
1180
  if (receiptHeader) {
513
1181
  try {
514
1182
  const parsed = JSON.parse(
515
- Buffer.from(receiptHeader, "base64").toString("utf8")
1183
+ base64ToUtf8(receiptHeader)
516
1184
  );
517
1185
  reference = parsed.reference;
518
1186
  } catch {
519
1187
  }
520
1188
  }
521
1189
  if (idempotencyKey) {
522
- await seenTxStore.set(idempotencyKey, {
523
- txHash: reference ?? null,
524
- headers: receiptHeaders,
525
- createdAt: Date.now()
526
- });
1190
+ await recordSettlement(
1191
+ seenTxStore,
1192
+ idempotencyKey,
1193
+ { txHash: reference ?? null, headers: receiptHeaders, createdAt: Date.now() },
1194
+ { logger, label: "[mpp-charge]", onCallbackError: opts.onCallbackError }
1195
+ );
527
1196
  }
528
1197
  if (reference && opts.onSettled) {
529
1198
  Promise.resolve().then(() => opts.onSettled(reference, opts.amount, "mpp-charge", payerAddress)).catch((err) => {
@@ -537,6 +1206,14 @@ function createMppChargeHandler(opts) {
537
1206
  }
538
1207
  };
539
1208
  }
1209
+
1210
+ // src/provider/internal/vaultRpc.ts
1211
+ async function createSorobanRpcServer(rpcUrl) {
1212
+ const { Server } = await import('@stellar/stellar-sdk/rpc');
1213
+ return new Server(rpcUrl);
1214
+ }
1215
+
1216
+ // src/provider/internal/vaultSettlement.ts
540
1217
  function resolveVaultSettlementAddresses(payer, payee) {
541
1218
  if (!payer) return null;
542
1219
  return { payer, payee };
@@ -556,6 +1233,111 @@ function buildSessionSettlementTransaction(source, vaultContractId, channelId, a
556
1233
  networkPassphrase
557
1234
  }).addOperation(op).setTimeout(30).build();
558
1235
  }
1236
+ var DEFAULT_RPC_URLS = {
1237
+ testnet: "https://soroban-testnet.stellar.org",
1238
+ mainnet: "https://mainnet.sorobanrpc.com"
1239
+ };
1240
+ var CONFIRM_TIMEOUT_MS = 12e4;
1241
+ var CONFIRM_POLL_MS = 1e3;
1242
+ function delay(ms) {
1243
+ return new Promise((resolve) => setTimeout(resolve, ms));
1244
+ }
1245
+ function describeSendError(sent) {
1246
+ const errorResult = sent.errorResult;
1247
+ try {
1248
+ const name = errorResult?.result?.().switch?.().name;
1249
+ if (name) return `sendTransaction returned ERROR: ${name}`;
1250
+ } catch {
1251
+ }
1252
+ return `sendTransaction returned status ${sent.status}`;
1253
+ }
1254
+ async function waitForConfirmation(server, txHash) {
1255
+ const deadline = Date.now() + CONFIRM_TIMEOUT_MS;
1256
+ for (; ; ) {
1257
+ const tx = await server.getTransaction(txHash);
1258
+ if (tx.status === "SUCCESS") return "success";
1259
+ if (tx.status === "FAILED") return "failed";
1260
+ if (Date.now() >= deadline) return "timeout";
1261
+ await delay(CONFIRM_POLL_MS);
1262
+ }
1263
+ }
1264
+ async function recordSessionSettlement(request, { logger, onCallbackError }) {
1265
+ const reportFailure = (fields) => {
1266
+ logger("error", "[vault] failed to record session_settled", {
1267
+ channelId: request.channelId,
1268
+ ...fields.txHash ? { txHash: fields.txHash } : {},
1269
+ reason: fields.reason,
1270
+ ...fields.error !== void 0 ? { error: fields.error } : {}
1271
+ });
1272
+ if (onCallbackError) {
1273
+ const err = fields.error ?? new Error(`session_settled vault record failed: ${fields.reason}`);
1274
+ onCallbackError(err, "vaultSettlement");
1275
+ }
1276
+ return { ok: false, reason: fields.reason, ...fields.txHash ? { txHash: fields.txHash } : {} };
1277
+ };
1278
+ try {
1279
+ const payeeKeypair = stellarSdk.Keypair.fromSecret(request.payeeSecretKey);
1280
+ const addresses = resolveVaultSettlementAddresses(request.payer, payeeKeypair.publicKey());
1281
+ if (!addresses) {
1282
+ logger("warn", "[vault] skipped session_settled record: payer address unavailable", {
1283
+ channelId: request.channelId
1284
+ });
1285
+ return { ok: false, reason: "payer address unavailable" };
1286
+ }
1287
+ const networkPassphrase = request.network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
1288
+ const rpcUrl = request.config.rpcUrl ?? DEFAULT_RPC_URLS[request.network];
1289
+ const server = await createSorobanRpcServer(rpcUrl);
1290
+ const sourceAccount = await server.getAccount(payeeKeypair.publicKey());
1291
+ const tx = buildSessionSettlementTransaction(
1292
+ sourceAccount,
1293
+ request.config.contract,
1294
+ request.channelId,
1295
+ addresses,
1296
+ request.amount,
1297
+ request.voucherCount,
1298
+ networkPassphrase
1299
+ );
1300
+ const preparedTx = await server.prepareTransaction(tx);
1301
+ preparedTx.sign(payeeKeypair);
1302
+ const sent = await server.sendTransaction(preparedTx);
1303
+ if (sent.status === "ERROR") {
1304
+ return reportFailure({
1305
+ txHash: sent.hash || preparedTx.hash().toString("hex"),
1306
+ reason: describeSendError(sent)
1307
+ });
1308
+ }
1309
+ const txHash = sent.hash || preparedTx.hash().toString("hex");
1310
+ const confirmation = await waitForConfirmation(server, txHash);
1311
+ if (confirmation === "success") {
1312
+ logger("debug", "[vault] recorded session_settled", {
1313
+ channelId: request.channelId,
1314
+ txHash
1315
+ });
1316
+ return { ok: true, txHash };
1317
+ }
1318
+ return reportFailure({
1319
+ txHash,
1320
+ reason: confirmation === "failed" ? "transaction failed on-chain" : `confirmation timed out after ${CONFIRM_TIMEOUT_MS}ms`
1321
+ });
1322
+ } catch (err) {
1323
+ const reason = err instanceof Error ? err.message : String(err);
1324
+ return reportFailure({ reason, error: err });
1325
+ }
1326
+ }
1327
+
1328
+ // src/internal/vaultConfig.ts
1329
+ function resolveVaultSettlementConfig(option, logger) {
1330
+ if (option) return option;
1331
+ if (typeof process === "undefined") return void 0;
1332
+ const fromEnv = process.env.AGENT_VAULT_CONTRACT ?? process.env.AGENT_VAULT_CONTRACT_ID;
1333
+ if (!fromEnv) return void 0;
1334
+ logger(
1335
+ "warn",
1336
+ "[vault] configuring the vault contract via the AGENT_VAULT_CONTRACT/AGENT_VAULT_CONTRACT_ID environment variables is deprecated and will be removed in the next minor release \u2014 pass the vaultSettlement option instead",
1337
+ {}
1338
+ );
1339
+ return { contract: fromEnv };
1340
+ }
559
1341
  function channelAuthorizer(envelopeSigner) {
560
1342
  return { envelopeSigner };
561
1343
  }
@@ -634,6 +1416,7 @@ function createMppSessionHandler(opts) {
634
1416
  let record = null;
635
1417
  let voucherCount = 0;
636
1418
  let sessionOpened = false;
1419
+ let orphanFlagged = false;
637
1420
  let idleTimer = null;
638
1421
  let settledCleanly = false;
639
1422
  let closeListenerArmed = false;
@@ -659,6 +1442,14 @@ function createMppSessionHandler(opts) {
659
1442
  return null;
660
1443
  }
661
1444
  record = { amount: BigInt(stored.amount), signature: stored.signature, payer: stored.payer ?? null };
1445
+ if (typeof stored.voucherCount === "number") {
1446
+ voucherCount = stored.voucherCount;
1447
+ }
1448
+ if (typeof stored.sessionOpened === "boolean") {
1449
+ sessionOpened = stored.sessionOpened;
1450
+ } else if (record !== null) {
1451
+ sessionOpened = true;
1452
+ }
662
1453
  } catch {
663
1454
  return null;
664
1455
  }
@@ -666,8 +1457,8 @@ function createMppSessionHandler(opts) {
666
1457
  }
667
1458
  async function flagOrphan(reason) {
668
1459
  await loadPersistedRecord();
669
- if (!sessionOpened || settledCleanly) return;
670
- sessionOpened = false;
1460
+ if (!sessionOpened || settledCleanly || orphanFlagged) return;
1461
+ orphanFlagged = true;
671
1462
  closeListenerArmed = false;
672
1463
  clearIdleTimer();
673
1464
  const cumulativeAmount = record ? (Number(record.amount) / 1e7).toFixed(7) : "0.0000000";
@@ -715,16 +1506,20 @@ function createMppSessionHandler(opts) {
715
1506
  if (record && amount < record.amount) return;
716
1507
  const payer = record?.payer ?? extractPayerAddress(credential.source);
717
1508
  record = { amount, signature: payload.signature, payer };
1509
+ voucherCount++;
1510
+ settledCleanly = false;
1511
+ orphanFlagged = false;
1512
+ armIdleTimer();
1513
+ const wasOpened = sessionOpened;
1514
+ sessionOpened = true;
718
1515
  await innerStore.put(voucherRecordKey, {
719
1516
  amount: record.amount.toString(),
720
1517
  signature: record.signature,
721
- payer: record.payer
1518
+ payer: record.payer,
1519
+ voucherCount,
1520
+ sessionOpened: true
722
1521
  });
723
- voucherCount++;
724
- settledCleanly = false;
725
- armIdleTimer();
726
- if (!sessionOpened) {
727
- sessionOpened = true;
1522
+ if (!wasOpened) {
728
1523
  if (opts.onSessionOpen) {
729
1524
  Promise.resolve().then(() => opts.onSessionOpen(opts.channelFactory, record.payer)).catch((err) => {
730
1525
  logger("error", "[mpp-session] onSessionOpen callback error", { error: err });
@@ -776,10 +1571,11 @@ function createMppSessionHandler(opts) {
776
1571
  }
777
1572
  if (closeAmount > 0n && closeSig) {
778
1573
  const closePayer = record?.payer ?? null;
1574
+ const vouchersClosed = voucherCount;
779
1575
  const closeTxHash = await server$3.close({
780
1576
  channel: opts.channelFactory,
781
1577
  amount: closeAmount,
782
- signature: Buffer.from(closeSig, "hex"),
1578
+ signature: hexToBytes(closeSig),
783
1579
  feePayer: channelAuthorizer(payeeKeypair),
784
1580
  network: networkId
785
1581
  });
@@ -792,46 +1588,28 @@ function createMppSessionHandler(opts) {
792
1588
  opts.onCallbackError?.(err, "onSettled");
793
1589
  });
794
1590
  }
795
- const vaultContract = process.env.AGENT_VAULT_CONTRACT;
796
- if (vaultContract) {
797
- const settlementAddresses = resolveVaultSettlementAddresses(
798
- closePayer,
799
- payeeKeypair.publicKey()
1591
+ res.json({ closeTxHash });
1592
+ const vaultConfig = resolveVaultSettlementConfig(opts.vaultSettlement, logger);
1593
+ if (vaultConfig) {
1594
+ await recordSessionSettlement(
1595
+ {
1596
+ config: vaultConfig,
1597
+ network: opts.network,
1598
+ payeeSecretKey: opts.payeeSecretKey,
1599
+ channelId: opts.channelFactory,
1600
+ payer: closePayer,
1601
+ amount: closeAmount,
1602
+ voucherCount: vouchersClosed
1603
+ },
1604
+ { logger, onCallbackError: opts.onCallbackError }
800
1605
  );
801
- if (!settlementAddresses) {
802
- logger("warn", "[mpp-session] skipped session_settled vault record: payer address unavailable");
803
- } else {
804
- try {
805
- const { Networks } = await import('@stellar/stellar-sdk');
806
- const { Server } = await import('@stellar/stellar-sdk/rpc');
807
- const networkPassphrase = opts.network === "mainnet" ? Networks.PUBLIC : Networks.TESTNET;
808
- const rpcUrl = opts.network === "mainnet" ? "https://mainnet.sorobanrpc.com" : "https://soroban-testnet.stellar.org";
809
- const server = new Server(rpcUrl);
810
- const payeePublicKey = payeeKeypair.publicKey();
811
- const sourceAccount = await server.getAccount(payeePublicKey);
812
- const tx = buildSessionSettlementTransaction(
813
- sourceAccount,
814
- vaultContract,
815
- opts.channelFactory,
816
- settlementAddresses,
817
- closeAmount,
818
- voucherCount,
819
- networkPassphrase
820
- );
821
- const preparedTx = await server.prepareTransaction(tx);
822
- preparedTx.sign(payeeKeypair);
823
- await server.sendTransaction(preparedTx);
824
- } catch (recordErr) {
825
- logger("error", "[mpp-session] failed to record session_settled on vault", { error: recordErr });
826
- }
827
- }
828
1606
  }
829
- res.json({ closeTxHash });
830
1607
  } else {
831
1608
  res.json({ closeTxHash: null, message: "no vouchers received" });
832
1609
  }
833
1610
  sessionOpened = false;
834
1611
  voucherCount = 0;
1612
+ orphanFlagged = false;
835
1613
  record = null;
836
1614
  await innerStore.delete(voucherRecordKey);
837
1615
  closeListenerArmed = false;
@@ -863,6 +1641,11 @@ function createMppSessionHandler(opts) {
863
1641
  }
864
1642
  };
865
1643
  }
1644
+ function sha256(data) {
1645
+ return sha2_js.sha256(typeof data === "string" ? utf8ToBytes(data) : data);
1646
+ }
1647
+
1648
+ // src/manifest/sign.ts
866
1649
  var MANIFEST_SIGNATURE_VERSION = "2";
867
1650
  function canonicalize(value) {
868
1651
  if (Array.isArray(value)) {
@@ -878,7 +1661,7 @@ function canonicalize(value) {
878
1661
  function manifestDigest(manifest) {
879
1662
  const { signature: _omit, ...rest } = manifest;
880
1663
  const canonical = JSON.stringify(canonicalize(rest));
881
- return crypto$1.createHash("sha256").update(canonical, "utf8").digest();
1664
+ return sha256(utf8ToBytes(canonical));
882
1665
  }
883
1666
  function signManifest(manifest, payeeSecretKey) {
884
1667
  const keypair = stellarSdk.Keypair.fromSecret(payeeSecretKey);
@@ -888,7 +1671,7 @@ function signManifest(manifest, payeeSecretKey) {
888
1671
  };
889
1672
  const digest = manifestDigest(versionedManifest);
890
1673
  const sig = keypair.sign(digest);
891
- return { ...versionedManifest, signature: Buffer.from(sig).toString("base64") };
1674
+ return { ...versionedManifest, signature: bytesToBase64(sig) };
892
1675
  }
893
1676
 
894
1677
  // src/provider/fastify.ts
@@ -1004,6 +1787,7 @@ function runExpressHandler(handler, fastifyRequest, fastifyReply) {
1004
1787
  }
1005
1788
  var MPP_MODES = ["mpp-session", "mpp-charge"];
1006
1789
  function routedockFastify(opts) {
1790
+ assertProviderConfig(opts, "routedockFastify");
1007
1791
  const signedManifest = signManifest(opts.manifest, opts.payeeSecretKey);
1008
1792
  const handlerMap = /* @__PURE__ */ new Map();
1009
1793
  if (opts.modes.includes("x402")) {
@@ -1067,7 +1851,8 @@ function routedockFastify(opts) {
1067
1851
  ...opts.onCallbackError ? { onCallbackError: opts.onCallbackError } : {},
1068
1852
  ...opts.onOrphaned ? { onOrphaned: opts.onOrphaned } : {},
1069
1853
  ...opts.idleTimeoutMs != null ? { idleTimeoutMs: opts.idleTimeoutMs } : {},
1070
- ...opts.logger ? { logger: opts.logger } : {}
1854
+ ...opts.logger ? { logger: opts.logger } : {},
1855
+ ...opts.vaultSettlement ? { vaultSettlement: opts.vaultSettlement } : {}
1071
1856
  })
1072
1857
  );
1073
1858
  }