@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/hono.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var stellarSdk = require('@stellar/stellar-sdk');
|
|
4
|
+
var nulthSdk = require('@routedock/nulth-sdk');
|
|
5
|
+
var jsonSchema = require('@cfworker/json-schema');
|
|
4
6
|
var facilitator = require('@x402/stellar/exact/facilitator');
|
|
5
7
|
var server$1 = require('@x402/stellar/exact/server');
|
|
6
8
|
var server = require('@x402/core/server');
|
|
@@ -9,11 +11,10 @@ var http = require('@x402/core/http');
|
|
|
9
11
|
var server$4 = require('@stellar/mpp/charge/server');
|
|
10
12
|
var server$2 = require('@stellar/mpp/channel/server');
|
|
11
13
|
var server$3 = require('mppx/server');
|
|
12
|
-
var
|
|
13
|
-
var nulthSdk = require('@routedock/nulth-sdk');
|
|
14
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
14
15
|
var mppx = require('mppx');
|
|
15
16
|
|
|
16
|
-
// src/provider/
|
|
17
|
+
// src/provider/internal/assertProviderConfig.ts
|
|
17
18
|
|
|
18
19
|
// src/errors.ts
|
|
19
20
|
var RouteDockError = class extends Error {
|
|
@@ -48,6 +49,658 @@ var RouteDockSignatureError = class extends RouteDockError {
|
|
|
48
49
|
this.name = "RouteDockSignatureError";
|
|
49
50
|
}
|
|
50
51
|
};
|
|
52
|
+
var usdcToUnits = nulthSdk.usdcToStroops;
|
|
53
|
+
|
|
54
|
+
// src/schemas/routedock.schema.json
|
|
55
|
+
var routedock_schema_default = {
|
|
56
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
57
|
+
$id: "https://routedock.xyz/schemas/routedock.schema.json",
|
|
58
|
+
title: "RouteDock Manifest",
|
|
59
|
+
description: "Discovery manifest served at /.well-known/routedock.json on every RouteDock provider endpoint",
|
|
60
|
+
type: "object",
|
|
61
|
+
required: [
|
|
62
|
+
"routedock",
|
|
63
|
+
"name",
|
|
64
|
+
"description",
|
|
65
|
+
"modes",
|
|
66
|
+
"network",
|
|
67
|
+
"asset",
|
|
68
|
+
"asset_contract",
|
|
69
|
+
"payee",
|
|
70
|
+
"pricing",
|
|
71
|
+
"endpoints",
|
|
72
|
+
"tags"
|
|
73
|
+
],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
properties: {
|
|
76
|
+
routedock: {
|
|
77
|
+
type: "string",
|
|
78
|
+
pattern: "^[0-9]+\\.[0-9]+$",
|
|
79
|
+
description: "Manifest schema version, e.g. '1.0', '1.1', '2.0'."
|
|
80
|
+
},
|
|
81
|
+
min_client_version: {
|
|
82
|
+
type: "string",
|
|
83
|
+
pattern: "^[0-9]+\\.[0-9]+$",
|
|
84
|
+
description: "Minimum SDK client version (major.minor) required to use this manifest. Clients below this version must refuse to pay."
|
|
85
|
+
},
|
|
86
|
+
deprecated_modes: {
|
|
87
|
+
type: "array",
|
|
88
|
+
items: {
|
|
89
|
+
$ref: "#/definitions/PaymentMode"
|
|
90
|
+
},
|
|
91
|
+
uniqueItems: true,
|
|
92
|
+
description: "Payment modes still listed in 'modes' for backwards compatibility but deprecated for new integrations."
|
|
93
|
+
},
|
|
94
|
+
sunset_at: {
|
|
95
|
+
type: "string",
|
|
96
|
+
format: "date-time",
|
|
97
|
+
description: "ISO 8601 timestamp after which this manifest version is no longer supported by the provider."
|
|
98
|
+
},
|
|
99
|
+
name: {
|
|
100
|
+
type: "string",
|
|
101
|
+
minLength: 1,
|
|
102
|
+
maxLength: 120,
|
|
103
|
+
description: "Human-readable provider name"
|
|
104
|
+
},
|
|
105
|
+
description: {
|
|
106
|
+
type: "string",
|
|
107
|
+
minLength: 1,
|
|
108
|
+
maxLength: 500,
|
|
109
|
+
description: "Human-readable description of the provider's data or service"
|
|
110
|
+
},
|
|
111
|
+
modes: {
|
|
112
|
+
type: "array",
|
|
113
|
+
minItems: 1,
|
|
114
|
+
uniqueItems: true,
|
|
115
|
+
items: {
|
|
116
|
+
$ref: "#/definitions/PaymentMode"
|
|
117
|
+
},
|
|
118
|
+
description: "Payment modes supported by this provider"
|
|
119
|
+
},
|
|
120
|
+
network: {
|
|
121
|
+
type: "string",
|
|
122
|
+
enum: ["testnet", "mainnet"],
|
|
123
|
+
description: "Stellar network this provider operates on"
|
|
124
|
+
},
|
|
125
|
+
asset: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Asset ticker symbol (e.g. 'USDC')"
|
|
128
|
+
},
|
|
129
|
+
asset_contract: {
|
|
130
|
+
type: "string",
|
|
131
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
132
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
133
|
+
},
|
|
134
|
+
assets: {
|
|
135
|
+
type: "array",
|
|
136
|
+
minItems: 1,
|
|
137
|
+
items: {
|
|
138
|
+
$ref: "#/definitions/AssetConfig"
|
|
139
|
+
},
|
|
140
|
+
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."
|
|
141
|
+
},
|
|
142
|
+
payee: {
|
|
143
|
+
type: "string",
|
|
144
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
145
|
+
description: "Stellar address (G...) that receives payments"
|
|
146
|
+
},
|
|
147
|
+
pricing: {
|
|
148
|
+
type: "object",
|
|
149
|
+
description: "Pricing configuration per supported payment mode",
|
|
150
|
+
additionalProperties: false,
|
|
151
|
+
properties: {
|
|
152
|
+
x402: {
|
|
153
|
+
$ref: "#/definitions/PricingConfig"
|
|
154
|
+
},
|
|
155
|
+
"mpp-charge": {
|
|
156
|
+
$ref: "#/definitions/PricingConfig"
|
|
157
|
+
},
|
|
158
|
+
"mpp-session": {
|
|
159
|
+
$ref: "#/definitions/SessionPricingConfig"
|
|
160
|
+
},
|
|
161
|
+
"mpp-session-ws": {
|
|
162
|
+
$ref: "#/definitions/SessionPricingConfig",
|
|
163
|
+
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."
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
minProperties: 1
|
|
167
|
+
},
|
|
168
|
+
endpoints: {
|
|
169
|
+
type: "object",
|
|
170
|
+
description: "Map of endpoint name to endpoint descriptors",
|
|
171
|
+
additionalProperties: {
|
|
172
|
+
$ref: "#/definitions/EndpointDescriptor"
|
|
173
|
+
},
|
|
174
|
+
minProperties: 1
|
|
175
|
+
},
|
|
176
|
+
sla: {
|
|
177
|
+
$ref: "#/definitions/SLAConfig"
|
|
178
|
+
},
|
|
179
|
+
tags: {
|
|
180
|
+
type: "array",
|
|
181
|
+
items: {
|
|
182
|
+
type: "string",
|
|
183
|
+
minLength: 1,
|
|
184
|
+
maxLength: 64
|
|
185
|
+
},
|
|
186
|
+
uniqueItems: true,
|
|
187
|
+
description: "Capability tags indexed with trigram search in the provider registry"
|
|
188
|
+
},
|
|
189
|
+
vault: {
|
|
190
|
+
$ref: "#/definitions/VaultMode"
|
|
191
|
+
},
|
|
192
|
+
nulth_account: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "Optional Nulth smart account address associated with this provider"
|
|
195
|
+
},
|
|
196
|
+
capabilities: {
|
|
197
|
+
type: "object",
|
|
198
|
+
description: "Optional protocol features this provider supports",
|
|
199
|
+
properties: {
|
|
200
|
+
streaming: {
|
|
201
|
+
type: "array",
|
|
202
|
+
items: {
|
|
203
|
+
type: "string",
|
|
204
|
+
enum: ["sse", "websocket"]
|
|
205
|
+
},
|
|
206
|
+
description: "Supported streaming transports"
|
|
207
|
+
},
|
|
208
|
+
webhooks: {
|
|
209
|
+
type: "boolean",
|
|
210
|
+
description: "Whether webhook callbacks are supported"
|
|
211
|
+
},
|
|
212
|
+
idempotency_keys: {
|
|
213
|
+
type: "boolean",
|
|
214
|
+
description: "Whether idempotency keys are supported"
|
|
215
|
+
},
|
|
216
|
+
content_types: {
|
|
217
|
+
type: "array",
|
|
218
|
+
items: {
|
|
219
|
+
type: "string"
|
|
220
|
+
},
|
|
221
|
+
description: "Supported content types for request/response"
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
additionalProperties: false
|
|
225
|
+
},
|
|
226
|
+
regions: {
|
|
227
|
+
type: "array",
|
|
228
|
+
items: {
|
|
229
|
+
type: "string",
|
|
230
|
+
minLength: 2,
|
|
231
|
+
maxLength: 10
|
|
232
|
+
},
|
|
233
|
+
uniqueItems: true,
|
|
234
|
+
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."
|
|
235
|
+
},
|
|
236
|
+
latency_hints: {
|
|
237
|
+
type: "object",
|
|
238
|
+
additionalProperties: {
|
|
239
|
+
type: "number",
|
|
240
|
+
minimum: 0
|
|
241
|
+
},
|
|
242
|
+
description: "Provider-declared p50 latency in milliseconds per region code. Keys must be a subset of 'regions'. Example: { 'IAD': 14, 'AMS': 22 }."
|
|
243
|
+
},
|
|
244
|
+
signature: {
|
|
245
|
+
type: "string",
|
|
246
|
+
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."
|
|
247
|
+
},
|
|
248
|
+
signature_version: {
|
|
249
|
+
type: "string",
|
|
250
|
+
const: "2",
|
|
251
|
+
description: "Manifest signature format. Version 2 recursively includes every nested manifest field in the signed digest."
|
|
252
|
+
},
|
|
253
|
+
categories: {
|
|
254
|
+
type: "array",
|
|
255
|
+
items: {
|
|
256
|
+
type: "string",
|
|
257
|
+
minLength: 1,
|
|
258
|
+
maxLength: 128
|
|
259
|
+
},
|
|
260
|
+
uniqueItems: true,
|
|
261
|
+
description: "Optional hierarchical categories from a published taxonomy (e.g. data/price/crypto)"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
definitions: {
|
|
265
|
+
VaultMode: {
|
|
266
|
+
type: "string",
|
|
267
|
+
enum: ["local-key", "agent-vault", "nulth"],
|
|
268
|
+
description: "Agent vault / custody mode for payments from this provider"
|
|
269
|
+
},
|
|
270
|
+
PaymentMode: {
|
|
271
|
+
type: "string",
|
|
272
|
+
enum: ["x402", "mpp-charge", "mpp-session", "mpp-session-ws"],
|
|
273
|
+
description: "A supported RouteDock payment mode"
|
|
274
|
+
},
|
|
275
|
+
AssetConfig: {
|
|
276
|
+
type: "object",
|
|
277
|
+
description: "Payment asset configuration with optional mode and endpoint scoping",
|
|
278
|
+
required: ["asset", "asset_contract"],
|
|
279
|
+
additionalProperties: false,
|
|
280
|
+
properties: {
|
|
281
|
+
asset: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Asset ticker symbol (e.g. 'USDC' or 'XLM')"
|
|
284
|
+
},
|
|
285
|
+
asset_contract: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
288
|
+
},
|
|
289
|
+
modes: {
|
|
290
|
+
type: "array",
|
|
291
|
+
items: {
|
|
292
|
+
$ref: "#/definitions/PaymentMode"
|
|
293
|
+
},
|
|
294
|
+
uniqueItems: true,
|
|
295
|
+
description: "Optional: restrict this asset to specific payment modes. If omitted, available for all modes."
|
|
296
|
+
},
|
|
297
|
+
endpoints: {
|
|
298
|
+
type: "array",
|
|
299
|
+
items: {
|
|
300
|
+
type: "string"
|
|
301
|
+
},
|
|
302
|
+
uniqueItems: true,
|
|
303
|
+
description: "Optional: restrict this asset to specific endpoints (by name). If omitted, available for all endpoints."
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
PricingConfig: {
|
|
308
|
+
type: "object",
|
|
309
|
+
description: "Per-request pricing for x402 or mpp-charge modes",
|
|
310
|
+
required: ["amount", "per"],
|
|
311
|
+
additionalProperties: false,
|
|
312
|
+
properties: {
|
|
313
|
+
amount: {
|
|
314
|
+
type: "string",
|
|
315
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
316
|
+
description: "Cost per request in the payment asset (decimal string, e.g. '0.001')"
|
|
317
|
+
},
|
|
318
|
+
per: {
|
|
319
|
+
type: "string",
|
|
320
|
+
const: "request",
|
|
321
|
+
description: "Billing unit \u2014 always 'request' for non-session modes"
|
|
322
|
+
},
|
|
323
|
+
facilitator: {
|
|
324
|
+
type: "string",
|
|
325
|
+
description: "x402 facilitator URL (required for x402 mode). OpenZeppelin Channels endpoint."
|
|
326
|
+
},
|
|
327
|
+
payee: {
|
|
328
|
+
type: "string",
|
|
329
|
+
pattern: "^G[A-Z2-7]{55}$",
|
|
330
|
+
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."
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
SessionPricingConfig: {
|
|
335
|
+
type: "object",
|
|
336
|
+
description: "Per-voucher pricing for mpp-session (one-way-channel) mode",
|
|
337
|
+
required: ["rate", "per", "channel_factory", "min_deposit", "refund_waiting_period_ledgers"],
|
|
338
|
+
additionalProperties: false,
|
|
339
|
+
properties: {
|
|
340
|
+
rate: {
|
|
341
|
+
type: "string",
|
|
342
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
343
|
+
description: "Cost per voucher in the payment asset (decimal string, e.g. '0.0001')"
|
|
344
|
+
},
|
|
345
|
+
per: {
|
|
346
|
+
type: "string",
|
|
347
|
+
const: "voucher",
|
|
348
|
+
description: "Billing unit \u2014 always 'voucher' for session mode"
|
|
349
|
+
},
|
|
350
|
+
channel_factory: {
|
|
351
|
+
type: "string",
|
|
352
|
+
pattern: "^C[A-Z2-7]{55}$",
|
|
353
|
+
description: "Stable Soroban factory address (C...) used to deploy a per-session channel"
|
|
354
|
+
},
|
|
355
|
+
min_deposit: {
|
|
356
|
+
type: "string",
|
|
357
|
+
pattern: "^[0-9]+(\\.[0-9]{1,7})?$",
|
|
358
|
+
description: "Minimum initial deposit to open a channel (decimal string)"
|
|
359
|
+
},
|
|
360
|
+
refund_waiting_period_ledgers: {
|
|
361
|
+
type: "integer",
|
|
362
|
+
minimum: 1,
|
|
363
|
+
description: "Ledgers the server has to settle before the funder can reclaim. Default: 17280 (~24h). Do not reduce in production."
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
SLAConfig: {
|
|
368
|
+
type: "object",
|
|
369
|
+
description: "Service Level Agreement for the provider",
|
|
370
|
+
required: ["uptime_30d_percent", "p95_latency_ms"],
|
|
371
|
+
additionalProperties: false,
|
|
372
|
+
properties: {
|
|
373
|
+
uptime_30d_percent: {
|
|
374
|
+
type: "number",
|
|
375
|
+
description: "Uptime percentage over the last 30 days"
|
|
376
|
+
},
|
|
377
|
+
p95_latency_ms: {
|
|
378
|
+
type: "number",
|
|
379
|
+
description: "P95 latency in milliseconds"
|
|
380
|
+
},
|
|
381
|
+
maintenance_windows: {
|
|
382
|
+
type: "array",
|
|
383
|
+
items: {
|
|
384
|
+
type: "object",
|
|
385
|
+
required: ["cron", "duration_minutes"],
|
|
386
|
+
additionalProperties: false,
|
|
387
|
+
properties: {
|
|
388
|
+
cron: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "Cron expression for the maintenance window"
|
|
391
|
+
},
|
|
392
|
+
duration_minutes: {
|
|
393
|
+
type: "number",
|
|
394
|
+
description: "Duration of the maintenance window in minutes"
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
EndpointDescriptor: {
|
|
402
|
+
type: "object",
|
|
403
|
+
description: "Descriptor for a provider endpoint",
|
|
404
|
+
required: ["method", "path"],
|
|
405
|
+
additionalProperties: false,
|
|
406
|
+
properties: {
|
|
407
|
+
method: {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "HTTP method for the endpoint"
|
|
410
|
+
},
|
|
411
|
+
path: {
|
|
412
|
+
type: "string",
|
|
413
|
+
description: "Path for the endpoint"
|
|
414
|
+
},
|
|
415
|
+
deprecated: {
|
|
416
|
+
type: "boolean",
|
|
417
|
+
description: "Whether this endpoint is retained only for backwards compatibility"
|
|
418
|
+
},
|
|
419
|
+
sunset_at: {
|
|
420
|
+
type: "string",
|
|
421
|
+
format: "date-time",
|
|
422
|
+
description: "ISO 8601 timestamp after which callers should stop using this endpoint"
|
|
423
|
+
},
|
|
424
|
+
headers: {
|
|
425
|
+
type: "object",
|
|
426
|
+
additionalProperties: {
|
|
427
|
+
type: "string"
|
|
428
|
+
},
|
|
429
|
+
description: "Expected request headers"
|
|
430
|
+
},
|
|
431
|
+
request_schema: {
|
|
432
|
+
description: "JSON schema for the request body"
|
|
433
|
+
},
|
|
434
|
+
response_schema: {
|
|
435
|
+
description: "JSON schema for the response body"
|
|
436
|
+
},
|
|
437
|
+
rate_limit: {
|
|
438
|
+
type: "object",
|
|
439
|
+
required: ["requests", "window_seconds"],
|
|
440
|
+
additionalProperties: false,
|
|
441
|
+
properties: {
|
|
442
|
+
requests: {
|
|
443
|
+
type: "number"
|
|
444
|
+
},
|
|
445
|
+
window_seconds: {
|
|
446
|
+
type: "number"
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// src/manifest/validate.ts
|
|
456
|
+
var validator = new jsonSchema.Validator(routedock_schema_default, "7");
|
|
457
|
+
function validateManifestSchema(manifest, source) {
|
|
458
|
+
const result = validator.validate(manifest);
|
|
459
|
+
if (!result.valid) {
|
|
460
|
+
const msgs = result.errors.map((err) => err.error).join("; ");
|
|
461
|
+
throw new RouteDockManifestError(`Invalid manifest${""}: ${msgs}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function assertManifestValid(manifest, baseUrl) {
|
|
465
|
+
const context = "";
|
|
466
|
+
if (manifest.latency_hints !== void 0) {
|
|
467
|
+
if (!Array.isArray(manifest.regions) || manifest.regions.length === 0) {
|
|
468
|
+
throw new RouteDockManifestError(
|
|
469
|
+
`Invalid manifest${context}: latency_hints defined without declaring regions`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const regionSet = new Set(manifest.regions);
|
|
473
|
+
for (const region of Object.keys(manifest.latency_hints)) {
|
|
474
|
+
if (!regionSet.has(region)) {
|
|
475
|
+
throw new RouteDockManifestError(
|
|
476
|
+
`Invalid manifest${context}: latency_hints key '${region}' is not declared in regions (${manifest.regions.join(", ")})`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (manifest.assets !== void 0) {
|
|
482
|
+
if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
|
|
483
|
+
throw new RouteDockManifestError(
|
|
484
|
+
`Invalid manifest${context}: assets must be a non-empty array when defined`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
for (let i = 0; i < manifest.assets.length; i++) {
|
|
488
|
+
const a = manifest.assets[i];
|
|
489
|
+
if (!a || typeof a.asset !== "string" || typeof a.asset_contract !== "string") {
|
|
490
|
+
throw new RouteDockManifestError(
|
|
491
|
+
`Invalid manifest${context}: assets[${i}] must define asset and asset_contract strings`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const first = manifest.assets[0];
|
|
496
|
+
if (first.asset !== manifest.asset || first.asset_contract !== manifest.asset_contract) {
|
|
497
|
+
throw new RouteDockManifestError(
|
|
498
|
+
`Invalid manifest${context}: assets[0] (${first.asset}:${first.asset_contract}) must match root asset fields (${manifest.asset}:${manifest.asset_contract})`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
assertPricingMatchesModes(manifest, context);
|
|
503
|
+
assertDeprecatedModesListed(manifest, context);
|
|
504
|
+
assertPricesParse(manifest, context);
|
|
505
|
+
}
|
|
506
|
+
function assertPricingMatchesModes(manifest, context) {
|
|
507
|
+
const pricing = manifest.pricing ?? {};
|
|
508
|
+
for (const mode of manifest.modes ?? []) {
|
|
509
|
+
if (pricing[mode] === void 0 || pricing[mode] === null) {
|
|
510
|
+
throw new RouteDockManifestError(
|
|
511
|
+
`Invalid manifest${context}: mode '${mode}' is listed in modes but pricing.${mode} is missing`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function assertDeprecatedModesListed(manifest, context) {
|
|
517
|
+
if (manifest.deprecated_modes === void 0) return;
|
|
518
|
+
const modes = new Set(manifest.modes ?? []);
|
|
519
|
+
for (const mode of manifest.deprecated_modes) {
|
|
520
|
+
if (!modes.has(mode)) {
|
|
521
|
+
throw new RouteDockManifestError(
|
|
522
|
+
`Invalid manifest${context}: deprecated_modes entry '${mode}' is not listed in modes`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
var PRICE_FIELDS = {
|
|
528
|
+
x402: ["amount"],
|
|
529
|
+
"mpp-charge": ["amount"],
|
|
530
|
+
"mpp-session": ["rate", "min_deposit"],
|
|
531
|
+
"mpp-session-ws": ["rate", "min_deposit"]
|
|
532
|
+
};
|
|
533
|
+
function assertPricesParse(manifest, context) {
|
|
534
|
+
const pricing = manifest.pricing ?? {};
|
|
535
|
+
for (const [mode, fields] of Object.entries(PRICE_FIELDS)) {
|
|
536
|
+
const entry = pricing[mode];
|
|
537
|
+
if (entry === void 0 || entry === null) continue;
|
|
538
|
+
for (const field of fields) {
|
|
539
|
+
const value = entry[field];
|
|
540
|
+
try {
|
|
541
|
+
if (typeof value !== "string") {
|
|
542
|
+
throw new RangeError(`expected a decimal string, got ${typeof value}`);
|
|
543
|
+
}
|
|
544
|
+
nulthSdk.usdcToStroops(value);
|
|
545
|
+
} catch (err) {
|
|
546
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
547
|
+
throw new RouteDockManifestError(
|
|
548
|
+
`Invalid manifest${context}: pricing.${mode}.${field} ${JSON.stringify(value)} is not a valid amount (${detail})`,
|
|
549
|
+
{ cause: err }
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/provider/internal/assertProviderConfig.ts
|
|
557
|
+
var FLAT_MODES = ["x402", "mpp-charge"];
|
|
558
|
+
var SESSION_MODES = ["mpp-session", "mpp-session-ws"];
|
|
559
|
+
function sameAmount(adapter, label, optionValue, manifestValue) {
|
|
560
|
+
const parse = (where, value) => {
|
|
561
|
+
try {
|
|
562
|
+
return nulthSdk.usdcToStroops(value);
|
|
563
|
+
} catch {
|
|
564
|
+
throw new RouteDockManifestError(
|
|
565
|
+
`${adapter}: ${label} has an unparseable ${where} amount "${value}"`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
return parse("option", optionValue) === parse("manifest", manifestValue);
|
|
570
|
+
}
|
|
571
|
+
function assertProviderConfig(opts, adapterName) {
|
|
572
|
+
const { manifest } = opts;
|
|
573
|
+
if (manifest === null || typeof manifest !== "object") {
|
|
574
|
+
throw new RouteDockManifestError(`${adapterName}: opts.manifest is required and must be an object`);
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
validateManifestSchema(manifest);
|
|
578
|
+
assertManifestValid(manifest);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (err instanceof RouteDockManifestError) {
|
|
581
|
+
throw new RouteDockManifestError(`${adapterName}: ${err.message}`, { cause: err });
|
|
582
|
+
}
|
|
583
|
+
throw err;
|
|
584
|
+
}
|
|
585
|
+
const served = new Set(opts.modes.filter((mode) => Boolean(opts.pricing[mode])));
|
|
586
|
+
const advertised = new Set(manifest.modes);
|
|
587
|
+
const notAdvertised = [...served].filter((mode) => !advertised.has(mode));
|
|
588
|
+
const notServed = [...advertised].filter((mode) => !served.has(mode));
|
|
589
|
+
if (notAdvertised.length > 0 || notServed.length > 0) {
|
|
590
|
+
const parts = [];
|
|
591
|
+
if (notAdvertised.length > 0) parts.push(`served but not in manifest.modes: ${notAdvertised.join(", ")}`);
|
|
592
|
+
if (notServed.length > 0) parts.push(`in manifest.modes but not served (missing from modes or pricing): ${notServed.join(", ")}`);
|
|
593
|
+
throw new RouteDockManifestError(`${adapterName}: modes do not match manifest.modes (${parts.join("; ")})`);
|
|
594
|
+
}
|
|
595
|
+
for (const mode of FLAT_MODES) {
|
|
596
|
+
if (!served.has(mode)) continue;
|
|
597
|
+
const optionValue = opts.pricing[mode];
|
|
598
|
+
const manifestValue = manifest.pricing[mode]?.amount;
|
|
599
|
+
if (manifestValue === void 0) {
|
|
600
|
+
throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode}.amount is missing but ${mode} is served`);
|
|
601
|
+
}
|
|
602
|
+
if (!sameAmount(adapterName, `pricing.${mode}`, optionValue, manifestValue)) {
|
|
603
|
+
throw new RouteDockManifestError(
|
|
604
|
+
`${adapterName}: pricing.${mode} "${optionValue}" does not match manifest.pricing.${mode}.amount "${manifestValue}"`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
for (const mode of SESSION_MODES) {
|
|
609
|
+
if (!served.has(mode)) continue;
|
|
610
|
+
const optionValue = opts.pricing[mode];
|
|
611
|
+
const manifestValue = manifest.pricing[mode];
|
|
612
|
+
if (!manifestValue) {
|
|
613
|
+
throw new RouteDockManifestError(`${adapterName}: manifest.pricing.${mode} is missing but ${mode} is served`);
|
|
614
|
+
}
|
|
615
|
+
if (!sameAmount(adapterName, `pricing.${mode}.rate`, optionValue.rate, manifestValue.rate)) {
|
|
616
|
+
throw new RouteDockManifestError(
|
|
617
|
+
`${adapterName}: pricing.${mode}.rate "${optionValue.rate}" does not match manifest.pricing.${mode}.rate "${manifestValue.rate}"`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
if (optionValue.channelFactory !== manifestValue.channel_factory) {
|
|
621
|
+
throw new RouteDockManifestError(
|
|
622
|
+
`${adapterName}: pricing.${mode}.channelFactory "${optionValue.channelFactory}" does not match manifest.pricing.${mode}.channel_factory "${manifestValue.channel_factory}"`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (opts.assetContract !== void 0 && opts.assetContract !== manifest.asset_contract) {
|
|
627
|
+
throw new RouteDockManifestError(
|
|
628
|
+
`${adapterName}: assetContract "${opts.assetContract}" does not match manifest.asset_contract "${manifest.asset_contract}"`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
if (opts.asset !== void 0 && opts.asset !== manifest.asset) {
|
|
632
|
+
throw new RouteDockManifestError(
|
|
633
|
+
`${adapterName}: asset "${opts.asset}" does not match manifest.asset "${manifest.asset}"`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
if (opts.network !== manifest.network) {
|
|
637
|
+
throw new RouteDockManifestError(
|
|
638
|
+
`${adapterName}: network "${opts.network}" does not match manifest.network "${manifest.network}"`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
let signerPublicKey;
|
|
642
|
+
try {
|
|
643
|
+
signerPublicKey = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey).publicKey();
|
|
644
|
+
} catch (err) {
|
|
645
|
+
throw new RouteDockManifestError(`${adapterName}: payeeSecretKey is not a valid Stellar secret key`, { cause: err });
|
|
646
|
+
}
|
|
647
|
+
if (signerPublicKey !== manifest.payee) {
|
|
648
|
+
throw new RouteDockManifestError(
|
|
649
|
+
`${adapterName}: payeeSecretKey belongs to ${signerPublicKey}, not manifest.payee ${manifest.payee}; clients would reject the signed manifest`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (opts.payee !== manifest.payee) {
|
|
653
|
+
throw new RouteDockManifestError(
|
|
654
|
+
`${adapterName}: payee "${opts.payee}" does not match manifest.payee "${manifest.payee}"`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/internal/encoding.ts
|
|
660
|
+
var encoder = new TextEncoder();
|
|
661
|
+
var decoder = new TextDecoder();
|
|
662
|
+
function utf8ToBytes(text) {
|
|
663
|
+
return encoder.encode(text);
|
|
664
|
+
}
|
|
665
|
+
function bytesToUtf8(bytes) {
|
|
666
|
+
return decoder.decode(bytes);
|
|
667
|
+
}
|
|
668
|
+
function bytesToBase64(bytes) {
|
|
669
|
+
const chunk = 32768;
|
|
670
|
+
let binary = "";
|
|
671
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
672
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
673
|
+
}
|
|
674
|
+
return btoa(binary);
|
|
675
|
+
}
|
|
676
|
+
function base64ToBytes(b64) {
|
|
677
|
+
const end = b64.indexOf("=");
|
|
678
|
+
let clean = (end === -1 ? b64 : b64.slice(0, end)).replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
|
|
679
|
+
if (clean.length % 4 === 1) clean = clean.slice(0, -1);
|
|
680
|
+
const binary = atob(clean);
|
|
681
|
+
const bytes = new Uint8Array(binary.length);
|
|
682
|
+
for (let i = 0; i < binary.length; i++) {
|
|
683
|
+
bytes[i] = binary.charCodeAt(i);
|
|
684
|
+
}
|
|
685
|
+
return bytes;
|
|
686
|
+
}
|
|
687
|
+
function base64ToUtf8(b64) {
|
|
688
|
+
return bytesToUtf8(base64ToBytes(b64));
|
|
689
|
+
}
|
|
690
|
+
function hexToBytes(hex) {
|
|
691
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
692
|
+
if (clean.length % 2 !== 0) {
|
|
693
|
+
throw new Error("hexToBytes: hex string must have an even length");
|
|
694
|
+
}
|
|
695
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
696
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
697
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
698
|
+
}
|
|
699
|
+
return bytes;
|
|
700
|
+
}
|
|
701
|
+
function sha256(data) {
|
|
702
|
+
return sha2_js.sha256(typeof data === "string" ? utf8ToBytes(data) : data);
|
|
703
|
+
}
|
|
51
704
|
|
|
52
705
|
// src/manifest/sign.ts
|
|
53
706
|
var MANIFEST_SIGNATURE_VERSION = "2";
|
|
@@ -65,7 +718,7 @@ function canonicalize(value) {
|
|
|
65
718
|
function manifestDigest(manifest) {
|
|
66
719
|
const { signature: _omit, ...rest } = manifest;
|
|
67
720
|
const canonical = JSON.stringify(canonicalize(rest));
|
|
68
|
-
return
|
|
721
|
+
return sha256(utf8ToBytes(canonical));
|
|
69
722
|
}
|
|
70
723
|
function signManifest(manifest, payeeSecretKey) {
|
|
71
724
|
const keypair = stellarSdk.Keypair.fromSecret(payeeSecretKey);
|
|
@@ -75,7 +728,7 @@ function signManifest(manifest, payeeSecretKey) {
|
|
|
75
728
|
};
|
|
76
729
|
const digest = manifestDigest(versionedManifest);
|
|
77
730
|
const sig = keypair.sign(digest);
|
|
78
|
-
return { ...versionedManifest, signature:
|
|
731
|
+
return { ...versionedManifest, signature: bytesToBase64(sig) };
|
|
79
732
|
}
|
|
80
733
|
function verifyManifestSignature(manifest, expectedPayee) {
|
|
81
734
|
if (!manifest.signature) {
|
|
@@ -88,11 +741,19 @@ function verifyManifestSignature(manifest, expectedPayee) {
|
|
|
88
741
|
`Manifest signature version must be ${MANIFEST_SIGNATURE_VERSION}; legacy signatures are not safe because nested fields were excluded from their digest`
|
|
89
742
|
);
|
|
90
743
|
}
|
|
91
|
-
|
|
744
|
+
let keypair;
|
|
745
|
+
try {
|
|
746
|
+
keypair = stellarSdk.Keypair.fromPublicKey(manifest.payee);
|
|
747
|
+
} catch (err) {
|
|
748
|
+
throw new RouteDockSignatureError(
|
|
749
|
+
`Manifest payee ${manifest.payee} is not a valid Stellar public key`,
|
|
750
|
+
{ cause: err }
|
|
751
|
+
);
|
|
752
|
+
}
|
|
92
753
|
const digest = manifestDigest(manifest);
|
|
93
754
|
let sigBytes;
|
|
94
755
|
try {
|
|
95
|
-
sigBytes =
|
|
756
|
+
sigBytes = base64ToBytes(manifest.signature);
|
|
96
757
|
} catch {
|
|
97
758
|
throw new RouteDockSignatureError("Manifest signature is not valid base64");
|
|
98
759
|
}
|
|
@@ -116,7 +777,6 @@ function resolvePayee(manifest, mode) {
|
|
|
116
777
|
const override = manifest.pricing[mode]?.payee;
|
|
117
778
|
return override ?? manifest.payee;
|
|
118
779
|
}
|
|
119
|
-
var usdcToUnits = nulthSdk.usdcToStroops;
|
|
120
780
|
function extractPayerAddress(key) {
|
|
121
781
|
if (typeof key !== "string") return null;
|
|
122
782
|
return stellarSdk.StrKey.isValidEd25519PublicKey(key) || stellarSdk.StrKey.isValidMed25519PublicKey(key) ? key : null;
|
|
@@ -200,30 +860,21 @@ function mapChannelError(error) {
|
|
|
200
860
|
return new mppx.Errors.VerificationFailedError({ reason });
|
|
201
861
|
}
|
|
202
862
|
|
|
203
|
-
// src/provider/encoding.ts
|
|
204
|
-
function base64ToUtf8(b64) {
|
|
205
|
-
const binary = atob(b64);
|
|
206
|
-
const bytes = new Uint8Array(binary.length);
|
|
207
|
-
for (let i = 0; i < binary.length; i++) {
|
|
208
|
-
bytes[i] = binary.charCodeAt(i);
|
|
209
|
-
}
|
|
210
|
-
return new TextDecoder().decode(bytes);
|
|
211
|
-
}
|
|
212
|
-
function hexToBytes(hex) {
|
|
213
|
-
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
214
|
-
if (clean.length % 2 !== 0) {
|
|
215
|
-
throw new Error("hexToBytes: hex string must have an even length");
|
|
216
|
-
}
|
|
217
|
-
const bytes = new Uint8Array(clean.length / 2);
|
|
218
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
219
|
-
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
220
|
-
}
|
|
221
|
-
return bytes;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
863
|
// src/provider/SeenTxStore.ts
|
|
225
864
|
var SETTLEMENT_REPLAY_WINDOW_MS = 6e4;
|
|
226
865
|
var MAX_SETTLEMENT_REPLAYS = 1;
|
|
866
|
+
async function recordSettlement(store, key, record, ctx) {
|
|
867
|
+
try {
|
|
868
|
+
await store.set(key, record);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
ctx.logger("error", `${ctx.label} seenTxStore.set failed after settlement`, { error: err });
|
|
871
|
+
try {
|
|
872
|
+
ctx.onCallbackError?.(err, "seenTxStore.set");
|
|
873
|
+
} catch (cbErr) {
|
|
874
|
+
ctx.logger("error", `${ctx.label} onCallbackError threw`, { error: cbErr });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
227
878
|
async function checkSettlementReplay(store, key, now = Date.now()) {
|
|
228
879
|
const record = await store.get(key);
|
|
229
880
|
if (!record) return { kind: "miss" };
|
|
@@ -363,7 +1014,7 @@ function selectAsset(manifest, mode, endpoint) {
|
|
|
363
1014
|
return eligible[0];
|
|
364
1015
|
}
|
|
365
1016
|
|
|
366
|
-
// src/
|
|
1017
|
+
// src/internal/settleResult.ts
|
|
367
1018
|
function readSettleResult(result) {
|
|
368
1019
|
const settle = result;
|
|
369
1020
|
const failed = (reason) => reason ? { ok: false, reason } : { ok: false };
|
|
@@ -374,6 +1025,138 @@ function readSettleResult(result) {
|
|
|
374
1025
|
return { ok: true, txHash: settle.transaction };
|
|
375
1026
|
}
|
|
376
1027
|
|
|
1028
|
+
// src/provider/internal/vaultRpc.ts
|
|
1029
|
+
async function createSorobanRpcServer(rpcUrl) {
|
|
1030
|
+
const { Server } = await import('@stellar/stellar-sdk/rpc');
|
|
1031
|
+
return new Server(rpcUrl);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/provider/internal/vaultSettlement.ts
|
|
1035
|
+
function resolveVaultSettlementAddresses(payer, payee) {
|
|
1036
|
+
if (!payer) return null;
|
|
1037
|
+
return { payer, payee };
|
|
1038
|
+
}
|
|
1039
|
+
function buildSessionSettlementTransaction(source, vaultContractId, channelId, addresses, amount, voucherCount, networkPassphrase) {
|
|
1040
|
+
const vault = new stellarSdk.Contract(vaultContractId);
|
|
1041
|
+
const op = vault.call(
|
|
1042
|
+
"record_session_settlement",
|
|
1043
|
+
stellarSdk.nativeToScVal(channelId, { type: "address" }),
|
|
1044
|
+
stellarSdk.nativeToScVal(addresses.payer, { type: "address" }),
|
|
1045
|
+
stellarSdk.nativeToScVal(addresses.payee, { type: "address" }),
|
|
1046
|
+
stellarSdk.nativeToScVal(amount, { type: "i128" }),
|
|
1047
|
+
stellarSdk.nativeToScVal(voucherCount, { type: "u32" })
|
|
1048
|
+
);
|
|
1049
|
+
return new stellarSdk.TransactionBuilder(source, {
|
|
1050
|
+
fee: stellarSdk.BASE_FEE,
|
|
1051
|
+
networkPassphrase
|
|
1052
|
+
}).addOperation(op).setTimeout(30).build();
|
|
1053
|
+
}
|
|
1054
|
+
var DEFAULT_RPC_URLS = {
|
|
1055
|
+
testnet: "https://soroban-testnet.stellar.org",
|
|
1056
|
+
mainnet: "https://mainnet.sorobanrpc.com"
|
|
1057
|
+
};
|
|
1058
|
+
var CONFIRM_TIMEOUT_MS = 12e4;
|
|
1059
|
+
var CONFIRM_POLL_MS = 1e3;
|
|
1060
|
+
function delay(ms) {
|
|
1061
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1062
|
+
}
|
|
1063
|
+
function describeSendError(sent) {
|
|
1064
|
+
const errorResult = sent.errorResult;
|
|
1065
|
+
try {
|
|
1066
|
+
const name = errorResult?.result?.().switch?.().name;
|
|
1067
|
+
if (name) return `sendTransaction returned ERROR: ${name}`;
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
1070
|
+
return `sendTransaction returned status ${sent.status}`;
|
|
1071
|
+
}
|
|
1072
|
+
async function waitForConfirmation(server, txHash) {
|
|
1073
|
+
const deadline = Date.now() + CONFIRM_TIMEOUT_MS;
|
|
1074
|
+
for (; ; ) {
|
|
1075
|
+
const tx = await server.getTransaction(txHash);
|
|
1076
|
+
if (tx.status === "SUCCESS") return "success";
|
|
1077
|
+
if (tx.status === "FAILED") return "failed";
|
|
1078
|
+
if (Date.now() >= deadline) return "timeout";
|
|
1079
|
+
await delay(CONFIRM_POLL_MS);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async function recordSessionSettlement(request, { logger, onCallbackError }) {
|
|
1083
|
+
const reportFailure = (fields) => {
|
|
1084
|
+
logger("error", "[vault] failed to record session_settled", {
|
|
1085
|
+
channelId: request.channelId,
|
|
1086
|
+
...fields.txHash ? { txHash: fields.txHash } : {},
|
|
1087
|
+
reason: fields.reason,
|
|
1088
|
+
...fields.error !== void 0 ? { error: fields.error } : {}
|
|
1089
|
+
});
|
|
1090
|
+
if (onCallbackError) {
|
|
1091
|
+
const err = fields.error ?? new Error(`session_settled vault record failed: ${fields.reason}`);
|
|
1092
|
+
onCallbackError(err, "vaultSettlement");
|
|
1093
|
+
}
|
|
1094
|
+
return { ok: false, reason: fields.reason, ...fields.txHash ? { txHash: fields.txHash } : {} };
|
|
1095
|
+
};
|
|
1096
|
+
try {
|
|
1097
|
+
const payeeKeypair = stellarSdk.Keypair.fromSecret(request.payeeSecretKey);
|
|
1098
|
+
const addresses = resolveVaultSettlementAddresses(request.payer, payeeKeypair.publicKey());
|
|
1099
|
+
if (!addresses) {
|
|
1100
|
+
logger("warn", "[vault] skipped session_settled record: payer address unavailable", {
|
|
1101
|
+
channelId: request.channelId
|
|
1102
|
+
});
|
|
1103
|
+
return { ok: false, reason: "payer address unavailable" };
|
|
1104
|
+
}
|
|
1105
|
+
const networkPassphrase = request.network === "mainnet" ? stellarSdk.Networks.PUBLIC : stellarSdk.Networks.TESTNET;
|
|
1106
|
+
const rpcUrl = request.config.rpcUrl ?? DEFAULT_RPC_URLS[request.network];
|
|
1107
|
+
const server = await createSorobanRpcServer(rpcUrl);
|
|
1108
|
+
const sourceAccount = await server.getAccount(payeeKeypair.publicKey());
|
|
1109
|
+
const tx = buildSessionSettlementTransaction(
|
|
1110
|
+
sourceAccount,
|
|
1111
|
+
request.config.contract,
|
|
1112
|
+
request.channelId,
|
|
1113
|
+
addresses,
|
|
1114
|
+
request.amount,
|
|
1115
|
+
request.voucherCount,
|
|
1116
|
+
networkPassphrase
|
|
1117
|
+
);
|
|
1118
|
+
const preparedTx = await server.prepareTransaction(tx);
|
|
1119
|
+
preparedTx.sign(payeeKeypair);
|
|
1120
|
+
const sent = await server.sendTransaction(preparedTx);
|
|
1121
|
+
if (sent.status === "ERROR") {
|
|
1122
|
+
return reportFailure({
|
|
1123
|
+
txHash: sent.hash || preparedTx.hash().toString("hex"),
|
|
1124
|
+
reason: describeSendError(sent)
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
const txHash = sent.hash || preparedTx.hash().toString("hex");
|
|
1128
|
+
const confirmation = await waitForConfirmation(server, txHash);
|
|
1129
|
+
if (confirmation === "success") {
|
|
1130
|
+
logger("debug", "[vault] recorded session_settled", {
|
|
1131
|
+
channelId: request.channelId,
|
|
1132
|
+
txHash
|
|
1133
|
+
});
|
|
1134
|
+
return { ok: true, txHash };
|
|
1135
|
+
}
|
|
1136
|
+
return reportFailure({
|
|
1137
|
+
txHash,
|
|
1138
|
+
reason: confirmation === "failed" ? "transaction failed on-chain" : `confirmation timed out after ${CONFIRM_TIMEOUT_MS}ms`
|
|
1139
|
+
});
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1142
|
+
return reportFailure({ reason, error: err });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/internal/vaultConfig.ts
|
|
1147
|
+
function resolveVaultSettlementConfig(option, logger) {
|
|
1148
|
+
if (option) return option;
|
|
1149
|
+
if (typeof process === "undefined") return void 0;
|
|
1150
|
+
const fromEnv = process.env.AGENT_VAULT_CONTRACT ?? process.env.AGENT_VAULT_CONTRACT_ID;
|
|
1151
|
+
if (!fromEnv) return void 0;
|
|
1152
|
+
logger(
|
|
1153
|
+
"warn",
|
|
1154
|
+
"[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",
|
|
1155
|
+
{}
|
|
1156
|
+
);
|
|
1157
|
+
return { contract: fromEnv };
|
|
1158
|
+
}
|
|
1159
|
+
|
|
377
1160
|
// src/provider/registerProvider.ts
|
|
378
1161
|
async function registerProvider(optionsOrSupabase, manifestArg, baseUrlArg, verifiedArg) {
|
|
379
1162
|
let supabase;
|
|
@@ -471,6 +1254,8 @@ var MPP_NETWORK = {
|
|
|
471
1254
|
testnet: "stellar:testnet",
|
|
472
1255
|
mainnet: "stellar:pubnet"
|
|
473
1256
|
};
|
|
1257
|
+
var DEFAULT_MAX_RECONCILE_ATTEMPTS = 5;
|
|
1258
|
+
var ALREADY_CLOSED_PATTERN = /channel has been closed|channel is closed|already closed|finalized/i;
|
|
474
1259
|
function decimalToStroops(amountStr) {
|
|
475
1260
|
return nulthSdk.usdcToStroops(amountStr);
|
|
476
1261
|
}
|
|
@@ -480,12 +1265,15 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
480
1265
|
recoveredCount: 0,
|
|
481
1266
|
skippedCount: 0,
|
|
482
1267
|
failedCount: 0,
|
|
1268
|
+
alreadyClosedCount: 0,
|
|
1269
|
+
terminalCount: 0,
|
|
483
1270
|
errors: []
|
|
484
1271
|
};
|
|
1272
|
+
const maxAttempts = opts.maxReconcileAttempts ?? DEFAULT_MAX_RECONCILE_ATTEMPTS;
|
|
485
1273
|
const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
|
|
486
1274
|
const networkId = MPP_NETWORK[opts.network];
|
|
487
1275
|
try {
|
|
488
|
-
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);
|
|
1276
|
+
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);
|
|
489
1277
|
if (error) {
|
|
490
1278
|
throw new Error(`Failed to query sessions: ${error.message}`);
|
|
491
1279
|
}
|
|
@@ -507,7 +1295,7 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
507
1295
|
continue;
|
|
508
1296
|
}
|
|
509
1297
|
const closeAmount = decimalToStroops(cumulative_amount);
|
|
510
|
-
const closeSig =
|
|
1298
|
+
const closeSig = hexToBytes(last_signature);
|
|
511
1299
|
const closeFn = opts.channelClose ?? (await import('@stellar/mpp/channel/server')).close;
|
|
512
1300
|
const closeTxHash = await closeFn({
|
|
513
1301
|
channel: channelId,
|
|
@@ -530,11 +1318,36 @@ async function reconcileAbandonedSessions(opts) {
|
|
|
530
1318
|
await opts.onRecovered(channelId, closeTxHash, totalPaid);
|
|
531
1319
|
}
|
|
532
1320
|
} catch (err) {
|
|
1321
|
+
const reason = formatMppError(err);
|
|
1322
|
+
if (ALREADY_CLOSED_PATTERN.test(reason)) {
|
|
1323
|
+
const { error: closeError } = await opts.supabase.from("sessions").update({
|
|
1324
|
+
status: "closed",
|
|
1325
|
+
last_reconcile_error: `channel already closed on-chain: ${reason}`,
|
|
1326
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1327
|
+
}).eq("channel_id", session.channel_id);
|
|
1328
|
+
if (!closeError) {
|
|
1329
|
+
stats.alreadyClosedCount++;
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
533
1333
|
stats.failedCount++;
|
|
534
|
-
stats.errors.push({
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
})
|
|
1334
|
+
stats.errors.push({ channelId: session.channel_id, reason });
|
|
1335
|
+
const attempts = (Number(session.reconcile_attempts) || 0) + 1;
|
|
1336
|
+
const terminal = attempts >= maxAttempts;
|
|
1337
|
+
const { error: bookkeepingError } = await opts.supabase.from("sessions").update({
|
|
1338
|
+
reconcile_attempts: attempts,
|
|
1339
|
+
last_reconcile_error: reason,
|
|
1340
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1341
|
+
...terminal ? { status: "failed" } : {}
|
|
1342
|
+
}).eq("channel_id", session.channel_id);
|
|
1343
|
+
if (bookkeepingError) {
|
|
1344
|
+
stats.errors.push({
|
|
1345
|
+
channelId: session.channel_id,
|
|
1346
|
+
reason: `Failed to record reconcile failure: ${bookkeepingError.message}`
|
|
1347
|
+
});
|
|
1348
|
+
} else if (terminal) {
|
|
1349
|
+
stats.terminalCount++;
|
|
1350
|
+
}
|
|
538
1351
|
}
|
|
539
1352
|
}
|
|
540
1353
|
return stats;
|
|
@@ -555,7 +1368,7 @@ async function runStartupReconciliation(opts, logger) {
|
|
|
555
1368
|
}
|
|
556
1369
|
log(
|
|
557
1370
|
"info",
|
|
558
|
-
`[SessionReconciler] Found ${stats.orphanedCount} orphaned session(s). Recovered: ${stats.recoveredCount}, Skipped: ${stats.skippedCount}, Failed: ${stats.failedCount}`
|
|
1371
|
+
`[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}`
|
|
559
1372
|
);
|
|
560
1373
|
if (stats.errors.length > 0) {
|
|
561
1374
|
log("warn", "[SessionReconciler] Reconciliation errors:");
|
|
@@ -697,12 +1510,9 @@ function createX402HonoHandler(opts) {
|
|
|
697
1510
|
requirements
|
|
698
1511
|
);
|
|
699
1512
|
if (!verifyResult.isValid) {
|
|
700
|
-
return
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
reason: verifyResult.invalidReason
|
|
704
|
-
},
|
|
705
|
-
401
|
|
1513
|
+
return respondPaymentRequired(
|
|
1514
|
+
"Payment verification failed",
|
|
1515
|
+
verifyResult.invalidReason
|
|
706
1516
|
);
|
|
707
1517
|
}
|
|
708
1518
|
const settleResult = await localFacilitator.settle(
|
|
@@ -722,7 +1532,12 @@ function createX402HonoHandler(opts) {
|
|
|
722
1532
|
if (idempotencyKey) {
|
|
723
1533
|
const headers = {};
|
|
724
1534
|
if (paymentResponseHeader) headers["X-Payment-Response"] = paymentResponseHeader;
|
|
725
|
-
await
|
|
1535
|
+
await recordSettlement(
|
|
1536
|
+
seenTxStore,
|
|
1537
|
+
idempotencyKey,
|
|
1538
|
+
{ txHash, headers, createdAt: Date.now() },
|
|
1539
|
+
{ logger, label: "[x402]", onCallbackError: opts.onCallbackError }
|
|
1540
|
+
);
|
|
726
1541
|
}
|
|
727
1542
|
if (txHash && opts.onSettled) {
|
|
728
1543
|
Promise.resolve().then(() => opts.onSettled(txHash, x402Price, "x402", payerAddress)).catch((err) => {
|
|
@@ -845,11 +1660,12 @@ function createMppChargeHonoHandler(opts) {
|
|
|
845
1660
|
}
|
|
846
1661
|
}
|
|
847
1662
|
if (idempotencyKey) {
|
|
848
|
-
await
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
createdAt: Date.now()
|
|
852
|
-
|
|
1663
|
+
await recordSettlement(
|
|
1664
|
+
seenTxStore,
|
|
1665
|
+
idempotencyKey,
|
|
1666
|
+
{ txHash: reference ?? null, headers: receiptHeaders, createdAt: Date.now() },
|
|
1667
|
+
{ logger, label: "[mpp-charge]", onCallbackError: opts.onCallbackError }
|
|
1668
|
+
);
|
|
853
1669
|
}
|
|
854
1670
|
if (reference && opts.onSettled) {
|
|
855
1671
|
Promise.resolve().then(() => opts.onSettled(reference, chargePrice, "mpp-charge", payerAddress)).catch((err) => {
|
|
@@ -859,7 +1675,11 @@ function createMppChargeHonoHandler(opts) {
|
|
|
859
1675
|
}
|
|
860
1676
|
await next();
|
|
861
1677
|
} catch (err) {
|
|
862
|
-
|
|
1678
|
+
if (err instanceof RouteDockManifestError) {
|
|
1679
|
+
throw err;
|
|
1680
|
+
}
|
|
1681
|
+
logger("error", "[mpp-charge] Settlement error", { error: err });
|
|
1682
|
+
return c.json({ error: "Payment settlement failed" }, 500);
|
|
863
1683
|
}
|
|
864
1684
|
};
|
|
865
1685
|
}
|
|
@@ -873,9 +1693,11 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
873
1693
|
const logger = resolveLogger(opts.logger);
|
|
874
1694
|
const voucherRecordKey = `routedock:session:voucher:${sessionPricing.channelFactory}`;
|
|
875
1695
|
const innerStore = opts.sessionStore ?? server$2.Store.memory();
|
|
1696
|
+
const cumulativeKey = `stellar:channel:cumulative:${sessionPricing.channelFactory}`;
|
|
876
1697
|
let record = null;
|
|
877
1698
|
let voucherCount = 0;
|
|
878
1699
|
let sessionOpened = false;
|
|
1700
|
+
let orphanFlagged = false;
|
|
879
1701
|
let idleTimer = null;
|
|
880
1702
|
let settledCleanly = false;
|
|
881
1703
|
let abortListenerArmed = false;
|
|
@@ -902,15 +1724,32 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
902
1724
|
return null;
|
|
903
1725
|
}
|
|
904
1726
|
record = { amount: BigInt(stored.amount), signature: stored.signature, payer: stored.payer ?? null };
|
|
1727
|
+
if (typeof stored.voucherCount === "number") {
|
|
1728
|
+
voucherCount = stored.voucherCount;
|
|
1729
|
+
}
|
|
1730
|
+
if (typeof stored.sessionOpened === "boolean") {
|
|
1731
|
+
sessionOpened = stored.sessionOpened;
|
|
1732
|
+
} else if (record !== null) {
|
|
1733
|
+
sessionOpened = true;
|
|
1734
|
+
}
|
|
905
1735
|
} catch {
|
|
906
1736
|
return null;
|
|
907
1737
|
}
|
|
908
1738
|
return record;
|
|
909
1739
|
}
|
|
1740
|
+
async function readStoredCumulative() {
|
|
1741
|
+
try {
|
|
1742
|
+
const raw = await innerStore.get(cumulativeKey);
|
|
1743
|
+
const amount = typeof raw === "object" && raw !== null ? raw.amount : void 0;
|
|
1744
|
+
return typeof amount === "string" && /^\d+$/.test(amount) ? BigInt(amount) : null;
|
|
1745
|
+
} catch {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
910
1749
|
async function flagOrphan(reason) {
|
|
911
1750
|
await loadPersistedRecord();
|
|
912
|
-
if (!sessionOpened || settledCleanly) return;
|
|
913
|
-
|
|
1751
|
+
if (!sessionOpened || settledCleanly || orphanFlagged) return;
|
|
1752
|
+
orphanFlagged = true;
|
|
914
1753
|
abortListenerArmed = false;
|
|
915
1754
|
clearIdleTimer();
|
|
916
1755
|
const cumulativeAmount = record ? (Number(record.amount) / 1e7).toFixed(7) : "0.0000000";
|
|
@@ -958,16 +1797,20 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
958
1797
|
if (record && amount < record.amount) return;
|
|
959
1798
|
const payer = record?.payer ?? extractPayerAddress(credential.source);
|
|
960
1799
|
record = { amount, signature: payload.signature, payer };
|
|
1800
|
+
voucherCount++;
|
|
1801
|
+
settledCleanly = false;
|
|
1802
|
+
orphanFlagged = false;
|
|
1803
|
+
armIdleTimer();
|
|
1804
|
+
const wasOpened = sessionOpened;
|
|
1805
|
+
sessionOpened = true;
|
|
961
1806
|
await innerStore.put(voucherRecordKey, {
|
|
962
1807
|
amount: record.amount.toString(),
|
|
963
1808
|
signature: record.signature,
|
|
964
|
-
payer: record.payer
|
|
1809
|
+
payer: record.payer,
|
|
1810
|
+
voucherCount,
|
|
1811
|
+
sessionOpened: true
|
|
965
1812
|
});
|
|
966
|
-
|
|
967
|
-
settledCleanly = false;
|
|
968
|
-
armIdleTimer();
|
|
969
|
-
if (!sessionOpened) {
|
|
970
|
-
sessionOpened = true;
|
|
1813
|
+
if (!wasOpened) {
|
|
971
1814
|
if (opts.onSessionOpen) {
|
|
972
1815
|
Promise.resolve().then(() => opts.onSessionOpen(sessionPricing.channelFactory, record.payer)).catch((err) => {
|
|
973
1816
|
logger("error", "[mpp-session] onSessionOpen callback error", { error: err });
|
|
@@ -1022,6 +1865,16 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
1022
1865
|
closeSig = record?.signature || (bodyAmount === recordAmount ? body?.signature ?? "" : "");
|
|
1023
1866
|
}
|
|
1024
1867
|
if (closeAmount > 0n && closeSig) {
|
|
1868
|
+
const storedCumulative = await readStoredCumulative();
|
|
1869
|
+
if (storedCumulative !== null && closeAmount < storedCumulative) {
|
|
1870
|
+
return c.json(
|
|
1871
|
+
{
|
|
1872
|
+
closeTxHash: null,
|
|
1873
|
+
error: "close amount below the highest verified voucher"
|
|
1874
|
+
},
|
|
1875
|
+
409
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1025
1878
|
const closeTxHash = await server$2.close({
|
|
1026
1879
|
channel: sessionPricing.channelFactory,
|
|
1027
1880
|
amount: closeAmount,
|
|
@@ -1039,8 +1892,25 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
1039
1892
|
opts.onCallbackError?.(err, "onSettled");
|
|
1040
1893
|
});
|
|
1041
1894
|
}
|
|
1895
|
+
const vaultConfig = resolveVaultSettlementConfig(opts.vaultSettlement, logger);
|
|
1896
|
+
if (vaultConfig) {
|
|
1897
|
+
const settlement = recordSessionSettlement(
|
|
1898
|
+
{
|
|
1899
|
+
config: vaultConfig,
|
|
1900
|
+
network: opts.network,
|
|
1901
|
+
payeeSecretKey: opts.payeeSecretKey,
|
|
1902
|
+
channelId: sessionPricing.channelFactory,
|
|
1903
|
+
payer: closePayer,
|
|
1904
|
+
amount: closeAmount,
|
|
1905
|
+
voucherCount
|
|
1906
|
+
},
|
|
1907
|
+
{ logger, onCallbackError: opts.onCallbackError }
|
|
1908
|
+
);
|
|
1909
|
+
if (opts.waitUntil) opts.waitUntil(settlement);
|
|
1910
|
+
}
|
|
1042
1911
|
sessionOpened = false;
|
|
1043
1912
|
voucherCount = 0;
|
|
1913
|
+
orphanFlagged = false;
|
|
1044
1914
|
record = null;
|
|
1045
1915
|
await innerStore.delete(voucherRecordKey);
|
|
1046
1916
|
abortListenerArmed = false;
|
|
@@ -1065,10 +1935,11 @@ function createMppSessionHandlerState(opts, sessionPricing, reportMode) {
|
|
|
1065
1935
|
}
|
|
1066
1936
|
function createMppSessionHonoHandler(opts, config, state) {
|
|
1067
1937
|
const sessionPricing = opts.pricing[config.mode];
|
|
1938
|
+
const logger = resolveLogger(opts.logger);
|
|
1068
1939
|
return async (c, next) => {
|
|
1069
1940
|
try {
|
|
1070
1941
|
if (c.req.method === "DELETE") {
|
|
1071
|
-
return state.handleDelete(c);
|
|
1942
|
+
return await state.handleDelete(c);
|
|
1072
1943
|
}
|
|
1073
1944
|
const result = await state.mppx.channel({
|
|
1074
1945
|
amount: sessionPricing.rate,
|
|
@@ -1090,7 +1961,11 @@ function createMppSessionHonoHandler(opts, config, state) {
|
|
|
1090
1961
|
}
|
|
1091
1962
|
await next();
|
|
1092
1963
|
} catch (err) {
|
|
1093
|
-
|
|
1964
|
+
if (err instanceof RouteDockManifestError) {
|
|
1965
|
+
throw err;
|
|
1966
|
+
}
|
|
1967
|
+
logger("error", `[${config.mode}] Session error`, { error: err });
|
|
1968
|
+
return c.json({ error: "Session payment failed" }, 500);
|
|
1094
1969
|
}
|
|
1095
1970
|
};
|
|
1096
1971
|
}
|
|
@@ -1099,6 +1974,7 @@ function isWebSocketUpgradeRequest(c) {
|
|
|
1099
1974
|
}
|
|
1100
1975
|
function routedockHono(opts) {
|
|
1101
1976
|
const handlers = [];
|
|
1977
|
+
assertProviderConfig(opts, "routedockHono");
|
|
1102
1978
|
const signedManifest = signManifest(opts.manifest, opts.payeeSecretKey);
|
|
1103
1979
|
if (opts.modes.includes("x402") && opts.pricing.x402) {
|
|
1104
1980
|
handlers.push(createX402HonoHandler({ ...opts, manifest: signedManifest }));
|