@routedock/routedock 0.1.0 → 0.1.2
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 +61 -22
- package/dist/index.cjs +106 -44
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +95 -33
- package/dist/provider.cjs +106 -44
- package/dist/provider.d.cts +3 -1
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +95 -33
- package/dist/{routedockMiddleware-Beqan6QS.d.cts → routedockMiddleware-B7WTB3LT.d.cts} +4 -0
- package/dist/{routedockMiddleware-CqnyHjhO.d.ts → routedockMiddleware-DRgdrycG.d.ts} +4 -0
- package/package.json +1 -1
- package/src/provider/MppSessionHandler.ts +22 -1
- package/src/provider/routedockMiddleware.ts +6 -0
- package/src/provider/x402Handler.ts +88 -36
package/dist/provider.cjs
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
var stellarSdk = require('@stellar/stellar-sdk');
|
|
4
4
|
var facilitator = require('@x402/stellar/exact/facilitator');
|
|
5
|
+
var server$1 = require('@x402/stellar/exact/server');
|
|
6
|
+
var server = require('@x402/core/server');
|
|
5
7
|
var stellar = require('@x402/stellar');
|
|
6
8
|
var http = require('@x402/core/http');
|
|
7
|
-
var server$
|
|
8
|
-
var server = require('mppx/server');
|
|
9
|
-
var server$
|
|
9
|
+
var server$3 = require('@stellar/mpp/charge/server');
|
|
10
|
+
var server$2 = require('mppx/server');
|
|
11
|
+
var server$4 = require('@stellar/mpp/channel/server');
|
|
10
12
|
|
|
11
13
|
// src/types.ts
|
|
12
14
|
var RouteDockError = class extends Error {
|
|
@@ -45,13 +47,29 @@ var CAIP2 = {
|
|
|
45
47
|
testnet: "stellar:testnet",
|
|
46
48
|
mainnet: "stellar:pubnet"
|
|
47
49
|
};
|
|
50
|
+
var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
|
|
48
51
|
function createX402Handler(opts) {
|
|
49
52
|
const caip2 = CAIP2[opts.network];
|
|
50
53
|
const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
|
|
51
54
|
const signer = stellar.createEd25519Signer(opts.payeeSecretKey, caip2);
|
|
52
|
-
const
|
|
55
|
+
const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
|
|
56
|
+
const localFacilitator = new facilitator.ExactStellarScheme([signer], {
|
|
53
57
|
areFeesSponsored: true
|
|
54
58
|
});
|
|
59
|
+
let ozServer = null;
|
|
60
|
+
if (useOzFacilitator) {
|
|
61
|
+
const apiKey = opts.facilitatorApiKey;
|
|
62
|
+
const facilitator = new server.HTTPFacilitatorClient({
|
|
63
|
+
url: OZ_FACILITATOR_URL,
|
|
64
|
+
createAuthHeaders: async () => ({
|
|
65
|
+
verify: { Authorization: `Bearer ${apiKey}` },
|
|
66
|
+
settle: { Authorization: `Bearer ${apiKey}` },
|
|
67
|
+
supported: { Authorization: `Bearer ${apiKey}` }
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
ozServer = new server.x402ResourceServer(facilitator);
|
|
71
|
+
ozServer.register(caip2, new server$1.ExactStellarScheme());
|
|
72
|
+
}
|
|
55
73
|
const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
|
|
56
74
|
const requirements = {
|
|
57
75
|
scheme: "exact",
|
|
@@ -62,52 +80,78 @@ function createX402Handler(opts) {
|
|
|
62
80
|
maxTimeoutSeconds: 60,
|
|
63
81
|
extra: {
|
|
64
82
|
areFeesSponsored: true,
|
|
65
|
-
facilitatorAddresses: [payeeKeypair.publicKey()]
|
|
83
|
+
...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
|
|
66
84
|
}
|
|
67
85
|
};
|
|
68
86
|
return async (req, res, next) => {
|
|
69
87
|
try {
|
|
70
88
|
const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
|
|
71
89
|
if (!paymentHeader) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
resource: {
|
|
90
|
+
if (ozServer) {
|
|
91
|
+
const resourceInfo = {
|
|
75
92
|
url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
|
|
76
93
|
description: opts.manifest.name
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
};
|
|
95
|
+
const paymentRequired = await ozServer.createPaymentRequiredResponse(
|
|
96
|
+
[requirements],
|
|
97
|
+
resourceInfo
|
|
98
|
+
);
|
|
99
|
+
res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
|
|
100
|
+
} else {
|
|
101
|
+
const x402Response = {
|
|
102
|
+
x402Version: 2,
|
|
103
|
+
resource: {
|
|
104
|
+
url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
|
|
105
|
+
description: opts.manifest.name
|
|
106
|
+
},
|
|
107
|
+
accepts: [requirements]
|
|
108
|
+
};
|
|
109
|
+
res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
|
|
110
|
+
}
|
|
81
111
|
return;
|
|
82
112
|
}
|
|
83
113
|
const payload = http.decodePaymentSignatureHeader(paymentHeader);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
requirements
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
let txHash = null;
|
|
115
|
+
if (ozServer) {
|
|
116
|
+
const settleResult = await ozServer.settlePayment(payload, requirements);
|
|
117
|
+
txHash = settleResult.transaction ?? null;
|
|
118
|
+
if (settleResult) {
|
|
119
|
+
res.setHeader(
|
|
120
|
+
"X-Payment-Response",
|
|
121
|
+
http.encodePaymentResponseHeader(
|
|
122
|
+
settleResult
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const verifyResult = await localFacilitator.verify(
|
|
128
|
+
payload,
|
|
129
|
+
requirements
|
|
130
|
+
);
|
|
131
|
+
if (!verifyResult.isValid) {
|
|
132
|
+
res.status(401).json({
|
|
133
|
+
error: "Payment verification failed",
|
|
134
|
+
reason: verifyResult.invalidReason
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const settleResult = await localFacilitator.settle(
|
|
139
|
+
payload,
|
|
140
|
+
requirements
|
|
141
|
+
);
|
|
142
|
+
txHash = settleResult.transaction ?? null;
|
|
143
|
+
if (settleResult) {
|
|
144
|
+
res.setHeader(
|
|
145
|
+
"X-Payment-Response",
|
|
146
|
+
http.encodePaymentResponseHeader(
|
|
147
|
+
settleResult
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
94
151
|
}
|
|
95
|
-
const settleResult = await facilitatorScheme.settle(
|
|
96
|
-
payload,
|
|
97
|
-
requirements
|
|
98
|
-
);
|
|
99
|
-
const txHash = settleResult.transaction ?? null;
|
|
100
152
|
if (txHash && opts.onSettled) {
|
|
101
153
|
await opts.onSettled(txHash, opts.amount, "x402");
|
|
102
154
|
}
|
|
103
|
-
if (settleResult) {
|
|
104
|
-
res.setHeader(
|
|
105
|
-
"X-Payment-Response",
|
|
106
|
-
http.encodePaymentResponseHeader(
|
|
107
|
-
settleResult
|
|
108
|
-
)
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
155
|
next();
|
|
112
156
|
} catch (err) {
|
|
113
157
|
console.error("[x402] Settlement error:", err);
|
|
@@ -122,10 +166,10 @@ var MPP_NETWORK = {
|
|
|
122
166
|
function createMppChargeHandler(opts) {
|
|
123
167
|
const networkId = MPP_NETWORK[opts.network];
|
|
124
168
|
const amountHumanReadable = opts.amount;
|
|
125
|
-
const mppx = server.Mppx.create({
|
|
169
|
+
const mppx = server$2.Mppx.create({
|
|
126
170
|
secretKey: opts.payeeSecretKey,
|
|
127
171
|
methods: [
|
|
128
|
-
server$
|
|
172
|
+
server$3.stellar.charge({
|
|
129
173
|
recipient: opts.manifest.payee,
|
|
130
174
|
currency: opts.assetContract,
|
|
131
175
|
network: networkId,
|
|
@@ -135,7 +179,7 @@ function createMppChargeHandler(opts) {
|
|
|
135
179
|
});
|
|
136
180
|
return async (req, res, next) => {
|
|
137
181
|
try {
|
|
138
|
-
const fetchReq = server.Request.fromNodeListener(req, res);
|
|
182
|
+
const fetchReq = server$2.Request.fromNodeListener(req, res);
|
|
139
183
|
const handler = mppx["stellar/charge"];
|
|
140
184
|
const result = await handler({
|
|
141
185
|
amount: amountHumanReadable,
|
|
@@ -179,8 +223,10 @@ function createMppSessionHandler(opts) {
|
|
|
179
223
|
const rateHuman = opts.rate;
|
|
180
224
|
const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
|
|
181
225
|
const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
|
|
182
|
-
const innerStore = server$
|
|
226
|
+
const innerStore = server$4.Store.memory();
|
|
183
227
|
let lastCumulativeAmount = 0n;
|
|
228
|
+
let voucherCount = 0;
|
|
229
|
+
let sessionOpened = false;
|
|
184
230
|
const wrappedStore = {
|
|
185
231
|
async get(key) {
|
|
186
232
|
return innerStore.get(key);
|
|
@@ -189,16 +235,27 @@ function createMppSessionHandler(opts) {
|
|
|
189
235
|
await innerStore.put(key, value);
|
|
190
236
|
if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
|
|
191
237
|
lastCumulativeAmount = BigInt(value.amount);
|
|
238
|
+
voucherCount++;
|
|
239
|
+
if (!sessionOpened) {
|
|
240
|
+
sessionOpened = true;
|
|
241
|
+
if (opts.onSessionOpen) {
|
|
242
|
+
await opts.onSessionOpen(opts.channelContract);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (opts.onVoucher) {
|
|
246
|
+
const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
|
|
247
|
+
await opts.onVoucher(voucherCount, humanAmount);
|
|
248
|
+
}
|
|
192
249
|
}
|
|
193
250
|
},
|
|
194
251
|
async delete(key) {
|
|
195
252
|
return innerStore.delete(key);
|
|
196
253
|
}
|
|
197
254
|
};
|
|
198
|
-
const mppx = server.Mppx.create({
|
|
255
|
+
const mppx = server$2.Mppx.create({
|
|
199
256
|
secretKey: opts.payeeSecretKey,
|
|
200
257
|
methods: [
|
|
201
|
-
server$
|
|
258
|
+
server$4.stellar.channel({
|
|
202
259
|
channel: opts.channelContract,
|
|
203
260
|
commitmentKey: opts.commitmentPublicKey,
|
|
204
261
|
network: networkId,
|
|
@@ -216,7 +273,7 @@ function createMppSessionHandler(opts) {
|
|
|
216
273
|
const closeAmount = body?.amount ? BigInt(body.amount) : lastCumulativeAmount;
|
|
217
274
|
const closeSig = body?.signature ?? lastSignatureHex;
|
|
218
275
|
if (closeAmount > 0n && closeSig) {
|
|
219
|
-
const closeTxHash = await server$
|
|
276
|
+
const closeTxHash = await server$4.close({
|
|
220
277
|
channel: opts.channelContract,
|
|
221
278
|
amount: closeAmount,
|
|
222
279
|
signature: Buffer.from(closeSig, "hex"),
|
|
@@ -231,6 +288,9 @@ function createMppSessionHandler(opts) {
|
|
|
231
288
|
} else {
|
|
232
289
|
res.json({ closeTxHash: null, message: "no vouchers received" });
|
|
233
290
|
}
|
|
291
|
+
sessionOpened = false;
|
|
292
|
+
voucherCount = 0;
|
|
293
|
+
lastCumulativeAmount = 0n;
|
|
234
294
|
return;
|
|
235
295
|
}
|
|
236
296
|
const authHeader = req.headers["authorization"];
|
|
@@ -247,7 +307,7 @@ function createMppSessionHandler(opts) {
|
|
|
247
307
|
} catch {
|
|
248
308
|
}
|
|
249
309
|
}
|
|
250
|
-
const fetchReq = server.Request.fromNodeListener(req, res);
|
|
310
|
+
const fetchReq = server$2.Request.fromNodeListener(req, res);
|
|
251
311
|
const result = await mppx.channel({
|
|
252
312
|
amount: rateHuman,
|
|
253
313
|
description: opts.manifest.name
|
|
@@ -316,7 +376,9 @@ function routedock(opts) {
|
|
|
316
376
|
assetContract: opts.assetContract,
|
|
317
377
|
manifest: opts.manifest,
|
|
318
378
|
commitmentPublicKey: opts.commitmentPublicKey,
|
|
319
|
-
...opts.onSettled ? { onSettled: opts.onSettled } : {}
|
|
379
|
+
...opts.onSettled ? { onSettled: opts.onSettled } : {},
|
|
380
|
+
...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
|
|
381
|
+
...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
|
|
320
382
|
})
|
|
321
383
|
);
|
|
322
384
|
}
|
package/dist/provider.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.cjs';
|
|
2
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-
|
|
3
|
+
export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-B7WTB3LT.cjs';
|
|
4
4
|
import { RequestHandler } from 'express';
|
|
5
5
|
import '@supabase/supabase-js';
|
|
6
6
|
|
|
@@ -38,6 +38,8 @@ interface MppSessionHandlerOptions {
|
|
|
38
38
|
manifest: RouteDockManifest;
|
|
39
39
|
commitmentPublicKey: string;
|
|
40
40
|
onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
|
|
41
|
+
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
42
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
41
43
|
}
|
|
42
44
|
declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
|
|
43
45
|
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.js';
|
|
2
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-
|
|
3
|
+
export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-DRgdrycG.js';
|
|
4
4
|
import { RequestHandler } from 'express';
|
|
5
5
|
import '@supabase/supabase-js';
|
|
6
6
|
|
|
@@ -38,6 +38,8 @@ interface MppSessionHandlerOptions {
|
|
|
38
38
|
manifest: RouteDockManifest;
|
|
39
39
|
commitmentPublicKey: string;
|
|
40
40
|
onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
|
|
41
|
+
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
42
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
41
43
|
}
|
|
42
44
|
declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
|
|
43
45
|
|
package/dist/provider.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Keypair } from '@stellar/stellar-sdk';
|
|
2
2
|
import { ExactStellarScheme } from '@x402/stellar/exact/facilitator';
|
|
3
|
+
import { ExactStellarScheme as ExactStellarScheme$1 } from '@x402/stellar/exact/server';
|
|
4
|
+
import { HTTPFacilitatorClient, x402ResourceServer } from '@x402/core/server';
|
|
3
5
|
import { createEd25519Signer } from '@x402/stellar';
|
|
4
6
|
import { encodePaymentRequiredHeader, decodePaymentSignatureHeader, encodePaymentResponseHeader } from '@x402/core/http';
|
|
5
7
|
import { stellar } from '@stellar/mpp/charge/server';
|
|
@@ -43,13 +45,29 @@ var CAIP2 = {
|
|
|
43
45
|
testnet: "stellar:testnet",
|
|
44
46
|
mainnet: "stellar:pubnet"
|
|
45
47
|
};
|
|
48
|
+
var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
|
|
46
49
|
function createX402Handler(opts) {
|
|
47
50
|
const caip2 = CAIP2[opts.network];
|
|
48
51
|
const payeeKeypair = Keypair.fromSecret(opts.payeeSecretKey);
|
|
49
52
|
const signer = createEd25519Signer(opts.payeeSecretKey, caip2);
|
|
50
|
-
const
|
|
53
|
+
const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
|
|
54
|
+
const localFacilitator = new ExactStellarScheme([signer], {
|
|
51
55
|
areFeesSponsored: true
|
|
52
56
|
});
|
|
57
|
+
let ozServer = null;
|
|
58
|
+
if (useOzFacilitator) {
|
|
59
|
+
const apiKey = opts.facilitatorApiKey;
|
|
60
|
+
const facilitator = new HTTPFacilitatorClient({
|
|
61
|
+
url: OZ_FACILITATOR_URL,
|
|
62
|
+
createAuthHeaders: async () => ({
|
|
63
|
+
verify: { Authorization: `Bearer ${apiKey}` },
|
|
64
|
+
settle: { Authorization: `Bearer ${apiKey}` },
|
|
65
|
+
supported: { Authorization: `Bearer ${apiKey}` }
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
ozServer = new x402ResourceServer(facilitator);
|
|
69
|
+
ozServer.register(caip2, new ExactStellarScheme$1());
|
|
70
|
+
}
|
|
53
71
|
const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
|
|
54
72
|
const requirements = {
|
|
55
73
|
scheme: "exact",
|
|
@@ -60,52 +78,78 @@ function createX402Handler(opts) {
|
|
|
60
78
|
maxTimeoutSeconds: 60,
|
|
61
79
|
extra: {
|
|
62
80
|
areFeesSponsored: true,
|
|
63
|
-
facilitatorAddresses: [payeeKeypair.publicKey()]
|
|
81
|
+
...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
|
|
64
82
|
}
|
|
65
83
|
};
|
|
66
84
|
return async (req, res, next) => {
|
|
67
85
|
try {
|
|
68
86
|
const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
|
|
69
87
|
if (!paymentHeader) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
resource: {
|
|
88
|
+
if (ozServer) {
|
|
89
|
+
const resourceInfo = {
|
|
73
90
|
url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
|
|
74
91
|
description: opts.manifest.name
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
};
|
|
93
|
+
const paymentRequired = await ozServer.createPaymentRequiredResponse(
|
|
94
|
+
[requirements],
|
|
95
|
+
resourceInfo
|
|
96
|
+
);
|
|
97
|
+
res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
|
|
98
|
+
} else {
|
|
99
|
+
const x402Response = {
|
|
100
|
+
x402Version: 2,
|
|
101
|
+
resource: {
|
|
102
|
+
url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
|
|
103
|
+
description: opts.manifest.name
|
|
104
|
+
},
|
|
105
|
+
accepts: [requirements]
|
|
106
|
+
};
|
|
107
|
+
res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
|
|
108
|
+
}
|
|
79
109
|
return;
|
|
80
110
|
}
|
|
81
111
|
const payload = decodePaymentSignatureHeader(paymentHeader);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
requirements
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
let txHash = null;
|
|
113
|
+
if (ozServer) {
|
|
114
|
+
const settleResult = await ozServer.settlePayment(payload, requirements);
|
|
115
|
+
txHash = settleResult.transaction ?? null;
|
|
116
|
+
if (settleResult) {
|
|
117
|
+
res.setHeader(
|
|
118
|
+
"X-Payment-Response",
|
|
119
|
+
encodePaymentResponseHeader(
|
|
120
|
+
settleResult
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const verifyResult = await localFacilitator.verify(
|
|
126
|
+
payload,
|
|
127
|
+
requirements
|
|
128
|
+
);
|
|
129
|
+
if (!verifyResult.isValid) {
|
|
130
|
+
res.status(401).json({
|
|
131
|
+
error: "Payment verification failed",
|
|
132
|
+
reason: verifyResult.invalidReason
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const settleResult = await localFacilitator.settle(
|
|
137
|
+
payload,
|
|
138
|
+
requirements
|
|
139
|
+
);
|
|
140
|
+
txHash = settleResult.transaction ?? null;
|
|
141
|
+
if (settleResult) {
|
|
142
|
+
res.setHeader(
|
|
143
|
+
"X-Payment-Response",
|
|
144
|
+
encodePaymentResponseHeader(
|
|
145
|
+
settleResult
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
92
149
|
}
|
|
93
|
-
const settleResult = await facilitatorScheme.settle(
|
|
94
|
-
payload,
|
|
95
|
-
requirements
|
|
96
|
-
);
|
|
97
|
-
const txHash = settleResult.transaction ?? null;
|
|
98
150
|
if (txHash && opts.onSettled) {
|
|
99
151
|
await opts.onSettled(txHash, opts.amount, "x402");
|
|
100
152
|
}
|
|
101
|
-
if (settleResult) {
|
|
102
|
-
res.setHeader(
|
|
103
|
-
"X-Payment-Response",
|
|
104
|
-
encodePaymentResponseHeader(
|
|
105
|
-
settleResult
|
|
106
|
-
)
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
153
|
next();
|
|
110
154
|
} catch (err) {
|
|
111
155
|
console.error("[x402] Settlement error:", err);
|
|
@@ -179,6 +223,8 @@ function createMppSessionHandler(opts) {
|
|
|
179
223
|
const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
|
|
180
224
|
const innerStore = Store.memory();
|
|
181
225
|
let lastCumulativeAmount = 0n;
|
|
226
|
+
let voucherCount = 0;
|
|
227
|
+
let sessionOpened = false;
|
|
182
228
|
const wrappedStore = {
|
|
183
229
|
async get(key) {
|
|
184
230
|
return innerStore.get(key);
|
|
@@ -187,6 +233,17 @@ function createMppSessionHandler(opts) {
|
|
|
187
233
|
await innerStore.put(key, value);
|
|
188
234
|
if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
|
|
189
235
|
lastCumulativeAmount = BigInt(value.amount);
|
|
236
|
+
voucherCount++;
|
|
237
|
+
if (!sessionOpened) {
|
|
238
|
+
sessionOpened = true;
|
|
239
|
+
if (opts.onSessionOpen) {
|
|
240
|
+
await opts.onSessionOpen(opts.channelContract);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (opts.onVoucher) {
|
|
244
|
+
const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
|
|
245
|
+
await opts.onVoucher(voucherCount, humanAmount);
|
|
246
|
+
}
|
|
190
247
|
}
|
|
191
248
|
},
|
|
192
249
|
async delete(key) {
|
|
@@ -229,6 +286,9 @@ function createMppSessionHandler(opts) {
|
|
|
229
286
|
} else {
|
|
230
287
|
res.json({ closeTxHash: null, message: "no vouchers received" });
|
|
231
288
|
}
|
|
289
|
+
sessionOpened = false;
|
|
290
|
+
voucherCount = 0;
|
|
291
|
+
lastCumulativeAmount = 0n;
|
|
232
292
|
return;
|
|
233
293
|
}
|
|
234
294
|
const authHeader = req.headers["authorization"];
|
|
@@ -314,7 +374,9 @@ function routedock(opts) {
|
|
|
314
374
|
assetContract: opts.assetContract,
|
|
315
375
|
manifest: opts.manifest,
|
|
316
376
|
commitmentPublicKey: opts.commitmentPublicKey,
|
|
317
|
-
...opts.onSettled ? { onSettled: opts.onSettled } : {}
|
|
377
|
+
...opts.onSettled ? { onSettled: opts.onSettled } : {},
|
|
378
|
+
...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
|
|
379
|
+
...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
|
|
318
380
|
})
|
|
319
381
|
);
|
|
320
382
|
}
|
|
@@ -26,6 +26,10 @@ interface RouteDockMiddlewareOptions {
|
|
|
26
26
|
manifest: RouteDockManifest;
|
|
27
27
|
/** Called after each successful on-chain settlement */
|
|
28
28
|
onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
|
|
29
|
+
/** Called when a new mpp-session is opened (first voucher received) */
|
|
30
|
+
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
31
|
+
/** Called after each verified voucher in an mpp-session */
|
|
32
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Express middleware factory for RouteDock provider endpoints.
|
|
@@ -26,6 +26,10 @@ interface RouteDockMiddlewareOptions {
|
|
|
26
26
|
manifest: RouteDockManifest;
|
|
27
27
|
/** Called after each successful on-chain settlement */
|
|
28
28
|
onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
|
|
29
|
+
/** Called when a new mpp-session is opened (first voucher received) */
|
|
30
|
+
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
31
|
+
/** Called after each verified voucher in an mpp-session */
|
|
32
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Express middleware factory for RouteDock provider endpoints.
|
package/package.json
CHANGED
|
@@ -20,6 +20,8 @@ export interface MppSessionHandlerOptions {
|
|
|
20
20
|
manifest: RouteDockManifest
|
|
21
21
|
commitmentPublicKey: string
|
|
22
22
|
onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>
|
|
23
|
+
onSessionOpen?: (channelId: string) => Promise<void>
|
|
24
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler {
|
|
@@ -30,14 +32,29 @@ export function createMppSessionHandler(opts: MppSessionHandlerOptions): Request
|
|
|
30
32
|
|
|
31
33
|
const innerStore = Store.memory()
|
|
32
34
|
|
|
33
|
-
// Wrap the store to intercept cumulative amount writes
|
|
34
35
|
let lastCumulativeAmount = 0n
|
|
36
|
+
let voucherCount = 0
|
|
37
|
+
let sessionOpened = false
|
|
38
|
+
|
|
35
39
|
const wrappedStore: ReturnType<typeof Store.memory> = {
|
|
36
40
|
async get(key: string) { return innerStore.get(key) },
|
|
37
41
|
async put(key: string, value: unknown) {
|
|
38
42
|
await innerStore.put(key, value)
|
|
39
43
|
if (key === cumulativeKey && value && typeof value === 'object' && 'amount' in (value as Record<string, unknown>)) {
|
|
40
44
|
lastCumulativeAmount = BigInt((value as { amount: string }).amount)
|
|
45
|
+
voucherCount++
|
|
46
|
+
|
|
47
|
+
if (!sessionOpened) {
|
|
48
|
+
sessionOpened = true
|
|
49
|
+
if (opts.onSessionOpen) {
|
|
50
|
+
await opts.onSessionOpen(opts.channelContract)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (opts.onVoucher) {
|
|
55
|
+
const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7)
|
|
56
|
+
await opts.onVoucher(voucherCount, humanAmount)
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
},
|
|
43
60
|
async delete(key: string) { return innerStore.delete(key) },
|
|
@@ -85,6 +102,10 @@ export function createMppSessionHandler(opts: MppSessionHandlerOptions): Request
|
|
|
85
102
|
} else {
|
|
86
103
|
res.json({ closeTxHash: null, message: 'no vouchers received' })
|
|
87
104
|
}
|
|
105
|
+
|
|
106
|
+
sessionOpened = false
|
|
107
|
+
voucherCount = 0
|
|
108
|
+
lastCumulativeAmount = 0n
|
|
88
109
|
return
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -26,6 +26,10 @@ export interface RouteDockMiddlewareOptions {
|
|
|
26
26
|
manifest: RouteDockManifest
|
|
27
27
|
/** Called after each successful on-chain settlement */
|
|
28
28
|
onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>
|
|
29
|
+
/** Called when a new mpp-session is opened (first voucher received) */
|
|
30
|
+
onSessionOpen?: (channelId: string) => Promise<void>
|
|
31
|
+
/** Called after each verified voucher in an mpp-session */
|
|
32
|
+
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
/**
|
|
@@ -97,6 +101,8 @@ export function routedock(opts: RouteDockMiddlewareOptions): RequestHandler {
|
|
|
97
101
|
manifest: opts.manifest,
|
|
98
102
|
commitmentPublicKey: opts.commitmentPublicKey,
|
|
99
103
|
...(opts.onSettled ? { onSettled: opts.onSettled } : {}),
|
|
104
|
+
...(opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {}),
|
|
105
|
+
...(opts.onVoucher ? { onVoucher: opts.onVoucher } : {}),
|
|
100
106
|
}),
|
|
101
107
|
)
|
|
102
108
|
}
|