@elizaos/plugin-x402 2.0.0-alpha.6 → 2.0.0-beta.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.
Files changed (39) hide show
  1. package/dist/index.d.ts +57 -2
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +30919 -1913
  4. package/dist/index.js.map +114 -21
  5. package/dist/payment-config.d.ts +256 -0
  6. package/dist/payment-config.d.ts.map +1 -0
  7. package/dist/payment-wrapper.d.ts +42 -0
  8. package/dist/payment-wrapper.d.ts.map +1 -0
  9. package/dist/startup-validator.d.ts +28 -0
  10. package/dist/startup-validator.d.ts.map +1 -0
  11. package/dist/types.d.ts +158 -0
  12. package/dist/types.d.ts.map +1 -0
  13. package/dist/x402-facilitator-binding.d.ts +9 -0
  14. package/dist/x402-facilitator-binding.d.ts.map +1 -0
  15. package/dist/x402-replay-durable.d.ts +30 -0
  16. package/dist/x402-replay-durable.d.ts.map +1 -0
  17. package/dist/x402-replay-guard.d.ts +28 -0
  18. package/dist/x402-replay-guard.d.ts.map +1 -0
  19. package/dist/x402-replay-keys.d.ts +21 -0
  20. package/dist/x402-replay-keys.d.ts.map +1 -0
  21. package/dist/x402-resolve.d.ts +6 -0
  22. package/dist/x402-resolve.d.ts.map +1 -0
  23. package/dist/x402-standard-payment.d.ts +130 -0
  24. package/dist/x402-standard-payment.d.ts.map +1 -0
  25. package/dist/x402-types.d.ts +130 -0
  26. package/dist/x402-types.d.ts.map +1 -0
  27. package/package.json +43 -94
  28. package/src/index.ts +113 -0
  29. package/src/payment-config.ts +737 -0
  30. package/src/payment-wrapper.ts +1991 -0
  31. package/src/startup-validator.ts +349 -0
  32. package/src/types.ts +177 -0
  33. package/src/x402-facilitator-binding.ts +104 -0
  34. package/src/x402-replay-durable.ts +320 -0
  35. package/src/x402-replay-guard.ts +165 -0
  36. package/src/x402-replay-keys.ts +151 -0
  37. package/src/x402-resolve.ts +43 -0
  38. package/src/x402-standard-payment.ts +519 -0
  39. package/src/x402-types.ts +376 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Startup validation for x402 payment system
3
+ * Validates payment configs and routes before the server starts
4
+ */
5
+
6
+ import type {
7
+ Character,
8
+ CharacterX402Settings,
9
+ PaymentEnabledRoute,
10
+ Route,
11
+ } from "@elizaos/core";
12
+ import { logger } from "@elizaos/core";
13
+ import {
14
+ BUILT_IN_NETWORKS,
15
+ getPaymentConfig,
16
+ getX402Health,
17
+ listX402Configs,
18
+ paymentAddressIsBundledExample,
19
+ } from "./payment-config.js";
20
+
21
+ /**
22
+ * Validation result with warnings and errors
23
+ */
24
+ export interface StartupValidationResult {
25
+ valid: boolean;
26
+ errors: string[];
27
+ warnings: string[];
28
+ }
29
+
30
+ /**
31
+ * Validate a payment config is properly configured
32
+ */
33
+ function validatePaymentConfig(
34
+ configName: string,
35
+ agentId?: string,
36
+ ): {
37
+ errors: string[];
38
+ warnings: string[];
39
+ } {
40
+ const errors: string[] = [];
41
+ const warnings: string[] = [];
42
+
43
+ try {
44
+ const config = getPaymentConfig(configName, agentId);
45
+
46
+ // Check required fields
47
+ if (!config.network) {
48
+ errors.push(`Config '${configName}': missing 'network'`);
49
+ }
50
+ if (!config.assetNamespace) {
51
+ errors.push(`Config '${configName}': missing 'assetNamespace'`);
52
+ }
53
+ if (!config.assetReference) {
54
+ errors.push(`Config '${configName}': missing 'assetReference'`);
55
+ }
56
+ if (!config.paymentAddress) {
57
+ errors.push(
58
+ `Config '${configName}': missing 'paymentAddress' (wallet address required)`,
59
+ );
60
+ }
61
+ if (!config.symbol) {
62
+ errors.push(`Config '${configName}': missing 'symbol'`);
63
+ }
64
+
65
+ // Validate address format
66
+ if (config.paymentAddress) {
67
+ // Solana addresses: base58, 32-44 chars
68
+ if (config.network === "SOLANA") {
69
+ if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(config.paymentAddress)) {
70
+ errors.push(`Config '${configName}': invalid Solana address format`);
71
+ }
72
+ }
73
+ // EVM addresses: 0x + 40 hex chars
74
+ else if (
75
+ config.network === "BASE" ||
76
+ config.network === "POLYGON" ||
77
+ config.assetNamespace === "erc20"
78
+ ) {
79
+ if (!/^0x[a-fA-F0-9]{40}$/.test(config.paymentAddress)) {
80
+ errors.push(
81
+ `Config '${configName}': invalid EVM address format (should be 0x...)`,
82
+ );
83
+ }
84
+ }
85
+
86
+ // Check if address looks like default/example
87
+ if (
88
+ config.paymentAddress === "0x0000000000000000000000000000000000000000"
89
+ ) {
90
+ warnings.push(
91
+ `Config '${configName}': using zero address (0x0...0) - is this intentional?`,
92
+ );
93
+ }
94
+ }
95
+
96
+ // Validate asset reference (contract address / token mint)
97
+ if (config.assetReference && config.assetNamespace === "erc20") {
98
+ if (!/^0x[a-fA-F0-9]{40}$/.test(config.assetReference)) {
99
+ errors.push(
100
+ `Config '${configName}': invalid ERC20 token address format`,
101
+ );
102
+ }
103
+ }
104
+
105
+ if (paymentAddressIsBundledExample(config.network, config.paymentAddress)) {
106
+ if (process.env.NODE_ENV === "production") {
107
+ errors.push(
108
+ `Config '${configName}': paymentAddress is the bundled dev example for ${config.network}. Set ${config.network}_PUBLIC_KEY or PAYMENT_WALLET_${config.network} to your payout wallet before production.`,
109
+ );
110
+ } else {
111
+ warnings.push(
112
+ `Config '${configName}': paymentAddress matches the bundled dev example for ${config.network} — set env payout keys for real settlement.`,
113
+ );
114
+ }
115
+ }
116
+
117
+ // Check if network is built-in (warn if custom)
118
+ if (
119
+ !(BUILT_IN_NETWORKS as readonly string[]).includes(
120
+ config.network as string,
121
+ )
122
+ ) {
123
+ warnings.push(
124
+ `Config '${configName}': using custom network '${config.network}' ` +
125
+ `(not in built-in networks: ${BUILT_IN_NETWORKS.join(", ")})`,
126
+ );
127
+ }
128
+ } catch (error) {
129
+ errors.push(
130
+ `Config '${configName}': ${error instanceof Error ? error.message : "unknown error"}`,
131
+ );
132
+ }
133
+
134
+ return { errors, warnings };
135
+ }
136
+
137
+ /**
138
+ * Validate an x402 route configuration
139
+ */
140
+ function validateX402Route(
141
+ route: Route,
142
+ character?: Character,
143
+ agentId?: string,
144
+ ): { errors: string[]; warnings: string[] } {
145
+ const errors: string[] = [];
146
+ const warnings: string[] = [];
147
+ const x402Route = route as PaymentEnabledRoute;
148
+
149
+ if (!route.path) {
150
+ errors.push(`Route missing 'path' property`);
151
+ return { errors, warnings };
152
+ }
153
+
154
+ const routePath = route.path;
155
+
156
+ if (x402Route.x402 == null) {
157
+ return { errors, warnings };
158
+ }
159
+
160
+ const cx = character?.settings?.x402 as CharacterX402Settings | undefined;
161
+ const raw = x402Route.x402;
162
+ let priceInCents: number | undefined;
163
+ let paymentConfigs: string[] | undefined;
164
+
165
+ if (raw === true) {
166
+ priceInCents = cx?.defaultPriceInCents;
167
+ paymentConfigs = cx?.defaultPaymentConfigs as string[] | undefined;
168
+ if (priceInCents == null) {
169
+ errors.push(
170
+ `${routePath}: x402: true requires character.settings.x402.defaultPriceInCents`,
171
+ );
172
+ }
173
+ if (!paymentConfigs?.length) {
174
+ errors.push(
175
+ `${routePath}: x402: true requires character.settings.x402.defaultPaymentConfigs (non-empty array)`,
176
+ );
177
+ }
178
+ } else if (typeof raw === "object") {
179
+ priceInCents = raw.priceInCents ?? cx?.defaultPriceInCents;
180
+ paymentConfigs = (raw.paymentConfigs ?? cx?.defaultPaymentConfigs) as
181
+ | string[]
182
+ | undefined;
183
+ if (priceInCents == null) {
184
+ errors.push(
185
+ `${routePath}: x402.priceInCents is required (or set character.settings.x402.defaultPriceInCents)`,
186
+ );
187
+ }
188
+ if (!paymentConfigs?.length) {
189
+ errors.push(
190
+ `${routePath}: x402.paymentConfigs is required (or set character.settings.x402.defaultPaymentConfigs)`,
191
+ );
192
+ }
193
+ }
194
+
195
+ if (priceInCents !== undefined && priceInCents !== null) {
196
+ if (typeof priceInCents !== "number") {
197
+ errors.push(`${routePath}: resolved x402.priceInCents must be a number`);
198
+ } else if (priceInCents <= 0) {
199
+ errors.push(`${routePath}: x402.priceInCents must be > 0`);
200
+ } else if (!Number.isInteger(priceInCents)) {
201
+ errors.push(`${routePath}: x402.priceInCents must be an integer (cents)`);
202
+ } else if (priceInCents > 10000) {
203
+ warnings.push(
204
+ `${routePath}: price is $${(priceInCents / 100).toFixed(2)} — is this intentional?`,
205
+ );
206
+ }
207
+ }
208
+
209
+ if (paymentConfigs && !Array.isArray(paymentConfigs)) {
210
+ errors.push(`${routePath}: x402.paymentConfigs must be an array`);
211
+ } else if (paymentConfigs?.length === 0) {
212
+ errors.push(`${routePath}: x402.paymentConfigs cannot be empty`);
213
+ } else if (paymentConfigs?.length) {
214
+ const availableConfigs = listX402Configs(agentId);
215
+ for (const configName of paymentConfigs) {
216
+ if (typeof configName !== "string") {
217
+ errors.push(
218
+ `${routePath}: x402.paymentConfigs contains non-string value`,
219
+ );
220
+ } else if (!availableConfigs.includes(configName)) {
221
+ errors.push(
222
+ `${routePath}: unknown payment config '${configName}'. Available: ${availableConfigs.join(", ")}`,
223
+ );
224
+ } else {
225
+ const configValidation = validatePaymentConfig(configName, agentId);
226
+ errors.push(
227
+ ...configValidation.errors.map((e) => `${routePath}: ${e}`),
228
+ );
229
+ warnings.push(
230
+ ...configValidation.warnings.map((w) => `${routePath}: ${w}`),
231
+ );
232
+ }
233
+ }
234
+ }
235
+
236
+ if (!route.handler) {
237
+ errors.push(
238
+ `${routePath}: route has x402 protection but no handler function`,
239
+ );
240
+ }
241
+
242
+ return { errors, warnings };
243
+ }
244
+
245
+ /**
246
+ * Validate environment configuration
247
+ */
248
+ function validateEnvironment(): { errors: string[]; warnings: string[] } {
249
+ const errors: string[] = [];
250
+ const warnings: string[] = [];
251
+
252
+ // Check network configuration
253
+ const health = getX402Health();
254
+
255
+ for (const network of health.networks) {
256
+ if (!network.configured || !network.address) {
257
+ warnings.push(
258
+ `Network '${network.network}' not configured. ` +
259
+ `Set ${network.network}_PUBLIC_KEY in .env to accept payments on this network.`,
260
+ );
261
+ }
262
+ }
263
+
264
+ // Check facilitator configuration (optional)
265
+ if (!health.facilitator.configured) {
266
+ warnings.push(
267
+ "X402_FACILITATOR_URL not set. Direct blockchain verification will be used. " +
268
+ "Consider setting up a facilitator for better UX.",
269
+ );
270
+ }
271
+
272
+ if (
273
+ process.env.NODE_ENV === "production" &&
274
+ (process.env.X402_TEST_MODE === "true" ||
275
+ process.env.X402_TEST_MODE === "1")
276
+ ) {
277
+ warnings.push(
278
+ "X402_TEST_MODE is set while NODE_ENV=production — clients can bypass payment verification; unset X402_TEST_MODE in production.",
279
+ );
280
+ }
281
+
282
+ return { errors, warnings };
283
+ }
284
+
285
+ /**
286
+ * Comprehensive startup validation
287
+ * Call this before starting the server to catch configuration issues early
288
+ */
289
+ export function validateX402Startup(
290
+ routes: Route[],
291
+ character?: Character,
292
+ options?: { agentId?: string },
293
+ ): StartupValidationResult {
294
+ const allErrors: string[] = [];
295
+ const allWarnings: string[] = [];
296
+
297
+ let protectedRouteCount = 0;
298
+ for (const route of routes) {
299
+ const x402Route = route as PaymentEnabledRoute;
300
+ if (x402Route.x402 != null) {
301
+ protectedRouteCount++;
302
+ const routeValidation = validateX402Route(
303
+ route,
304
+ character,
305
+ options?.agentId,
306
+ );
307
+ allErrors.push(...routeValidation.errors);
308
+ allWarnings.push(...routeValidation.warnings);
309
+ }
310
+ }
311
+
312
+ if (protectedRouteCount > 0) {
313
+ const envValidation = validateEnvironment();
314
+ allErrors.push(...envValidation.errors);
315
+ allWarnings.push(...envValidation.warnings);
316
+
317
+ logger.info(
318
+ `[x402] validated ${protectedRouteCount}/${routes.length} protected route(s); ` +
319
+ `configs=${listX402Configs(options?.agentId).length}, ` +
320
+ `errors=${allErrors.length}, warnings=${allWarnings.length}`,
321
+ );
322
+ }
323
+
324
+ return {
325
+ valid: allErrors.length === 0,
326
+ errors: allErrors,
327
+ warnings: allWarnings,
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Validate routes and throw if invalid
333
+ * This is used by applyPaymentProtection to fail fast on startup
334
+ */
335
+ export function validateAndThrowIfInvalid(
336
+ routes: Route[],
337
+ character?: Character,
338
+ options?: { agentId?: string },
339
+ ): void {
340
+ const result = validateX402Startup(routes, character, options);
341
+
342
+ if (!result.valid) {
343
+ throw new Error(
344
+ `x402 Configuration Invalid (${result.errors.length} error${result.errors.length > 1 ? "s" : ""}):\n\n` +
345
+ result.errors.map((e) => ` • ${e}`).join("\n") +
346
+ "\n\nPlease fix these errors and try again.",
347
+ );
348
+ }
349
+ }
package/src/types.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Strict TypeScript types for x402 payment middleware
3
+ * Replaces all 'any' types with proper interfaces
4
+ */
5
+
6
+ import type { AgentRuntime, RouteRequest } from "@elizaos/core";
7
+
8
+ /**
9
+ * Request shape for x402 (matches plugin routes + IncomingMessage headers)
10
+ */
11
+ export type X402Request = RouteRequest & {
12
+ method: string;
13
+ path: string;
14
+ headers: Record<string, string | string[] | undefined>;
15
+ query: Record<string, string | string[] | undefined>;
16
+ params: Record<string, string>;
17
+ };
18
+
19
+ /**
20
+ * Express-like response object
21
+ */
22
+ export interface X402Response {
23
+ status(code: number): X402ResponseStatus;
24
+ json(data: unknown): void;
25
+ setHeader?(name: string, value: string | readonly string[]): void;
26
+ headersSent?: boolean;
27
+ }
28
+
29
+ export interface X402ResponseStatus {
30
+ json(data: unknown): void;
31
+ }
32
+
33
+ /**
34
+ * EIP-712 Authorization data structure
35
+ */
36
+ export interface EIP712Authorization {
37
+ from: string;
38
+ to: string;
39
+ value: string;
40
+ validAfter: string;
41
+ validBefore: string;
42
+ nonce: string;
43
+ }
44
+
45
+ /**
46
+ * EIP-712 Domain structure
47
+ */
48
+ export interface EIP712Domain {
49
+ name: string;
50
+ version: string;
51
+ chainId: number;
52
+ verifyingContract: string;
53
+ }
54
+
55
+ // Export for use in payment-wrapper
56
+ export type {
57
+ EIP712Authorization as EIP712AuthorizationType,
58
+ EIP712Domain as EIP712DomainType,
59
+ };
60
+
61
+ /**
62
+ * Payment proof data (EIP-712 format)
63
+ */
64
+ export interface EIP712PaymentProof {
65
+ signature: string;
66
+ authorization: EIP712Authorization;
67
+ domain?: EIP712Domain;
68
+ network?: string;
69
+ scheme?: string;
70
+ // Alternative format with v, r, s
71
+ v?: number;
72
+ r?: string;
73
+ s?: string;
74
+ // Wrapped format from gateways
75
+ payload?: {
76
+ signature: string;
77
+ authorization: EIP712Authorization;
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Solana payment proof
83
+ */
84
+ export interface SolanaPaymentProof {
85
+ signature: string;
86
+ network: "SOLANA";
87
+ }
88
+
89
+ /**
90
+ * Legacy payment proof format
91
+ */
92
+ export interface LegacyPaymentProof {
93
+ network: string;
94
+ address: string;
95
+ signature: string;
96
+ }
97
+
98
+ /**
99
+ * Runtime interface with required methods for x402
100
+ * Uses IAgentRuntime directly to avoid type conflicts
101
+ */
102
+ export type X402Runtime = AgentRuntime;
103
+
104
+ /**
105
+ * Payment verification parameters (route price + allowed presets)
106
+ */
107
+ export interface PaymentVerificationParams {
108
+ paymentProof?: string;
109
+ paymentId?: string;
110
+ route: string;
111
+ /** Integer USD cents (same as route `x402.priceInCents`) */
112
+ priceInCents: number;
113
+ /** Names from `x402.paymentConfigs` (resolved), in declaration order */
114
+ paymentConfigNames: string[];
115
+ agentId?: string;
116
+ runtime: X402Runtime;
117
+ req?: X402Request;
118
+ }
119
+
120
+ /** Successful verification metadata for events / receipts */
121
+ export interface PaymentVerifiedDetails {
122
+ paymentConfig: string;
123
+ network: string;
124
+ /** Smallest units of the paid asset */
125
+ amountAtomic: string;
126
+ symbol?: string;
127
+ payer?: string;
128
+ proofId?: string;
129
+ paymentResponse?: string;
130
+ }
131
+
132
+ export type VerifyPaymentResult =
133
+ | { ok: false }
134
+ | { ok: true; details: PaymentVerifiedDetails };
135
+
136
+ /**
137
+ * Payment receipt for tracking
138
+ */
139
+ export interface PaymentReceipt {
140
+ paymentId: string;
141
+ route: string;
142
+ amount: string;
143
+ network: string;
144
+ timestamp: number;
145
+ signature?: string;
146
+ verified: boolean;
147
+ }
148
+
149
+ /**
150
+ * Facilitator verification response
151
+ */
152
+ export interface FacilitatorVerificationResponse {
153
+ valid?: boolean;
154
+ verified?: boolean;
155
+ status?: string;
156
+ message?: string;
157
+ /** When present, must equal the route’s `resource` URL we sent on verify */
158
+ resource?: string;
159
+ /** When present, must match the plugin route path */
160
+ routePath?: string;
161
+ /** Alias some facilitators use for path */
162
+ route?: string;
163
+ /** When present, must match the route’s `priceInCents` */
164
+ priceInCents?: number;
165
+ /** When present, must be one of the route’s allowed preset names */
166
+ paymentConfig?: string;
167
+ /** When present, every entry must be in the route’s allowlist */
168
+ paymentConfigs?: string[];
169
+ }
170
+
171
+ /** Sent to the facilitator so responses can be bound to a specific purchase */
172
+ export interface FacilitatorVerifyContext {
173
+ resource: string;
174
+ routePath: string;
175
+ priceInCents: number;
176
+ paymentConfigNames: string[];
177
+ }
@@ -0,0 +1,104 @@
1
+ import type {
2
+ FacilitatorVerificationResponse,
3
+ FacilitatorVerifyContext,
4
+ } from "./types.js";
5
+
6
+ /**
7
+ * When true, facilitator JSON must echo `resource`, route, `priceInCents`, and
8
+ * `paymentConfig` so a generic 200 cannot unlock unrelated routes.
9
+ * Set `X402_FACILITATOR_RELAXED_BINDING=1` if your facilitator does not return these fields yet.
10
+ */
11
+ export function isFacilitatorBindingRelaxed(): boolean {
12
+ return (
13
+ process.env.X402_FACILITATOR_RELAXED_BINDING === "true" ||
14
+ process.env.X402_FACILITATOR_RELAXED_BINDING === "1"
15
+ );
16
+ }
17
+
18
+ function relaxedPayloadMatchesContext(
19
+ data: FacilitatorVerificationResponse,
20
+ ctx: FacilitatorVerifyContext,
21
+ ): boolean {
22
+ if (typeof data.resource === "string" && data.resource !== ctx.resource) {
23
+ return false;
24
+ }
25
+ if (typeof data.routePath === "string" && data.routePath !== ctx.routePath) {
26
+ return false;
27
+ }
28
+ if (typeof data.route === "string" && data.route !== ctx.routePath) {
29
+ return false;
30
+ }
31
+ if (
32
+ typeof data.priceInCents === "number" &&
33
+ Number.isFinite(data.priceInCents) &&
34
+ data.priceInCents !== ctx.priceInCents
35
+ ) {
36
+ return false;
37
+ }
38
+ if (typeof data.paymentConfig === "string") {
39
+ if (!ctx.paymentConfigNames.includes(data.paymentConfig)) {
40
+ return false;
41
+ }
42
+ }
43
+ if (Array.isArray(data.paymentConfigs)) {
44
+ for (const n of data.paymentConfigs) {
45
+ if (typeof n === "string" && !ctx.paymentConfigNames.includes(n)) {
46
+ return false;
47
+ }
48
+ }
49
+ }
50
+ return true;
51
+ }
52
+
53
+ function strictPaymentConfigOk(
54
+ data: FacilitatorVerificationResponse,
55
+ ctx: FacilitatorVerifyContext,
56
+ ): boolean {
57
+ if (typeof data.paymentConfig === "string") {
58
+ return ctx.paymentConfigNames.includes(data.paymentConfig);
59
+ }
60
+ if (Array.isArray(data.paymentConfigs) && data.paymentConfigs.length > 0) {
61
+ const names = data.paymentConfigs.filter(
62
+ (x): x is string => typeof x === "string",
63
+ );
64
+ if (names.length === 0) return false;
65
+ return names.every((n) => ctx.paymentConfigNames.includes(n));
66
+ }
67
+ return false;
68
+ }
69
+
70
+ function strictPayloadMatchesContext(
71
+ data: FacilitatorVerificationResponse,
72
+ ctx: FacilitatorVerifyContext,
73
+ ): boolean {
74
+ if (typeof data.resource !== "string" || data.resource !== ctx.resource) {
75
+ return false;
76
+ }
77
+ const routeOk =
78
+ (typeof data.routePath === "string" && data.routePath === ctx.routePath) ||
79
+ (typeof data.route === "string" && data.route === ctx.routePath);
80
+ if (!routeOk) {
81
+ return false;
82
+ }
83
+ if (
84
+ typeof data.priceInCents !== "number" ||
85
+ !Number.isFinite(data.priceInCents) ||
86
+ data.priceInCents !== ctx.priceInCents
87
+ ) {
88
+ return false;
89
+ }
90
+ if (!strictPaymentConfigOk(data, ctx)) {
91
+ return false;
92
+ }
93
+ return true;
94
+ }
95
+
96
+ export function facilitatorVerifyResponseMatchesRoute(
97
+ data: FacilitatorVerificationResponse,
98
+ ctx: FacilitatorVerifyContext,
99
+ relaxed: boolean,
100
+ ): boolean {
101
+ return relaxed
102
+ ? relaxedPayloadMatchesContext(data, ctx)
103
+ : strictPayloadMatchesContext(data, ctx);
104
+ }