@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.
- package/README.md +113 -12
- package/dist/{MppSessionHandler-1EL8QBAn.d.cts → MppSessionHandler-3QfCHI5x.d.ts} +40 -2
- package/dist/{MppSessionHandler-Bq4S-JTE.d.ts → MppSessionHandler-xu4Kxu6N.d.cts} +40 -2
- package/dist/{ProviderRegistry-w4_hkI_1.d.ts → ProviderRegistry-D9KqyuH-.d.cts} +16 -36
- package/dist/{ProviderRegistry-CyZ5eryO.d.cts → ProviderRegistry-DVi2uzgk.d.ts} +16 -36
- package/dist/{RouteDockClient-CjM2VyOd.d.ts → RouteDockClient-6rmgkbYb.d.cts} +124 -144
- package/dist/{RouteDockClient-CXvWXix5.d.cts → RouteDockClient-CnAkxwrW.d.ts} +124 -144
- package/dist/{SessionReconciler-Bw-PoBVl.d.ts → SessionReconciler-Cce9MXO1.d.ts} +10 -3
- package/dist/{SessionReconciler-Cz2nuOlJ.d.cts → SessionReconciler-Dpe9qXdD.d.cts} +10 -3
- package/dist/{SessionStore-BaYiegP0.d.ts → SessionStore-DkLBxFQu.d.cts} +28 -3
- package/dist/{SessionStore-B4-Hi4H8.d.cts → SessionStore-_ZkoBzFY.d.ts} +28 -3
- package/dist/SpendStore-BUclV02i.d.ts +73 -0
- package/dist/SpendStore-s_dfcemj.d.cts +73 -0
- package/dist/client.cjs +767 -482
- package/dist/client.d.cts +80 -10
- package/dist/client.d.ts +80 -10
- package/dist/client.js +733 -463
- package/dist/index.cjs +1295 -585
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/index.js +1247 -554
- package/dist/logger-DVa7VT4c.d.cts +41 -0
- package/dist/logger-DVa7VT4c.d.ts +41 -0
- package/dist/network.cjs +57 -0
- package/dist/network.d.cts +72 -0
- package/dist/network.d.ts +72 -0
- package/dist/network.js +49 -0
- package/dist/node.cjs +166 -0
- package/dist/node.d.cts +77 -0
- package/dist/node.d.ts +77 -0
- package/dist/node.js +159 -0
- package/dist/provider/fastify.cjs +847 -62
- package/dist/provider/fastify.d.cts +10 -3
- package/dist/provider/fastify.d.ts +10 -3
- package/dist/provider/fastify.js +848 -63
- package/dist/provider/hono.cjs +936 -60
- package/dist/provider/hono.d.cts +23 -8
- package/dist/provider/hono.d.ts +23 -8
- package/dist/provider/hono.js +937 -61
- package/dist/provider.cjs +894 -70
- package/dist/provider.d.cts +9 -8
- package/dist/provider.d.ts +9 -8
- package/dist/provider.js +895 -72
- package/dist/react.cjs +736 -431
- package/dist/react.d.cts +7 -5
- package/dist/react.d.ts +7 -5
- package/dist/react.js +725 -420
- package/dist/{routedockMiddleware-57gUaEMf.d.ts → routedockMiddleware-Cq9HOJEa.d.ts} +9 -2
- package/dist/{routedockMiddleware-DlyLrxp7.d.cts → routedockMiddleware-CxsWkSKa.d.cts} +9 -2
- package/dist/schema.cjs +7 -0
- package/dist/schema.js +7 -0
- package/dist/{sign-AKH67sft.d.cts → sign-Bq08SeFq.d.ts} +2 -2
- package/dist/{sign-BEq-OsSq.d.ts → sign-DqGE0Plv.d.cts} +2 -2
- package/dist/testing.d.cts +4 -3
- package/dist/testing.d.ts +4 -3
- package/dist/{logger-D8_Q2ytp.d.cts → types-DXaS5Wip.d.cts} +85 -47
- package/dist/{logger-D8_Q2ytp.d.ts → types-DXaS5Wip.d.ts} +85 -47
- package/package.json +50 -4
package/dist/provider.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
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 {
|
|
13
|
+
import { sha256 as sha256$1 } from '@noble/hashes/sha2.js';
|
|
13
14
|
|
|
14
15
|
// src/errors.ts
|
|
15
16
|
var RouteDockError = class extends Error {
|
|
@@ -196,13 +197,618 @@ function isAssetEligible(manifest, assetIdentifier, mode, endpoint) {
|
|
|
196
197
|
(asset) => asset.asset === assetIdentifier || asset.asset_contract === assetIdentifier
|
|
197
198
|
);
|
|
198
199
|
}
|
|
200
|
+
var usdcToUnits = usdcToStroops;
|
|
201
|
+
|
|
202
|
+
// src/schemas/routedock.schema.json
|
|
203
|
+
var routedock_schema_default = {
|
|
204
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
205
|
+
$id: "https://routedock.xyz/schemas/routedock.schema.json",
|
|
206
|
+
title: "RouteDock Manifest",
|
|
207
|
+
description: "Discovery manifest served at /.well-known/routedock.json on every RouteDock provider endpoint",
|
|
208
|
+
type: "object",
|
|
209
|
+
required: [
|
|
210
|
+
"routedock",
|
|
211
|
+
"name",
|
|
212
|
+
"description",
|
|
213
|
+
"modes",
|
|
214
|
+
"network",
|
|
215
|
+
"asset",
|
|
216
|
+
"asset_contract",
|
|
217
|
+
"payee",
|
|
218
|
+
"pricing",
|
|
219
|
+
"endpoints",
|
|
220
|
+
"tags"
|
|
221
|
+
],
|
|
222
|
+
additionalProperties: false,
|
|
223
|
+
properties: {
|
|
224
|
+
routedock: {
|
|
225
|
+
type: "string",
|
|
226
|
+
pattern: "^[0-9]+\\.[0-9]+$",
|
|
227
|
+
description: "Manifest schema version, e.g. '1.0', '1.1', '2.0'."
|
|
228
|
+
},
|
|
229
|
+
min_client_version: {
|
|
230
|
+
type: "string",
|
|
231
|
+
pattern: "^[0-9]+\\.[0-9]+$",
|
|
232
|
+
description: "Minimum SDK client version (major.minor) required to use this manifest. Clients below this version must refuse to pay."
|
|
233
|
+
},
|
|
234
|
+
deprecated_modes: {
|
|
235
|
+
type: "array",
|
|
236
|
+
items: {
|
|
237
|
+
$ref: "#/definitions/PaymentMode"
|
|
238
|
+
},
|
|
239
|
+
uniqueItems: true,
|
|
240
|
+
description: "Payment modes still listed in 'modes' for backwards compatibility but deprecated for new integrations."
|
|
241
|
+
},
|
|
242
|
+
sunset_at: {
|
|
243
|
+
type: "string",
|
|
244
|
+
format: "date-time",
|
|
245
|
+
description: "ISO 8601 timestamp after which this manifest version is no longer supported by the provider."
|
|
246
|
+
},
|
|
247
|
+
name: {
|
|
248
|
+
type: "string",
|
|
249
|
+
minLength: 1,
|
|
250
|
+
maxLength: 120,
|
|
251
|
+
description: "Human-readable provider name"
|
|
252
|
+
},
|
|
253
|
+
description: {
|
|
254
|
+
type: "string",
|
|
255
|
+
minLength: 1,
|
|
256
|
+
maxLength: 500,
|
|
257
|
+
description: "Human-readable description of the provider's data or service"
|
|
258
|
+
},
|
|
259
|
+
modes: {
|
|
260
|
+
type: "array",
|
|
261
|
+
minItems: 1,
|
|
262
|
+
uniqueItems: true,
|
|
263
|
+
items: {
|
|
264
|
+
$ref: "#/definitions/PaymentMode"
|
|
265
|
+
},
|
|
266
|
+
description: "Payment modes supported by this provider"
|
|
267
|
+
},
|
|
268
|
+
network: {
|
|
269
|
+
type: "string",
|
|
270
|
+
enum: ["testnet", "mainnet"],
|
|
271
|
+
description: "Stellar network this provider operates on"
|
|
272
|
+
},
|
|
273
|
+
asset: {
|
|
274
|
+
type: "string",
|
|
275
|
+
description: "Asset ticker symbol (e.g. 'USDC')"
|
|
276
|
+
},
|
|
277
|
+
asset_contract: {
|
|
278
|
+
type: "string",
|
|
279
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
280
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
281
|
+
},
|
|
282
|
+
assets: {
|
|
283
|
+
type: "array",
|
|
284
|
+
minItems: 1,
|
|
285
|
+
items: {
|
|
286
|
+
$ref: "#/definitions/AssetConfig"
|
|
287
|
+
},
|
|
288
|
+
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."
|
|
289
|
+
},
|
|
290
|
+
payee: {
|
|
291
|
+
type: "string",
|
|
292
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
293
|
+
description: "Stellar address (G...) that receives payments"
|
|
294
|
+
},
|
|
295
|
+
pricing: {
|
|
296
|
+
type: "object",
|
|
297
|
+
description: "Pricing configuration per supported payment mode",
|
|
298
|
+
additionalProperties: false,
|
|
299
|
+
properties: {
|
|
300
|
+
x402: {
|
|
301
|
+
$ref: "#/definitions/PricingConfig"
|
|
302
|
+
},
|
|
303
|
+
"mpp-charge": {
|
|
304
|
+
$ref: "#/definitions/PricingConfig"
|
|
305
|
+
},
|
|
306
|
+
"mpp-session": {
|
|
307
|
+
$ref: "#/definitions/SessionPricingConfig"
|
|
308
|
+
},
|
|
309
|
+
"mpp-session-ws": {
|
|
310
|
+
$ref: "#/definitions/SessionPricingConfig",
|
|
311
|
+
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."
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
minProperties: 1
|
|
315
|
+
},
|
|
316
|
+
endpoints: {
|
|
317
|
+
type: "object",
|
|
318
|
+
description: "Map of endpoint name to endpoint descriptors",
|
|
319
|
+
additionalProperties: {
|
|
320
|
+
$ref: "#/definitions/EndpointDescriptor"
|
|
321
|
+
},
|
|
322
|
+
minProperties: 1
|
|
323
|
+
},
|
|
324
|
+
sla: {
|
|
325
|
+
$ref: "#/definitions/SLAConfig"
|
|
326
|
+
},
|
|
327
|
+
tags: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: {
|
|
330
|
+
type: "string",
|
|
331
|
+
minLength: 1,
|
|
332
|
+
maxLength: 64
|
|
333
|
+
},
|
|
334
|
+
uniqueItems: true,
|
|
335
|
+
description: "Capability tags indexed with trigram search in the provider registry"
|
|
336
|
+
},
|
|
337
|
+
vault: {
|
|
338
|
+
$ref: "#/definitions/VaultMode"
|
|
339
|
+
},
|
|
340
|
+
nulth_account: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "Optional Nulth smart account address associated with this provider"
|
|
343
|
+
},
|
|
344
|
+
capabilities: {
|
|
345
|
+
type: "object",
|
|
346
|
+
description: "Optional protocol features this provider supports",
|
|
347
|
+
properties: {
|
|
348
|
+
streaming: {
|
|
349
|
+
type: "array",
|
|
350
|
+
items: {
|
|
351
|
+
type: "string",
|
|
352
|
+
enum: ["sse", "websocket"]
|
|
353
|
+
},
|
|
354
|
+
description: "Supported streaming transports"
|
|
355
|
+
},
|
|
356
|
+
webhooks: {
|
|
357
|
+
type: "boolean",
|
|
358
|
+
description: "Whether webhook callbacks are supported"
|
|
359
|
+
},
|
|
360
|
+
idempotency_keys: {
|
|
361
|
+
type: "boolean",
|
|
362
|
+
description: "Whether idempotency keys are supported"
|
|
363
|
+
},
|
|
364
|
+
content_types: {
|
|
365
|
+
type: "array",
|
|
366
|
+
items: {
|
|
367
|
+
type: "string"
|
|
368
|
+
},
|
|
369
|
+
description: "Supported content types for request/response"
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
additionalProperties: false
|
|
373
|
+
},
|
|
374
|
+
regions: {
|
|
375
|
+
type: "array",
|
|
376
|
+
items: {
|
|
377
|
+
type: "string",
|
|
378
|
+
minLength: 2,
|
|
379
|
+
maxLength: 10
|
|
380
|
+
},
|
|
381
|
+
uniqueItems: true,
|
|
382
|
+
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."
|
|
383
|
+
},
|
|
384
|
+
latency_hints: {
|
|
385
|
+
type: "object",
|
|
386
|
+
additionalProperties: {
|
|
387
|
+
type: "number",
|
|
388
|
+
minimum: 0
|
|
389
|
+
},
|
|
390
|
+
description: "Provider-declared p50 latency in milliseconds per region code. Keys must be a subset of 'regions'. Example: { 'IAD': 14, 'AMS': 22 }."
|
|
391
|
+
},
|
|
392
|
+
signature: {
|
|
393
|
+
type: "string",
|
|
394
|
+
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."
|
|
395
|
+
},
|
|
396
|
+
signature_version: {
|
|
397
|
+
type: "string",
|
|
398
|
+
const: "2",
|
|
399
|
+
description: "Manifest signature format. Version 2 recursively includes every nested manifest field in the signed digest."
|
|
400
|
+
},
|
|
401
|
+
categories: {
|
|
402
|
+
type: "array",
|
|
403
|
+
items: {
|
|
404
|
+
type: "string",
|
|
405
|
+
minLength: 1,
|
|
406
|
+
maxLength: 128
|
|
407
|
+
},
|
|
408
|
+
uniqueItems: true,
|
|
409
|
+
description: "Optional hierarchical categories from a published taxonomy (e.g. data/price/crypto)"
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
definitions: {
|
|
413
|
+
VaultMode: {
|
|
414
|
+
type: "string",
|
|
415
|
+
enum: ["local-key", "agent-vault", "nulth"],
|
|
416
|
+
description: "Agent vault / custody mode for payments from this provider"
|
|
417
|
+
},
|
|
418
|
+
PaymentMode: {
|
|
419
|
+
type: "string",
|
|
420
|
+
enum: ["x402", "mpp-charge", "mpp-session", "mpp-session-ws"],
|
|
421
|
+
description: "A supported RouteDock payment mode"
|
|
422
|
+
},
|
|
423
|
+
AssetConfig: {
|
|
424
|
+
type: "object",
|
|
425
|
+
description: "Payment asset configuration with optional mode and endpoint scoping",
|
|
426
|
+
required: ["asset", "asset_contract"],
|
|
427
|
+
additionalProperties: false,
|
|
428
|
+
properties: {
|
|
429
|
+
asset: {
|
|
430
|
+
type: "string",
|
|
431
|
+
description: "Asset ticker symbol (e.g. 'USDC' or 'XLM')"
|
|
432
|
+
},
|
|
433
|
+
asset_contract: {
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
436
|
+
},
|
|
437
|
+
modes: {
|
|
438
|
+
type: "array",
|
|
439
|
+
items: {
|
|
440
|
+
$ref: "#/definitions/PaymentMode"
|
|
441
|
+
},
|
|
442
|
+
uniqueItems: true,
|
|
443
|
+
description: "Optional: restrict this asset to specific payment modes. If omitted, available for all modes."
|
|
444
|
+
},
|
|
445
|
+
endpoints: {
|
|
446
|
+
type: "array",
|
|
447
|
+
items: {
|
|
448
|
+
type: "string"
|
|
449
|
+
},
|
|
450
|
+
uniqueItems: true,
|
|
451
|
+
description: "Optional: restrict this asset to specific endpoints (by name). If omitted, available for all endpoints."
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
PricingConfig: {
|
|
456
|
+
type: "object",
|
|
457
|
+
description: "Per-request pricing for x402 or mpp-charge modes",
|
|
458
|
+
required: ["amount", "per"],
|
|
459
|
+
additionalProperties: false,
|
|
460
|
+
properties: {
|
|
461
|
+
amount: {
|
|
462
|
+
type: "string",
|
|
463
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
464
|
+
description: "Cost per request in the payment asset (decimal string, e.g. '0.001')"
|
|
465
|
+
},
|
|
466
|
+
per: {
|
|
467
|
+
type: "string",
|
|
468
|
+
const: "request",
|
|
469
|
+
description: "Billing unit \u2014 always 'request' for non-session modes"
|
|
470
|
+
},
|
|
471
|
+
facilitator: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "x402 facilitator URL (required for x402 mode). OpenZeppelin Channels endpoint."
|
|
474
|
+
},
|
|
475
|
+
payee: {
|
|
476
|
+
type: "string",
|
|
477
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
478
|
+
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."
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
SessionPricingConfig: {
|
|
483
|
+
type: "object",
|
|
484
|
+
description: "Per-voucher pricing for mpp-session (one-way-channel) mode",
|
|
485
|
+
required: ["rate", "per", "channel_factory", "min_deposit", "refund_waiting_period_ledgers"],
|
|
486
|
+
additionalProperties: false,
|
|
487
|
+
properties: {
|
|
488
|
+
rate: {
|
|
489
|
+
type: "string",
|
|
490
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
491
|
+
description: "Cost per voucher in the payment asset (decimal string, e.g. '0.0001')"
|
|
492
|
+
},
|
|
493
|
+
per: {
|
|
494
|
+
type: "string",
|
|
495
|
+
const: "voucher",
|
|
496
|
+
description: "Billing unit \u2014 always 'voucher' for session mode"
|
|
497
|
+
},
|
|
498
|
+
channel_factory: {
|
|
499
|
+
type: "string",
|
|
500
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
501
|
+
description: "Stable Soroban factory address (C...) used to deploy a per-session channel"
|
|
502
|
+
},
|
|
503
|
+
min_deposit: {
|
|
504
|
+
type: "string",
|
|
505
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
506
|
+
description: "Minimum initial deposit to open a channel (decimal string)"
|
|
507
|
+
},
|
|
508
|
+
refund_waiting_period_ledgers: {
|
|
509
|
+
type: "integer",
|
|
510
|
+
minimum: 1,
|
|
511
|
+
description: "Ledgers the server has to settle before the funder can reclaim. Default: 17280 (~24h). Do not reduce in production."
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
SLAConfig: {
|
|
516
|
+
type: "object",
|
|
517
|
+
description: "Service Level Agreement for the provider",
|
|
518
|
+
required: ["uptime_30d_percent", "p95_latency_ms"],
|
|
519
|
+
additionalProperties: false,
|
|
520
|
+
properties: {
|
|
521
|
+
uptime_30d_percent: {
|
|
522
|
+
type: "number",
|
|
523
|
+
description: "Uptime percentage over the last 30 days"
|
|
524
|
+
},
|
|
525
|
+
p95_latency_ms: {
|
|
526
|
+
type: "number",
|
|
527
|
+
description: "P95 latency in milliseconds"
|
|
528
|
+
},
|
|
529
|
+
maintenance_windows: {
|
|
530
|
+
type: "array",
|
|
531
|
+
items: {
|
|
532
|
+
type: "object",
|
|
533
|
+
required: ["cron", "duration_minutes"],
|
|
534
|
+
additionalProperties: false,
|
|
535
|
+
properties: {
|
|
536
|
+
cron: {
|
|
537
|
+
type: "string",
|
|
538
|
+
description: "Cron expression for the maintenance window"
|
|
539
|
+
},
|
|
540
|
+
duration_minutes: {
|
|
541
|
+
type: "number",
|
|
542
|
+
description: "Duration of the maintenance window in minutes"
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
EndpointDescriptor: {
|
|
550
|
+
type: "object",
|
|
551
|
+
description: "Descriptor for a provider endpoint",
|
|
552
|
+
required: ["method", "path"],
|
|
553
|
+
additionalProperties: false,
|
|
554
|
+
properties: {
|
|
555
|
+
method: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: "HTTP method for the endpoint"
|
|
558
|
+
},
|
|
559
|
+
path: {
|
|
560
|
+
type: "string",
|
|
561
|
+
description: "Path for the endpoint"
|
|
562
|
+
},
|
|
563
|
+
deprecated: {
|
|
564
|
+
type: "boolean",
|
|
565
|
+
description: "Whether this endpoint is retained only for backwards compatibility"
|
|
566
|
+
},
|
|
567
|
+
sunset_at: {
|
|
568
|
+
type: "string",
|
|
569
|
+
format: "date-time",
|
|
570
|
+
description: "ISO 8601 timestamp after which callers should stop using this endpoint"
|
|
571
|
+
},
|
|
572
|
+
headers: {
|
|
573
|
+
type: "object",
|
|
574
|
+
additionalProperties: {
|
|
575
|
+
type: "string"
|
|
576
|
+
},
|
|
577
|
+
description: "Expected request headers"
|
|
578
|
+
},
|
|
579
|
+
request_schema: {
|
|
580
|
+
description: "JSON schema for the request body"
|
|
581
|
+
},
|
|
582
|
+
response_schema: {
|
|
583
|
+
description: "JSON schema for the response body"
|
|
584
|
+
},
|
|
585
|
+
rate_limit: {
|
|
586
|
+
type: "object",
|
|
587
|
+
required: ["requests", "window_seconds"],
|
|
588
|
+
additionalProperties: false,
|
|
589
|
+
properties: {
|
|
590
|
+
requests: {
|
|
591
|
+
type: "number"
|
|
592
|
+
},
|
|
593
|
+
window_seconds: {
|
|
594
|
+
type: "number"
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// src/manifest/validate.ts
|
|
604
|
+
var validator = new Validator(routedock_schema_default, "7");
|
|
605
|
+
function validateManifestSchema(manifest, source) {
|
|
606
|
+
const result = validator.validate(manifest);
|
|
607
|
+
if (!result.valid) {
|
|
608
|
+
const msgs = result.errors.map((err) => err.error).join("; ");
|
|
609
|
+
throw new RouteDockManifestError(`Invalid manifest${""}: ${msgs}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function assertManifestValid(manifest, baseUrl) {
|
|
613
|
+
const context = "";
|
|
614
|
+
if (manifest.latency_hints !== void 0) {
|
|
615
|
+
if (!Array.isArray(manifest.regions) || manifest.regions.length === 0) {
|
|
616
|
+
throw new RouteDockManifestError(
|
|
617
|
+
`Invalid manifest${context}: latency_hints defined without declaring regions`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const regionSet = new Set(manifest.regions);
|
|
621
|
+
for (const region of Object.keys(manifest.latency_hints)) {
|
|
622
|
+
if (!regionSet.has(region)) {
|
|
623
|
+
throw new RouteDockManifestError(
|
|
624
|
+
`Invalid manifest${context}: latency_hints key '${region}' is not declared in regions (${manifest.regions.join(", ")})`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (manifest.assets !== void 0) {
|
|
630
|
+
if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
|
|
631
|
+
throw new RouteDockManifestError(
|
|
632
|
+
`Invalid manifest${context}: assets must be a non-empty array when defined`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
for (let i = 0; i < manifest.assets.length; i++) {
|
|
636
|
+
const a = manifest.assets[i];
|
|
637
|
+
if (!a || typeof a.asset !== "string" || typeof a.asset_contract !== "string") {
|
|
638
|
+
throw new RouteDockManifestError(
|
|
639
|
+
`Invalid manifest${context}: assets[${i}] must define asset and asset_contract strings`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const first = manifest.assets[0];
|
|
644
|
+
if (first.asset !== manifest.asset || first.asset_contract !== manifest.asset_contract) {
|
|
645
|
+
throw new RouteDockManifestError(
|
|
646
|
+
`Invalid manifest${context}: assets[0] (${first.asset}:${first.asset_contract}) must match root asset fields (${manifest.asset}:${manifest.asset_contract})`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
assertPricingMatchesModes(manifest, context);
|
|
651
|
+
assertDeprecatedModesListed(manifest, context);
|
|
652
|
+
assertPricesParse(manifest, context);
|
|
653
|
+
}
|
|
654
|
+
function assertPricingMatchesModes(manifest, context) {
|
|
655
|
+
const pricing = manifest.pricing ?? {};
|
|
656
|
+
for (const mode of manifest.modes ?? []) {
|
|
657
|
+
if (pricing[mode] === void 0 || pricing[mode] === null) {
|
|
658
|
+
throw new RouteDockManifestError(
|
|
659
|
+
`Invalid manifest${context}: mode '${mode}' is listed in modes but pricing.${mode} is missing`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function assertDeprecatedModesListed(manifest, context) {
|
|
665
|
+
if (manifest.deprecated_modes === void 0) return;
|
|
666
|
+
const modes = new Set(manifest.modes ?? []);
|
|
667
|
+
for (const mode of manifest.deprecated_modes) {
|
|
668
|
+
if (!modes.has(mode)) {
|
|
669
|
+
throw new RouteDockManifestError(
|
|
670
|
+
`Invalid manifest${context}: deprecated_modes entry '${mode}' is not listed in modes`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
var PRICE_FIELDS = {
|
|
676
|
+
x402: ["amount"],
|
|
677
|
+
"mpp-charge": ["amount"],
|
|
678
|
+
"mpp-session": ["rate", "min_deposit"],
|
|
679
|
+
"mpp-session-ws": ["rate", "min_deposit"]
|
|
680
|
+
};
|
|
681
|
+
function assertPricesParse(manifest, context) {
|
|
682
|
+
const pricing = manifest.pricing ?? {};
|
|
683
|
+
for (const [mode, fields] of Object.entries(PRICE_FIELDS)) {
|
|
684
|
+
const entry = pricing[mode];
|
|
685
|
+
if (entry === void 0 || entry === null) continue;
|
|
686
|
+
for (const field of fields) {
|
|
687
|
+
const value = entry[field];
|
|
688
|
+
try {
|
|
689
|
+
if (typeof value !== "string") {
|
|
690
|
+
throw new RangeError(`expected a decimal string, got ${typeof value}`);
|
|
691
|
+
}
|
|
692
|
+
usdcToStroops(value);
|
|
693
|
+
} catch (err) {
|
|
694
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
695
|
+
throw new RouteDockManifestError(
|
|
696
|
+
`Invalid manifest${context}: pricing.${mode}.${field} ${JSON.stringify(value)} is not a valid amount (${detail})`,
|
|
697
|
+
{ cause: err }
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/provider/internal/assertProviderConfig.ts
|
|
705
|
+
var FLAT_MODES = ["x402", "mpp-charge"];
|
|
706
|
+
var SESSION_MODES = ["mpp-session", "mpp-session-ws"];
|
|
707
|
+
function sameAmount(adapter, label, optionValue, manifestValue) {
|
|
708
|
+
const parse = (where, value) => {
|
|
709
|
+
try {
|
|
710
|
+
return usdcToStroops(value);
|
|
711
|
+
} catch {
|
|
712
|
+
throw new RouteDockManifestError(
|
|
713
|
+
`${adapter}: ${label} has an unparseable ${where} amount "${value}"`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
return parse("option", optionValue) === parse("manifest", manifestValue);
|
|
718
|
+
}
|
|
719
|
+
function assertProviderConfig(opts, adapterName) {
|
|
720
|
+
const { manifest } = opts;
|
|
721
|
+
if (manifest === null || typeof manifest !== "object") {
|
|
722
|
+
throw new RouteDockManifestError(`${adapterName}: opts.manifest is required and must be an object`);
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
validateManifestSchema(manifest);
|
|
726
|
+
assertManifestValid(manifest);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
if (err instanceof RouteDockManifestError) {
|
|
729
|
+
throw new RouteDockManifestError(`${adapterName}: ${err.message}`, { cause: err });
|
|
730
|
+
}
|
|
731
|
+
throw err;
|
|
732
|
+
}
|
|
733
|
+
const served = new Set(opts.modes.filter((mode) => Boolean(opts.pricing[mode])));
|
|
734
|
+
const advertised = new Set(manifest.modes);
|
|
735
|
+
const notAdvertised = [...served].filter((mode) => !advertised.has(mode));
|
|
736
|
+
const notServed = [...advertised].filter((mode) => !served.has(mode));
|
|
737
|
+
if (notAdvertised.length > 0 || notServed.length > 0) {
|
|
738
|
+
const parts = [];
|
|
739
|
+
if (notAdvertised.length > 0) parts.push(`served but not in manifest.modes: ${notAdvertised.join(", ")}`);
|
|
740
|
+
if (notServed.length > 0) parts.push(`in manifest.modes but not served (missing from modes or pricing): ${notServed.join(", ")}`);
|
|
741
|
+
throw new RouteDockManifestError(`${adapterName}: modes do not match manifest.modes (${parts.join("; ")})`);
|
|
742
|
+
}
|
|
743
|
+
for (const mode of FLAT_MODES) {
|
|
744
|
+
if (!served.has(mode)) continue;
|
|
745
|
+
const optionValue = opts.pricing[mode];
|
|
746
|
+
const manifestValue = manifest.pricing[mode]?.amount;
|
|
747
|
+
if (manifestValue === void 0) {
|
|
748
|
+
throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode}.amount is missing but ${mode} is served`);
|
|
749
|
+
}
|
|
750
|
+
if (!sameAmount(adapterName, `pricing.${mode}`, optionValue, manifestValue)) {
|
|
751
|
+
throw new RouteDockManifestError(
|
|
752
|
+
`${adapterName}: pricing.${mode} "${optionValue}" does not match manifest.pricing.${mode}.amount "${manifestValue}"`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
for (const mode of SESSION_MODES) {
|
|
757
|
+
if (!served.has(mode)) continue;
|
|
758
|
+
const optionValue = opts.pricing[mode];
|
|
759
|
+
const manifestValue = manifest.pricing[mode];
|
|
760
|
+
if (!manifestValue) {
|
|
761
|
+
throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode} is missing but ${mode} is served`);
|
|
762
|
+
}
|
|
763
|
+
if (!sameAmount(adapterName, `pricing.${mode}.rate`, optionValue.rate, manifestValue.rate)) {
|
|
764
|
+
throw new RouteDockManifestError(
|
|
765
|
+
`${adapterName}: pricing.${mode}.rate "${optionValue.rate}" does not match manifest.pricing.${mode}.rate "${manifestValue.rate}"`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
if (optionValue.channelFactory !== manifestValue.channel_factory) {
|
|
769
|
+
throw new RouteDockManifestError(
|
|
770
|
+
`${adapterName}: pricing.${mode}.channelFactory "${optionValue.channelFactory}" does not match manifest.pricing.${mode}.channel_factory "${manifestValue.channel_factory}"`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (opts.assetContract !== void 0 && opts.assetContract !== manifest.asset_contract) {
|
|
775
|
+
throw new RouteDockManifestError(
|
|
776
|
+
`${adapterName}: assetContract "${opts.assetContract}" does not match manifest.asset_contract "${manifest.asset_contract}"`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
if (opts.asset !== void 0 && opts.asset !== manifest.asset) {
|
|
780
|
+
throw new RouteDockManifestError(
|
|
781
|
+
`${adapterName}: asset "${opts.asset}" does not match manifest.asset "${manifest.asset}"`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
if (opts.network !== manifest.network) {
|
|
785
|
+
throw new RouteDockManifestError(
|
|
786
|
+
`${adapterName}: network "${opts.network}" does not match manifest.network "${manifest.network}"`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
let signerPublicKey;
|
|
790
|
+
try {
|
|
791
|
+
signerPublicKey = Keypair.fromSecret(opts.payeeSecretKey).publicKey();
|
|
792
|
+
} catch (err) {
|
|
793
|
+
throw new RouteDockManifestError(`${adapterName}: payeeSecretKey is not a valid Stellar secret key`, { cause: err });
|
|
794
|
+
}
|
|
795
|
+
if (signerPublicKey !== manifest.payee) {
|
|
796
|
+
throw new RouteDockManifestError(
|
|
797
|
+
`${adapterName}: payeeSecretKey belongs to ${signerPublicKey}, not manifest.payee ${manifest.payee}; clients would reject the signed manifest`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
if (opts.payee !== manifest.payee) {
|
|
801
|
+
throw new RouteDockManifestError(
|
|
802
|
+
`${adapterName}: payee "${opts.payee}" does not match manifest.payee "${manifest.payee}"`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
199
806
|
|
|
200
807
|
// src/internal/payee.ts
|
|
201
808
|
function resolvePayee(manifest, mode) {
|
|
202
809
|
const override = manifest.pricing[mode]?.payee;
|
|
203
810
|
return override ?? manifest.payee;
|
|
204
811
|
}
|
|
205
|
-
var usdcToUnits = usdcToStroops;
|
|
206
812
|
function extractPayerAddress(key) {
|
|
207
813
|
if (typeof key !== "string") return null;
|
|
208
814
|
return StrKey.isValidEd25519PublicKey(key) || StrKey.isValidMed25519PublicKey(key) ? key : null;
|
|
@@ -227,6 +833,18 @@ function resolveLogger(logger, fallback = consoleLogger) {
|
|
|
227
833
|
// src/provider/SeenTxStore.ts
|
|
228
834
|
var SETTLEMENT_REPLAY_WINDOW_MS = 6e4;
|
|
229
835
|
var MAX_SETTLEMENT_REPLAYS = 1;
|
|
836
|
+
async function recordSettlement(store, key, record, ctx) {
|
|
837
|
+
try {
|
|
838
|
+
await store.set(key, record);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
ctx.logger("error", `${ctx.label} seenTxStore.set failed after settlement`, { error: err });
|
|
841
|
+
try {
|
|
842
|
+
ctx.onCallbackError?.(err, "seenTxStore.set");
|
|
843
|
+
} catch (cbErr) {
|
|
844
|
+
ctx.logger("error", `${ctx.label} onCallbackError threw`, { error: cbErr });
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
230
848
|
async function checkSettlementReplay(store, key, now = Date.now()) {
|
|
231
849
|
const record = await store.get(key);
|
|
232
850
|
if (!record) return { kind: "miss" };
|
|
@@ -294,7 +912,7 @@ async function paymentIdempotencyKey(getHeader, scope) {
|
|
|
294
912
|
);
|
|
295
913
|
}
|
|
296
914
|
|
|
297
|
-
// src/
|
|
915
|
+
// src/internal/settleResult.ts
|
|
298
916
|
function readSettleResult(result) {
|
|
299
917
|
const settle = result;
|
|
300
918
|
const failed = (reason) => reason ? { ok: false, reason } : { ok: false };
|
|
@@ -444,10 +1062,10 @@ function createX402Handler(opts) {
|
|
|
444
1062
|
requirements
|
|
445
1063
|
);
|
|
446
1064
|
if (!verifyResult.isValid) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1065
|
+
await respondPaymentRequired(
|
|
1066
|
+
"Payment verification failed",
|
|
1067
|
+
verifyResult.invalidReason
|
|
1068
|
+
);
|
|
451
1069
|
return;
|
|
452
1070
|
}
|
|
453
1071
|
const settleResult = await localFacilitator.settle(
|
|
@@ -473,7 +1091,12 @@ function createX402Handler(opts) {
|
|
|
473
1091
|
if (typeof paymentResponse === "string") {
|
|
474
1092
|
headers["X-Payment-Response"] = paymentResponse;
|
|
475
1093
|
}
|
|
476
|
-
await
|
|
1094
|
+
await recordSettlement(
|
|
1095
|
+
seenTxStore,
|
|
1096
|
+
idempotencyKey,
|
|
1097
|
+
{ txHash, headers, createdAt: Date.now() },
|
|
1098
|
+
{ logger, label: "[x402]", onCallbackError: opts.onCallbackError }
|
|
1099
|
+
);
|
|
477
1100
|
}
|
|
478
1101
|
if (txHash && opts.onSettled) {
|
|
479
1102
|
Promise.resolve().then(() => opts.onSettled(txHash, opts.amount, "x402", payerAddress)).catch((err) => {
|
|
@@ -492,6 +1115,51 @@ function createX402Handler(opts) {
|
|
|
492
1115
|
}
|
|
493
1116
|
};
|
|
494
1117
|
}
|
|
1118
|
+
|
|
1119
|
+
// src/internal/encoding.ts
|
|
1120
|
+
var encoder = new TextEncoder();
|
|
1121
|
+
var decoder = new TextDecoder();
|
|
1122
|
+
function utf8ToBytes(text) {
|
|
1123
|
+
return encoder.encode(text);
|
|
1124
|
+
}
|
|
1125
|
+
function bytesToUtf8(bytes) {
|
|
1126
|
+
return decoder.decode(bytes);
|
|
1127
|
+
}
|
|
1128
|
+
function bytesToBase64(bytes) {
|
|
1129
|
+
const chunk = 32768;
|
|
1130
|
+
let binary = "";
|
|
1131
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
1132
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
1133
|
+
}
|
|
1134
|
+
return btoa(binary);
|
|
1135
|
+
}
|
|
1136
|
+
function base64ToBytes(b64) {
|
|
1137
|
+
const end = b64.indexOf("=");
|
|
1138
|
+
let clean = (end === -1 ? b64 : b64.slice(0, end)).replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
|
|
1139
|
+
if (clean.length % 4 === 1) clean = clean.slice(0, -1);
|
|
1140
|
+
const binary = atob(clean);
|
|
1141
|
+
const bytes = new Uint8Array(binary.length);
|
|
1142
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1143
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1144
|
+
}
|
|
1145
|
+
return bytes;
|
|
1146
|
+
}
|
|
1147
|
+
function base64ToUtf8(b64) {
|
|
1148
|
+
return bytesToUtf8(base64ToBytes(b64));
|
|
1149
|
+
}
|
|
1150
|
+
function hexToBytes(hex) {
|
|
1151
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1152
|
+
if (clean.length % 2 !== 0) {
|
|
1153
|
+
throw new Error("hexToBytes: hex string must have an even length");
|
|
1154
|
+
}
|
|
1155
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
1156
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1157
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1158
|
+
}
|
|
1159
|
+
return bytes;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// src/provider/MppChargeHandler.ts
|
|
495
1163
|
var MPP_NETWORK = {
|
|
496
1164
|
testnet: "stellar:testnet",
|
|
497
1165
|
mainnet: "stellar:pubnet"
|
|
@@ -538,7 +1206,7 @@ function createMppChargeHandler(opts) {
|
|
|
538
1206
|
const credPart = authHeader.replace(/^Payment\s+/, "").split(",").find((p) => p.trim().startsWith("credential="));
|
|
539
1207
|
if (credPart) {
|
|
540
1208
|
const b64 = credPart.split("=").slice(1).join("=").replace(/^"|"$/g, "");
|
|
541
|
-
const credJson =
|
|
1209
|
+
const credJson = base64ToUtf8(b64);
|
|
542
1210
|
const cred = JSON.parse(credJson);
|
|
543
1211
|
const key = cred.sender ?? cred.payload?.sender ?? cred.payload?.from;
|
|
544
1212
|
payerAddress = extractPayerAddress(key);
|
|
@@ -600,18 +1268,19 @@ function createMppChargeHandler(opts) {
|
|
|
600
1268
|
if (receiptHeader) {
|
|
601
1269
|
try {
|
|
602
1270
|
const parsed = JSON.parse(
|
|
603
|
-
|
|
1271
|
+
base64ToUtf8(receiptHeader)
|
|
604
1272
|
);
|
|
605
1273
|
reference = parsed.reference;
|
|
606
1274
|
} catch {
|
|
607
1275
|
}
|
|
608
1276
|
}
|
|
609
1277
|
if (idempotencyKey) {
|
|
610
|
-
await
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
createdAt: Date.now()
|
|
614
|
-
|
|
1278
|
+
await recordSettlement(
|
|
1279
|
+
seenTxStore,
|
|
1280
|
+
idempotencyKey,
|
|
1281
|
+
{ txHash: reference ?? null, headers: receiptHeaders, createdAt: Date.now() },
|
|
1282
|
+
{ logger, label: "[mpp-charge]", onCallbackError: opts.onCallbackError }
|
|
1283
|
+
);
|
|
615
1284
|
}
|
|
616
1285
|
if (reference && opts.onSettled) {
|
|
617
1286
|
Promise.resolve().then(() => opts.onSettled(reference, opts.amount, "mpp-charge", payerAddress)).catch((err) => {
|
|
@@ -625,6 +1294,14 @@ function createMppChargeHandler(opts) {
|
|
|
625
1294
|
}
|
|
626
1295
|
};
|
|
627
1296
|
}
|
|
1297
|
+
|
|
1298
|
+
// src/provider/internal/vaultRpc.ts
|
|
1299
|
+
async function createSorobanRpcServer(rpcUrl) {
|
|
1300
|
+
const { Server } = await import('@stellar/stellar-sdk/rpc');
|
|
1301
|
+
return new Server(rpcUrl);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/provider/internal/vaultSettlement.ts
|
|
628
1305
|
function resolveVaultSettlementAddresses(payer, payee) {
|
|
629
1306
|
if (!payer) return null;
|
|
630
1307
|
return { payer, payee };
|
|
@@ -644,6 +1321,111 @@ function buildSessionSettlementTransaction(source, vaultContractId, channelId, a
|
|
|
644
1321
|
networkPassphrase
|
|
645
1322
|
}).addOperation(op).setTimeout(30).build();
|
|
646
1323
|
}
|
|
1324
|
+
var DEFAULT_RPC_URLS = {
|
|
1325
|
+
testnet: "https://soroban-testnet.stellar.org",
|
|
1326
|
+
mainnet: "https://mainnet.sorobanrpc.com"
|
|
1327
|
+
};
|
|
1328
|
+
var CONFIRM_TIMEOUT_MS = 12e4;
|
|
1329
|
+
var CONFIRM_POLL_MS = 1e3;
|
|
1330
|
+
function delay(ms) {
|
|
1331
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1332
|
+
}
|
|
1333
|
+
function describeSendError(sent) {
|
|
1334
|
+
const errorResult = sent.errorResult;
|
|
1335
|
+
try {
|
|
1336
|
+
const name = errorResult?.result?.().switch?.().name;
|
|
1337
|
+
if (name) return `sendTransaction returned ERROR: ${name}`;
|
|
1338
|
+
} catch {
|
|
1339
|
+
}
|
|
1340
|
+
return `sendTransaction returned status ${sent.status}`;
|
|
1341
|
+
}
|
|
1342
|
+
async function waitForConfirmation(server, txHash) {
|
|
1343
|
+
const deadline = Date.now() + CONFIRM_TIMEOUT_MS;
|
|
1344
|
+
for (; ; ) {
|
|
1345
|
+
const tx = await server.getTransaction(txHash);
|
|
1346
|
+
if (tx.status === "SUCCESS") return "success";
|
|
1347
|
+
if (tx.status === "FAILED") return "failed";
|
|
1348
|
+
if (Date.now() >= deadline) return "timeout";
|
|
1349
|
+
await delay(CONFIRM_POLL_MS);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
async function recordSessionSettlement(request, { logger, onCallbackError }) {
|
|
1353
|
+
const reportFailure = (fields) => {
|
|
1354
|
+
logger("error", "[vault] failed to record session_settled", {
|
|
1355
|
+
channelId: request.channelId,
|
|
1356
|
+
...fields.txHash ? { txHash: fields.txHash } : {},
|
|
1357
|
+
reason: fields.reason,
|
|
1358
|
+
...fields.error !== void 0 ? { error: fields.error } : {}
|
|
1359
|
+
});
|
|
1360
|
+
if (onCallbackError) {
|
|
1361
|
+
const err = fields.error ?? new Error(`session_settled vault record failed: ${fields.reason}`);
|
|
1362
|
+
onCallbackError(err, "vaultSettlement");
|
|
1363
|
+
}
|
|
1364
|
+
return { ok: false, reason: fields.reason, ...fields.txHash ? { txHash: fields.txHash } : {} };
|
|
1365
|
+
};
|
|
1366
|
+
try {
|
|
1367
|
+
const payeeKeypair = Keypair.fromSecret(request.payeeSecretKey);
|
|
1368
|
+
const addresses = resolveVaultSettlementAddresses(request.payer, payeeKeypair.publicKey());
|
|
1369
|
+
if (!addresses) {
|
|
1370
|
+
logger("warn", "[vault] skipped session_settled record: payer address unavailable", {
|
|
1371
|
+
channelId: request.channelId
|
|
1372
|
+
});
|
|
1373
|
+
return { ok: false, reason: "payer address unavailable" };
|
|
1374
|
+
}
|
|
1375
|
+
const networkPassphrase = request.network === "mainnet" ? Networks.PUBLIC : Networks.TESTNET;
|
|
1376
|
+
const rpcUrl = request.config.rpcUrl ?? DEFAULT_RPC_URLS[request.network];
|
|
1377
|
+
const server = await createSorobanRpcServer(rpcUrl);
|
|
1378
|
+
const sourceAccount = await server.getAccount(payeeKeypair.publicKey());
|
|
1379
|
+
const tx = buildSessionSettlementTransaction(
|
|
1380
|
+
sourceAccount,
|
|
1381
|
+
request.config.contract,
|
|
1382
|
+
request.channelId,
|
|
1383
|
+
addresses,
|
|
1384
|
+
request.amount,
|
|
1385
|
+
request.voucherCount,
|
|
1386
|
+
networkPassphrase
|
|
1387
|
+
);
|
|
1388
|
+
const preparedTx = await server.prepareTransaction(tx);
|
|
1389
|
+
preparedTx.sign(payeeKeypair);
|
|
1390
|
+
const sent = await server.sendTransaction(preparedTx);
|
|
1391
|
+
if (sent.status === "ERROR") {
|
|
1392
|
+
return reportFailure({
|
|
1393
|
+
txHash: sent.hash || preparedTx.hash().toString("hex"),
|
|
1394
|
+
reason: describeSendError(sent)
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
const txHash = sent.hash || preparedTx.hash().toString("hex");
|
|
1398
|
+
const confirmation = await waitForConfirmation(server, txHash);
|
|
1399
|
+
if (confirmation === "success") {
|
|
1400
|
+
logger("debug", "[vault] recorded session_settled", {
|
|
1401
|
+
channelId: request.channelId,
|
|
1402
|
+
txHash
|
|
1403
|
+
});
|
|
1404
|
+
return { ok: true, txHash };
|
|
1405
|
+
}
|
|
1406
|
+
return reportFailure({
|
|
1407
|
+
txHash,
|
|
1408
|
+
reason: confirmation === "failed" ? "transaction failed on-chain" : `confirmation timed out after ${CONFIRM_TIMEOUT_MS}ms`
|
|
1409
|
+
});
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1412
|
+
return reportFailure({ reason, error: err });
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/internal/vaultConfig.ts
|
|
1417
|
+
function resolveVaultSettlementConfig(option, logger) {
|
|
1418
|
+
if (option) return option;
|
|
1419
|
+
if (typeof process === "undefined") return void 0;
|
|
1420
|
+
const fromEnv = process.env.AGENT_VAULT_CONTRACT ?? process.env.AGENT_VAULT_CONTRACT_ID;
|
|
1421
|
+
if (!fromEnv) return void 0;
|
|
1422
|
+
logger(
|
|
1423
|
+
"warn",
|
|
1424
|
+
"[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",
|
|
1425
|
+
{}
|
|
1426
|
+
);
|
|
1427
|
+
return { contract: fromEnv };
|
|
1428
|
+
}
|
|
647
1429
|
function channelAuthorizer(envelopeSigner) {
|
|
648
1430
|
return { envelopeSigner };
|
|
649
1431
|
}
|
|
@@ -729,6 +1511,7 @@ function createMppSessionHandler(opts) {
|
|
|
729
1511
|
let record = null;
|
|
730
1512
|
let voucherCount = 0;
|
|
731
1513
|
let sessionOpened = false;
|
|
1514
|
+
let orphanFlagged = false;
|
|
732
1515
|
let idleTimer = null;
|
|
733
1516
|
let settledCleanly = false;
|
|
734
1517
|
let closeListenerArmed = false;
|
|
@@ -754,6 +1537,14 @@ function createMppSessionHandler(opts) {
|
|
|
754
1537
|
return null;
|
|
755
1538
|
}
|
|
756
1539
|
record = { amount: BigInt(stored.amount), signature: stored.signature, payer: stored.payer ?? null };
|
|
1540
|
+
if (typeof stored.voucherCount === "number") {
|
|
1541
|
+
voucherCount = stored.voucherCount;
|
|
1542
|
+
}
|
|
1543
|
+
if (typeof stored.sessionOpened === "boolean") {
|
|
1544
|
+
sessionOpened = stored.sessionOpened;
|
|
1545
|
+
} else if (record !== null) {
|
|
1546
|
+
sessionOpened = true;
|
|
1547
|
+
}
|
|
757
1548
|
} catch {
|
|
758
1549
|
return null;
|
|
759
1550
|
}
|
|
@@ -761,8 +1552,8 @@ function createMppSessionHandler(opts) {
|
|
|
761
1552
|
}
|
|
762
1553
|
async function flagOrphan(reason) {
|
|
763
1554
|
await loadPersistedRecord();
|
|
764
|
-
if (!sessionOpened || settledCleanly) return;
|
|
765
|
-
|
|
1555
|
+
if (!sessionOpened || settledCleanly || orphanFlagged) return;
|
|
1556
|
+
orphanFlagged = true;
|
|
766
1557
|
closeListenerArmed = false;
|
|
767
1558
|
clearIdleTimer();
|
|
768
1559
|
const cumulativeAmount = record ? (Number(record.amount) / 1e7).toFixed(7) : "0.0000000";
|
|
@@ -810,16 +1601,20 @@ function createMppSessionHandler(opts) {
|
|
|
810
1601
|
if (record && amount < record.amount) return;
|
|
811
1602
|
const payer = record?.payer ?? extractPayerAddress(credential.source);
|
|
812
1603
|
record = { amount, signature: payload.signature, payer };
|
|
1604
|
+
voucherCount++;
|
|
1605
|
+
settledCleanly = false;
|
|
1606
|
+
orphanFlagged = false;
|
|
1607
|
+
armIdleTimer();
|
|
1608
|
+
const wasOpened = sessionOpened;
|
|
1609
|
+
sessionOpened = true;
|
|
813
1610
|
await innerStore.put(voucherRecordKey, {
|
|
814
1611
|
amount: record.amount.toString(),
|
|
815
1612
|
signature: record.signature,
|
|
816
|
-
payer: record.payer
|
|
1613
|
+
payer: record.payer,
|
|
1614
|
+
voucherCount,
|
|
1615
|
+
sessionOpened: true
|
|
817
1616
|
});
|
|
818
|
-
|
|
819
|
-
settledCleanly = false;
|
|
820
|
-
armIdleTimer();
|
|
821
|
-
if (!sessionOpened) {
|
|
822
|
-
sessionOpened = true;
|
|
1617
|
+
if (!wasOpened) {
|
|
823
1618
|
if (opts.onSessionOpen) {
|
|
824
1619
|
Promise.resolve().then(() => opts.onSessionOpen(opts.channelFactory, record.payer)).catch((err) => {
|
|
825
1620
|
logger("error", "[mpp-session] onSessionOpen callback error", { error: err });
|
|
@@ -871,10 +1666,11 @@ function createMppSessionHandler(opts) {
|
|
|
871
1666
|
}
|
|
872
1667
|
if (closeAmount > 0n && closeSig) {
|
|
873
1668
|
const closePayer = record?.payer ?? null;
|
|
1669
|
+
const vouchersClosed = voucherCount;
|
|
874
1670
|
const closeTxHash = await close({
|
|
875
1671
|
channel: opts.channelFactory,
|
|
876
1672
|
amount: closeAmount,
|
|
877
|
-
signature:
|
|
1673
|
+
signature: hexToBytes(closeSig),
|
|
878
1674
|
feePayer: channelAuthorizer(payeeKeypair),
|
|
879
1675
|
network: networkId
|
|
880
1676
|
});
|
|
@@ -887,46 +1683,28 @@ function createMppSessionHandler(opts) {
|
|
|
887
1683
|
opts.onCallbackError?.(err, "onSettled");
|
|
888
1684
|
});
|
|
889
1685
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1686
|
+
res.json({ closeTxHash });
|
|
1687
|
+
const vaultConfig = resolveVaultSettlementConfig(opts.vaultSettlement, logger);
|
|
1688
|
+
if (vaultConfig) {
|
|
1689
|
+
await recordSessionSettlement(
|
|
1690
|
+
{
|
|
1691
|
+
config: vaultConfig,
|
|
1692
|
+
network: opts.network,
|
|
1693
|
+
payeeSecretKey: opts.payeeSecretKey,
|
|
1694
|
+
channelId: opts.channelFactory,
|
|
1695
|
+
payer: closePayer,
|
|
1696
|
+
amount: closeAmount,
|
|
1697
|
+
voucherCount: vouchersClosed
|
|
1698
|
+
},
|
|
1699
|
+
{ logger, onCallbackError: opts.onCallbackError }
|
|
895
1700
|
);
|
|
896
|
-
if (!settlementAddresses) {
|
|
897
|
-
logger("warn", "[mpp-session] skipped session_settled vault record: payer address unavailable");
|
|
898
|
-
} else {
|
|
899
|
-
try {
|
|
900
|
-
const { Networks } = await import('@stellar/stellar-sdk');
|
|
901
|
-
const { Server } = await import('@stellar/stellar-sdk/rpc');
|
|
902
|
-
const networkPassphrase = opts.network === "mainnet" ? Networks.PUBLIC : Networks.TESTNET;
|
|
903
|
-
const rpcUrl = opts.network === "mainnet" ? "https://mainnet.sorobanrpc.com" : "https://soroban-testnet.stellar.org";
|
|
904
|
-
const server = new Server(rpcUrl);
|
|
905
|
-
const payeePublicKey = payeeKeypair.publicKey();
|
|
906
|
-
const sourceAccount = await server.getAccount(payeePublicKey);
|
|
907
|
-
const tx = buildSessionSettlementTransaction(
|
|
908
|
-
sourceAccount,
|
|
909
|
-
vaultContract,
|
|
910
|
-
opts.channelFactory,
|
|
911
|
-
settlementAddresses,
|
|
912
|
-
closeAmount,
|
|
913
|
-
voucherCount,
|
|
914
|
-
networkPassphrase
|
|
915
|
-
);
|
|
916
|
-
const preparedTx = await server.prepareTransaction(tx);
|
|
917
|
-
preparedTx.sign(payeeKeypair);
|
|
918
|
-
await server.sendTransaction(preparedTx);
|
|
919
|
-
} catch (recordErr) {
|
|
920
|
-
logger("error", "[mpp-session] failed to record session_settled on vault", { error: recordErr });
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
1701
|
}
|
|
924
|
-
res.json({ closeTxHash });
|
|
925
1702
|
} else {
|
|
926
1703
|
res.json({ closeTxHash: null, message: "no vouchers received" });
|
|
927
1704
|
}
|
|
928
1705
|
sessionOpened = false;
|
|
929
1706
|
voucherCount = 0;
|
|
1707
|
+
orphanFlagged = false;
|
|
930
1708
|
record = null;
|
|
931
1709
|
await innerStore.delete(voucherRecordKey);
|
|
932
1710
|
closeListenerArmed = false;
|
|
@@ -958,6 +1736,11 @@ function createMppSessionHandler(opts) {
|
|
|
958
1736
|
}
|
|
959
1737
|
};
|
|
960
1738
|
}
|
|
1739
|
+
function sha256(data) {
|
|
1740
|
+
return sha256$1(typeof data === "string" ? utf8ToBytes(data) : data);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/manifest/sign.ts
|
|
961
1744
|
var MANIFEST_SIGNATURE_VERSION = "2";
|
|
962
1745
|
function canonicalize(value) {
|
|
963
1746
|
if (Array.isArray(value)) {
|
|
@@ -973,7 +1756,7 @@ function canonicalize(value) {
|
|
|
973
1756
|
function manifestDigest(manifest) {
|
|
974
1757
|
const { signature: _omit, ...rest } = manifest;
|
|
975
1758
|
const canonical = JSON.stringify(canonicalize(rest));
|
|
976
|
-
return
|
|
1759
|
+
return sha256(utf8ToBytes(canonical));
|
|
977
1760
|
}
|
|
978
1761
|
function signManifest(manifest, payeeSecretKey) {
|
|
979
1762
|
const keypair = Keypair.fromSecret(payeeSecretKey);
|
|
@@ -983,7 +1766,7 @@ function signManifest(manifest, payeeSecretKey) {
|
|
|
983
1766
|
};
|
|
984
1767
|
const digest = manifestDigest(versionedManifest);
|
|
985
1768
|
const sig = keypair.sign(digest);
|
|
986
|
-
return { ...versionedManifest, signature:
|
|
1769
|
+
return { ...versionedManifest, signature: bytesToBase64(sig) };
|
|
987
1770
|
}
|
|
988
1771
|
function verifyManifestSignature(manifest, expectedPayee) {
|
|
989
1772
|
if (!manifest.signature) {
|
|
@@ -996,11 +1779,19 @@ function verifyManifestSignature(manifest, expectedPayee) {
|
|
|
996
1779
|
`Manifest signature version must be ${MANIFEST_SIGNATURE_VERSION}; legacy signatures are not safe because nested fields were excluded from their digest`
|
|
997
1780
|
);
|
|
998
1781
|
}
|
|
999
|
-
|
|
1782
|
+
let keypair;
|
|
1783
|
+
try {
|
|
1784
|
+
keypair = Keypair.fromPublicKey(manifest.payee);
|
|
1785
|
+
} catch (err) {
|
|
1786
|
+
throw new RouteDockSignatureError(
|
|
1787
|
+
`Manifest payee ${manifest.payee} is not a valid Stellar public key`,
|
|
1788
|
+
{ cause: err }
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1000
1791
|
const digest = manifestDigest(manifest);
|
|
1001
1792
|
let sigBytes;
|
|
1002
1793
|
try {
|
|
1003
|
-
sigBytes =
|
|
1794
|
+
sigBytes = base64ToBytes(manifest.signature);
|
|
1004
1795
|
} catch {
|
|
1005
1796
|
throw new RouteDockSignatureError("Manifest signature is not valid base64");
|
|
1006
1797
|
}
|
|
@@ -1023,6 +1814,7 @@ function verifyManifestSignature(manifest, expectedPayee) {
|
|
|
1023
1814
|
var MPP_MODES = ["mpp-session", "mpp-charge"];
|
|
1024
1815
|
function routedock(opts) {
|
|
1025
1816
|
const startTime = Date.now();
|
|
1817
|
+
assertProviderConfig(opts, "routedock");
|
|
1026
1818
|
const signedManifest = signManifest(opts.manifest, opts.payeeSecretKey);
|
|
1027
1819
|
const handlerMap = /* @__PURE__ */ new Map();
|
|
1028
1820
|
if (opts.modes.includes("x402")) {
|
|
@@ -1086,7 +1878,8 @@ function routedock(opts) {
|
|
|
1086
1878
|
...opts.onCallbackError ? { onCallbackError: opts.onCallbackError } : {},
|
|
1087
1879
|
...opts.onOrphaned ? { onOrphaned: opts.onOrphaned } : {},
|
|
1088
1880
|
...opts.idleTimeoutMs != null ? { idleTimeoutMs: opts.idleTimeoutMs } : {},
|
|
1089
|
-
...opts.logger ? { logger: opts.logger } : {}
|
|
1881
|
+
...opts.logger ? { logger: opts.logger } : {},
|
|
1882
|
+
...opts.vaultSettlement ? { vaultSettlement: opts.vaultSettlement } : {}
|
|
1090
1883
|
})
|
|
1091
1884
|
);
|
|
1092
1885
|
}
|
|
@@ -1133,6 +1926,8 @@ var MPP_NETWORK3 = {
|
|
|
1133
1926
|
testnet: "stellar:testnet",
|
|
1134
1927
|
mainnet: "stellar:pubnet"
|
|
1135
1928
|
};
|
|
1929
|
+
var DEFAULT_MAX_RECONCILE_ATTEMPTS = 5;
|
|
1930
|
+
var ALREADY_CLOSED_PATTERN = /channel has been closed|channel is closed|already closed|finalized/i;
|
|
1136
1931
|
function decimalToStroops(amountStr) {
|
|
1137
1932
|
return usdcToStroops(amountStr);
|
|
1138
1933
|
}
|
|
@@ -1142,12 +1937,15 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
1142
1937
|
recoveredCount: 0,
|
|
1143
1938
|
skippedCount: 0,
|
|
1144
1939
|
failedCount: 0,
|
|
1940
|
+
alreadyClosedCount: 0,
|
|
1941
|
+
terminalCount: 0,
|
|
1145
1942
|
errors: []
|
|
1146
1943
|
};
|
|
1944
|
+
const maxAttempts = opts.maxReconcileAttempts ?? DEFAULT_MAX_RECONCILE_ATTEMPTS;
|
|
1147
1945
|
const payeeKeypair = Keypair.fromSecret(opts.payeeSecretKey);
|
|
1148
1946
|
const networkId = MPP_NETWORK3[opts.network];
|
|
1149
1947
|
try {
|
|
1150
|
-
const { data: closingSessions, error } = await opts.supabase.from("sessions").select("channel_id, channel_contract, cumulative_amount, last_signature, settlement_tx_hash").eq("status", "closing").eq("network", opts.network).eq("payee", payeeKeypair.publicKey()).is("settlement_tx_hash", null).not("last_signature", "is", null).order("updated_at", { ascending: true }).limit(100);
|
|
1948
|
+
const { data: closingSessions, error } = await opts.supabase.from("sessions").select("channel_id, channel_contract, cumulative_amount, last_signature, settlement_tx_hash, reconcile_attempts").eq("status", "closing").eq("network", opts.network).eq("payee", payeeKeypair.publicKey()).is("settlement_tx_hash", null).not("last_signature", "is", null).order("updated_at", { ascending: true }).limit(100);
|
|
1151
1949
|
if (error) {
|
|
1152
1950
|
throw new Error(`Failed to query sessions: ${error.message}`);
|
|
1153
1951
|
}
|
|
@@ -1169,7 +1967,7 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
1169
1967
|
continue;
|
|
1170
1968
|
}
|
|
1171
1969
|
const closeAmount = decimalToStroops(cumulative_amount);
|
|
1172
|
-
const closeSig =
|
|
1970
|
+
const closeSig = hexToBytes(last_signature);
|
|
1173
1971
|
const closeFn = opts.channelClose ?? (await import('@stellar/mpp/channel/server')).close;
|
|
1174
1972
|
const closeTxHash = await closeFn({
|
|
1175
1973
|
channel: channelId,
|
|
@@ -1192,11 +1990,36 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
1192
1990
|
await opts.onRecovered(channelId, closeTxHash, totalPaid);
|
|
1193
1991
|
}
|
|
1194
1992
|
} catch (err) {
|
|
1993
|
+
const reason = formatMppError(err);
|
|
1994
|
+
if (ALREADY_CLOSED_PATTERN.test(reason)) {
|
|
1995
|
+
const { error: closeError } = await opts.supabase.from("sessions").update({
|
|
1996
|
+
status: "closed",
|
|
1997
|
+
last_reconcile_error: `channel already closed on-chain: ${reason}`,
|
|
1998
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1999
|
+
}).eq("channel_id", session.channel_id);
|
|
2000
|
+
if (!closeError) {
|
|
2001
|
+
stats.alreadyClosedCount++;
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1195
2005
|
stats.failedCount++;
|
|
1196
|
-
stats.errors.push({
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
})
|
|
2006
|
+
stats.errors.push({ channelId: session.channel_id, reason });
|
|
2007
|
+
const attempts = (Number(session.reconcile_attempts) || 0) + 1;
|
|
2008
|
+
const terminal = attempts >= maxAttempts;
|
|
2009
|
+
const { error: bookkeepingError } = await opts.supabase.from("sessions").update({
|
|
2010
|
+
reconcile_attempts: attempts,
|
|
2011
|
+
last_reconcile_error: reason,
|
|
2012
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2013
|
+
...terminal ? { status: "failed" } : {}
|
|
2014
|
+
}).eq("channel_id", session.channel_id);
|
|
2015
|
+
if (bookkeepingError) {
|
|
2016
|
+
stats.errors.push({
|
|
2017
|
+
channelId: session.channel_id,
|
|
2018
|
+
reason: `Failed to record reconcile failure: ${bookkeepingError.message}`
|
|
2019
|
+
});
|
|
2020
|
+
} else if (terminal) {
|
|
2021
|
+
stats.terminalCount++;
|
|
2022
|
+
}
|
|
1200
2023
|
}
|
|
1201
2024
|
}
|
|
1202
2025
|
return stats;
|
|
@@ -1217,7 +2040,7 @@ async function runStartupReconciliation(opts, logger) {
|
|
|
1217
2040
|
}
|
|
1218
2041
|
log(
|
|
1219
2042
|
"info",
|
|
1220
|
-
`[SessionReconciler] Found ${stats.orphanedCount} orphaned session(s). Recovered: ${stats.recoveredCount}, Skipped: ${stats.skippedCount}, Failed: ${stats.failedCount}`
|
|
2043
|
+
`[SessionReconciler] Found ${stats.orphanedCount} orphaned session(s). Recovered: ${stats.recoveredCount}, Skipped: ${stats.skippedCount}, Failed: ${stats.failedCount}, Already closed: ${stats.alreadyClosedCount}, Moved to failed: ${stats.terminalCount}`
|
|
1221
2044
|
);
|
|
1222
2045
|
if (stats.errors.length > 0) {
|
|
1223
2046
|
log("warn", "[SessionReconciler] Reconciliation errors:");
|
|
@@ -1404,4 +2227,4 @@ var SupabaseSeenTxStore = class {
|
|
|
1404
2227
|
}
|
|
1405
2228
|
};
|
|
1406
2229
|
|
|
1407
|
-
export { DEFAULT_MAX_SESSION_DURATION_MS, InMemorySeenTxStore, MAX_SETTLEMENT_REPLAYS, RouteDockChannelStateError, RouteDockClientVersionError, RouteDockDisputeError, RouteDockError, RouteDockFacilitatorError, RouteDockManifestError, RouteDockNetworkError, RouteDockNoSupportedModeError, RouteDockPolicyRejectError, RouteDockPolicyRejectedError, RouteDockRefundWindowError, RouteDockSessionError, RouteDockSignatureError, RouteDockTrustlineError, RouteDockVoucherMonotonicityError, SETTLEMENT_REPLAY_WINDOW_MS, SupabaseSeenTxStore, SupabaseSessionStore, checkSettlementReplay, createMppChargeHandler, createMppSessionHandler, createX402Handler, decimalToStroops, getEligibleAssets, isAssetEligible, isVoucherStoreValue, normalizeManifestAssets, paymentIdempotencyKey, reconcileAbandonedSessions, registerProvider, resolveAssetContract, routedock, runStartupReconciliation, selectAsset };
|
|
2230
|
+
export { DEFAULT_MAX_SESSION_DURATION_MS, InMemorySeenTxStore, MAX_SETTLEMENT_REPLAYS, RouteDockChannelStateError, RouteDockClientVersionError, RouteDockDisputeError, RouteDockError, RouteDockFacilitatorError, RouteDockManifestError, RouteDockNetworkError, RouteDockNoSupportedModeError, RouteDockPolicyRejectError, RouteDockPolicyRejectedError, RouteDockRefundWindowError, RouteDockSessionError, RouteDockSignatureError, RouteDockTrustlineError, RouteDockVoucherMonotonicityError, SETTLEMENT_REPLAY_WINDOW_MS, SupabaseSeenTxStore, SupabaseSessionStore, checkSettlementReplay, createMppChargeHandler, createMppSessionHandler, createX402Handler, decimalToStroops, getEligibleAssets, isAssetEligible, isVoucherStoreValue, normalizeManifestAssets, paymentIdempotencyKey, reconcileAbandonedSessions, recordSettlement, registerProvider, resolveAssetContract, routedock, runStartupReconciliation, selectAsset };
|