@routedock/routedock 0.1.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/PUBLISH_CHECKLIST.md +70 -0
- package/README.md +88 -0
- package/dist/SessionStore-CCVO0PHM.d.cts +152 -0
- package/dist/SessionStore-CCVO0PHM.d.ts +152 -0
- package/dist/client.cjs +618 -0
- package/dist/client.d.cts +98 -0
- package/dist/client.d.ts +98 -0
- package/dist/client.js +601 -0
- package/dist/index.cjs +932 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +914 -0
- package/dist/provider.cjs +425 -0
- package/dist/provider.d.cts +44 -0
- package/dist/provider.d.ts +44 -0
- package/dist/provider.js +414 -0
- package/dist/routedockMiddleware-Beqan6QS.d.cts +49 -0
- package/dist/routedockMiddleware-CqnyHjhO.d.ts +49 -0
- package/package.json +61 -0
- package/src/client/ModeRouter.ts +104 -0
- package/src/client/MppChargeClient.ts +44 -0
- package/src/client/MppSessionClient.ts +144 -0
- package/src/client/RouteDockClient.ts +124 -0
- package/src/client/index.ts +8 -0
- package/src/client/x402Client.ts +84 -0
- package/src/index.ts +9 -0
- package/src/provider/MppChargeHandler.ts +90 -0
- package/src/provider/MppSessionHandler.ts +136 -0
- package/src/provider/index.ts +7 -0
- package/src/provider/routedockMiddleware.ts +148 -0
- package/src/provider/x402Handler.ts +112 -0
- package/src/schemas/routedock.schema.json +159 -0
- package/src/store/SessionStore.ts +90 -0
- package/src/tests/smoke.test.ts +228 -0
- package/src/types.ts +165 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +15 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var stellarSdk = require('@stellar/stellar-sdk');
|
|
4
|
+
var Ajv = require('ajv');
|
|
5
|
+
var stellar = require('@x402/stellar');
|
|
6
|
+
var client = require('@x402/stellar/exact/client');
|
|
7
|
+
var client$1 = require('@x402/core/client');
|
|
8
|
+
var http = require('@x402/core/http');
|
|
9
|
+
var client$3 = require('@stellar/mpp/charge/client');
|
|
10
|
+
var client$2 = require('mppx/client');
|
|
11
|
+
var client$4 = require('@stellar/mpp/channel/client');
|
|
12
|
+
var facilitator = require('@x402/stellar/exact/facilitator');
|
|
13
|
+
var server$1 = require('@stellar/mpp/charge/server');
|
|
14
|
+
var server = require('mppx/server');
|
|
15
|
+
var server$2 = require('@stellar/mpp/channel/server');
|
|
16
|
+
|
|
17
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
|
|
19
|
+
var Ajv__default = /*#__PURE__*/_interopDefault(Ajv);
|
|
20
|
+
|
|
21
|
+
// src/types.ts
|
|
22
|
+
var RouteDockError = class extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "RouteDockError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var RouteDockManifestError = class extends RouteDockError {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "RouteDockManifestError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var RouteDockNoSupportedModeError = class extends RouteDockError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "RouteDockNoSupportedModeError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var RouteDockSessionError = class extends RouteDockError {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "RouteDockSessionError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var RouteDockPolicyRejectedError = class extends RouteDockError {
|
|
47
|
+
reason;
|
|
48
|
+
constructor(reason) {
|
|
49
|
+
super(`Payment rejected by policy: ${reason}`);
|
|
50
|
+
this.name = "RouteDockPolicyRejectedError";
|
|
51
|
+
this.reason = reason;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/schemas/routedock.schema.json
|
|
56
|
+
var routedock_schema_default = {
|
|
57
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
58
|
+
$id: "https://routedock.dev/schemas/routedock.schema.json",
|
|
59
|
+
title: "RouteDock Manifest",
|
|
60
|
+
description: "Discovery manifest served at /.well-known/routedock.json on every RouteDock provider endpoint",
|
|
61
|
+
type: "object",
|
|
62
|
+
required: [
|
|
63
|
+
"routedock",
|
|
64
|
+
"name",
|
|
65
|
+
"description",
|
|
66
|
+
"modes",
|
|
67
|
+
"network",
|
|
68
|
+
"asset",
|
|
69
|
+
"asset_contract",
|
|
70
|
+
"payee",
|
|
71
|
+
"pricing",
|
|
72
|
+
"endpoints",
|
|
73
|
+
"tags"
|
|
74
|
+
],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
properties: {
|
|
77
|
+
routedock: {
|
|
78
|
+
type: "string",
|
|
79
|
+
const: "1.0",
|
|
80
|
+
description: "Manifest schema version. Always '1.0' for this version."
|
|
81
|
+
},
|
|
82
|
+
name: {
|
|
83
|
+
type: "string",
|
|
84
|
+
minLength: 1,
|
|
85
|
+
maxLength: 120,
|
|
86
|
+
description: "Human-readable provider name"
|
|
87
|
+
},
|
|
88
|
+
description: {
|
|
89
|
+
type: "string",
|
|
90
|
+
minLength: 1,
|
|
91
|
+
maxLength: 500,
|
|
92
|
+
description: "Human-readable description of the provider's data or service"
|
|
93
|
+
},
|
|
94
|
+
modes: {
|
|
95
|
+
type: "array",
|
|
96
|
+
minItems: 1,
|
|
97
|
+
uniqueItems: true,
|
|
98
|
+
items: {
|
|
99
|
+
$ref: "#/definitions/PaymentMode"
|
|
100
|
+
},
|
|
101
|
+
description: "Payment modes supported by this provider"
|
|
102
|
+
},
|
|
103
|
+
network: {
|
|
104
|
+
type: "string",
|
|
105
|
+
enum: ["testnet", "mainnet"],
|
|
106
|
+
description: "Stellar network this provider operates on"
|
|
107
|
+
},
|
|
108
|
+
asset: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Asset ticker symbol (e.g. 'USDC')"
|
|
111
|
+
},
|
|
112
|
+
asset_contract: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Stellar Asset Contract (SAC) address for the payment asset"
|
|
115
|
+
},
|
|
116
|
+
payee: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "Stellar address (G...) that receives payments"
|
|
119
|
+
},
|
|
120
|
+
pricing: {
|
|
121
|
+
type: "object",
|
|
122
|
+
description: "Pricing configuration per supported payment mode",
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
properties: {
|
|
125
|
+
x402: {
|
|
126
|
+
$ref: "#/definitions/PricingConfig"
|
|
127
|
+
},
|
|
128
|
+
"mpp-charge": {
|
|
129
|
+
$ref: "#/definitions/PricingConfig"
|
|
130
|
+
},
|
|
131
|
+
"mpp-session": {
|
|
132
|
+
$ref: "#/definitions/SessionPricingConfig"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
minProperties: 1
|
|
136
|
+
},
|
|
137
|
+
endpoints: {
|
|
138
|
+
type: "object",
|
|
139
|
+
description: "Map of endpoint name to HTTP method + path (e.g. { 'price': 'GET /price' })",
|
|
140
|
+
additionalProperties: {
|
|
141
|
+
type: "string"
|
|
142
|
+
},
|
|
143
|
+
minProperties: 1
|
|
144
|
+
},
|
|
145
|
+
tags: {
|
|
146
|
+
type: "array",
|
|
147
|
+
items: {
|
|
148
|
+
type: "string",
|
|
149
|
+
minLength: 1,
|
|
150
|
+
maxLength: 64
|
|
151
|
+
},
|
|
152
|
+
uniqueItems: true,
|
|
153
|
+
description: "Capability tags indexed with trigram search in the provider registry"
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
definitions: {
|
|
157
|
+
PaymentMode: {
|
|
158
|
+
type: "string",
|
|
159
|
+
enum: ["x402", "mpp-charge", "mpp-session"],
|
|
160
|
+
description: "A supported RouteDock payment mode"
|
|
161
|
+
},
|
|
162
|
+
PricingConfig: {
|
|
163
|
+
type: "object",
|
|
164
|
+
description: "Per-request pricing for x402 or mpp-charge modes",
|
|
165
|
+
required: ["amount", "per"],
|
|
166
|
+
additionalProperties: false,
|
|
167
|
+
properties: {
|
|
168
|
+
amount: {
|
|
169
|
+
type: "string",
|
|
170
|
+
description: "Cost per request in the payment asset (decimal string, e.g. '0.001')"
|
|
171
|
+
},
|
|
172
|
+
per: {
|
|
173
|
+
type: "string",
|
|
174
|
+
const: "request",
|
|
175
|
+
description: "Billing unit \u2014 always 'request' for non-session modes"
|
|
176
|
+
},
|
|
177
|
+
facilitator: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "x402 facilitator URL (required for x402 mode). OpenZeppelin Channels endpoint."
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
SessionPricingConfig: {
|
|
184
|
+
type: "object",
|
|
185
|
+
description: "Per-voucher pricing for mpp-session (one-way-channel) mode",
|
|
186
|
+
required: ["rate", "per", "channel_contract", "min_deposit", "refund_waiting_period_ledgers"],
|
|
187
|
+
additionalProperties: false,
|
|
188
|
+
properties: {
|
|
189
|
+
rate: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "Cost per voucher in the payment asset (decimal string, e.g. '0.0001')"
|
|
192
|
+
},
|
|
193
|
+
per: {
|
|
194
|
+
type: "string",
|
|
195
|
+
const: "voucher",
|
|
196
|
+
description: "Billing unit \u2014 always 'voucher' for session mode"
|
|
197
|
+
},
|
|
198
|
+
channel_contract: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Soroban contract address (C...) for the one-way-channel contract"
|
|
201
|
+
},
|
|
202
|
+
min_deposit: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "Minimum initial deposit to open a channel (decimal string)"
|
|
205
|
+
},
|
|
206
|
+
refund_waiting_period_ledgers: {
|
|
207
|
+
type: "integer",
|
|
208
|
+
minimum: 1,
|
|
209
|
+
description: "Ledgers the server has to settle before the funder can reclaim. Default: 17280 (~24h). Do not reduce in production."
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// src/client/ModeRouter.ts
|
|
217
|
+
var ajv = new Ajv__default.default();
|
|
218
|
+
var validateManifest = ajv.compile(routedock_schema_default);
|
|
219
|
+
var CACHE_TTL_MS = 6e4;
|
|
220
|
+
var manifestCache = /* @__PURE__ */ new Map();
|
|
221
|
+
async function fetchManifest(baseUrl) {
|
|
222
|
+
const cached = manifestCache.get(baseUrl);
|
|
223
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
224
|
+
return cached.manifest;
|
|
225
|
+
}
|
|
226
|
+
const url = baseUrl.replace(/\/$/, "") + "/.well-known/routedock.json";
|
|
227
|
+
let raw;
|
|
228
|
+
try {
|
|
229
|
+
const resp = await fetch(url);
|
|
230
|
+
if (!resp.ok) {
|
|
231
|
+
throw new RouteDockManifestError(`Manifest fetch failed: HTTP ${resp.status} from ${url}`);
|
|
232
|
+
}
|
|
233
|
+
raw = await resp.json();
|
|
234
|
+
} catch (err) {
|
|
235
|
+
if (err instanceof RouteDockManifestError) throw err;
|
|
236
|
+
throw new RouteDockManifestError(`Manifest fetch error: ${String(err)}`);
|
|
237
|
+
}
|
|
238
|
+
if (!validateManifest(raw)) {
|
|
239
|
+
const msgs = ajv.errorsText(validateManifest.errors);
|
|
240
|
+
throw new RouteDockManifestError(`Invalid manifest at ${url}: ${msgs}`);
|
|
241
|
+
}
|
|
242
|
+
const manifest = raw;
|
|
243
|
+
manifestCache.set(baseUrl, { manifest, fetchedAt: Date.now() });
|
|
244
|
+
return manifest;
|
|
245
|
+
}
|
|
246
|
+
function selectMode(manifest, options = {}) {
|
|
247
|
+
const modes = manifest.modes;
|
|
248
|
+
if (options.forceMode) {
|
|
249
|
+
if (!modes.includes(options.forceMode)) {
|
|
250
|
+
throw new RouteDockNoSupportedModeError(
|
|
251
|
+
`Provider does not support forced mode: ${options.forceMode} (available: ${modes.join(", ")})`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
console.log(`[RouteDock] ${manifest.name} \u2192 ${options.forceMode} (forced)`);
|
|
255
|
+
return options.forceMode;
|
|
256
|
+
}
|
|
257
|
+
if ((options.sustained || options.session) && modes.includes("mpp-session")) {
|
|
258
|
+
const mode = "mpp-session";
|
|
259
|
+
console.log(`[RouteDock] ${manifest.name} \u2192 ${mode}`);
|
|
260
|
+
return mode;
|
|
261
|
+
}
|
|
262
|
+
if (modes.includes("mpp-charge")) {
|
|
263
|
+
const mode = "mpp-charge";
|
|
264
|
+
console.log(`[RouteDock] ${manifest.name} \u2192 ${mode}`);
|
|
265
|
+
return mode;
|
|
266
|
+
}
|
|
267
|
+
if (modes.includes("x402")) {
|
|
268
|
+
const mode = "x402";
|
|
269
|
+
console.log(`[RouteDock] ${manifest.name} \u2192 ${mode}`);
|
|
270
|
+
return mode;
|
|
271
|
+
}
|
|
272
|
+
throw new RouteDockNoSupportedModeError(
|
|
273
|
+
`No supported payment mode found in manifest (modes: ${modes.join(", ")})`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
var CAIP2 = {
|
|
277
|
+
testnet: "stellar:testnet",
|
|
278
|
+
mainnet: "stellar:pubnet"
|
|
279
|
+
};
|
|
280
|
+
var X402Client = class {
|
|
281
|
+
constructor(secretKey, network) {
|
|
282
|
+
this.secretKey = secretKey;
|
|
283
|
+
this.network = network;
|
|
284
|
+
const caip2 = CAIP2[network];
|
|
285
|
+
const signer = stellar.createEd25519Signer(secretKey, caip2);
|
|
286
|
+
const scheme = new client.ExactStellarScheme(signer);
|
|
287
|
+
const core = new client$1.x402Client();
|
|
288
|
+
core.register(caip2, scheme);
|
|
289
|
+
this.httpClient = new client$1.x402HTTPClient(core);
|
|
290
|
+
}
|
|
291
|
+
secretKey;
|
|
292
|
+
network;
|
|
293
|
+
httpClient;
|
|
294
|
+
async pay(url, manifest) {
|
|
295
|
+
const pricing = manifest.pricing.x402;
|
|
296
|
+
if (!pricing) {
|
|
297
|
+
throw new RouteDockManifestError("manifest.pricing.x402 missing");
|
|
298
|
+
}
|
|
299
|
+
if (!pricing.facilitator) {
|
|
300
|
+
throw new RouteDockManifestError("manifest.pricing.x402.facilitator missing");
|
|
301
|
+
}
|
|
302
|
+
const init = await fetch(url, { headers: { "X-Preferred-Mode": "x402" } });
|
|
303
|
+
if (init.status !== 402) {
|
|
304
|
+
const data2 = await init.json();
|
|
305
|
+
return { data: data2, txHash: null, mode: "x402", amount: pricing.amount, timestamp: Date.now() };
|
|
306
|
+
}
|
|
307
|
+
const reqHeader = init.headers.get("X-Payment-Requirements");
|
|
308
|
+
if (!reqHeader) {
|
|
309
|
+
throw new RouteDockManifestError("402 response missing X-Payment-Requirements header");
|
|
310
|
+
}
|
|
311
|
+
const paymentRequired = http.decodePaymentRequiredHeader(reqHeader);
|
|
312
|
+
const paymentPayload = await this.httpClient.createPaymentPayload(paymentRequired);
|
|
313
|
+
const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
314
|
+
const settled = await fetch(url, { headers: paymentHeaders });
|
|
315
|
+
if (!settled.ok) {
|
|
316
|
+
throw new RouteDockManifestError(`x402 payment failed: HTTP ${settled.status}`);
|
|
317
|
+
}
|
|
318
|
+
let txHash = null;
|
|
319
|
+
const responseHeader = settled.headers.get("X-Payment-Response");
|
|
320
|
+
if (responseHeader) {
|
|
321
|
+
try {
|
|
322
|
+
const settleResponse = http.decodePaymentResponseHeader(responseHeader);
|
|
323
|
+
txHash = settleResponse.transaction ?? null;
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const data = await settled.json();
|
|
328
|
+
return { data, txHash, mode: "x402", amount: pricing.amount, timestamp: Date.now() };
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var MppChargeClient = class {
|
|
332
|
+
constructor(keypair, network) {
|
|
333
|
+
this.keypair = keypair;
|
|
334
|
+
this.network = network;
|
|
335
|
+
}
|
|
336
|
+
keypair;
|
|
337
|
+
network;
|
|
338
|
+
async pay(url, manifest) {
|
|
339
|
+
const pricing = manifest.pricing["mpp-charge"];
|
|
340
|
+
if (!pricing) {
|
|
341
|
+
throw new RouteDockManifestError("manifest.pricing.mpp-charge missing");
|
|
342
|
+
}
|
|
343
|
+
let txHash = null;
|
|
344
|
+
const mppx = client$2.Mppx.create({
|
|
345
|
+
polyfill: false,
|
|
346
|
+
methods: [
|
|
347
|
+
client$3.stellar.charge({
|
|
348
|
+
keypair: this.keypair,
|
|
349
|
+
mode: "pull",
|
|
350
|
+
onProgress(event) {
|
|
351
|
+
if (event.type === "paid") {
|
|
352
|
+
txHash = event.hash;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
]
|
|
357
|
+
});
|
|
358
|
+
const response = await mppx.fetch(url);
|
|
359
|
+
if (!response.ok) {
|
|
360
|
+
throw new RouteDockManifestError(`MPP charge failed: HTTP ${response.status}`);
|
|
361
|
+
}
|
|
362
|
+
const data = await response.json();
|
|
363
|
+
return { data, txHash, mode: "mpp-charge", amount: pricing.amount, timestamp: Date.now() };
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var MIN_REFUND_WAITING_PERIOD = 17280;
|
|
367
|
+
var MppSessionClient = class {
|
|
368
|
+
constructor(keypair, network) {
|
|
369
|
+
this.keypair = keypair;
|
|
370
|
+
this.network = network;
|
|
371
|
+
}
|
|
372
|
+
keypair;
|
|
373
|
+
network;
|
|
374
|
+
async openSession(url, manifest, commitmentSecret) {
|
|
375
|
+
const pricing = manifest.pricing["mpp-session"];
|
|
376
|
+
if (!pricing) {
|
|
377
|
+
throw new RouteDockManifestError("manifest.pricing.mpp-session missing");
|
|
378
|
+
}
|
|
379
|
+
const refundPeriod = pricing.refund_waiting_period_ledgers;
|
|
380
|
+
if (refundPeriod < MIN_REFUND_WAITING_PERIOD) {
|
|
381
|
+
throw new RouteDockManifestError(
|
|
382
|
+
`refund_waiting_period_ledgers ${refundPeriod} < minimum ${MIN_REFUND_WAITING_PERIOD}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const commitmentKey = stellarSdk.Keypair.fromSecret(commitmentSecret);
|
|
386
|
+
const channelContract = pricing.channel_contract;
|
|
387
|
+
const agentPublicKey = this.keypair.publicKey();
|
|
388
|
+
let currentCumulative = 0n;
|
|
389
|
+
let vouchersIssued = 0;
|
|
390
|
+
const mppx = client$2.Mppx.create({
|
|
391
|
+
polyfill: false,
|
|
392
|
+
methods: [
|
|
393
|
+
client$4.stellar.channel({
|
|
394
|
+
commitmentKey,
|
|
395
|
+
sourceAccount: this.keypair.publicKey(),
|
|
396
|
+
onProgress(event) {
|
|
397
|
+
if (event.type === "signed") {
|
|
398
|
+
currentCumulative = BigInt(event.cumulativeAmount);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
]
|
|
403
|
+
});
|
|
404
|
+
const openTxHash = channelContract;
|
|
405
|
+
const handle = {
|
|
406
|
+
channelId: channelContract,
|
|
407
|
+
openTxHash,
|
|
408
|
+
async *stream() {
|
|
409
|
+
while (true) {
|
|
410
|
+
let resp = null;
|
|
411
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
412
|
+
try {
|
|
413
|
+
resp = await mppx.fetch(url);
|
|
414
|
+
if (resp.ok) break;
|
|
415
|
+
resp = null;
|
|
416
|
+
} catch {
|
|
417
|
+
if (attempt === 2) throw new RouteDockSessionError("Voucher request failed after 3 retries");
|
|
418
|
+
await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!resp || !resp.ok) {
|
|
422
|
+
throw new RouteDockSessionError(`Voucher request failed: HTTP ${resp?.status ?? "unknown"}`);
|
|
423
|
+
}
|
|
424
|
+
vouchersIssued++;
|
|
425
|
+
yield await resp.json();
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
async close() {
|
|
429
|
+
const { rpc: rpcMod, Contract, nativeToScVal, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk');
|
|
430
|
+
const rpcUrl = "https://soroban-testnet.stellar.org";
|
|
431
|
+
const server = new rpcMod.Server(rpcUrl);
|
|
432
|
+
const contract = new Contract(channelContract);
|
|
433
|
+
const passphrase = "Test SDF Network ; September 2015";
|
|
434
|
+
const account = await server.getAccount(agentPublicKey);
|
|
435
|
+
const simTx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase }).addOperation(contract.call("prepare_commitment", nativeToScVal(currentCumulative, { type: "i128" }))).setTimeout(30).build();
|
|
436
|
+
const simResult = await server.simulateTransaction(simTx);
|
|
437
|
+
if (rpcMod.Api.isSimulationError(simResult)) {
|
|
438
|
+
throw new RouteDockSessionError(`prepare_commitment simulation failed: ${simResult.error}`);
|
|
439
|
+
}
|
|
440
|
+
const commitmentBytes = simResult.result?.retval?.bytes();
|
|
441
|
+
if (!commitmentBytes) throw new RouteDockSessionError("prepare_commitment returned no bytes");
|
|
442
|
+
const signature = commitmentKey.sign(Buffer.from(commitmentBytes));
|
|
443
|
+
const closeResp = await fetch(url, {
|
|
444
|
+
method: "DELETE",
|
|
445
|
+
headers: { "Content-Type": "application/json" },
|
|
446
|
+
body: JSON.stringify({
|
|
447
|
+
amount: currentCumulative.toString(),
|
|
448
|
+
signature: Buffer.from(signature).toString("hex")
|
|
449
|
+
})
|
|
450
|
+
});
|
|
451
|
+
if (!closeResp.ok) {
|
|
452
|
+
throw new RouteDockSessionError(`Channel close failed: HTTP ${closeResp.status}`);
|
|
453
|
+
}
|
|
454
|
+
const closeData = await closeResp.json();
|
|
455
|
+
const closeTxHash = closeData.closeTxHash ?? null;
|
|
456
|
+
if (!closeTxHash) {
|
|
457
|
+
throw new RouteDockSessionError("Channel close response missing closeTxHash");
|
|
458
|
+
}
|
|
459
|
+
const totalPaid = (Number(currentCumulative) / 1e7).toFixed(7);
|
|
460
|
+
return { closeTxHash, totalPaid, vouchersIssued };
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
return handle;
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// src/client/RouteDockClient.ts
|
|
468
|
+
var RouteDockClient = class {
|
|
469
|
+
keypair;
|
|
470
|
+
network;
|
|
471
|
+
spendCap;
|
|
472
|
+
commitmentSecret;
|
|
473
|
+
/** Local daily accumulator keyed by YYYY-MM-DD */
|
|
474
|
+
dailySpend = { date: "", total: 0 };
|
|
475
|
+
x402;
|
|
476
|
+
charge;
|
|
477
|
+
session;
|
|
478
|
+
constructor(config) {
|
|
479
|
+
this.keypair = typeof config.wallet === "string" ? stellarSdk.Keypair.fromSecret(config.wallet) : config.wallet;
|
|
480
|
+
this.network = config.network;
|
|
481
|
+
this.spendCap = config.spendCap;
|
|
482
|
+
this.commitmentSecret = config.commitmentSecret;
|
|
483
|
+
const secretKey = this.keypair.secret();
|
|
484
|
+
this.x402 = new X402Client(secretKey, this.network);
|
|
485
|
+
this.charge = new MppChargeClient(this.keypair, this.network);
|
|
486
|
+
this.session = new MppSessionClient(this.keypair, this.network);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Pay for one request at `url`. Fetches manifest, selects payment mode,
|
|
490
|
+
* checks local spend cap, executes payment, returns result.
|
|
491
|
+
*/
|
|
492
|
+
async pay(url, options) {
|
|
493
|
+
const baseUrl = new URL(url).origin;
|
|
494
|
+
const manifest = await fetchManifest(baseUrl);
|
|
495
|
+
const mode = selectMode(manifest, options);
|
|
496
|
+
let result;
|
|
497
|
+
switch (mode) {
|
|
498
|
+
case "x402":
|
|
499
|
+
result = await this.x402.pay(url, manifest);
|
|
500
|
+
break;
|
|
501
|
+
case "mpp-charge":
|
|
502
|
+
result = await this.charge.pay(url, manifest);
|
|
503
|
+
break;
|
|
504
|
+
case "mpp-session":
|
|
505
|
+
throw new RouteDockManifestError(
|
|
506
|
+
"Use client.openSession() for mpp-session mode \u2014 client.pay() only handles discrete payments"
|
|
507
|
+
);
|
|
508
|
+
default:
|
|
509
|
+
throw new RouteDockManifestError(`Unknown payment mode: ${mode}`);
|
|
510
|
+
}
|
|
511
|
+
this._checkAndRecordSpend(result.amount);
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Open a sustained MPP session at `url`. Verifies mpp-session is supported
|
|
516
|
+
* before opening a channel. Returns a SessionHandle for streaming + closing.
|
|
517
|
+
*/
|
|
518
|
+
async openSession(url) {
|
|
519
|
+
const baseUrl = new URL(url).origin;
|
|
520
|
+
const manifest = await fetchManifest(baseUrl);
|
|
521
|
+
if (!manifest.modes.includes("mpp-session")) {
|
|
522
|
+
throw new RouteDockManifestError(
|
|
523
|
+
`Provider at ${baseUrl} does not support mpp-session mode`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
if (!this.commitmentSecret) {
|
|
527
|
+
throw new RouteDockManifestError(
|
|
528
|
+
"commitmentSecret is required in RouteDockClientConfig for mpp-session mode"
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return this.session.openSession(url, manifest, this.commitmentSecret);
|
|
532
|
+
}
|
|
533
|
+
/** Check local daily cap and record the spend. Throws if cap exceeded. */
|
|
534
|
+
_checkAndRecordSpend(amount) {
|
|
535
|
+
if (!this.spendCap) return;
|
|
536
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
537
|
+
if (this.dailySpend.date !== today) {
|
|
538
|
+
this.dailySpend = { date: today, total: 0 };
|
|
539
|
+
}
|
|
540
|
+
const amountNum = parseFloat(amount);
|
|
541
|
+
const capNum = parseFloat(this.spendCap.daily);
|
|
542
|
+
if (this.dailySpend.total + amountNum > capNum) {
|
|
543
|
+
throw new RouteDockPolicyRejectedError("local_daily_cap_exceeded");
|
|
544
|
+
}
|
|
545
|
+
this.dailySpend.total += amountNum;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/store/SessionStore.ts
|
|
550
|
+
var SupabaseSessionStore = class {
|
|
551
|
+
constructor(supabase) {
|
|
552
|
+
this.supabase = supabase;
|
|
553
|
+
}
|
|
554
|
+
supabase;
|
|
555
|
+
async get(channelId) {
|
|
556
|
+
const { data, error } = await this.supabase.from("sessions").select("*").eq("channel_id", channelId).maybeSingle();
|
|
557
|
+
if (error) throw new RouteDockSessionError(`SessionStore.get failed: ${error.message}`);
|
|
558
|
+
if (!data) return null;
|
|
559
|
+
return {
|
|
560
|
+
channel_id: data.channel_id,
|
|
561
|
+
payee: data.payee,
|
|
562
|
+
payer: data.payer,
|
|
563
|
+
cumulative_amount: String(data.cumulative_amount),
|
|
564
|
+
last_signature: data.last_signature,
|
|
565
|
+
status: data.status,
|
|
566
|
+
opened_at: data.opened_at,
|
|
567
|
+
updated_at: data.updated_at,
|
|
568
|
+
settlement_tx_hash: data.settlement_tx_hash
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
async upsert(channelId, state) {
|
|
572
|
+
const existing = await this.get(channelId);
|
|
573
|
+
if (existing) {
|
|
574
|
+
const prev = parseFloat(existing.cumulative_amount);
|
|
575
|
+
const next = parseFloat(state.cumulative_amount);
|
|
576
|
+
if (next <= prev) {
|
|
577
|
+
throw new RouteDockSessionError(
|
|
578
|
+
`cumulative_amount must be strictly increasing: ${next} <= ${prev}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const { error } = await this.supabase.from("sessions").upsert(
|
|
583
|
+
{
|
|
584
|
+
channel_id: state.channel_id,
|
|
585
|
+
payee: state.payee,
|
|
586
|
+
payer: state.payer,
|
|
587
|
+
cumulative_amount: state.cumulative_amount,
|
|
588
|
+
last_signature: state.last_signature,
|
|
589
|
+
status: state.status,
|
|
590
|
+
opened_at: state.opened_at,
|
|
591
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
592
|
+
settlement_tx_hash: state.settlement_tx_hash
|
|
593
|
+
},
|
|
594
|
+
{ onConflict: "channel_id" }
|
|
595
|
+
);
|
|
596
|
+
if (error) {
|
|
597
|
+
if (error.message.includes("strictly increasing")) {
|
|
598
|
+
throw new RouteDockSessionError(
|
|
599
|
+
`cumulative_amount must be strictly increasing (DB): ${error.message}`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
throw new RouteDockSessionError(`SessionStore.upsert failed: ${error.message}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async close(channelId) {
|
|
606
|
+
const { error } = await this.supabase.from("sessions").update({ status: "closed", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("channel_id", channelId);
|
|
607
|
+
if (error) throw new RouteDockSessionError(`SessionStore.close failed: ${error.message}`);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var CAIP22 = {
|
|
611
|
+
testnet: "stellar:testnet",
|
|
612
|
+
mainnet: "stellar:pubnet"
|
|
613
|
+
};
|
|
614
|
+
function createX402Handler(opts) {
|
|
615
|
+
const caip2 = CAIP22[opts.network];
|
|
616
|
+
const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
|
|
617
|
+
const signer = stellar.createEd25519Signer(opts.payeeSecretKey, caip2);
|
|
618
|
+
const facilitatorScheme = new facilitator.ExactStellarScheme([signer], {
|
|
619
|
+
areFeesSponsored: true
|
|
620
|
+
});
|
|
621
|
+
const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
|
|
622
|
+
const requirements = {
|
|
623
|
+
scheme: "exact",
|
|
624
|
+
network: caip2,
|
|
625
|
+
asset: opts.assetContract,
|
|
626
|
+
amount: amountInBaseUnits,
|
|
627
|
+
payTo: opts.manifest.payee,
|
|
628
|
+
maxTimeoutSeconds: 60,
|
|
629
|
+
extra: {
|
|
630
|
+
areFeesSponsored: true,
|
|
631
|
+
facilitatorAddresses: [payeeKeypair.publicKey()]
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
return async (req, res, next) => {
|
|
635
|
+
try {
|
|
636
|
+
const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
|
|
637
|
+
if (!paymentHeader) {
|
|
638
|
+
const x402Response = {
|
|
639
|
+
x402Version: 2,
|
|
640
|
+
resource: {
|
|
641
|
+
url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
|
|
642
|
+
description: opts.manifest.name
|
|
643
|
+
},
|
|
644
|
+
accepts: [requirements]
|
|
645
|
+
};
|
|
646
|
+
res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const payload = http.decodePaymentSignatureHeader(paymentHeader);
|
|
650
|
+
const verifyResult = await facilitatorScheme.verify(
|
|
651
|
+
payload,
|
|
652
|
+
requirements
|
|
653
|
+
);
|
|
654
|
+
if (!verifyResult.isValid) {
|
|
655
|
+
res.status(401).json({
|
|
656
|
+
error: "Payment verification failed",
|
|
657
|
+
reason: verifyResult.invalidReason
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const settleResult = await facilitatorScheme.settle(
|
|
662
|
+
payload,
|
|
663
|
+
requirements
|
|
664
|
+
);
|
|
665
|
+
const txHash = settleResult.transaction ?? null;
|
|
666
|
+
if (txHash && opts.onSettled) {
|
|
667
|
+
await opts.onSettled(txHash, opts.amount, "x402");
|
|
668
|
+
}
|
|
669
|
+
if (settleResult) {
|
|
670
|
+
res.setHeader(
|
|
671
|
+
"X-Payment-Response",
|
|
672
|
+
http.encodePaymentResponseHeader(
|
|
673
|
+
settleResult
|
|
674
|
+
)
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
next();
|
|
678
|
+
} catch (err) {
|
|
679
|
+
console.error("[x402] Settlement error:", err);
|
|
680
|
+
res.status(500).json({ error: "Payment settlement failed" });
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
var MPP_NETWORK = {
|
|
685
|
+
testnet: "stellar:testnet",
|
|
686
|
+
mainnet: "stellar:pubnet"
|
|
687
|
+
};
|
|
688
|
+
function createMppChargeHandler(opts) {
|
|
689
|
+
const networkId = MPP_NETWORK[opts.network];
|
|
690
|
+
const amountHumanReadable = opts.amount;
|
|
691
|
+
const mppx = server.Mppx.create({
|
|
692
|
+
secretKey: opts.payeeSecretKey,
|
|
693
|
+
methods: [
|
|
694
|
+
server$1.stellar.charge({
|
|
695
|
+
recipient: opts.manifest.payee,
|
|
696
|
+
currency: opts.assetContract,
|
|
697
|
+
network: networkId,
|
|
698
|
+
feePayer: { envelopeSigner: opts.payeeSecretKey }
|
|
699
|
+
})
|
|
700
|
+
]
|
|
701
|
+
});
|
|
702
|
+
return async (req, res, next) => {
|
|
703
|
+
try {
|
|
704
|
+
const fetchReq = server.Request.fromNodeListener(req, res);
|
|
705
|
+
const handler = mppx["stellar/charge"];
|
|
706
|
+
const result = await handler({
|
|
707
|
+
amount: amountHumanReadable,
|
|
708
|
+
currency: opts.assetContract,
|
|
709
|
+
recipient: opts.manifest.payee,
|
|
710
|
+
description: opts.manifest.name
|
|
711
|
+
})(fetchReq);
|
|
712
|
+
if (result.status === 402) {
|
|
713
|
+
const challenge = result.challenge;
|
|
714
|
+
res.status(402);
|
|
715
|
+
challenge.headers.forEach((v, k) => res.setHeader(k, v));
|
|
716
|
+
res.send(await challenge.text());
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const receipt = result.withReceipt(new Response(""));
|
|
720
|
+
receipt.headers.forEach((v, k) => res.setHeader(k, v));
|
|
721
|
+
const receiptHeader = receipt.headers.get("payment-receipt");
|
|
722
|
+
if (receiptHeader && opts.onSettled) {
|
|
723
|
+
try {
|
|
724
|
+
const parsed = JSON.parse(
|
|
725
|
+
Buffer.from(receiptHeader, "base64").toString("utf8")
|
|
726
|
+
);
|
|
727
|
+
if (parsed.reference) {
|
|
728
|
+
await opts.onSettled(parsed.reference, opts.amount, "mpp-charge");
|
|
729
|
+
}
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
next();
|
|
734
|
+
} catch (err) {
|
|
735
|
+
next(err);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
var MPP_NETWORK2 = {
|
|
740
|
+
testnet: "stellar:testnet",
|
|
741
|
+
mainnet: "stellar:pubnet"
|
|
742
|
+
};
|
|
743
|
+
function createMppSessionHandler(opts) {
|
|
744
|
+
const networkId = MPP_NETWORK2[opts.network];
|
|
745
|
+
const rateHuman = opts.rate;
|
|
746
|
+
const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
|
|
747
|
+
const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
|
|
748
|
+
const innerStore = server$2.Store.memory();
|
|
749
|
+
let lastCumulativeAmount = 0n;
|
|
750
|
+
const wrappedStore = {
|
|
751
|
+
async get(key) {
|
|
752
|
+
return innerStore.get(key);
|
|
753
|
+
},
|
|
754
|
+
async put(key, value) {
|
|
755
|
+
await innerStore.put(key, value);
|
|
756
|
+
if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
|
|
757
|
+
lastCumulativeAmount = BigInt(value.amount);
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
async delete(key) {
|
|
761
|
+
return innerStore.delete(key);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
const mppx = server.Mppx.create({
|
|
765
|
+
secretKey: opts.payeeSecretKey,
|
|
766
|
+
methods: [
|
|
767
|
+
server$2.stellar.channel({
|
|
768
|
+
channel: opts.channelContract,
|
|
769
|
+
commitmentKey: opts.commitmentPublicKey,
|
|
770
|
+
network: networkId,
|
|
771
|
+
store: wrappedStore,
|
|
772
|
+
sourceAccount: payeeKeypair.publicKey(),
|
|
773
|
+
feePayer: { envelopeSigner: payeeKeypair }
|
|
774
|
+
})
|
|
775
|
+
]
|
|
776
|
+
});
|
|
777
|
+
let lastSignatureHex = "";
|
|
778
|
+
return async (req, res, next) => {
|
|
779
|
+
try {
|
|
780
|
+
if (req.method === "DELETE") {
|
|
781
|
+
const body = req.body;
|
|
782
|
+
const closeAmount = body?.amount ? BigInt(body.amount) : lastCumulativeAmount;
|
|
783
|
+
const closeSig = body?.signature ?? lastSignatureHex;
|
|
784
|
+
if (closeAmount > 0n && closeSig) {
|
|
785
|
+
const closeTxHash = await server$2.close({
|
|
786
|
+
channel: opts.channelContract,
|
|
787
|
+
amount: closeAmount,
|
|
788
|
+
signature: Buffer.from(closeSig, "hex"),
|
|
789
|
+
feePayer: { envelopeSigner: payeeKeypair },
|
|
790
|
+
network: networkId
|
|
791
|
+
});
|
|
792
|
+
if (opts.onSettled) {
|
|
793
|
+
const totalPaid = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
|
|
794
|
+
await opts.onSettled(closeTxHash, totalPaid, "mpp-session");
|
|
795
|
+
}
|
|
796
|
+
res.json({ closeTxHash });
|
|
797
|
+
} else {
|
|
798
|
+
res.json({ closeTxHash: null, message: "no vouchers received" });
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const authHeader = req.headers["authorization"];
|
|
803
|
+
if (typeof authHeader === "string" && authHeader.startsWith("Payment ")) {
|
|
804
|
+
try {
|
|
805
|
+
const credB64 = authHeader.replace(/^Payment\s+/, "").split(",").find((p) => p.trim().startsWith("credential="));
|
|
806
|
+
if (credB64) {
|
|
807
|
+
const credJson = Buffer.from(credB64.split("=").slice(1).join("=").replace(/^"|"$/g, ""), "base64").toString("utf8");
|
|
808
|
+
const cred = JSON.parse(credJson);
|
|
809
|
+
if (cred.payload?.signature) {
|
|
810
|
+
lastSignatureHex = cred.payload.signature;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
} catch {
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const fetchReq = server.Request.fromNodeListener(req, res);
|
|
817
|
+
const result = await mppx.channel({
|
|
818
|
+
amount: rateHuman,
|
|
819
|
+
description: opts.manifest.name
|
|
820
|
+
})(fetchReq);
|
|
821
|
+
if (result.status === 402) {
|
|
822
|
+
const challenge = result.challenge;
|
|
823
|
+
res.status(402);
|
|
824
|
+
challenge.headers.forEach((v, k) => res.setHeader(k, v));
|
|
825
|
+
const body = await challenge.text();
|
|
826
|
+
res.send(body);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
next();
|
|
830
|
+
} catch (err) {
|
|
831
|
+
next(err);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// src/provider/routedockMiddleware.ts
|
|
837
|
+
function routedock(opts) {
|
|
838
|
+
const handlers = [];
|
|
839
|
+
if (opts.modes.includes("x402")) {
|
|
840
|
+
const x402Price = opts.pricing.x402;
|
|
841
|
+
if (x402Price) {
|
|
842
|
+
handlers.push(
|
|
843
|
+
createX402Handler({
|
|
844
|
+
payeeSecretKey: opts.payeeSecretKey,
|
|
845
|
+
network: opts.network,
|
|
846
|
+
amount: x402Price,
|
|
847
|
+
assetContract: opts.assetContract,
|
|
848
|
+
...opts.facilitatorApiKey ? { facilitatorApiKey: opts.facilitatorApiKey } : {},
|
|
849
|
+
manifest: opts.manifest,
|
|
850
|
+
...opts.onSettled ? { onSettled: opts.onSettled } : {}
|
|
851
|
+
})
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (opts.modes.includes("mpp-charge")) {
|
|
856
|
+
const chargePrice = opts.pricing["mpp-charge"];
|
|
857
|
+
if (chargePrice) {
|
|
858
|
+
handlers.push(
|
|
859
|
+
createMppChargeHandler({
|
|
860
|
+
payeeSecretKey: opts.payeeSecretKey,
|
|
861
|
+
network: opts.network,
|
|
862
|
+
amount: chargePrice,
|
|
863
|
+
assetContract: opts.assetContract,
|
|
864
|
+
manifest: opts.manifest,
|
|
865
|
+
...opts.onSettled ? { onSettled: opts.onSettled } : {}
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (opts.modes.includes("mpp-session")) {
|
|
871
|
+
const sessionPricing = opts.pricing["mpp-session"];
|
|
872
|
+
if (sessionPricing) {
|
|
873
|
+
if (!opts.commitmentPublicKey) {
|
|
874
|
+
throw new Error("routedock: mpp-session mode requires commitmentPublicKey");
|
|
875
|
+
}
|
|
876
|
+
handlers.push(
|
|
877
|
+
createMppSessionHandler({
|
|
878
|
+
payeeSecretKey: opts.payeeSecretKey,
|
|
879
|
+
network: opts.network,
|
|
880
|
+
channelContract: sessionPricing.channelContract,
|
|
881
|
+
rate: sessionPricing.rate,
|
|
882
|
+
assetContract: opts.assetContract,
|
|
883
|
+
manifest: opts.manifest,
|
|
884
|
+
commitmentPublicKey: opts.commitmentPublicKey,
|
|
885
|
+
...opts.onSettled ? { onSettled: opts.onSettled } : {}
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return (req, res, next) => {
|
|
891
|
+
if (req.path === "/.well-known/routedock.json") {
|
|
892
|
+
res.json(opts.manifest);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (handlers.length === 0) {
|
|
896
|
+
next();
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const hasX402Header = !!(req.headers["payment-signature"] || req.headers["x-payment"]);
|
|
900
|
+
const prefersX402 = req.headers["x-preferred-mode"] === "x402";
|
|
901
|
+
let handler;
|
|
902
|
+
if (hasX402Header || prefersX402) {
|
|
903
|
+
handler = handlers[0];
|
|
904
|
+
} else {
|
|
905
|
+
handler = handlers[handlers.length - 1];
|
|
906
|
+
}
|
|
907
|
+
if (!handler) {
|
|
908
|
+
next();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
void Promise.resolve().then(() => new Promise((resolve, reject) => {
|
|
912
|
+
handler(req, res, (handlerErr) => {
|
|
913
|
+
if (handlerErr != null) reject(handlerErr);
|
|
914
|
+
else resolve();
|
|
915
|
+
});
|
|
916
|
+
})).then(() => next()).catch((err) => next(err));
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
exports.MppChargeClient = MppChargeClient;
|
|
921
|
+
exports.MppSessionClient = MppSessionClient;
|
|
922
|
+
exports.RouteDockClient = RouteDockClient;
|
|
923
|
+
exports.RouteDockError = RouteDockError;
|
|
924
|
+
exports.RouteDockManifestError = RouteDockManifestError;
|
|
925
|
+
exports.RouteDockNoSupportedModeError = RouteDockNoSupportedModeError;
|
|
926
|
+
exports.RouteDockPolicyRejectedError = RouteDockPolicyRejectedError;
|
|
927
|
+
exports.RouteDockSessionError = RouteDockSessionError;
|
|
928
|
+
exports.SupabaseSessionStore = SupabaseSessionStore;
|
|
929
|
+
exports.X402Client = X402Client;
|
|
930
|
+
exports.fetchManifest = fetchManifest;
|
|
931
|
+
exports.routedock = routedock;
|
|
932
|
+
exports.selectMode = selectMode;
|