@m2msentinel/sdk 1.2.2 → 1.2.5

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,869 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Customer-owned Base Account wallet_sendCalls execution-identity boundary.
5
+ *
6
+ * This module is intentionally an adapter around the public SDK client. It
7
+ * observes each top-level wallet call, requires a caller-owned affirmative
8
+ * policy for every observation, and only then forwards the original detached
9
+ * JSON request to the caller's EIP-1193 provider. It is not a signer,
10
+ * bundler, paymaster, custody layer, or execution simulator.
11
+ *
12
+ * The accepted envelope is a deliberately restricted, fully specified
13
+ * EIP-5792 subset: version `1.0`, canonical wallet chainId `0x2105`, explicit
14
+ * `from`, and explicit `to`/`data` for every call. `atomicRequired` remains optional for
15
+ * compatibility with Base Account sponsor-gas clients; optional call values
16
+ * use canonical 0x quantities and are never rewritten for the provider.
17
+ */
18
+
19
+ const BASE_CHAIN_ID = 8453;
20
+ const BASE_CHAIN_HEX = '0x2105';
21
+ const BASE_NETWORK = 'eip155:8453';
22
+ const MAX_UINT256 = (2n ** 256n) - 1n;
23
+ const MAX_CALLS = 128;
24
+ const MAX_PREFLIGHT_CONCURRENCY = 4;
25
+ const MAX_RPC_OBSERVATION_CALLS = 4096;
26
+ const MAX_ID_BYTES = 4096;
27
+ const MAX_DATA_BYTES = 128 * 1024;
28
+ const MAX_DISSECTION_BYTECODE_BYTES = 2 * 1024 * 1024;
29
+ const MAX_JSON_DEPTH = 24;
30
+ const MAX_JSON_NODES = 8192;
31
+
32
+ const ADDRESS_RE = /^0x[0-9a-f]{40}$/i;
33
+ const DATA_RE = /^0x(?:[0-9a-f]{2})*$/i;
34
+ const BLOCK_HASH_RE = /^0x[0-9a-f]{64}$/i;
35
+ const BYTECODE_HASH_RE = /^sha256:[0-9a-f]{64}$/i;
36
+ const BLOCK_TAG_RE = /^0x(?:0|[1-9a-f][0-9a-f]*)$/i;
37
+ const SIMULATION_OUTCOMES = new Set(['NOT_ATTEMPTED', 'SUCCEEDED', 'REVERTED', 'UNAVAILABLE', 'MALFORMED']);
38
+ const RPC_STATE_BEARING_METHODS = new Set(['eth_getCode', 'eth_getStorageAt', 'eth_call']);
39
+ const RPC_ALLOWED_METHODS = new Set(RPC_STATE_BEARING_METHODS);
40
+ const RPC_ALLOWED_STATUSES = new Set(['OK', 'UNSUPPORTED_OPTIONAL_METHOD', 'EXECUTION_ERROR']);
41
+ const DIAMOND_FACETS_SELECTOR = '0x7a0ed627';
42
+ const DIAMOND_LOUPE_SUPPORTS_INTERFACE_DATA = `0x01ffc9a7${'48e2b093'.padStart(64, '0')}`;
43
+ const CAPABILITY_EXECUTION_ROLES = new Set(['RESOLVED_EXECUTING_CODE', 'SELECTED_DIAMOND_FACET']);
44
+ // These are public response trust labels, not private engine imports.
45
+ const TRUSTED_LEVELS = new Set(['HIGH_TRUST_PRIMARY', 'QUORUM_PUBLIC']);
46
+ const REQUEST_FIELDS = new Set(['method', 'params']);
47
+ const PARAM_FIELDS = new Set(['version', 'id', 'from', 'chainId', 'atomicRequired', 'calls', 'capabilities']);
48
+ const CALL_FIELDS = new Set(['to', 'data', 'value', 'capabilities', 'flowControl']);
49
+ const TRANSACTION_FIELDS = new Set(['chainId', 'to', 'data', 'from', 'value', 'blockNumber']);
50
+
51
+ class WalletSendCallsInputError extends TypeError {
52
+ constructor(field, message) {
53
+ super(`${field}: ${message}`);
54
+ this.name = 'WalletSendCallsInputError';
55
+ this.code = 'INVALID_WALLET_SEND_CALLS_INPUT';
56
+ this.field = field;
57
+ }
58
+ }
59
+
60
+ class ExecutionIdentityBlockedError extends Error {
61
+ constructor(code, message) {
62
+ super(message);
63
+ this.name = 'ExecutionIdentityBlockedError';
64
+ this.code = code;
65
+ }
66
+ }
67
+
68
+ // Short aliases retain the naming used by existing single-call examples.
69
+ const PreflightInputError = WalletSendCallsInputError;
70
+ const PreflightBlockedError = ExecutionIdentityBlockedError;
71
+
72
+ function hasOwn(value, key) {
73
+ return Object.prototype.hasOwnProperty.call(value, key);
74
+ }
75
+
76
+ function isPlainRecord(value) {
77
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
78
+ const prototype = Object.getPrototypeOf(value);
79
+ if (prototype === null) return true;
80
+ // Accept cross-realm ordinary objects (whose Object.prototype identity is
81
+ // different) while rejecting Object.create({ ... }) and class instances.
82
+ return Object.getPrototypeOf(prototype) === null && hasOwn(prototype, 'constructor') &&
83
+ typeof prototype.constructor === 'function' && prototype.constructor.name === 'Object';
84
+ }
85
+
86
+ function failInput(field, message) {
87
+ throw new WalletSendCallsInputError(field, message);
88
+ }
89
+
90
+ function assertJsonValue(value, field, state, depth = 0) {
91
+ if (depth > MAX_JSON_DEPTH) failInput(field, 'is nested too deeply');
92
+ state.nodes += 1;
93
+ if (state.nodes > MAX_JSON_NODES) failInput(field, 'contains too many nested values');
94
+
95
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
96
+ if (typeof value === 'number') {
97
+ if (!Number.isFinite(value)) failInput(field, 'must contain only finite JSON numbers');
98
+ return;
99
+ }
100
+ if (typeof value !== 'object' || (!isPlainRecord(value) && !Array.isArray(value))) {
101
+ failInput(field, 'must contain only JSON-compatible values');
102
+ }
103
+ if (state.stack.has(value)) failInput(field, 'must not contain circular references');
104
+ state.stack.add(value);
105
+ if (Array.isArray(value)) {
106
+ for (let index = 0; index < value.length; index += 1) {
107
+ if (!hasOwn(value, index)) failInput(`${field}[${index}]`, 'must not contain sparse entries');
108
+ assertJsonValue(value[index], `${field}[${index}]`, state, depth + 1);
109
+ }
110
+ } else {
111
+ for (const key of Object.keys(value)) {
112
+ if (value[key] === undefined) failInput(`${field}.${key}`, 'must not be undefined');
113
+ assertJsonValue(value[key], `${field}.${key}`, state, depth + 1);
114
+ }
115
+ }
116
+ state.stack.delete(value);
117
+ }
118
+
119
+ function cloneJson(value, field = 'request') {
120
+ const state = { nodes: 0, stack: new Set() };
121
+ assertJsonValue(value, field, state);
122
+ try {
123
+ const encoded = JSON.stringify(value);
124
+ if (encoded === undefined) failInput(field, 'must be JSON serializable');
125
+ return JSON.parse(encoded);
126
+ } catch (error) {
127
+ if (error instanceof WalletSendCallsInputError) throw error;
128
+ failInput(field, 'must be JSON serializable');
129
+ }
130
+ }
131
+
132
+ function parseQuantity(value, field, options = {}) {
133
+ let parsed;
134
+ if (typeof value === 'bigint') {
135
+ parsed = value;
136
+ } else if (typeof value === 'number' && options.allowNumber !== false) {
137
+ if (!Number.isSafeInteger(value) || value < 0) {
138
+ failInput(field, 'must be a non-negative safe integer or canonical quantity');
139
+ }
140
+ parsed = BigInt(value);
141
+ } else if (typeof value === 'string') {
142
+ const hex = /^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value);
143
+ const decimal = /^(?:0|[1-9][0-9]*)$/.test(value);
144
+ if (!hex && !decimal) failInput(field, 'must be a canonical decimal integer or 0x-prefixed quantity');
145
+ try {
146
+ parsed = BigInt(value);
147
+ } catch (_) {
148
+ failInput(field, 'is not a valid integer');
149
+ }
150
+ } else {
151
+ failInput(field, 'must be a non-negative integer');
152
+ }
153
+ if (parsed < 0n || parsed > MAX_UINT256) failInput(field, 'is outside the supported integer range');
154
+ return parsed;
155
+ }
156
+
157
+ function parseChainId(value, field = 'chainId') {
158
+ if (parseQuantity(value, field) !== BigInt(BASE_CHAIN_ID)) {
159
+ failInput(field, `must identify Base mainnet (${BASE_CHAIN_HEX} / ${BASE_CHAIN_ID})`);
160
+ }
161
+ return BASE_CHAIN_ID;
162
+ }
163
+
164
+ function normalizeAddress(value, field) {
165
+ if (typeof value !== 'string' || !ADDRESS_RE.test(value)) {
166
+ failInput(field, 'must be a 20-byte 0x-prefixed hexadecimal address');
167
+ }
168
+ return value.toLowerCase();
169
+ }
170
+
171
+ function validateData(value, field) {
172
+ if (typeof value !== 'string' || !DATA_RE.test(value)) {
173
+ failInput(field, 'must be an even-length 0x-prefixed hexadecimal byte string');
174
+ }
175
+ if ((value.length - 2) / 2 > MAX_DATA_BYTES) failInput(field, `must not exceed ${MAX_DATA_BYTES} bytes`);
176
+ }
177
+
178
+ function utf8ByteLength(value) {
179
+ if (typeof TextEncoder === 'function') return new TextEncoder().encode(value).length;
180
+ let length = 0;
181
+ for (let index = 0; index < value.length; index += 1) {
182
+ const code = value.charCodeAt(index);
183
+ if (code <= 0x7f) length += 1;
184
+ else if (code <= 0x7ff) length += 2;
185
+ else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length &&
186
+ value.charCodeAt(index + 1) >= 0xdc00 && value.charCodeAt(index + 1) <= 0xdfff) {
187
+ length += 4;
188
+ index += 1;
189
+ } else length += 3;
190
+ }
191
+ return length;
192
+ }
193
+
194
+ function validateId(value, field) {
195
+ if (typeof value !== 'string' || value.length === 0 || utf8ByteLength(value) > MAX_ID_BYTES) {
196
+ failInput(field, `must be a non-empty string of at most ${MAX_ID_BYTES} UTF-8 bytes`);
197
+ }
198
+ }
199
+
200
+ function validateCapabilityRecord(value, field) {
201
+ if (!isPlainRecord(value)) failInput(field, 'must be an object');
202
+ for (const [name, capability] of Object.entries(value)) {
203
+ if (!isPlainRecord(capability)) failInput(`${field}.${name}`, 'must be an object');
204
+ if (hasOwn(capability, 'optional') && typeof capability.optional !== 'boolean') {
205
+ failInput(`${field}.${name}.optional`, 'must be boolean when supplied');
206
+ }
207
+ }
208
+ if (hasOwn(value, 'paymasterService')) {
209
+ const paymaster = value.paymasterService;
210
+ if (!isPlainRecord(paymaster) || typeof paymaster.url !== 'string') {
211
+ failInput(`${field}.paymasterService`, 'must contain a URL string');
212
+ }
213
+ let url;
214
+ try {
215
+ url = new URL(paymaster.url);
216
+ } catch (_) {
217
+ failInput(`${field}.paymasterService.url`, 'must be a valid HTTPS URL');
218
+ }
219
+ if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {
220
+ failInput(`${field}.paymasterService.url`, 'must be an HTTPS URL without embedded credentials or query values');
221
+ }
222
+ }
223
+ }
224
+
225
+ function validateCall(call, index) {
226
+ const field = `params[0].calls[${index}]`;
227
+ if (!isPlainRecord(call)) failInput(field, 'must be an object');
228
+ for (const key of Object.keys(call)) {
229
+ if (!CALL_FIELDS.has(key)) failInput(`${field}.${key}`, 'is not a supported wallet_sendCalls field');
230
+ }
231
+ if (!hasOwn(call, 'to')) failInput(`${field}.to`, 'is required for transaction preflight');
232
+ if (!hasOwn(call, 'data')) failInput(`${field}.data`, 'is required for transaction preflight');
233
+ normalizeAddress(call.to, `${field}.to`);
234
+ validateData(call.data, `${field}.data`);
235
+ if (hasOwn(call, 'value')) {
236
+ if (typeof call.value !== 'string' || !/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(call.value)) {
237
+ failInput(`${field}.value`, 'must be a canonical 0x-prefixed quantity when supplied');
238
+ }
239
+ parseQuantity(call.value, `${field}.value`);
240
+ }
241
+ if (hasOwn(call, 'capabilities')) validateCapabilityRecord(call.capabilities, `${field}.capabilities`);
242
+ if (hasOwn(call, 'flowControl') && !isPlainRecord(call.flowControl)) {
243
+ failInput(`${field}.flowControl`, 'must be an object');
244
+ }
245
+ }
246
+
247
+ /** Return a detached, exact provider request after validating the documented shape. */
248
+ function validateWalletSendCallsRequest(request) {
249
+ if (!isPlainRecord(request)) failInput('request', 'must be an object');
250
+ for (const key of Object.keys(request)) {
251
+ if (!REQUEST_FIELDS.has(key)) failInput(`request.${key}`, 'is not a supported EIP-1193 field');
252
+ }
253
+ if (request.method !== 'wallet_sendCalls') failInput('request.method', 'must equal wallet_sendCalls');
254
+ if (!Array.isArray(request.params) || request.params.length !== 1) {
255
+ failInput('request.params', 'must contain exactly one wallet_sendCalls parameter object');
256
+ }
257
+
258
+ const params = request.params[0];
259
+ if (!isPlainRecord(params)) failInput('params[0]', 'must be an object');
260
+ for (const key of Object.keys(params)) {
261
+ if (!PARAM_FIELDS.has(key)) failInput(`params[0].${key}`, 'is not a supported wallet_sendCalls field');
262
+ }
263
+ if (params.version !== '1.0') {
264
+ failInput('params[0].version', 'must be the documented wallet_sendCalls version 1.0');
265
+ }
266
+ if (!hasOwn(params, 'chainId')) failInput('params[0].chainId', 'is required');
267
+ if (params.chainId !== BASE_CHAIN_HEX) {
268
+ failInput('params[0].chainId', 'must be the canonical Base mainnet wallet value 0x2105');
269
+ }
270
+ if (!hasOwn(params, 'from')) failInput('params[0].from', 'is required so execution identity is explicit');
271
+ normalizeAddress(params.from, 'params[0].from');
272
+ if (hasOwn(params, 'id')) validateId(params.id, 'params[0].id');
273
+ if (hasOwn(params, 'atomicRequired') && typeof params.atomicRequired !== 'boolean') {
274
+ failInput('params[0].atomicRequired', 'must be boolean when supplied');
275
+ }
276
+ if (!Array.isArray(params.calls) || params.calls.length === 0) failInput('params[0].calls', 'must be a non-empty array');
277
+ if (params.calls.length > MAX_CALLS) failInput('params[0].calls', `must contain at most ${MAX_CALLS} calls`);
278
+ for (let index = 0; index < params.calls.length; index += 1) validateCall(params.calls[index], index);
279
+ if (hasOwn(params, 'capabilities')) validateCapabilityRecord(params.capabilities, 'params[0].capabilities');
280
+ return cloneJson(request);
281
+ }
282
+
283
+ const normalizeWalletSendCallsRequest = validateWalletSendCallsRequest;
284
+
285
+ function transactionFromCall(params, call, index) {
286
+ if (!params || !call) failInput(`calls[${index}]`, 'must be present');
287
+ const transaction = {
288
+ chainId: params.chainId,
289
+ from: params.from,
290
+ to: call.to,
291
+ data: call.data
292
+ };
293
+ if (hasOwn(call, 'value')) transaction.value = call.value;
294
+ return transaction;
295
+ }
296
+
297
+ function toPreflightTransaction(params, call, index) {
298
+ return transactionFromCall(params, call, index);
299
+ }
300
+
301
+ function parseObservedQuantity(value) {
302
+ try {
303
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
304
+ if (typeof value === 'string' && /^(?:0|[1-9][0-9]*)$/.test(value)) return BigInt(value);
305
+ if (typeof value === 'string' && /^0x(?:0|[1-9a-f][0-9a-f]*)$/i.test(value)) return BigInt(value);
306
+ } catch (_) { /* treated as an observation mismatch */ }
307
+ return null;
308
+ }
309
+
310
+ function quantityToHex(value) {
311
+ return `0x${value.toString(16)}`;
312
+ }
313
+
314
+ function quantityToInput(value) {
315
+ return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : quantityToHex(value);
316
+ }
317
+
318
+ function normalizeAddressForObservation(value, field) {
319
+ if (typeof value !== 'string' || !ADDRESS_RE.test(value)) throw new Error(`${field} is not an address`);
320
+ return value.toLowerCase();
321
+ }
322
+
323
+ function normalizeDataForObservation(value, field) {
324
+ if (typeof value !== 'string' || !DATA_RE.test(value) || (value.length - 2) / 2 > MAX_DATA_BYTES) {
325
+ throw new Error(`${field} is not valid calldata`);
326
+ }
327
+ return value.toLowerCase();
328
+ }
329
+
330
+ function canonicalTransaction(value, field = 'transaction') {
331
+ if (!isPlainRecord(value)) throw new Error(`${field} is not an object`);
332
+ for (const key of Object.keys(value)) {
333
+ if (!TRANSACTION_FIELDS.has(key)) throw new Error(`${field}.${key} is unsupported`);
334
+ }
335
+ if (!hasOwn(value, 'chainId') || !hasOwn(value, 'to') || !hasOwn(value, 'data')) {
336
+ throw new Error(`${field} is missing a required field`);
337
+ }
338
+ if (parseObservedQuantity(value.chainId) !== BigInt(BASE_CHAIN_ID)) throw new Error(`${field}.chainId is not Base`);
339
+ const normalized = {
340
+ chainId: BASE_CHAIN_ID,
341
+ to: normalizeAddressForObservation(value.to, `${field}.to`),
342
+ data: normalizeDataForObservation(value.data, `${field}.data`)
343
+ };
344
+ if (hasOwn(value, 'from')) normalized.from = normalizeAddressForObservation(value.from, `${field}.from`);
345
+ if (hasOwn(value, 'value')) normalized.value = quantityToHex(parseObservedQuantity(value.value));
346
+ if (hasOwn(value, 'blockNumber')) {
347
+ const blockNumber = parseObservedQuantity(value.blockNumber);
348
+ if (blockNumber === null) throw new Error(`${field}.blockNumber is not a quantity`);
349
+ normalized.blockNumber = quantityToHex(blockNumber);
350
+ }
351
+ return normalized;
352
+ }
353
+
354
+ function sameJson(left, right) {
355
+ return JSON.stringify(left) === JSON.stringify(right);
356
+ }
357
+
358
+ function blockParts(observation) {
359
+ const block = observation && observation.observationBlock;
360
+ const blockTag = block && block.blockTag;
361
+ const parsedTag = typeof blockTag === 'string' && BLOCK_TAG_RE.test(blockTag) ? BigInt(blockTag) : null;
362
+ const parsedNumber = parseObservedQuantity(block && block.blockNumber);
363
+ return { block, blockTag, parsedTag, parsedNumber };
364
+ }
365
+
366
+ function blocked(code, message) {
367
+ throw new ExecutionIdentityBlockedError(code, message);
368
+ }
369
+
370
+ function isAddressForObservation(value) {
371
+ return typeof value === 'string' && ADDRESS_RE.test(value);
372
+ }
373
+
374
+ function isBoundedNonNegativeInteger(value) {
375
+ return Number.isSafeInteger(value) && value >= 0 && value <= MAX_RPC_OBSERVATION_CALLS;
376
+ }
377
+
378
+ function isKnownOptionalDiamondProbe(call) {
379
+ if (call.method !== 'eth_call' || !Array.isArray(call.params) || !isPlainRecord(call.params[0])) return false;
380
+ const transaction = call.params[0];
381
+ if (!isAddressForObservation(transaction.to) || typeof transaction.data !== 'string') return false;
382
+ const data = transaction.data.toLowerCase();
383
+ return data === DIAMOND_FACETS_SELECTOR || data === DIAMOND_LOUPE_SUPPORTS_INTERFACE_DATA;
384
+ }
385
+
386
+ function isQualifiedExecutionRevert(call) {
387
+ const evidence = call.failureEvidence;
388
+ const evidenceFields = new Set(['sampledProviders', 'failures', 'counts', 'optionalRevertQualified', 'qualification']);
389
+ const failureFields = new Set(['providerName', 'source', 'trustLevel', 'failureCode']);
390
+ if (call.method !== 'eth_call' || !TRUSTED_LEVELS.has(call.trustLevel) ||
391
+ typeof call.providerName !== 'string' || call.providerName.length === 0 ||
392
+ call.providerName === 'NONE_AVAILABLE' || !isBoundedNonNegativeInteger(call.agreement) ||
393
+ call.agreement < 1 || !isBoundedNonNegativeInteger(call.sampled) || call.sampled < call.agreement ||
394
+ !isPlainRecord(evidence) || Object.keys(evidence).some((key) => !evidenceFields.has(key)) ||
395
+ !hasOwn(evidence, 'optionalRevertQualified') || evidence.optionalRevertQualified !== true ||
396
+ !hasOwn(evidence, 'qualification') ||
397
+ !hasOwn(evidence, 'sampledProviders') || !hasOwn(evidence, 'failures') || !hasOwn(evidence, 'counts') ||
398
+ !Array.isArray(evidence.sampledProviders) || evidence.sampledProviders.length !== call.sampled ||
399
+ !evidence.sampledProviders.every((provider) => typeof provider === 'string' && provider.length > 0) ||
400
+ !Array.isArray(evidence.failures) || evidence.failures.length !== call.agreement ||
401
+ !evidence.failures.every((failure) => isPlainRecord(failure) &&
402
+ Object.keys(failure).length === failureFields.size &&
403
+ Object.keys(failure).every((key) => failureFields.has(key)) &&
404
+ hasOwn(failure, 'providerName') && typeof failure.providerName === 'string' && failure.providerName.length > 0 &&
405
+ hasOwn(failure, 'source') && hasOwn(failure, 'trustLevel') &&
406
+ hasOwn(failure, 'failureCode') && failure.failureCode === 'OPTIONAL_EXECUTION_REVERT') ||
407
+ !isPlainRecord(evidence.counts) || Object.keys(evidence.counts).length !== 1 ||
408
+ !hasOwn(evidence.counts, 'OPTIONAL_EXECUTION_REVERT') ||
409
+ evidence.counts.OPTIONAL_EXECUTION_REVERT !== call.agreement) {
410
+ return false;
411
+ }
412
+ const failureNames = evidence.failures.map((failure) => failure.providerName);
413
+ if (new Set(failureNames).size !== failureNames.length ||
414
+ !failureNames.every((name, index) => name === evidence.sampledProviders[index])) {
415
+ return false;
416
+ }
417
+ if (call.trustLevel === 'HIGH_TRUST_PRIMARY') {
418
+ return call.agreement === 1 && call.sampled === 1 && call.providerName === failureNames[0] &&
419
+ evidence.qualification === 'CREDENTIALLED_PROVIDER_REVERT' &&
420
+ evidence.failures.every((failure) => failure.source === 'CREDENTIALED' &&
421
+ failure.trustLevel === 'HIGH_TRUST_PRIMARY');
422
+ }
423
+ return call.agreement >= 2 && call.sampled === call.agreement &&
424
+ call.providerName === failureNames.join('+') &&
425
+ evidence.qualification === 'PUBLIC_REVERT_QUORUM' &&
426
+ evidence.failures.every((failure) => failure.source === 'PUBLIC' &&
427
+ failure.trustLevel === 'DEGRADED_LOW_TRUST');
428
+ }
429
+
430
+ function isLegacyUnsupportedOptionalProbe(call) {
431
+ return isKnownOptionalDiamondProbe(call) && call.trustLevel === 'DEGRADED_LOW_TRUST' &&
432
+ call.providerName === 'NONE_AVAILABLE' && call.agreement === 0 &&
433
+ isBoundedNonNegativeInteger(call.sampled) && !hasOwn(call, 'failureEvidence');
434
+ }
435
+
436
+ /** Validate one complete public preflight response without making a safety claim. */
437
+ function assertObservationReady(observation, transaction) {
438
+ if (!isPlainRecord(observation)) blocked('MALFORMED_OBSERVATION', 'The preflight response is not an object.');
439
+ if (observation.evidenceGrade !== true || observation.evidenceStatus !== 'VERIFIED') {
440
+ blocked('EVIDENCE_GRADE_FALSE', 'The preflight response is not decision-grade evidence.');
441
+ }
442
+ if (observation.status !== 'SUCCESS' || observation.operation !== 'TRANSACTION_PREFLIGHT_OBSERVATION') {
443
+ blocked('MALFORMED_OBSERVATION', 'The preflight response is not a successful transaction observation.');
444
+ }
445
+ if (observation.notASafetyGuarantee !== true || observation.reachability !== 'NOT_ESTABLISHED' ||
446
+ !Array.isArray(observation.limitations)) {
447
+ blocked('MALFORMED_OBSERVATION', 'The preflight response did not preserve Sentinel observation limitations.');
448
+ }
449
+ if (hasOwn(observation, 'conclusion') && observation.conclusion !== null) {
450
+ blocked('MALFORMED_OBSERVATION', 'The preflight response contains an unsupported conclusion.');
451
+ }
452
+
453
+ const { block, blockTag, parsedTag, parsedNumber } = blockParts(observation);
454
+ if (!block || block.status !== 'PINNED' || block.network !== BASE_NETWORK ||
455
+ parseObservedQuantity(block.chainId) !== BigInt(BASE_CHAIN_ID) ||
456
+ parsedTag === null || parsedNumber === null || parsedTag !== parsedNumber ||
457
+ typeof block.blockHash !== 'string' || !BLOCK_HASH_RE.test(block.blockHash)) {
458
+ blocked('OBSERVATION_MISMATCH', 'The preflight block identity is missing or internally inconsistent.');
459
+ }
460
+ if (!TRUSTED_LEVELS.has(observation.effectiveTrust) || !TRUSTED_LEVELS.has(block.trustLevel)) {
461
+ blocked('LOW_TRUST_OBSERVATION', 'The preflight response does not have a trusted Base observation level.');
462
+ }
463
+ if (observation.effectiveTrust === 'HIGH_TRUST_PRIMARY' && block.trustLevel !== 'HIGH_TRUST_PRIMARY') {
464
+ blocked('OBSERVATION_MISMATCH', 'The effective trust cannot exceed the observation block trust.');
465
+ }
466
+ if (!isPlainRecord(observation.bytecodeHashes) ||
467
+ typeof observation.bytecodeHashes.finalTargetBytecodeHash !== 'string' ||
468
+ !BYTECODE_HASH_RE.test(observation.bytecodeHashes.finalTargetBytecodeHash)) {
469
+ blocked('MALFORMED_OBSERVATION', 'The preflight response does not include a valid final target bytecode hash.');
470
+ }
471
+
472
+ let expectedTransaction;
473
+ let observedTransaction;
474
+ try {
475
+ expectedTransaction = canonicalTransaction(transaction, 'transaction');
476
+ observedTransaction = canonicalTransaction(observation.request, 'observation.request');
477
+ } catch (_) {
478
+ blocked('OBSERVATION_MISMATCH', 'The response request is missing or malformed.');
479
+ }
480
+ const expectedSelector = expectedTransaction.data.length >= 10
481
+ ? expectedTransaction.data.slice(0, 10).toLowerCase()
482
+ : null;
483
+ if (!sameJson(expectedTransaction, observedTransaction) || observation.selectorHex !== expectedSelector ||
484
+ typeof observation.surfaceTarget !== 'string' || observation.surfaceTarget.toLowerCase() !== expectedTransaction.to) {
485
+ blocked('OBSERVATION_MISMATCH', 'The observation is not bound to the exact requested call.');
486
+ }
487
+
488
+ if (!isAddressForObservation(observation.resolvedExecutionTarget) ||
489
+ !isPlainRecord(observation.resolution) || observation.resolution.status !== 'COMPLETE' ||
490
+ observation.resolution.resolutionComplete !== true) {
491
+ blocked('EXECUTION_TARGET_UNRESOLVED', 'The resolved execution target is incomplete.');
492
+ }
493
+
494
+ const simulation = observation.simulationObservation;
495
+ if (!isPlainRecord(simulation) || simulation.attempted !== true || simulation.trusted !== true ||
496
+ simulation.evidenceOnly !== true || !SIMULATION_OUTCOMES.has(simulation.outcome) ||
497
+ typeof simulation.blockTag !== 'string' || !BLOCK_TAG_RE.test(simulation.blockTag) ||
498
+ BigInt(simulation.blockTag) !== parsedTag) {
499
+ blocked('OBSERVATION_MISMATCH', 'The simulation observation is missing or not pinned to the same block.');
500
+ }
501
+
502
+ const rpc = observation.rpcObservation;
503
+ if (!isPlainRecord(rpc) || rpc.failedReadCount !== 0 || !Array.isArray(rpc.failures) ||
504
+ rpc.failures.length !== 0 || !Number.isSafeInteger(rpc.stateBearingCallCount) ||
505
+ rpc.stateBearingCallCount < 0 || rpc.stateBearingCallCount > MAX_RPC_OBSERVATION_CALLS ||
506
+ !Array.isArray(rpc.calls) || rpc.calls.length > MAX_RPC_OBSERVATION_CALLS ||
507
+ typeof rpc.pinnedBlockTag !== 'string' || !BLOCK_TAG_RE.test(rpc.pinnedBlockTag) ||
508
+ BigInt(rpc.pinnedBlockTag) !== parsedTag) {
509
+ blocked('OBSERVATION_MISMATCH', 'The RPC observation is missing, malformed, failed, or not pinned to the same block.');
510
+ }
511
+ let observedStateBearingCallCount = 0;
512
+ for (let index = 0; index < rpc.calls.length; index += 1) {
513
+ const call = rpc.calls[index];
514
+ if (!isPlainRecord(call)) {
515
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} is not an object.`);
516
+ }
517
+ if (!hasOwn(call, 'method') || typeof call.method !== 'string' || call.method.length === 0) {
518
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} has an invalid method.`);
519
+ }
520
+ if (!RPC_ALLOWED_METHODS.has(call.method)) {
521
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} uses an unsupported method.`);
522
+ }
523
+ if (!hasOwn(call, 'params') || !Array.isArray(call.params)) {
524
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} has invalid params.`);
525
+ }
526
+ if (!hasOwn(call, 'blockTag')) {
527
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} is missing its block tag.`);
528
+ }
529
+ const stateBearing = RPC_STATE_BEARING_METHODS.has(call.method);
530
+ if (stateBearing) observedStateBearingCallCount += 1;
531
+ if (stateBearing) {
532
+ if (typeof call.blockTag !== 'string' || !BLOCK_TAG_RE.test(call.blockTag) ||
533
+ BigInt(call.blockTag) !== parsedTag) {
534
+ blocked('OBSERVATION_MISMATCH', `The state-bearing RPC call at index ${index} is not pinned to the observation block.`);
535
+ }
536
+ } else if (call.blockTag !== null) {
537
+ blocked('OBSERVATION_MISMATCH', `The non-state RPC call at index ${index} has an unexpected block tag.`);
538
+ }
539
+ if (!hasOwn(call, 'status') || typeof call.status !== 'string' || !RPC_ALLOWED_STATUSES.has(call.status)) {
540
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} has a failure status.`);
541
+ }
542
+ const qualifiedExecutionRevert = call.status === 'EXECUTION_ERROR' && isQualifiedExecutionRevert(call);
543
+ const supportedOptionalAbsence = call.status === 'UNSUPPORTED_OPTIONAL_METHOD' &&
544
+ isKnownOptionalDiamondProbe(call) &&
545
+ (isQualifiedExecutionRevert(call) || isLegacyUnsupportedOptionalProbe(call));
546
+ if (call.status === 'OK') {
547
+ if (!hasOwn(call, 'trustLevel') || typeof call.trustLevel !== 'string' ||
548
+ !TRUSTED_LEVELS.has(call.trustLevel) ||
549
+ (observation.effectiveTrust === 'HIGH_TRUST_PRIMARY' && call.trustLevel !== 'HIGH_TRUST_PRIMARY')) {
550
+ blocked('LOW_TRUST_OBSERVATION', `The RPC observation call at index ${index} is not trusted.`);
551
+ }
552
+ } else if (!qualifiedExecutionRevert && !supportedOptionalAbsence) {
553
+ blocked('LOW_TRUST_OBSERVATION', `The non-success RPC observation call at index ${index} is not qualified evidence.`);
554
+ } else if (qualifiedExecutionRevert && observation.effectiveTrust === 'HIGH_TRUST_PRIMARY' &&
555
+ call.trustLevel !== 'HIGH_TRUST_PRIMARY') {
556
+ blocked('OBSERVATION_MISMATCH', `The RPC observation call at index ${index} is weaker than the reported effective trust.`);
557
+ }
558
+ if (hasOwn(call, 'ok') && call.ok !== true) {
559
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} is not successful.`);
560
+ }
561
+ if (hasOwn(call, 'error') || hasOwn(call, 'failure') || hasOwn(call, 'hashBoundFailure') ||
562
+ (hasOwn(call, 'failureEvidence') && !qualifiedExecutionRevert && !supportedOptionalAbsence)) {
563
+ blocked('MALFORMED_OBSERVATION', `The RPC observation call at index ${index} contains failure evidence.`);
564
+ }
565
+ }
566
+ if (rpc.stateBearingCallCount !== observedStateBearingCallCount) {
567
+ blocked('OBSERVATION_MISMATCH', 'The RPC state-bearing call count does not match the recorded calls.');
568
+ }
569
+
570
+ const capabilityEvidence = observation.capabilityEvidence;
571
+ if (!isPlainRecord(capabilityEvidence) ||
572
+ capabilityEvidence.transactionSelector !== observation.selectorHex ||
573
+ typeof capabilityEvidence.sourceAddress !== 'string' ||
574
+ !isAddressForObservation(capabilityEvidence.sourceAddress) ||
575
+ capabilityEvidence.sourceAddress.toLowerCase() !== observation.resolvedExecutionTarget.toLowerCase() ||
576
+ !CAPABILITY_EXECUTION_ROLES.has(capabilityEvidence.executionRole) ||
577
+ (observation.resolution.proxyType === 'EIP2535_DIAMOND'
578
+ ? capabilityEvidence.executionRole !== 'SELECTED_DIAMOND_FACET'
579
+ : capabilityEvidence.executionRole !== 'RESOLVED_EXECUTING_CODE') ||
580
+ capabilityEvidence.notASafetyGuarantee !== true ||
581
+ capabilityEvidence.reachability !== 'NOT_ESTABLISHED') {
582
+ blocked('OBSERVATION_MISMATCH', 'Capability evidence is missing or not bound to the requested execution identity.');
583
+ }
584
+ const dissection = capabilityEvidence.dissection;
585
+ if (!isPlainRecord(dissection) || dissection.isValidContract !== true ||
586
+ !Number.isSafeInteger(dissection.bytecodeSizeBytes) || dissection.bytecodeSizeBytes < 0 ||
587
+ dissection.bytecodeSizeBytes > MAX_DISSECTION_BYTECODE_BYTES ||
588
+ typeof dissection.targetBytecodeHash !== 'string' ||
589
+ !BYTECODE_HASH_RE.test(dissection.targetBytecodeHash) ||
590
+ dissection.targetBytecodeHash.toLowerCase() !== observation.bytecodeHashes.finalTargetBytecodeHash.toLowerCase() ||
591
+ !Array.isArray(dissection.capabilities) ||
592
+ !dissection.capabilities.every((capability) => isPlainRecord(capability) &&
593
+ typeof capability.type === 'string' && capability.type.length > 0) ||
594
+ !Array.isArray(dissection.detectedCapabilities) ||
595
+ !dissection.detectedCapabilities.every((capability) => typeof capability === 'string')) {
596
+ blocked('MALFORMED_OBSERVATION', 'Capability evidence dissection is missing or malformed.');
597
+ }
598
+
599
+ if (observation.resolution.proxyType === 'EIP2535_DIAMOND') {
600
+ const selector = observation.selectorHex;
601
+ const diamond = observation.resolution.diamond;
602
+ const selectedFacet = diamond && diamond.selectedFacet;
603
+ const selected = diamond && Array.isArray(diamond.facets)
604
+ ? diamond.facets.find((facet) => isPlainRecord(facet) &&
605
+ typeof facet.address === 'string' && facet.address.toLowerCase() === String(selectedFacet).toLowerCase())
606
+ : null;
607
+ const selectedFacetHash = selected && selected.runtimeBytecodeHash;
608
+ const facetBytecodeHash = observation.bytecodeHashes.facetBytecodeHash;
609
+ const finalTargetBytecodeHash = observation.bytecodeHashes.finalTargetBytecodeHash;
610
+ if (!selector || !isAddressForObservation(selectedFacet) ||
611
+ selectedFacet.toLowerCase() !== observation.resolvedExecutionTarget.toLowerCase() ||
612
+ !selected || !Array.isArray(selected.selectors) ||
613
+ !selected.selectors.some((item) => typeof item === 'string' && item.toLowerCase() === selector) ||
614
+ typeof selectedFacetHash !== 'string' || !BYTECODE_HASH_RE.test(selectedFacetHash) ||
615
+ typeof facetBytecodeHash !== 'string' || !BYTECODE_HASH_RE.test(facetBytecodeHash) ||
616
+ typeof finalTargetBytecodeHash !== 'string' || !BYTECODE_HASH_RE.test(finalTargetBytecodeHash) ||
617
+ selectedFacetHash.toLowerCase() !== facetBytecodeHash.toLowerCase() ||
618
+ selectedFacetHash.toLowerCase() !== finalTargetBytecodeHash.toLowerCase()) {
619
+ blocked('DIAMOND_FACET_MAPPING_MISSING', 'The supplied selector has no complete Diamond facet mapping.');
620
+ }
621
+ }
622
+
623
+ return {
624
+ blockNumber: parsedNumber,
625
+ blockTag: quantityToHex(parsedNumber),
626
+ blockHash: block.blockHash.toLowerCase(),
627
+ effectiveTrust: observation.effectiveTrust,
628
+ blockTrustLevel: block.trustLevel,
629
+ finalTargetBytecodeHash: observation.bytecodeHashes.finalTargetBytecodeHash.toLowerCase()
630
+ };
631
+ }
632
+
633
+ function assertCoherentBatchIdentity(observation, anchor, index) {
634
+ const identity = assertObservationIdentity(observation);
635
+ if (identity.blockNumber !== anchor.blockNumber || identity.blockHash !== anchor.blockHash) {
636
+ blocked('OBSERVATION_MISMATCH', `Call ${index} was observed at a different Base block identity.`);
637
+ }
638
+ if (identity.effectiveTrust !== anchor.effectiveTrust || identity.blockTrustLevel !== anchor.blockTrustLevel) {
639
+ blocked('OBSERVATION_MISMATCH', `Call ${index} was observed at a different trust level.`);
640
+ }
641
+ return identity;
642
+ }
643
+
644
+ function assertObservationIdentity(observation) {
645
+ const { block, parsedTag, parsedNumber } = blockParts(observation);
646
+ if (!block || parsedTag === null || parsedNumber === null || parsedTag !== parsedNumber ||
647
+ typeof block.blockHash !== 'string' || !BLOCK_HASH_RE.test(block.blockHash)) {
648
+ blocked('OBSERVATION_MISMATCH', 'The preflight block identity is missing or internally inconsistent.');
649
+ }
650
+ return {
651
+ blockNumber: parsedNumber,
652
+ blockHash: block.blockHash.toLowerCase(),
653
+ effectiveTrust: observation.effectiveTrust,
654
+ blockTrustLevel: block.trustLevel
655
+ };
656
+ }
657
+
658
+ function isAffirmativePolicyResult(value) {
659
+ if (value === true) return true;
660
+ if (!isPlainRecord(value)) return false;
661
+ const keys = Object.keys(value);
662
+ return hasOwn(value, 'allow') &&
663
+ !keys.some((key) => key !== 'allow' && key !== 'reason') &&
664
+ value.allow === true &&
665
+ (!hasOwn(value, 'reason') || typeof value.reason === 'string');
666
+ }
667
+
668
+ function policyRejectionReason(value) {
669
+ if (isPlainRecord(value) && hasOwn(value, 'reason') &&
670
+ typeof value.reason === 'string' && value.reason.length > 0) {
671
+ return value.reason.slice(0, 256);
672
+ }
673
+ return 'caller-owned policy did not return true or { allow: true }';
674
+ }
675
+
676
+ function assertClient(client) {
677
+ if (!client || typeof client.preflightTransaction !== 'function') {
678
+ throw new TypeError('A public client with preflightTransaction(transaction) is required.');
679
+ }
680
+ }
681
+
682
+ function assertProvider(provider) {
683
+ if (!provider || typeof provider.request !== 'function') {
684
+ throw new TypeError('A caller-supplied EIP-1193 provider with request(request) is required.');
685
+ }
686
+ }
687
+
688
+ async function preflightOne(client, transaction, index) {
689
+ const response = await client.preflightTransaction(cloneJson(transaction, `calls[${index}]`));
690
+ return cloneJson(response, `observations[${index}]`);
691
+ }
692
+
693
+ /**
694
+ * Preflight the anchor, then process remaining calls in bounded waves anchored
695
+ * to the same trusted block. Evaluate one explicit caller policy per call in
696
+ * request order, and only then invoke the provider once.
697
+ */
698
+ async function guardWalletSendCalls({ client, provider, request, policy, context } = {}) {
699
+ assertClient(client);
700
+ assertProvider(provider);
701
+ if (typeof policy !== 'function') throw new TypeError('An explicit caller-owned policy function is required.');
702
+
703
+ // Snapshot synchronously before any caller-owned asynchronous participant.
704
+ const requestSnapshot = validateWalletSendCallsRequest(request);
705
+ const paramsSnapshot = requestSnapshot.params[0];
706
+ const transactions = paramsSnapshot.calls.map((call, index) => toPreflightTransaction(paramsSnapshot, call, index));
707
+
708
+ let callerContext = {};
709
+ if (context !== undefined) {
710
+ callerContext = cloneJson(context, 'context');
711
+ if (!isPlainRecord(callerContext)) throw new TypeError('context must be a JSON object when supplied.');
712
+ }
713
+
714
+ const preflightTransactions = transactions.slice();
715
+
716
+ function policyContextFor(index) {
717
+ return {
718
+ ...cloneJson(callerContext, 'context'),
719
+ callIndex: index,
720
+ index,
721
+ call: cloneJson(paramsSnapshot.calls[index], `params[0].calls[${index}]`),
722
+ transaction: cloneJson(preflightTransactions[index], `calls[${index}]`),
723
+ request: cloneJson(requestSnapshot, 'request'),
724
+ params: cloneJson(paramsSnapshot, 'params[0]')
725
+ };
726
+ }
727
+
728
+ async function evaluatePolicy(index, observation) {
729
+ const policyResult = await policy(cloneJson(observation, `observations[${index}]`), policyContextFor(index));
730
+ if (!isAffirmativePolicyResult(policyResult)) {
731
+ blocked('CALLER_POLICY_REJECTED', `Call ${index} was rejected: ${policyRejectionReason(policyResult)}`);
732
+ }
733
+ }
734
+
735
+ // The first request selects the only permitted block identity for this
736
+ // wallet_sendCalls batch. Its caller policy is evaluated before any
737
+ // remaining request starts, so an anchor rejection spends one observation.
738
+ const firstObservation = await preflightOne(client, preflightTransactions[0], 0);
739
+ const anchor = assertObservationReady(firstObservation, preflightTransactions[0]);
740
+ await evaluatePolicy(0, firstObservation);
741
+
742
+ for (let index = 1; index < preflightTransactions.length; index += 1) {
743
+ preflightTransactions[index] = {
744
+ ...preflightTransactions[index],
745
+ blockNumber: quantityToInput(anchor.blockNumber)
746
+ };
747
+ }
748
+
749
+ // Schedule bounded waves rather than a continuously draining worker pool.
750
+ // Every request in one wave is allowed to settle, then its observations and
751
+ // policies are processed in ascending call-index order. A failed wave never
752
+ // schedules a later wave, so a rejected batch can consume at most
753
+ // MAX_PREFLIGHT_CONCURRENCY additional preflight requests after the anchor.
754
+ let nextIndex = 1;
755
+ while (nextIndex < preflightTransactions.length) {
756
+ const waveIndexes = [];
757
+ while (waveIndexes.length < MAX_PREFLIGHT_CONCURRENCY && nextIndex < preflightTransactions.length) {
758
+ waveIndexes.push(nextIndex);
759
+ nextIndex += 1;
760
+ }
761
+
762
+ const waveResults = await Promise.all(waveIndexes.map(async (index) => {
763
+ try {
764
+ return {
765
+ index,
766
+ observation: await preflightOne(client, preflightTransactions[index], index)
767
+ };
768
+ } catch (error) {
769
+ return { index, error };
770
+ }
771
+ }));
772
+ waveResults.sort((left, right) => left.index - right.index);
773
+
774
+ for (const result of waveResults) {
775
+ if (result.error) throw result.error;
776
+ const { index, observation } = result;
777
+ assertObservationReady(observation, preflightTransactions[index]);
778
+ assertCoherentBatchIdentity(observation, anchor, index);
779
+ await evaluatePolicy(index, observation);
780
+ }
781
+ }
782
+
783
+ // No observation or policy-owned object is reused. The provider receives the
784
+ // original exact request shape, without the internal block pinning field.
785
+ return provider.request(cloneJson(requestSnapshot, 'request'));
786
+ }
787
+
788
+ const preflightWalletSendCalls = guardWalletSendCalls;
789
+ const sendCallsWithPreflight = guardWalletSendCalls;
790
+ const executeWalletSendCalls = guardWalletSendCalls;
791
+
792
+ class BaseAccountPaymasterGuard {
793
+ constructor({ client, policy } = {}) {
794
+ assertClient(client);
795
+ if (typeof policy !== 'function') throw new TypeError('An explicit caller-owned policy function is required.');
796
+ this.client = client;
797
+ this.policy = policy;
798
+ }
799
+
800
+ request(provider, request, options = {}) {
801
+ return guardWalletSendCalls({
802
+ client: this.client,
803
+ provider,
804
+ request,
805
+ policy: options.policy === undefined ? this.policy : options.policy,
806
+ context: options.context
807
+ });
808
+ }
809
+
810
+ sendCalls(provider, request, options = {}) {
811
+ return this.request(provider, request, options);
812
+ }
813
+ }
814
+
815
+ const BaseAccountExecutionGuard = BaseAccountPaymasterGuard;
816
+
817
+ /** Construct the public SDK client while keeping credentials caller-owned. */
818
+ function createPublicClient({ baseUrl, apiKey, timeoutMs, sdk } = {}) {
819
+ if (typeof apiKey !== 'string' || apiKey.length === 0) {
820
+ throw new TypeError('apiKey must be supplied by the caller secret manager.');
821
+ }
822
+ if (baseUrl !== undefined) {
823
+ if (typeof baseUrl !== 'string') throw new TypeError('baseUrl must be a valid HTTP(S) URL.');
824
+ let parsed;
825
+ try { parsed = new URL(baseUrl); } catch (_) { throw new TypeError('baseUrl must be a valid HTTP(S) URL.'); }
826
+ const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname.toLowerCase());
827
+ if ((parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback)) ||
828
+ parsed.username || parsed.password || parsed.search || parsed.hash) {
829
+ throw new TypeError('baseUrl must use HTTPS (HTTP is allowed only for loopback tests) without credentials or query values.');
830
+ }
831
+ }
832
+ const publicSdk = sdk || require('./index.js');
833
+ if (!publicSdk || typeof publicSdk.M2MSentinelClient !== 'function') {
834
+ throw new TypeError('The public SDK must expose M2MSentinelClient.');
835
+ }
836
+ const options = { apiKey };
837
+ if (baseUrl !== undefined) options.baseUrl = baseUrl;
838
+ if (timeoutMs !== undefined) options.timeoutMs = timeoutMs;
839
+ return new publicSdk.M2MSentinelClient(options);
840
+ }
841
+
842
+ module.exports = {
843
+ BASE_CHAIN_ID,
844
+ BASE_CHAIN_HEX,
845
+ BASE_NETWORK,
846
+ MAX_CALLS,
847
+ MAX_PREFLIGHT_CONCURRENCY,
848
+ MAX_RPC_OBSERVATION_CALLS,
849
+ MAX_DATA_BYTES,
850
+ WalletSendCallsInputError,
851
+ ExecutionIdentityBlockedError,
852
+ PreflightInputError,
853
+ PreflightBlockedError,
854
+ isAffirmativePolicyResult,
855
+ validateWalletSendCallsRequest,
856
+ normalizeWalletSendCallsRequest,
857
+ transactionFromCall,
858
+ toPreflightTransaction,
859
+ assertObservationReady,
860
+ guardWalletSendCalls,
861
+ preflightWalletSendCalls,
862
+ sendCallsWithPreflight,
863
+ executeWalletSendCalls,
864
+ BaseAccountPaymasterGuard,
865
+ BaseAccountExecutionGuard,
866
+ createBaseAccountPaymasterGuard: (options) => new BaseAccountPaymasterGuard(options),
867
+ createExecutionIdentityGuard: (options) => new BaseAccountPaymasterGuard(options),
868
+ createPublicClient
869
+ };