mppx 0.11.0 → 0.13.1
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/CHANGELOG.md +83 -0
- package/README.md +22 -1
- package/dist/Method.d.ts +7 -1
- package/dist/Method.js +7 -2
- package/dist/Receipt.d.ts +2 -0
- package/dist/Receipt.js +2 -0
- package/dist/cli/agent.d.ts +3 -0
- package/dist/cli/agent.js +20 -0
- package/dist/cli/cli.d.ts +9 -1
- package/dist/cli/cli.js +134 -136
- package/dist/cli/plugins/index.js +12 -14
- package/dist/cli/plugins/tempo.js +9 -2
- package/dist/cli/utils.d.ts +11 -1
- package/dist/cli/utils.js +15 -2
- package/dist/cli/validate/challenge.d.ts +2 -0
- package/dist/cli/validate/challenge.js +19 -4
- package/dist/cli/validate/discovery.d.ts +1 -0
- package/dist/cli/validate/discovery.js +10 -1
- package/dist/cli/validate/payment.js +63 -24
- package/dist/client/Methods.d.ts +0 -2
- package/dist/client/Methods.js +0 -2
- package/dist/client/Mppx.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.js +8 -2
- package/dist/client/internal/MethodChallenge.d.ts +1 -0
- package/dist/evm/Types.js +1 -1
- package/dist/evm/server/Charge.js +5 -0
- package/dist/evm/server/Methods.d.ts +164 -3
- package/dist/evm/server/Methods.js +50 -2
- package/dist/integrations/x402/Evm.js +4 -3
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.js +4 -7
- package/dist/middlewares/elysia.d.ts +11 -8
- package/dist/middlewares/elysia.js +67 -14
- package/dist/middlewares/express.js +13 -6
- package/dist/middlewares/hono.js +4 -4
- package/dist/middlewares/nextjs.js +9 -5
- package/dist/proxy/internal/Headers.js +65 -0
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/server/Mppx.d.ts +18 -2
- package/dist/server/Mppx.js +204 -92
- package/dist/server/NodeListener.js +71 -6
- package/dist/server/Request.d.ts +11 -1
- package/dist/server/Request.js +132 -13
- package/dist/server/Transport.d.ts +14 -1
- package/dist/server/Transport.js +1 -0
- package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
- package/dist/stripe/server/Methods.d.ts +10 -3
- package/dist/stripe/server/Methods.js +19 -6
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +1 -0
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/client/Charge.js +17 -13
- package/dist/tempo/client/Methods.d.ts +1 -5
- package/dist/tempo/client/Methods.js +10 -5
- package/dist/tempo/client/ResolveAccount.d.ts +3 -0
- package/dist/tempo/client/index.d.ts +0 -3
- package/dist/tempo/client/index.js +0 -2
- package/dist/tempo/internal/currencies.d.ts +19 -0
- package/dist/tempo/internal/currencies.js +41 -0
- package/dist/tempo/internal/defaults.d.ts +5 -3
- package/dist/tempo/internal/defaults.js +6 -2
- package/dist/tempo/internal/fee-payer.d.ts +11 -7
- package/dist/tempo/internal/fee-payer.js +30 -11
- package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
- package/dist/tempo/internal/machine-token-charge.js +2 -1
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/server/Charge.d.ts +12 -1
- package/dist/tempo/server/Charge.js +109 -78
- package/dist/tempo/server/Methods.d.ts +5048 -40
- package/dist/tempo/server/Methods.js +104 -13
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.js +31 -4
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.js +28 -11
- package/dist/tempo/session/client/ChannelOps.js +5 -0
- package/dist/tempo/session/client/Session.d.ts +1 -0
- package/dist/tempo/session/client/Session.js +7 -4
- package/dist/tempo/session/client/SessionManager.js +3 -0
- package/dist/tempo/session/client/Transports.d.ts +8 -1
- package/dist/tempo/session/client/Transports.js +12 -3
- package/dist/tempo/session/precompile/Chain.js +16 -1
- package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
- package/dist/tempo/session/precompile/Voucher.js +48 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +22 -2
- package/dist/tempo/session/server/ChannelStore.js +37 -7
- package/dist/tempo/session/server/CredentialVerification.d.ts +6 -1
- package/dist/tempo/session/server/CredentialVerification.js +56 -19
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
- package/dist/tempo/session/server/MeteredStream.js +85 -29
- package/dist/tempo/session/server/RequestState.d.ts +6 -0
- package/dist/tempo/session/server/RequestState.js +11 -4
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +10 -2
- package/dist/tempo/session/server/Settlement.d.ts +23 -0
- package/dist/tempo/session/server/Settlement.js +105 -25
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.js +105 -32
- package/dist/tempo/session/server/Transports.d.ts +24 -19
- package/dist/tempo/session/server/Transports.js +169 -58
- package/dist/tempo/session/server/Ws.d.ts +5 -1
- package/dist/tempo/session/server/Ws.js +75 -20
- package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.js +3 -0
- package/dist/validation/core.js +2 -1
- package/dist/x402/server/EvmCharge.d.ts +3 -1
- package/dist/x402/server/EvmCharge.js +14 -1
- package/package.json +8 -8
- package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
- package/dist/tempo/legacy/client/ChannelOps.js +0 -168
- package/dist/tempo/legacy/client/Session.d.ts +0 -185
- package/dist/tempo/legacy/client/Session.js +0 -279
- package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
- package/dist/tempo/legacy/client/SessionManager.js +0 -706
- package/dist/tempo/legacy/client/index.d.ts +0 -7
- package/dist/tempo/legacy/client/index.js +0 -5
|
@@ -3,7 +3,7 @@ import * as Credential from '../../../Credential.js';
|
|
|
3
3
|
import { createSessionReceipt, deserializeSessionReceipt, formatApplicationMessage, formatCloseReadyMessage, formatErrorMessage, formatNeedVoucherMessage, formatReceiptMessage, parseMessage, readSessionChallengeAmount, requireSessionCredentialContext, } from '../precompile/Protocol.js';
|
|
4
4
|
import * as ChannelStore from './ChannelStore.js';
|
|
5
5
|
import { meterIterable } from './MeteredStream.js';
|
|
6
|
-
import { send, subscribe, toText } from './Transports.js';
|
|
6
|
+
import { send, subscribe, toText, WebSocketOutboundBufferLimitError, } from './Transports.js';
|
|
7
7
|
/** Public WebSocket payment frame helpers. */
|
|
8
8
|
export { formatApplicationMessage, formatAuthorizationMessage, formatCloseReadyMessage, formatCloseRequestMessage, formatErrorMessage, formatNeedVoucherMessage, formatReceiptMessage, parseMessage, } from '../precompile/Protocol.js';
|
|
9
9
|
/**
|
|
@@ -17,10 +17,11 @@ export { formatApplicationMessage, formatAuthorizationMessage, formatCloseReadyM
|
|
|
17
17
|
* HTTP-specific context beyond the `Authorization` header.
|
|
18
18
|
*/
|
|
19
19
|
export async function serve(options) {
|
|
20
|
-
const { amount: expectedAmount, generate, pollIntervalMs = 100, route, socket, store: rawStore, url, } = options;
|
|
20
|
+
const { amount: expectedAmount, generate, maxIncomingMessageBytes = 64 * 1024, maxBufferedAmount = 1024 * 1024, pollIntervalMs = 100, route, socket, store: rawStore, url, } = options;
|
|
21
21
|
const store = 'getChannel' in rawStore ? rawStore : ChannelStore.fromStore(rawStore);
|
|
22
22
|
const requestUrl = normalizeHttpUrl(url);
|
|
23
23
|
const maxQueuedPaymentMessages = 32;
|
|
24
|
+
const sendFrame = (data) => send(socket, data, { maxBufferedAmount });
|
|
24
25
|
const abortController = new AbortController();
|
|
25
26
|
const runtime = {
|
|
26
27
|
closed: false,
|
|
@@ -55,7 +56,7 @@ export async function serve(options) {
|
|
|
55
56
|
spent: channel.spent,
|
|
56
57
|
units: channel.units,
|
|
57
58
|
});
|
|
58
|
-
await
|
|
59
|
+
await sendFrame(formatCloseReadyMessage(receipt));
|
|
59
60
|
};
|
|
60
61
|
const runStream = async (context) => {
|
|
61
62
|
try {
|
|
@@ -67,23 +68,28 @@ export async function serve(options) {
|
|
|
67
68
|
onChargeCommitted: options.onChargeCommitted ?? options.settleScheduled,
|
|
68
69
|
pollIntervalMs,
|
|
69
70
|
signal: abortController.signal,
|
|
70
|
-
emitNeedVoucher: (message) =>
|
|
71
|
+
emitNeedVoucher: (message) => sendFrame(message),
|
|
71
72
|
formatNeedVoucher: formatNeedVoucherMessage,
|
|
72
73
|
})) {
|
|
73
74
|
if (abortController.signal.aborted)
|
|
74
75
|
break;
|
|
75
|
-
await
|
|
76
|
+
await sendFrame(formatApplicationMessage(value));
|
|
76
77
|
}
|
|
77
78
|
if (!abortController.signal.aborted)
|
|
78
79
|
await sendCloseReady();
|
|
79
80
|
}
|
|
80
81
|
catch (error) {
|
|
81
82
|
if (!abortController.signal.aborted) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
if (error instanceof WebSocketOutboundBufferLimitError) {
|
|
84
|
+
await close(4008, 'outbound buffer limit exceeded').catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await sendFrame(formatErrorMessage({
|
|
88
|
+
message: error instanceof Error ? error.message : 'websocket session failed',
|
|
89
|
+
status: 500,
|
|
90
|
+
})).catch(() => { });
|
|
91
|
+
await close(1011, 'websocket session failed').catch(() => { });
|
|
92
|
+
}
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
finally {
|
|
@@ -109,7 +115,7 @@ export async function serve(options) {
|
|
|
109
115
|
if (payload.action === 'close')
|
|
110
116
|
runtime.closeRequested = true;
|
|
111
117
|
if (expectedAmount && credential.challenge.request.amount !== expectedAmount) {
|
|
112
|
-
await
|
|
118
|
+
await sendFrame(formatErrorMessage({
|
|
113
119
|
message: 'credential amount does not match this endpoint',
|
|
114
120
|
status: 402,
|
|
115
121
|
}));
|
|
@@ -118,7 +124,7 @@ export async function serve(options) {
|
|
|
118
124
|
}
|
|
119
125
|
const authorizationResult = await authorizePaymentFrame({ authorization, requestUrl, route });
|
|
120
126
|
if (authorizationResult.status === 'rejected') {
|
|
121
|
-
await
|
|
127
|
+
await sendFrame(formatErrorMessage({
|
|
122
128
|
message: authorizationResult.message,
|
|
123
129
|
status: authorizationResult.responseStatus,
|
|
124
130
|
}));
|
|
@@ -129,7 +135,7 @@ export async function serve(options) {
|
|
|
129
135
|
if (payload.action === 'voucher' && runtime.streamContext) {
|
|
130
136
|
runtime.streamContext.challengeId = credential.challenge.id;
|
|
131
137
|
}
|
|
132
|
-
await
|
|
138
|
+
await sendFrame(formatReceiptMessage(receipt));
|
|
133
139
|
if (payload.action === 'close') {
|
|
134
140
|
await close(1000, 'payment session closed');
|
|
135
141
|
return;
|
|
@@ -155,6 +161,15 @@ export async function serve(options) {
|
|
|
155
161
|
const onMessage = (payload) => {
|
|
156
162
|
if (runtime.closed)
|
|
157
163
|
return;
|
|
164
|
+
const messageBytes = frameByteLength(payload);
|
|
165
|
+
if (messageBytes !== null && messageBytes > maxIncomingMessageBytes) {
|
|
166
|
+
runtime.closed = true;
|
|
167
|
+
abortController.abort();
|
|
168
|
+
unsubscribe();
|
|
169
|
+
void send(socket, formatErrorMessage({ message: 'payment message is too large', status: 413 })).catch(() => { });
|
|
170
|
+
void Promise.resolve(socket.close(1009, 'payment message is too large')).catch(() => { });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
158
173
|
const raw = toText(payload);
|
|
159
174
|
if (!raw)
|
|
160
175
|
return;
|
|
@@ -173,7 +188,7 @@ export async function serve(options) {
|
|
|
173
188
|
if (!work)
|
|
174
189
|
return;
|
|
175
190
|
if (runtime.queuedActions >= maxQueuedPaymentMessages) {
|
|
176
|
-
void
|
|
191
|
+
void sendFrame(formatErrorMessage({
|
|
177
192
|
message: 'too many queued payment messages',
|
|
178
193
|
status: 429,
|
|
179
194
|
})).catch(() => { });
|
|
@@ -194,11 +209,16 @@ export async function serve(options) {
|
|
|
194
209
|
})
|
|
195
210
|
.catch(async (error) => {
|
|
196
211
|
if (!runtime.closed) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
if (error instanceof WebSocketOutboundBufferLimitError) {
|
|
213
|
+
await close(4008, 'outbound buffer limit exceeded').catch(() => { });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
await sendFrame(formatErrorMessage({
|
|
217
|
+
message: error instanceof Error ? error.message : 'invalid payment message',
|
|
218
|
+
status: 400,
|
|
219
|
+
})).catch(() => { });
|
|
220
|
+
await close(1008, 'invalid payment message').catch(() => { });
|
|
221
|
+
}
|
|
202
222
|
}
|
|
203
223
|
});
|
|
204
224
|
};
|
|
@@ -215,6 +235,41 @@ export async function serve(options) {
|
|
|
215
235
|
message: onMessage,
|
|
216
236
|
});
|
|
217
237
|
}
|
|
238
|
+
function frameByteLength(value) {
|
|
239
|
+
if (value instanceof ArrayBuffer)
|
|
240
|
+
return value.byteLength;
|
|
241
|
+
if (ArrayBuffer.isView(value))
|
|
242
|
+
return value.byteLength;
|
|
243
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob)
|
|
244
|
+
return value.size;
|
|
245
|
+
if (Array.isArray(value)) {
|
|
246
|
+
let bytes = 0;
|
|
247
|
+
for (const fragment of value) {
|
|
248
|
+
const fragmentBytes = frameByteLength(fragment);
|
|
249
|
+
if (fragmentBytes === null)
|
|
250
|
+
return null;
|
|
251
|
+
bytes += fragmentBytes;
|
|
252
|
+
}
|
|
253
|
+
return bytes;
|
|
254
|
+
}
|
|
255
|
+
if (typeof value !== 'string')
|
|
256
|
+
return null;
|
|
257
|
+
let bytes = 0;
|
|
258
|
+
for (let index = 0; index < value.length; index++) {
|
|
259
|
+
const codePoint = value.codePointAt(index);
|
|
260
|
+
if (codePoint <= 0x7f)
|
|
261
|
+
bytes += 1;
|
|
262
|
+
else if (codePoint <= 0x7ff)
|
|
263
|
+
bytes += 2;
|
|
264
|
+
else if (codePoint <= 0xffff)
|
|
265
|
+
bytes += 3;
|
|
266
|
+
else {
|
|
267
|
+
bytes += 4;
|
|
268
|
+
index += 1;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return bytes;
|
|
272
|
+
}
|
|
218
273
|
function normalizeHttpUrl(value) {
|
|
219
274
|
const url = new URL(value.toString());
|
|
220
275
|
if (url.protocol === 'ws:')
|
|
@@ -239,7 +294,7 @@ async function authorizePaymentFrame(parameters) {
|
|
|
239
294
|
responseStatus: response.status,
|
|
240
295
|
};
|
|
241
296
|
}
|
|
242
|
-
const response = result.withReceipt(new Response(null, { status: 204 }));
|
|
297
|
+
const response = await result.withReceipt(new Response(null, { status: 204 }));
|
|
243
298
|
const receiptHeader = response.headers.get(Constants.Headers.paymentReceipt);
|
|
244
299
|
if (!receiptHeader)
|
|
245
300
|
throw new Error('management response missing Payment-Receipt header');
|
|
@@ -74,7 +74,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
74
74
|
};
|
|
75
75
|
type: 'p256';
|
|
76
76
|
account?: undefined;
|
|
77
|
-
|
|
77
|
+
config?: undefined;
|
|
78
78
|
inner?: undefined;
|
|
79
79
|
keyId?: undefined;
|
|
80
80
|
metadata?: undefined;
|
|
@@ -89,7 +89,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
89
89
|
};
|
|
90
90
|
type: 'secp256k1';
|
|
91
91
|
account?: undefined;
|
|
92
|
-
|
|
92
|
+
config?: undefined;
|
|
93
93
|
inner?: undefined;
|
|
94
94
|
keyId?: undefined;
|
|
95
95
|
metadata?: undefined;
|
|
@@ -103,25 +103,87 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
103
103
|
authenticatorData: Hex.Hex;
|
|
104
104
|
clientDataJSON: string;
|
|
105
105
|
};
|
|
106
|
-
signature: {
|
|
107
|
-
r: bigint;
|
|
108
|
-
s: bigint;
|
|
109
|
-
yParity?: number | undefined;
|
|
110
|
-
};
|
|
111
106
|
publicKey: {
|
|
112
107
|
prefix: number;
|
|
113
108
|
x: bigint;
|
|
114
109
|
y: bigint;
|
|
115
110
|
};
|
|
111
|
+
signature: {
|
|
112
|
+
r: bigint;
|
|
113
|
+
s: bigint;
|
|
114
|
+
yParity?: number | undefined;
|
|
115
|
+
};
|
|
116
116
|
type: 'webAuthn';
|
|
117
117
|
account?: undefined;
|
|
118
|
-
|
|
118
|
+
config?: undefined;
|
|
119
119
|
inner?: undefined;
|
|
120
120
|
keyId?: undefined;
|
|
121
121
|
prehash?: undefined;
|
|
122
122
|
signatures?: undefined;
|
|
123
123
|
userAddress?: undefined;
|
|
124
124
|
version?: undefined;
|
|
125
|
+
} | {
|
|
126
|
+
account: import("ox/Address").Address;
|
|
127
|
+
config: {
|
|
128
|
+
owners: readonly {
|
|
129
|
+
owner: import("ox/Address").Address;
|
|
130
|
+
weight: number;
|
|
131
|
+
}[];
|
|
132
|
+
salt: Hex.Hex;
|
|
133
|
+
threshold: number;
|
|
134
|
+
version: bigint;
|
|
135
|
+
};
|
|
136
|
+
signatures: readonly ({
|
|
137
|
+
prehash: boolean;
|
|
138
|
+
publicKey: {
|
|
139
|
+
prefix: number;
|
|
140
|
+
x: bigint;
|
|
141
|
+
y: bigint;
|
|
142
|
+
};
|
|
143
|
+
signature: {
|
|
144
|
+
r: bigint;
|
|
145
|
+
s: bigint;
|
|
146
|
+
yParity?: number | undefined;
|
|
147
|
+
};
|
|
148
|
+
type: 'p256';
|
|
149
|
+
metadata?: undefined;
|
|
150
|
+
} | {
|
|
151
|
+
signature: {
|
|
152
|
+
r: bigint;
|
|
153
|
+
s: bigint;
|
|
154
|
+
yParity: number;
|
|
155
|
+
};
|
|
156
|
+
type: 'secp256k1';
|
|
157
|
+
metadata?: undefined;
|
|
158
|
+
prehash?: undefined;
|
|
159
|
+
publicKey?: undefined;
|
|
160
|
+
} | {
|
|
161
|
+
metadata: {
|
|
162
|
+
authenticatorData: Hex.Hex;
|
|
163
|
+
clientDataJSON: string;
|
|
164
|
+
};
|
|
165
|
+
publicKey: {
|
|
166
|
+
prefix: number;
|
|
167
|
+
x: bigint;
|
|
168
|
+
y: bigint;
|
|
169
|
+
};
|
|
170
|
+
signature: {
|
|
171
|
+
r: bigint;
|
|
172
|
+
s: bigint;
|
|
173
|
+
yParity?: number | undefined;
|
|
174
|
+
};
|
|
175
|
+
type: 'webAuthn';
|
|
176
|
+
prehash?: undefined;
|
|
177
|
+
})[];
|
|
178
|
+
type: 'multisig';
|
|
179
|
+
inner?: undefined;
|
|
180
|
+
keyId?: undefined;
|
|
181
|
+
metadata?: undefined;
|
|
182
|
+
prehash?: undefined;
|
|
183
|
+
publicKey?: undefined;
|
|
184
|
+
signature?: undefined;
|
|
185
|
+
userAddress?: undefined;
|
|
186
|
+
version?: undefined;
|
|
125
187
|
};
|
|
126
188
|
readonly address: `0x${string}`;
|
|
127
189
|
readonly chainId: bigint;
|
|
@@ -6,6 +6,8 @@ export type SubscriptionStore = {
|
|
|
6
6
|
activate<result extends {
|
|
7
7
|
subscription: SubscriptionRecord;
|
|
8
8
|
}>(parameters: ActivateParameters<result>): Promise<ActivateResult<result>>;
|
|
9
|
+
/** Releases a transient credential claim so the same challenge can be retried. */
|
|
10
|
+
releaseCredential(challengeId: string): Promise<void>;
|
|
9
11
|
/** Looks up a subscription by subscription ID. */
|
|
10
12
|
get(subscriptionId: string): Promise<SubscriptionRecord | null>;
|
|
11
13
|
/** Looks up a generated access key for a resolved request key. */
|
|
@@ -146,6 +146,9 @@ export function fromStore(store, options) {
|
|
|
146
146
|
await clearActivationState(subscription.lookupKey, challengeId);
|
|
147
147
|
return { status: 'activated', result };
|
|
148
148
|
},
|
|
149
|
+
async releaseCredential(challengeId) {
|
|
150
|
+
await store.delete(credentialKey(challengeId));
|
|
151
|
+
},
|
|
149
152
|
async get(subscriptionId) {
|
|
150
153
|
return (await store.get(recordKey(subscriptionId)));
|
|
151
154
|
},
|
package/dist/validation/core.js
CHANGED
|
@@ -35,6 +35,7 @@ export async function* validateStream(options) {
|
|
|
35
35
|
});
|
|
36
36
|
const effectiveBody = resolvedBody ?? body;
|
|
37
37
|
const isMpp = challengeResults.some((r) => r.severity === 'pass' && r.label === 'Challenge parseable');
|
|
38
|
+
const challengeIsValid = !challengeResults.some((result) => result.severity === 'fail');
|
|
38
39
|
const chainIds = (parsedChallenges ?? [])
|
|
39
40
|
.filter((ch) => ch.method !== 'stripe')
|
|
40
41
|
.map((ch) => ch.request.methodDetails?.chainId)
|
|
@@ -63,7 +64,7 @@ export async function* validateStream(options) {
|
|
|
63
64
|
query: options.query,
|
|
64
65
|
});
|
|
65
66
|
yield { phase: 'errorHandling', endpoint, results: errorResults };
|
|
66
|
-
if (!options.skipPayment) {
|
|
67
|
+
if (!options.skipPayment && challengeIsValid) {
|
|
67
68
|
const onResults = options.onPaymentResults;
|
|
68
69
|
const paymentResults = await validatePaymentFlow(baseUrl, endpoint, verbose, {
|
|
69
70
|
body: effectiveBody,
|
|
@@ -25,7 +25,7 @@ export type Options = {
|
|
|
25
25
|
* and client bugs, not an active attacker. Body binding is unaffected either
|
|
26
26
|
* way — `challenge.digest` is verified against the actual body.
|
|
27
27
|
*
|
|
28
|
-
* @default '
|
|
28
|
+
* @default 'required'
|
|
29
29
|
*/
|
|
30
30
|
routeBinding?: RouteBindingMode | undefined;
|
|
31
31
|
};
|
|
@@ -38,8 +38,10 @@ export type ResolvedOptions = {
|
|
|
38
38
|
routeBinding: RouteBindingMode;
|
|
39
39
|
};
|
|
40
40
|
export type Path = {
|
|
41
|
+
credentialHeaders: readonly string[];
|
|
41
42
|
bindCredential: NonNullable<ServerTransport.Http['bindCredential']>;
|
|
42
43
|
getCredential: ServerTransport.Http['getCredential'];
|
|
44
|
+
matchCredential: NonNullable<ServerTransport.Http['matchCredential']>;
|
|
43
45
|
respondChallenge: (options: Parameters<ServerTransport.Http['respondChallenge']>[0], response?: Response | undefined) => Response | Promise<Response>;
|
|
44
46
|
respondReceipt: (options: Parameters<ServerTransport.Http['respondReceipt']>[0], response: Response) => Response;
|
|
45
47
|
};
|
|
@@ -37,12 +37,13 @@ export function resolveOptions(parameters) {
|
|
|
37
37
|
}
|
|
38
38
|
: {}),
|
|
39
39
|
maxTimeoutSeconds: parameters.options?.maxTimeoutSeconds ?? 300,
|
|
40
|
-
routeBinding: parameters.options?.routeBinding ?? '
|
|
40
|
+
routeBinding: parameters.options?.routeBinding ?? 'required',
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
/** Creates the x402 wire path for an EVM charge method. */
|
|
44
44
|
export function createPath(config) {
|
|
45
45
|
return {
|
|
46
|
+
credentialHeaders: [x402_Types.paymentSignatureHeader],
|
|
46
47
|
getCredential(request) {
|
|
47
48
|
const paymentSignature = request.headers.get(x402_Types.paymentSignatureHeader);
|
|
48
49
|
if (!paymentSignature)
|
|
@@ -53,6 +54,18 @@ export function createPath(config) {
|
|
|
53
54
|
payload: paymentPayload,
|
|
54
55
|
}));
|
|
55
56
|
},
|
|
57
|
+
matchCredential({ input, request }) {
|
|
58
|
+
const paymentSignature = input.headers.get(x402_Types.paymentSignatureHeader);
|
|
59
|
+
if (!paymentSignature)
|
|
60
|
+
return false;
|
|
61
|
+
try {
|
|
62
|
+
const paymentPayload = x402_Header.decodePaymentSignature(paymentSignature);
|
|
63
|
+
return isDeepStrictEqual(paymentPayload.accepted, toPaymentRequirements(request, config));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
56
69
|
async bindCredential({ challenge, credential, input }) {
|
|
57
70
|
const paymentPayload = parsePaymentPayload(credential.payload);
|
|
58
71
|
if (!paymentPayload)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mppx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.1",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/wevm/mppx#readme",
|
|
@@ -163,11 +163,11 @@
|
|
|
163
163
|
"@x402/hono": ">=2.22.0",
|
|
164
164
|
"@x402/mcp": ">=2.22.0",
|
|
165
165
|
"@x402/next": ">=2.22.0",
|
|
166
|
-
"elysia": ">=1",
|
|
166
|
+
"elysia": ">=1.2.0",
|
|
167
167
|
"express": ">=5",
|
|
168
168
|
"hono": ">=4.12.25",
|
|
169
169
|
"next": ">=16.2.6",
|
|
170
|
-
"viem": ">=2.
|
|
170
|
+
"viem": ">=2.57.1"
|
|
171
171
|
},
|
|
172
172
|
"peerDependenciesMeta": {
|
|
173
173
|
"@modelcontextprotocol/sdk": {
|
|
@@ -202,11 +202,11 @@
|
|
|
202
202
|
}
|
|
203
203
|
},
|
|
204
204
|
"dependencies": {
|
|
205
|
-
"@stripe/stripe-js": "9.
|
|
206
|
-
"eventsource-parser": "
|
|
207
|
-
"ox": "0.14.
|
|
208
|
-
"structured-headers": "2.0
|
|
209
|
-
"zod": "^4.
|
|
205
|
+
"@stripe/stripe-js": "9.17.0",
|
|
206
|
+
"eventsource-parser": "4.1.1",
|
|
207
|
+
"ox": "0.14.45",
|
|
208
|
+
"structured-headers": "2.1.0",
|
|
209
|
+
"zod": "^4.6.5"
|
|
210
210
|
},
|
|
211
211
|
"repository": {
|
|
212
212
|
"type": "git",
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared client-side channel operations.
|
|
3
|
-
*
|
|
4
|
-
* Provides the low-level helpers that both `session()`
|
|
5
|
-
* and `sessionManager()` (orchestrator) rely on: escrow resolution, channel
|
|
6
|
-
* ID computation, on-chain open/voucher/close payload construction, channel
|
|
7
|
-
* recovery from on-chain state, and credential serialization.
|
|
8
|
-
*/
|
|
9
|
-
import { Hex } from 'ox';
|
|
10
|
-
import { type Address, type Account as viem_Account, type Client as viem_Client } from 'viem';
|
|
11
|
-
import type { Challenge } from '../../../Challenge.js';
|
|
12
|
-
import type { LegacySessionCredentialPayload } from '../session/Types.js';
|
|
13
|
-
/** Cached channel metadata used by the legacy auto-driving session client. */
|
|
14
|
-
export type ChannelEntry = {
|
|
15
|
-
/** Voucher authority the channel was opened with. */
|
|
16
|
-
authorizedSigner: Address;
|
|
17
|
-
/** Legacy contract-backed channel ID. */
|
|
18
|
-
channelId: Hex.Hex;
|
|
19
|
-
/** Salt used to derive the channel ID. */
|
|
20
|
-
salt: Hex.Hex;
|
|
21
|
-
/** Highest cumulative voucher amount locally authorized. */
|
|
22
|
-
cumulativeAmount: bigint;
|
|
23
|
-
/** Escrow contract backing this channel. */
|
|
24
|
-
escrowContract: Address;
|
|
25
|
-
/** Chain ID used for channel ID and voucher domain separation. */
|
|
26
|
-
chainId: number;
|
|
27
|
-
/** Whether the client considers the channel reusable. */
|
|
28
|
-
opened: boolean;
|
|
29
|
-
};
|
|
30
|
-
/** Resolves the chain ID embedded in a legacy session challenge. */
|
|
31
|
-
export declare function resolveChainId(challenge: Challenge): number;
|
|
32
|
-
/** Resolves the server-advertised escrow against the client's trust policy. */
|
|
33
|
-
export declare function resolveEscrow(challenge: {
|
|
34
|
-
request: {
|
|
35
|
-
methodDetails?: unknown;
|
|
36
|
-
};
|
|
37
|
-
}, chainId: number, escrowContractOverride?: Address, allowCustomEscrow?: boolean): Address;
|
|
38
|
-
/** Serializes a legacy session credential with a payer DID source. */
|
|
39
|
-
export declare function serializeCredential(challenge: Challenge, payload: LegacySessionCredentialPayload, chainId: number, account: viem_Account): string;
|
|
40
|
-
/** Creates a legacy cumulative voucher credential payload. */
|
|
41
|
-
export declare function createVoucherPayload(client: viem_Client, account: viem_Account, channelId: Hex.Hex, cumulativeAmount: bigint, escrowContract: Address, chainId: number, voucherSigner?: viem_Account | undefined): Promise<LegacySessionCredentialPayload>;
|
|
42
|
-
/** Creates a legacy cooperative close credential payload. */
|
|
43
|
-
export declare function createClosePayload(client: viem_Client, account: viem_Account, channelId: Hex.Hex, cumulativeAmount: bigint, escrowContract: Address, chainId: number, voucherSigner?: viem_Account | undefined): Promise<LegacySessionCredentialPayload>;
|
|
44
|
-
/** Creates a legacy open transaction credential payload and local channel entry. */
|
|
45
|
-
export declare function createOpenPayload(client: viem_Client, account: viem_Account, options: {
|
|
46
|
-
voucherSigner?: viem_Account | undefined;
|
|
47
|
-
escrowContract: Address;
|
|
48
|
-
payee: Address;
|
|
49
|
-
currency: Address;
|
|
50
|
-
deposit: bigint;
|
|
51
|
-
initialAmount: bigint;
|
|
52
|
-
chainId: number;
|
|
53
|
-
feePayer?: boolean | undefined;
|
|
54
|
-
}): Promise<{
|
|
55
|
-
entry: ChannelEntry;
|
|
56
|
-
payload: LegacySessionCredentialPayload;
|
|
57
|
-
}>;
|
|
58
|
-
/**
|
|
59
|
-
* Attempt to recover an existing on-chain channel by reading its state.
|
|
60
|
-
*
|
|
61
|
-
* If the channel has a positive deposit and is not finalized, returns a
|
|
62
|
-
* {@link ChannelEntry} with `cumulativeAmount` set to the on-chain settled
|
|
63
|
-
* amount (the safe starting point for new vouchers).
|
|
64
|
-
*
|
|
65
|
-
* Returns `undefined` if the channel doesn't exist, has zero deposit,
|
|
66
|
-
* or is already finalized.
|
|
67
|
-
*/
|
|
68
|
-
export declare function tryRecoverChannel(client: viem_Client, escrowContract: Address, channelId: Hex.Hex, chainId: number): Promise<ChannelEntry | undefined>;
|
|
69
|
-
//# sourceMappingURL=ChannelOps.d.ts.map
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared client-side channel operations.
|
|
3
|
-
*
|
|
4
|
-
* Provides the low-level helpers that both `session()`
|
|
5
|
-
* and `sessionManager()` (orchestrator) rely on: escrow resolution, channel
|
|
6
|
-
* ID computation, on-chain open/voucher/close payload construction, channel
|
|
7
|
-
* recovery from on-chain state, and credential serialization.
|
|
8
|
-
*/
|
|
9
|
-
import { Hex } from 'ox';
|
|
10
|
-
import { encodeFunctionData, } from 'viem';
|
|
11
|
-
import { prepareTransactionRequest, signTransaction } from 'viem/actions';
|
|
12
|
-
import { Abis } from 'viem/tempo';
|
|
13
|
-
import * as Credential from '../../../Credential.js';
|
|
14
|
-
import { getAccountSignerAddress } from '../../internal/account.js';
|
|
15
|
-
import * as defaults from '../../internal/defaults.js';
|
|
16
|
-
import { escrowAbi, getOnChainChannel } from '../session/Chain.js';
|
|
17
|
-
import * as Channel from '../session/Channel.js';
|
|
18
|
-
import { signVoucher } from '../session/Voucher.js';
|
|
19
|
-
function resolveVoucherSigner(account, voucherSigner) {
|
|
20
|
-
return voucherSigner ?? account;
|
|
21
|
-
}
|
|
22
|
-
/** Resolves the chain ID embedded in a legacy session challenge. */
|
|
23
|
-
export function resolveChainId(challenge) {
|
|
24
|
-
const md = challenge.request.methodDetails;
|
|
25
|
-
return md?.chainId ?? 0;
|
|
26
|
-
}
|
|
27
|
-
/** Resolves the server-advertised escrow against the client's trust policy. */
|
|
28
|
-
export function resolveEscrow(challenge, chainId, escrowContractOverride, allowCustomEscrow = false) {
|
|
29
|
-
const challengeEscrow = challenge.request.methodDetails
|
|
30
|
-
?.escrowContract;
|
|
31
|
-
const escrow = escrowContractOverride ??
|
|
32
|
-
defaults.escrowContract[chainId];
|
|
33
|
-
if (!escrow && !(allowCustomEscrow && challengeEscrow))
|
|
34
|
-
throw new Error('No trusted `escrowContract` available. Provide it in client parameters or use a supported chain.');
|
|
35
|
-
if (challengeEscrow &&
|
|
36
|
-
escrow &&
|
|
37
|
-
challengeEscrow.toLowerCase() !== escrow.toLowerCase() &&
|
|
38
|
-
(!allowCustomEscrow || escrowContractOverride !== undefined))
|
|
39
|
-
throw new Error(`Tempo session escrow ${challengeEscrow} does not match client escrow ${escrow}.`);
|
|
40
|
-
if (challengeEscrow && (!escrow || challengeEscrow.toLowerCase() !== escrow.toLowerCase()))
|
|
41
|
-
return challengeEscrow;
|
|
42
|
-
return escrow;
|
|
43
|
-
}
|
|
44
|
-
/** Serializes a legacy session credential with a payer DID source. */
|
|
45
|
-
export function serializeCredential(challenge, payload, chainId, account) {
|
|
46
|
-
return Credential.serialize({
|
|
47
|
-
challenge,
|
|
48
|
-
payload,
|
|
49
|
-
source: `did:pkh:eip155:${chainId}:${account.address}`,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/** Creates a legacy cumulative voucher credential payload. */
|
|
53
|
-
export async function createVoucherPayload(client, account, channelId, cumulativeAmount, escrowContract, chainId, voucherSigner) {
|
|
54
|
-
const signer = resolveVoucherSigner(account, voucherSigner);
|
|
55
|
-
const signature = await signVoucher(client, account, { channelId, cumulativeAmount }, escrowContract, chainId, signer);
|
|
56
|
-
return {
|
|
57
|
-
action: 'voucher',
|
|
58
|
-
channelId,
|
|
59
|
-
cumulativeAmount: cumulativeAmount.toString(),
|
|
60
|
-
signature,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/** Creates a legacy cooperative close credential payload. */
|
|
64
|
-
export async function createClosePayload(client, account, channelId, cumulativeAmount, escrowContract, chainId, voucherSigner) {
|
|
65
|
-
const signer = resolveVoucherSigner(account, voucherSigner);
|
|
66
|
-
const signature = await signVoucher(client, account, { channelId, cumulativeAmount }, escrowContract, chainId, signer);
|
|
67
|
-
return {
|
|
68
|
-
action: 'close',
|
|
69
|
-
channelId,
|
|
70
|
-
cumulativeAmount: cumulativeAmount.toString(),
|
|
71
|
-
signature,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
/** Creates a legacy open transaction credential payload and local channel entry. */
|
|
75
|
-
export async function createOpenPayload(client, account, options) {
|
|
76
|
-
const { escrowContract, payee, currency, deposit, initialAmount, chainId, feePayer } = options;
|
|
77
|
-
const voucherSigner = resolveVoucherSigner(account, options.voucherSigner);
|
|
78
|
-
const authorizedSigner = getAccountSignerAddress(voucherSigner);
|
|
79
|
-
const salt = Hex.random(32);
|
|
80
|
-
const channelId = Channel.computeId({
|
|
81
|
-
authorizedSigner,
|
|
82
|
-
chainId,
|
|
83
|
-
escrowContract,
|
|
84
|
-
payee,
|
|
85
|
-
payer: account.address,
|
|
86
|
-
salt,
|
|
87
|
-
token: currency,
|
|
88
|
-
});
|
|
89
|
-
const approveData = encodeFunctionData({
|
|
90
|
-
abi: Abis.tip20,
|
|
91
|
-
functionName: 'approve',
|
|
92
|
-
args: [escrowContract, deposit],
|
|
93
|
-
});
|
|
94
|
-
const openData = encodeFunctionData({
|
|
95
|
-
abi: escrowAbi,
|
|
96
|
-
functionName: 'open',
|
|
97
|
-
args: [payee, currency, deposit, salt, authorizedSigner],
|
|
98
|
-
});
|
|
99
|
-
const validBefore = Math.floor(Date.now() / 1_000) + 25;
|
|
100
|
-
const prepared = await prepareTransactionRequest(client, {
|
|
101
|
-
account,
|
|
102
|
-
calls: [
|
|
103
|
-
{ to: currency, data: approveData },
|
|
104
|
-
{ to: escrowContract, data: openData },
|
|
105
|
-
],
|
|
106
|
-
feeToken: currency,
|
|
107
|
-
nonceKey: 'expiring',
|
|
108
|
-
validBefore,
|
|
109
|
-
});
|
|
110
|
-
// Estimate before enabling fee-payer mode so Tempo includes sender
|
|
111
|
-
// signature and access-key verification costs in the gas budget.
|
|
112
|
-
prepared.gas = (prepared.gas ?? 0n) + 5000n;
|
|
113
|
-
if (feePayer)
|
|
114
|
-
prepared.feePayer = true;
|
|
115
|
-
const transaction = (await signTransaction(client, prepared));
|
|
116
|
-
const signature = await signVoucher(client, account, { channelId, cumulativeAmount: initialAmount }, escrowContract, chainId, voucherSigner);
|
|
117
|
-
return {
|
|
118
|
-
entry: {
|
|
119
|
-
authorizedSigner,
|
|
120
|
-
channelId,
|
|
121
|
-
salt,
|
|
122
|
-
cumulativeAmount: initialAmount,
|
|
123
|
-
escrowContract,
|
|
124
|
-
chainId,
|
|
125
|
-
opened: true,
|
|
126
|
-
},
|
|
127
|
-
payload: {
|
|
128
|
-
action: 'open',
|
|
129
|
-
type: 'transaction',
|
|
130
|
-
channelId,
|
|
131
|
-
transaction,
|
|
132
|
-
authorizedSigner,
|
|
133
|
-
cumulativeAmount: initialAmount.toString(),
|
|
134
|
-
signature,
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Attempt to recover an existing on-chain channel by reading its state.
|
|
140
|
-
*
|
|
141
|
-
* If the channel has a positive deposit and is not finalized, returns a
|
|
142
|
-
* {@link ChannelEntry} with `cumulativeAmount` set to the on-chain settled
|
|
143
|
-
* amount (the safe starting point for new vouchers).
|
|
144
|
-
*
|
|
145
|
-
* Returns `undefined` if the channel doesn't exist, has zero deposit,
|
|
146
|
-
* or is already finalized.
|
|
147
|
-
*/
|
|
148
|
-
export async function tryRecoverChannel(client, escrowContract, channelId, chainId) {
|
|
149
|
-
try {
|
|
150
|
-
const onChain = await getOnChainChannel(client, escrowContract, channelId);
|
|
151
|
-
if (onChain.deposit > 0n && !onChain.finalized) {
|
|
152
|
-
return {
|
|
153
|
-
// A zero on-chain authorizedSigner means the escrow verifies vouchers
|
|
154
|
-
// against the payer, so record the payer as the voucher authority.
|
|
155
|
-
authorizedSigner: BigInt(onChain.authorizedSigner) === 0n ? onChain.payer : onChain.authorizedSigner,
|
|
156
|
-
channelId,
|
|
157
|
-
salt: '0x',
|
|
158
|
-
cumulativeAmount: onChain.settled,
|
|
159
|
-
escrowContract,
|
|
160
|
-
chainId,
|
|
161
|
-
opened: true,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch { }
|
|
166
|
-
return undefined;
|
|
167
|
-
}
|
|
168
|
-
//# sourceMappingURL=ChannelOps.js.map
|