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