@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.
@@ -0,0 +1,425 @@
1
+ 'use strict';
2
+
3
+ var stellarSdk = require('@stellar/stellar-sdk');
4
+ var facilitator = require('@x402/stellar/exact/facilitator');
5
+ var stellar = require('@x402/stellar');
6
+ var http = require('@x402/core/http');
7
+ var server$1 = require('@stellar/mpp/charge/server');
8
+ var server = require('mppx/server');
9
+ var server$2 = require('@stellar/mpp/channel/server');
10
+
11
+ // src/types.ts
12
+ var RouteDockError = class extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "RouteDockError";
16
+ }
17
+ };
18
+ var RouteDockManifestError = class extends RouteDockError {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "RouteDockManifestError";
22
+ }
23
+ };
24
+ var RouteDockNoSupportedModeError = class extends RouteDockError {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "RouteDockNoSupportedModeError";
28
+ }
29
+ };
30
+ var RouteDockSessionError = class extends RouteDockError {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "RouteDockSessionError";
34
+ }
35
+ };
36
+ var RouteDockPolicyRejectedError = class extends RouteDockError {
37
+ reason;
38
+ constructor(reason) {
39
+ super(`Payment rejected by policy: ${reason}`);
40
+ this.name = "RouteDockPolicyRejectedError";
41
+ this.reason = reason;
42
+ }
43
+ };
44
+ var CAIP2 = {
45
+ testnet: "stellar:testnet",
46
+ mainnet: "stellar:pubnet"
47
+ };
48
+ function createX402Handler(opts) {
49
+ const caip2 = CAIP2[opts.network];
50
+ const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
51
+ const signer = stellar.createEd25519Signer(opts.payeeSecretKey, caip2);
52
+ const facilitatorScheme = new facilitator.ExactStellarScheme([signer], {
53
+ areFeesSponsored: true
54
+ });
55
+ const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
56
+ const requirements = {
57
+ scheme: "exact",
58
+ network: caip2,
59
+ asset: opts.assetContract,
60
+ amount: amountInBaseUnits,
61
+ payTo: opts.manifest.payee,
62
+ maxTimeoutSeconds: 60,
63
+ extra: {
64
+ areFeesSponsored: true,
65
+ facilitatorAddresses: [payeeKeypair.publicKey()]
66
+ }
67
+ };
68
+ return async (req, res, next) => {
69
+ try {
70
+ const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
71
+ if (!paymentHeader) {
72
+ const x402Response = {
73
+ x402Version: 2,
74
+ resource: {
75
+ url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
76
+ description: opts.manifest.name
77
+ },
78
+ accepts: [requirements]
79
+ };
80
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
81
+ return;
82
+ }
83
+ const payload = http.decodePaymentSignatureHeader(paymentHeader);
84
+ const verifyResult = await facilitatorScheme.verify(
85
+ payload,
86
+ requirements
87
+ );
88
+ if (!verifyResult.isValid) {
89
+ res.status(401).json({
90
+ error: "Payment verification failed",
91
+ reason: verifyResult.invalidReason
92
+ });
93
+ return;
94
+ }
95
+ const settleResult = await facilitatorScheme.settle(
96
+ payload,
97
+ requirements
98
+ );
99
+ const txHash = settleResult.transaction ?? null;
100
+ if (txHash && opts.onSettled) {
101
+ await opts.onSettled(txHash, opts.amount, "x402");
102
+ }
103
+ if (settleResult) {
104
+ res.setHeader(
105
+ "X-Payment-Response",
106
+ http.encodePaymentResponseHeader(
107
+ settleResult
108
+ )
109
+ );
110
+ }
111
+ next();
112
+ } catch (err) {
113
+ console.error("[x402] Settlement error:", err);
114
+ res.status(500).json({ error: "Payment settlement failed" });
115
+ }
116
+ };
117
+ }
118
+ var MPP_NETWORK = {
119
+ testnet: "stellar:testnet",
120
+ mainnet: "stellar:pubnet"
121
+ };
122
+ function createMppChargeHandler(opts) {
123
+ const networkId = MPP_NETWORK[opts.network];
124
+ const amountHumanReadable = opts.amount;
125
+ const mppx = server.Mppx.create({
126
+ secretKey: opts.payeeSecretKey,
127
+ methods: [
128
+ server$1.stellar.charge({
129
+ recipient: opts.manifest.payee,
130
+ currency: opts.assetContract,
131
+ network: networkId,
132
+ feePayer: { envelopeSigner: opts.payeeSecretKey }
133
+ })
134
+ ]
135
+ });
136
+ return async (req, res, next) => {
137
+ try {
138
+ const fetchReq = server.Request.fromNodeListener(req, res);
139
+ const handler = mppx["stellar/charge"];
140
+ const result = await handler({
141
+ amount: amountHumanReadable,
142
+ currency: opts.assetContract,
143
+ recipient: opts.manifest.payee,
144
+ description: opts.manifest.name
145
+ })(fetchReq);
146
+ if (result.status === 402) {
147
+ const challenge = result.challenge;
148
+ res.status(402);
149
+ challenge.headers.forEach((v, k) => res.setHeader(k, v));
150
+ res.send(await challenge.text());
151
+ return;
152
+ }
153
+ const receipt = result.withReceipt(new Response(""));
154
+ receipt.headers.forEach((v, k) => res.setHeader(k, v));
155
+ const receiptHeader = receipt.headers.get("payment-receipt");
156
+ if (receiptHeader && opts.onSettled) {
157
+ try {
158
+ const parsed = JSON.parse(
159
+ Buffer.from(receiptHeader, "base64").toString("utf8")
160
+ );
161
+ if (parsed.reference) {
162
+ await opts.onSettled(parsed.reference, opts.amount, "mpp-charge");
163
+ }
164
+ } catch {
165
+ }
166
+ }
167
+ next();
168
+ } catch (err) {
169
+ next(err);
170
+ }
171
+ };
172
+ }
173
+ var MPP_NETWORK2 = {
174
+ testnet: "stellar:testnet",
175
+ mainnet: "stellar:pubnet"
176
+ };
177
+ function createMppSessionHandler(opts) {
178
+ const networkId = MPP_NETWORK2[opts.network];
179
+ const rateHuman = opts.rate;
180
+ const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
181
+ const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
182
+ const innerStore = server$2.Store.memory();
183
+ let lastCumulativeAmount = 0n;
184
+ const wrappedStore = {
185
+ async get(key) {
186
+ return innerStore.get(key);
187
+ },
188
+ async put(key, value) {
189
+ await innerStore.put(key, value);
190
+ if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
191
+ lastCumulativeAmount = BigInt(value.amount);
192
+ }
193
+ },
194
+ async delete(key) {
195
+ return innerStore.delete(key);
196
+ }
197
+ };
198
+ const mppx = server.Mppx.create({
199
+ secretKey: opts.payeeSecretKey,
200
+ methods: [
201
+ server$2.stellar.channel({
202
+ channel: opts.channelContract,
203
+ commitmentKey: opts.commitmentPublicKey,
204
+ network: networkId,
205
+ store: wrappedStore,
206
+ sourceAccount: payeeKeypair.publicKey(),
207
+ feePayer: { envelopeSigner: payeeKeypair }
208
+ })
209
+ ]
210
+ });
211
+ let lastSignatureHex = "";
212
+ return async (req, res, next) => {
213
+ try {
214
+ if (req.method === "DELETE") {
215
+ const body = req.body;
216
+ const closeAmount = body?.amount ? BigInt(body.amount) : lastCumulativeAmount;
217
+ const closeSig = body?.signature ?? lastSignatureHex;
218
+ if (closeAmount > 0n && closeSig) {
219
+ const closeTxHash = await server$2.close({
220
+ channel: opts.channelContract,
221
+ amount: closeAmount,
222
+ signature: Buffer.from(closeSig, "hex"),
223
+ feePayer: { envelopeSigner: payeeKeypair },
224
+ network: networkId
225
+ });
226
+ if (opts.onSettled) {
227
+ const totalPaid = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
228
+ await opts.onSettled(closeTxHash, totalPaid, "mpp-session");
229
+ }
230
+ res.json({ closeTxHash });
231
+ } else {
232
+ res.json({ closeTxHash: null, message: "no vouchers received" });
233
+ }
234
+ return;
235
+ }
236
+ const authHeader = req.headers["authorization"];
237
+ if (typeof authHeader === "string" && authHeader.startsWith("Payment ")) {
238
+ try {
239
+ const credB64 = authHeader.replace(/^Payment\s+/, "").split(",").find((p) => p.trim().startsWith("credential="));
240
+ if (credB64) {
241
+ const credJson = Buffer.from(credB64.split("=").slice(1).join("=").replace(/^"|"$/g, ""), "base64").toString("utf8");
242
+ const cred = JSON.parse(credJson);
243
+ if (cred.payload?.signature) {
244
+ lastSignatureHex = cred.payload.signature;
245
+ }
246
+ }
247
+ } catch {
248
+ }
249
+ }
250
+ const fetchReq = server.Request.fromNodeListener(req, res);
251
+ const result = await mppx.channel({
252
+ amount: rateHuman,
253
+ description: opts.manifest.name
254
+ })(fetchReq);
255
+ if (result.status === 402) {
256
+ const challenge = result.challenge;
257
+ res.status(402);
258
+ challenge.headers.forEach((v, k) => res.setHeader(k, v));
259
+ const body = await challenge.text();
260
+ res.send(body);
261
+ return;
262
+ }
263
+ next();
264
+ } catch (err) {
265
+ next(err);
266
+ }
267
+ };
268
+ }
269
+
270
+ // src/provider/routedockMiddleware.ts
271
+ function routedock(opts) {
272
+ const handlers = [];
273
+ if (opts.modes.includes("x402")) {
274
+ const x402Price = opts.pricing.x402;
275
+ if (x402Price) {
276
+ handlers.push(
277
+ createX402Handler({
278
+ payeeSecretKey: opts.payeeSecretKey,
279
+ network: opts.network,
280
+ amount: x402Price,
281
+ assetContract: opts.assetContract,
282
+ ...opts.facilitatorApiKey ? { facilitatorApiKey: opts.facilitatorApiKey } : {},
283
+ manifest: opts.manifest,
284
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {}
285
+ })
286
+ );
287
+ }
288
+ }
289
+ if (opts.modes.includes("mpp-charge")) {
290
+ const chargePrice = opts.pricing["mpp-charge"];
291
+ if (chargePrice) {
292
+ handlers.push(
293
+ createMppChargeHandler({
294
+ payeeSecretKey: opts.payeeSecretKey,
295
+ network: opts.network,
296
+ amount: chargePrice,
297
+ assetContract: opts.assetContract,
298
+ manifest: opts.manifest,
299
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {}
300
+ })
301
+ );
302
+ }
303
+ }
304
+ if (opts.modes.includes("mpp-session")) {
305
+ const sessionPricing = opts.pricing["mpp-session"];
306
+ if (sessionPricing) {
307
+ if (!opts.commitmentPublicKey) {
308
+ throw new Error("routedock: mpp-session mode requires commitmentPublicKey");
309
+ }
310
+ handlers.push(
311
+ createMppSessionHandler({
312
+ payeeSecretKey: opts.payeeSecretKey,
313
+ network: opts.network,
314
+ channelContract: sessionPricing.channelContract,
315
+ rate: sessionPricing.rate,
316
+ assetContract: opts.assetContract,
317
+ manifest: opts.manifest,
318
+ commitmentPublicKey: opts.commitmentPublicKey,
319
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {}
320
+ })
321
+ );
322
+ }
323
+ }
324
+ return (req, res, next) => {
325
+ if (req.path === "/.well-known/routedock.json") {
326
+ res.json(opts.manifest);
327
+ return;
328
+ }
329
+ if (handlers.length === 0) {
330
+ next();
331
+ return;
332
+ }
333
+ const hasX402Header = !!(req.headers["payment-signature"] || req.headers["x-payment"]);
334
+ const prefersX402 = req.headers["x-preferred-mode"] === "x402";
335
+ let handler;
336
+ if (hasX402Header || prefersX402) {
337
+ handler = handlers[0];
338
+ } else {
339
+ handler = handlers[handlers.length - 1];
340
+ }
341
+ if (!handler) {
342
+ next();
343
+ return;
344
+ }
345
+ void Promise.resolve().then(() => new Promise((resolve, reject) => {
346
+ handler(req, res, (handlerErr) => {
347
+ if (handlerErr != null) reject(handlerErr);
348
+ else resolve();
349
+ });
350
+ })).then(() => next()).catch((err) => next(err));
351
+ };
352
+ }
353
+
354
+ // src/store/SessionStore.ts
355
+ var SupabaseSessionStore = class {
356
+ constructor(supabase) {
357
+ this.supabase = supabase;
358
+ }
359
+ supabase;
360
+ async get(channelId) {
361
+ const { data, error } = await this.supabase.from("sessions").select("*").eq("channel_id", channelId).maybeSingle();
362
+ if (error) throw new RouteDockSessionError(`SessionStore.get failed: ${error.message}`);
363
+ if (!data) return null;
364
+ return {
365
+ channel_id: data.channel_id,
366
+ payee: data.payee,
367
+ payer: data.payer,
368
+ cumulative_amount: String(data.cumulative_amount),
369
+ last_signature: data.last_signature,
370
+ status: data.status,
371
+ opened_at: data.opened_at,
372
+ updated_at: data.updated_at,
373
+ settlement_tx_hash: data.settlement_tx_hash
374
+ };
375
+ }
376
+ async upsert(channelId, state) {
377
+ const existing = await this.get(channelId);
378
+ if (existing) {
379
+ const prev = parseFloat(existing.cumulative_amount);
380
+ const next = parseFloat(state.cumulative_amount);
381
+ if (next <= prev) {
382
+ throw new RouteDockSessionError(
383
+ `cumulative_amount must be strictly increasing: ${next} <= ${prev}`
384
+ );
385
+ }
386
+ }
387
+ const { error } = await this.supabase.from("sessions").upsert(
388
+ {
389
+ channel_id: state.channel_id,
390
+ payee: state.payee,
391
+ payer: state.payer,
392
+ cumulative_amount: state.cumulative_amount,
393
+ last_signature: state.last_signature,
394
+ status: state.status,
395
+ opened_at: state.opened_at,
396
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
397
+ settlement_tx_hash: state.settlement_tx_hash
398
+ },
399
+ { onConflict: "channel_id" }
400
+ );
401
+ if (error) {
402
+ if (error.message.includes("strictly increasing")) {
403
+ throw new RouteDockSessionError(
404
+ `cumulative_amount must be strictly increasing (DB): ${error.message}`
405
+ );
406
+ }
407
+ throw new RouteDockSessionError(`SessionStore.upsert failed: ${error.message}`);
408
+ }
409
+ }
410
+ async close(channelId) {
411
+ const { error } = await this.supabase.from("sessions").update({ status: "closed", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("channel_id", channelId);
412
+ if (error) throw new RouteDockSessionError(`SessionStore.close failed: ${error.message}`);
413
+ }
414
+ };
415
+
416
+ exports.RouteDockError = RouteDockError;
417
+ exports.RouteDockManifestError = RouteDockManifestError;
418
+ exports.RouteDockNoSupportedModeError = RouteDockNoSupportedModeError;
419
+ exports.RouteDockPolicyRejectedError = RouteDockPolicyRejectedError;
420
+ exports.RouteDockSessionError = RouteDockSessionError;
421
+ exports.SupabaseSessionStore = SupabaseSessionStore;
422
+ exports.createMppChargeHandler = createMppChargeHandler;
423
+ exports.createMppSessionHandler = createMppSessionHandler;
424
+ exports.createX402Handler = createX402Handler;
425
+ exports.routedock = routedock;
@@ -0,0 +1,44 @@
1
+ import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.cjs';
2
+ export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.cjs';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-Beqan6QS.cjs';
4
+ import { RequestHandler } from 'express';
5
+ import '@supabase/supabase-js';
6
+
7
+ type Network$2 = 'testnet' | 'mainnet';
8
+ interface X402HandlerOptions {
9
+ payeeSecretKey: string;
10
+ network: Network$2;
11
+ amount: string;
12
+ assetContract: string;
13
+ facilitatorApiKey?: string;
14
+ manifest: RouteDockManifest;
15
+ onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
16
+ }
17
+ declare function createX402Handler(opts: X402HandlerOptions): RequestHandler;
18
+
19
+ type Network$1 = 'testnet' | 'mainnet';
20
+ interface MppChargeHandlerOptions {
21
+ payeeSecretKey: string;
22
+ network: Network$1;
23
+ amount: string;
24
+ assetContract: string;
25
+ manifest: RouteDockManifest;
26
+ store?: SessionStore;
27
+ onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
28
+ }
29
+ declare function createMppChargeHandler(opts: MppChargeHandlerOptions): RequestHandler;
30
+
31
+ type Network = 'testnet' | 'mainnet';
32
+ interface MppSessionHandlerOptions {
33
+ payeeSecretKey: string;
34
+ network: Network;
35
+ channelContract: string;
36
+ rate: string;
37
+ assetContract: string;
38
+ manifest: RouteDockManifest;
39
+ commitmentPublicKey: string;
40
+ onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
41
+ }
42
+ declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
43
+
44
+ export { type MppChargeHandlerOptions, type MppSessionHandlerOptions, RouteDockManifest, SessionStore, type X402HandlerOptions, createMppChargeHandler, createMppSessionHandler, createX402Handler };
@@ -0,0 +1,44 @@
1
+ import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.js';
2
+ export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.js';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-CqnyHjhO.js';
4
+ import { RequestHandler } from 'express';
5
+ import '@supabase/supabase-js';
6
+
7
+ type Network$2 = 'testnet' | 'mainnet';
8
+ interface X402HandlerOptions {
9
+ payeeSecretKey: string;
10
+ network: Network$2;
11
+ amount: string;
12
+ assetContract: string;
13
+ facilitatorApiKey?: string;
14
+ manifest: RouteDockManifest;
15
+ onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
16
+ }
17
+ declare function createX402Handler(opts: X402HandlerOptions): RequestHandler;
18
+
19
+ type Network$1 = 'testnet' | 'mainnet';
20
+ interface MppChargeHandlerOptions {
21
+ payeeSecretKey: string;
22
+ network: Network$1;
23
+ amount: string;
24
+ assetContract: string;
25
+ manifest: RouteDockManifest;
26
+ store?: SessionStore;
27
+ onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
28
+ }
29
+ declare function createMppChargeHandler(opts: MppChargeHandlerOptions): RequestHandler;
30
+
31
+ type Network = 'testnet' | 'mainnet';
32
+ interface MppSessionHandlerOptions {
33
+ payeeSecretKey: string;
34
+ network: Network;
35
+ channelContract: string;
36
+ rate: string;
37
+ assetContract: string;
38
+ manifest: RouteDockManifest;
39
+ commitmentPublicKey: string;
40
+ onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
41
+ }
42
+ declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
43
+
44
+ export { type MppChargeHandlerOptions, type MppSessionHandlerOptions, RouteDockManifest, SessionStore, type X402HandlerOptions, createMppChargeHandler, createMppSessionHandler, createX402Handler };