@naculus/connect-appkit-core 0.2.5 → 0.2.7
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/dist/index.cjs +786 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +249 -5
- package/dist/index.d.ts +249 -5
- package/dist/index.js +759 -10
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -141,6 +141,32 @@ function writeDecimals(cache, token, value) {
|
|
|
141
141
|
cache.value = value;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// src/erc20-calldata.ts
|
|
145
|
+
import { isChecksumAddress } from "@naculus/connect-core";
|
|
146
|
+
var UINT256_MAX = (1n << 256n) - 1n;
|
|
147
|
+
var EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
148
|
+
function addressWord(address) {
|
|
149
|
+
if (typeof address !== "string" || !EVM_ADDRESS.test(address) || address !== address.toLowerCase() && !isChecksumAddress(address)) {
|
|
150
|
+
throw new TypeError("Invalid EVM address checksum");
|
|
151
|
+
}
|
|
152
|
+
return address.slice(2).toLowerCase().padStart(64, "0");
|
|
153
|
+
}
|
|
154
|
+
function uint256Word(value) {
|
|
155
|
+
if (typeof value !== "bigint" || value < 0n || value > UINT256_MAX) {
|
|
156
|
+
throw new RangeError("Amount must be a uint256 bigint");
|
|
157
|
+
}
|
|
158
|
+
return value.toString(16).padStart(64, "0");
|
|
159
|
+
}
|
|
160
|
+
function encodeErc20Transfer(to, amount) {
|
|
161
|
+
return `0xa9059cbb${addressWord(to)}${uint256Word(amount)}`;
|
|
162
|
+
}
|
|
163
|
+
function encodeErc20Approve(spender, amount) {
|
|
164
|
+
return `0x095ea7b3${addressWord(spender)}${uint256Word(amount)}`;
|
|
165
|
+
}
|
|
166
|
+
function encodeErc20TransferFrom(from, to, amount) {
|
|
167
|
+
return `0x23b872dd${addressWord(from)}${addressWord(to)}${uint256Word(amount)}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
144
170
|
// src/provider-errors.ts
|
|
145
171
|
import { WalletError as WalletError2 } from "@naculus/connect-core";
|
|
146
172
|
var USER_REJECTED = 4001;
|
|
@@ -214,6 +240,173 @@ function resolveRevertReason(data, fallbackMessage) {
|
|
|
214
240
|
return decodeRevertReason(data) ?? (fallbackMessage.includes("revert") ? fallbackMessage : "Transaction reverted");
|
|
215
241
|
}
|
|
216
242
|
|
|
243
|
+
// src/simulation-endpoint.ts
|
|
244
|
+
import { WalletError as WalletError3 } from "@naculus/connect-core";
|
|
245
|
+
function resolveSimulationEndpoint(requestedChainId, fallbackChainId, requestedRpcUrl, fallbackRpcUrl) {
|
|
246
|
+
const chainId = requestedChainId ?? fallbackChainId;
|
|
247
|
+
if (chainId === void 0) {
|
|
248
|
+
throw new WalletError3(
|
|
249
|
+
"invalid_chain",
|
|
250
|
+
"No chain to simulate on. Connect a wallet or pass a chainId."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (requestedChainId !== void 0 && fallbackChainId !== void 0 && requestedChainId !== fallbackChainId && requestedRpcUrl === void 0) {
|
|
254
|
+
throw new WalletError3(
|
|
255
|
+
"invalid_chain",
|
|
256
|
+
"Simulation chain override requires an RPC URL for that chain."
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return { chainId, rpcUrl: requestedRpcUrl ?? fallbackRpcUrl };
|
|
260
|
+
}
|
|
261
|
+
function resolveContextSimulationEndpoint(requestedChainId, currentChain) {
|
|
262
|
+
const contextChainId = currentChain ? chainNumber(currentChain) ?? void 0 : void 0;
|
|
263
|
+
if (requestedChainId !== void 0 && currentChain && requestedChainId !== contextChainId) {
|
|
264
|
+
throw new WalletError3(
|
|
265
|
+
"invalid_chain",
|
|
266
|
+
"Simulation chain does not match the connected chain endpoint."
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return resolveSimulationEndpoint(
|
|
270
|
+
requestedChainId,
|
|
271
|
+
contextChainId,
|
|
272
|
+
void 0,
|
|
273
|
+
currentChain?.rpcUrl
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/transaction-simulation.ts
|
|
278
|
+
import { WalletError as WalletError4 } from "@naculus/connect-core";
|
|
279
|
+
var coverage = {
|
|
280
|
+
balanceChanges: false,
|
|
281
|
+
approvalChanges: false,
|
|
282
|
+
risk: false
|
|
283
|
+
};
|
|
284
|
+
function baseResult(status, summary, changesDetected, warnings = []) {
|
|
285
|
+
return {
|
|
286
|
+
status,
|
|
287
|
+
coverage,
|
|
288
|
+
balanceChanges: [],
|
|
289
|
+
approvalChanges: [],
|
|
290
|
+
riskAssessment: { level: "unknown", score: 0, warnings },
|
|
291
|
+
provider: "eth_call",
|
|
292
|
+
summary,
|
|
293
|
+
changesDetected
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function simulateTransactionPreview(tx, context) {
|
|
297
|
+
if (!tx)
|
|
298
|
+
return baseResult("unavailable", "No transaction to simulate", false);
|
|
299
|
+
const { chainId, rpcUrl } = resolveContextSimulationEndpoint(
|
|
300
|
+
context.chainId,
|
|
301
|
+
context.currentChain
|
|
302
|
+
);
|
|
303
|
+
const from = context.evmAccount?.includes(":") ? context.evmAccount.split(":").pop() : context.evmAccount;
|
|
304
|
+
if (context.publicClient) {
|
|
305
|
+
if (context.publicClient.chain?.id !== chainId) {
|
|
306
|
+
throw new WalletError4(
|
|
307
|
+
"invalid_chain",
|
|
308
|
+
"Simulation client chain does not match the requested chain."
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
await context.publicClient.call({
|
|
313
|
+
to: tx.to,
|
|
314
|
+
data: tx.data ?? "0x",
|
|
315
|
+
value: tx.value ? BigInt(tx.value) : 0n,
|
|
316
|
+
...from ? { from } : {}
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
...baseResult(
|
|
320
|
+
"success",
|
|
321
|
+
"Transaction simulation succeeded (basic revert check only)",
|
|
322
|
+
true
|
|
323
|
+
),
|
|
324
|
+
gasInfo: tx.gas ? { gasLimit: BigInt(tx.gas), estimatedFeeEth: "0" } : void 0
|
|
325
|
+
};
|
|
326
|
+
} catch (cause) {
|
|
327
|
+
const error = cause;
|
|
328
|
+
const reason = resolveRevertReason(
|
|
329
|
+
error?.data ?? error?.cause?.data,
|
|
330
|
+
error?.message ?? String(cause)
|
|
331
|
+
);
|
|
332
|
+
return {
|
|
333
|
+
...baseResult(
|
|
334
|
+
"reverted",
|
|
335
|
+
reason ? `Transaction reverted: ${reason}` : "Transaction reverted",
|
|
336
|
+
false,
|
|
337
|
+
[
|
|
338
|
+
{
|
|
339
|
+
category: "simulation_failed",
|
|
340
|
+
severity: "high",
|
|
341
|
+
message: reason ? `Transaction would revert: ${reason}` : "Transaction would revert"
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
),
|
|
345
|
+
revertReason: reason
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!rpcUrl) {
|
|
350
|
+
return baseResult(
|
|
351
|
+
"unavailable",
|
|
352
|
+
"Simulation unavailable: no RPC URL",
|
|
353
|
+
false,
|
|
354
|
+
[
|
|
355
|
+
{
|
|
356
|
+
category: "simulation_failed",
|
|
357
|
+
severity: "low",
|
|
358
|
+
message: "No RPC URL or public client available for simulation"
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const response = await (context.fetcher ?? fetch)(rpcUrl, {
|
|
365
|
+
method: "POST",
|
|
366
|
+
headers: { "Content-Type": "application/json" },
|
|
367
|
+
body: JSON.stringify({
|
|
368
|
+
jsonrpc: "2.0",
|
|
369
|
+
id: Date.now(),
|
|
370
|
+
method: "eth_call",
|
|
371
|
+
params: [
|
|
372
|
+
{
|
|
373
|
+
to: tx.to,
|
|
374
|
+
from,
|
|
375
|
+
data: tx.data ?? "0x",
|
|
376
|
+
value: tx.value ?? "0x0"
|
|
377
|
+
},
|
|
378
|
+
"latest"
|
|
379
|
+
]
|
|
380
|
+
})
|
|
381
|
+
});
|
|
382
|
+
const json = await response.json();
|
|
383
|
+
if (json.error) {
|
|
384
|
+
return baseResult("reverted", "Transaction reverted", false, [
|
|
385
|
+
{
|
|
386
|
+
category: "simulation_failed",
|
|
387
|
+
severity: "high",
|
|
388
|
+
message: `Transaction would revert: ${json.error.message}`
|
|
389
|
+
}
|
|
390
|
+
]);
|
|
391
|
+
}
|
|
392
|
+
return baseResult("success", "Transaction simulation succeeded", true);
|
|
393
|
+
} catch (cause) {
|
|
394
|
+
const error = cause;
|
|
395
|
+
return baseResult(
|
|
396
|
+
"unavailable",
|
|
397
|
+
"Simulation unavailable due to network error",
|
|
398
|
+
false,
|
|
399
|
+
[
|
|
400
|
+
{
|
|
401
|
+
category: "simulation_failed",
|
|
402
|
+
severity: "medium",
|
|
403
|
+
message: `Network error: ${error?.message ?? "Unknown"}`
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
217
410
|
// src/name-resolver.ts
|
|
218
411
|
import { NameResolver } from "@naculus/connect-core";
|
|
219
412
|
var sharedResolver = null;
|
|
@@ -246,7 +439,10 @@ function createNotificationSettingsStorage() {
|
|
|
246
439
|
try {
|
|
247
440
|
return await storage.get(key);
|
|
248
441
|
} catch (cause) {
|
|
249
|
-
console.warn(
|
|
442
|
+
console.warn(
|
|
443
|
+
"notification settings: failed to read from storage:",
|
|
444
|
+
cause
|
|
445
|
+
);
|
|
250
446
|
return null;
|
|
251
447
|
}
|
|
252
448
|
},
|
|
@@ -254,14 +450,20 @@ function createNotificationSettingsStorage() {
|
|
|
254
450
|
try {
|
|
255
451
|
await storage.set(key, value);
|
|
256
452
|
} catch (cause) {
|
|
257
|
-
console.warn(
|
|
453
|
+
console.warn(
|
|
454
|
+
"notification settings: failed to write to storage:",
|
|
455
|
+
cause
|
|
456
|
+
);
|
|
258
457
|
}
|
|
259
458
|
},
|
|
260
459
|
removeItem: async (key) => {
|
|
261
460
|
try {
|
|
262
461
|
await storage.remove(key);
|
|
263
462
|
} catch (cause) {
|
|
264
|
-
console.warn(
|
|
463
|
+
console.warn(
|
|
464
|
+
"notification settings: failed to remove from storage:",
|
|
465
|
+
cause
|
|
466
|
+
);
|
|
265
467
|
}
|
|
266
468
|
}
|
|
267
469
|
};
|
|
@@ -321,14 +523,13 @@ async function clearOneInAppNotification(channel, id) {
|
|
|
321
523
|
}
|
|
322
524
|
|
|
323
525
|
// src/destination.ts
|
|
324
|
-
import {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
526
|
+
import {
|
|
527
|
+
isBurnAddress,
|
|
528
|
+
isEvmAddress,
|
|
529
|
+
isZeroAddress
|
|
530
|
+
} from "@naculus/connect-core";
|
|
329
531
|
function isBurnDestination(address) {
|
|
330
|
-
|
|
331
|
-
return DEAD_ADDRESSES.has(lower) || lower.includes("dead");
|
|
532
|
+
return isBurnAddress(address);
|
|
332
533
|
}
|
|
333
534
|
function validateDestination(address) {
|
|
334
535
|
if (!address) return { isValid: false, level: "blocked", issue: "empty" };
|
|
@@ -392,6 +593,490 @@ function toExecuteRouteError(cause) {
|
|
|
392
593
|
};
|
|
393
594
|
}
|
|
394
595
|
|
|
596
|
+
// src/session-key-manager.ts
|
|
597
|
+
import {
|
|
598
|
+
DEFAULT_SESSION_KEY_CONFIG,
|
|
599
|
+
LocalStorageAdapter,
|
|
600
|
+
MemoryStorageAdapter,
|
|
601
|
+
SessionKeyManager,
|
|
602
|
+
WalletError as WalletError5
|
|
603
|
+
} from "@naculus/connect-core";
|
|
604
|
+
var sharedManager = null;
|
|
605
|
+
var sharedFingerprint = null;
|
|
606
|
+
function sessionKeyConfigFingerprint(config) {
|
|
607
|
+
if (!config) return "";
|
|
608
|
+
return JSON.stringify({
|
|
609
|
+
defaultMaxTotalValue: config.defaultMaxTotalValue?.toString(),
|
|
610
|
+
defaultMaxTxCount: config.defaultMaxTxCount,
|
|
611
|
+
defaultExpiryMs: config.defaultExpiryMs,
|
|
612
|
+
requireAllowedContracts: config.requireAllowedContracts,
|
|
613
|
+
forbiddenMethods: config.forbiddenMethods,
|
|
614
|
+
pbkdf2Iterations: config.pbkdf2Iterations,
|
|
615
|
+
unsafeAllowWeakKdf: config.unsafeAllowWeakKdf,
|
|
616
|
+
storagePrefix: config.storagePrefix,
|
|
617
|
+
encryptionKey: config.encryptionKey,
|
|
618
|
+
encryptionSalt: config.encryptionSalt
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function getSharedSessionKeyManager(config) {
|
|
622
|
+
const fingerprint = sessionKeyConfigFingerprint(config);
|
|
623
|
+
if (!sharedManager) {
|
|
624
|
+
const storagePrefix = config?.storagePrefix ?? DEFAULT_SESSION_KEY_CONFIG.storagePrefix;
|
|
625
|
+
sharedManager = new SessionKeyManager(
|
|
626
|
+
config,
|
|
627
|
+
typeof window !== "undefined" ? new LocalStorageAdapter(`${storagePrefix}:`) : new MemoryStorageAdapter()
|
|
628
|
+
);
|
|
629
|
+
sharedFingerprint = fingerprint;
|
|
630
|
+
return sharedManager;
|
|
631
|
+
}
|
|
632
|
+
if (fingerprint !== "" && fingerprint !== sharedFingerprint) {
|
|
633
|
+
throw new WalletError5(
|
|
634
|
+
"invalid_input",
|
|
635
|
+
"useSessionKeys is backed by a process-wide SessionKeyManager, and a different spending configuration was supplied after it was created. The second configuration would be ignored, so the caller would be operating under limits it did not set. Use one configuration per application, or construct a SessionKeyManager directly."
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return sharedManager;
|
|
639
|
+
}
|
|
640
|
+
function resetSharedSessionKeyManager() {
|
|
641
|
+
sharedManager = null;
|
|
642
|
+
sharedFingerprint = null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/user-op-receipt.ts
|
|
646
|
+
var InvalidUserOperationReceiptError = class extends Error {
|
|
647
|
+
constructor(message) {
|
|
648
|
+
super(message);
|
|
649
|
+
this.name = "InvalidUserOperationReceiptError";
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
function isRecord(value) {
|
|
653
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
654
|
+
}
|
|
655
|
+
function isAddress(value) {
|
|
656
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value);
|
|
657
|
+
}
|
|
658
|
+
function isHash(value) {
|
|
659
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
660
|
+
}
|
|
661
|
+
function isHexData(value) {
|
|
662
|
+
return typeof value === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(value);
|
|
663
|
+
}
|
|
664
|
+
function parseQuantity(value, field) {
|
|
665
|
+
if (typeof value !== "string" || !/^(?:0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)|(?:0|[1-9][0-9]*))$/.test(value)) {
|
|
666
|
+
throw new InvalidUserOperationReceiptError(
|
|
667
|
+
`Bundler returned an invalid ${field}.`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
return BigInt(value);
|
|
671
|
+
}
|
|
672
|
+
function parseUserOperationReceipt(value, expectedHash) {
|
|
673
|
+
if (!isRecord(value)) {
|
|
674
|
+
throw new InvalidUserOperationReceiptError(
|
|
675
|
+
"Bundler returned an invalid UserOperation receipt."
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
if (!isHash(value.userOpHash) || value.userOpHash.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
679
|
+
throw new InvalidUserOperationReceiptError(
|
|
680
|
+
"Bundler receipt does not match the requested UserOperation hash."
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (!isAddress(value.entryPoint) || !isAddress(value.sender)) {
|
|
684
|
+
throw new InvalidUserOperationReceiptError(
|
|
685
|
+
"Bundler receipt contains an invalid account address."
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (value.paymaster !== void 0 && value.paymaster !== null && !isAddress(value.paymaster)) {
|
|
689
|
+
throw new InvalidUserOperationReceiptError(
|
|
690
|
+
"Bundler receipt contains an invalid paymaster address."
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
if (typeof value.success !== "boolean" || !isHash(value.transactionHash)) {
|
|
694
|
+
throw new InvalidUserOperationReceiptError(
|
|
695
|
+
"Bundler receipt contains an invalid execution result."
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
if (!Array.isArray(value.logs)) {
|
|
699
|
+
throw new InvalidUserOperationReceiptError(
|
|
700
|
+
"Bundler receipt contains invalid logs."
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
const logs = value.logs.map((candidate) => {
|
|
704
|
+
if (!isRecord(candidate) || !isAddress(candidate.address) || !Array.isArray(candidate.topics) || !candidate.topics.every(isHash) || !isHexData(candidate.data)) {
|
|
705
|
+
throw new InvalidUserOperationReceiptError(
|
|
706
|
+
"Bundler receipt contains an invalid log entry."
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
address: candidate.address,
|
|
711
|
+
topics: candidate.topics,
|
|
712
|
+
data: candidate.data
|
|
713
|
+
};
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
userOpHash: value.userOpHash,
|
|
717
|
+
entryPoint: value.entryPoint,
|
|
718
|
+
sender: value.sender,
|
|
719
|
+
nonce: parseQuantity(value.nonce, "nonce"),
|
|
720
|
+
...value.paymaster === void 0 || value.paymaster === null ? {} : { paymaster: value.paymaster },
|
|
721
|
+
actualGasUsed: parseQuantity(value.actualGasUsed, "actualGasUsed"),
|
|
722
|
+
actualGasCost: parseQuantity(value.actualGasCost, "actualGasCost"),
|
|
723
|
+
success: value.success,
|
|
724
|
+
transactionHash: value.transactionHash,
|
|
725
|
+
logs
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
async function fetchUserOperationReceipt(bundlerUrl, hash, signal) {
|
|
729
|
+
const response = await fetch(bundlerUrl, {
|
|
730
|
+
method: "POST",
|
|
731
|
+
headers: { "Content-Type": "application/json" },
|
|
732
|
+
body: JSON.stringify({
|
|
733
|
+
jsonrpc: "2.0",
|
|
734
|
+
id: 1,
|
|
735
|
+
method: "eth_getUserOperationReceipt",
|
|
736
|
+
params: [hash]
|
|
737
|
+
}),
|
|
738
|
+
signal
|
|
739
|
+
});
|
|
740
|
+
if (!response.ok) {
|
|
741
|
+
throw new Error(
|
|
742
|
+
`Bundler returned HTTP ${response.status} while reading UserOperation status.`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
const json = await response.json();
|
|
746
|
+
if (json.error) {
|
|
747
|
+
throw new Error(
|
|
748
|
+
typeof json.error.message === "string" ? json.error.message : "Bundler returned a JSON-RPC error."
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
if (json.result === void 0 || json.result === null) return null;
|
|
752
|
+
return parseUserOperationReceipt(json.result, hash);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/smart-account.ts
|
|
756
|
+
import {
|
|
757
|
+
AA_SUPPORTED_CHAINS,
|
|
758
|
+
DEFAULT_ENTRY_POINT,
|
|
759
|
+
WalletError as WalletError6
|
|
760
|
+
} from "@naculus/connect-core";
|
|
761
|
+
function resolveUserOpChain(optionsChainId, connectedChainId) {
|
|
762
|
+
const chainId = resolveEvmChainId(optionsChainId, connectedChainId);
|
|
763
|
+
if (!chainId) {
|
|
764
|
+
throw new WalletError6(
|
|
765
|
+
"invalid_chain",
|
|
766
|
+
optionsChainId || connectedChainId ? `Invalid EVM chain ID: ${optionsChainId ?? connectedChainId}` : "No EVM chain to send on. Connect a wallet or pass options.chainId."
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (connectedChainId && connectedChainId !== chainId) {
|
|
770
|
+
throw new WalletError6(
|
|
771
|
+
"chain_mismatch",
|
|
772
|
+
`Connected chain ${connectedChainId} does not match UserOperation chain ${chainId}.`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
return chainId;
|
|
776
|
+
}
|
|
777
|
+
function buildSmartAccountConfig(owner, chainId, options = {}) {
|
|
778
|
+
return {
|
|
779
|
+
owner,
|
|
780
|
+
accountType: options.accountType ?? "simple",
|
|
781
|
+
entryPoint: options.entryPoint ?? AA_SUPPORTED_CHAINS[chainId]?.entryPoint ?? DEFAULT_ENTRY_POINT,
|
|
782
|
+
chainId,
|
|
783
|
+
salt: options.salt
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function assertHexSignature(value) {
|
|
787
|
+
if (typeof value !== "string" || !/^0x(?:[0-9a-fA-F]{2})+$/.test(value)) {
|
|
788
|
+
throw new WalletError6(
|
|
789
|
+
"signature_rejected",
|
|
790
|
+
"Wallet returned an invalid UserOperation signature."
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return value;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/delegation-policy.ts
|
|
797
|
+
import { WalletError as WalletError7 } from "@naculus/connect-core";
|
|
798
|
+
function sorted(values) {
|
|
799
|
+
return values ? [...values].map((value) => value.toLowerCase()).sort() : null;
|
|
800
|
+
}
|
|
801
|
+
function sameExecutionIntent(requested, prepared) {
|
|
802
|
+
const sameHex = (left, right, empty) => {
|
|
803
|
+
const l = left === void 0 ? empty : left.toLowerCase();
|
|
804
|
+
const r = right === void 0 ? empty : right.toLowerCase();
|
|
805
|
+
return l === r;
|
|
806
|
+
};
|
|
807
|
+
const sameQuantity = (left, right) => {
|
|
808
|
+
try {
|
|
809
|
+
return BigInt(left ?? "0") === BigInt(right ?? "0");
|
|
810
|
+
} catch {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
return sameHex(requested.to, prepared.to) && sameHex(requested.data, prepared.data, "0x") && sameQuantity(requested.value, prepared.value) && requested.chainId === prepared.chainId;
|
|
815
|
+
}
|
|
816
|
+
function buildDelegationPolicyMessage(policy, origin) {
|
|
817
|
+
const tokenAllowances = policy.scope.tokenAllowances ? Object.entries(policy.scope.tokenAllowances).map(
|
|
818
|
+
([token, value]) => [token.toLowerCase(), value.toString()]
|
|
819
|
+
).sort(([left], [right]) => left.localeCompare(right)) : null;
|
|
820
|
+
const payload = {
|
|
821
|
+
version: 1,
|
|
822
|
+
origin,
|
|
823
|
+
signer: policy.signerAddress.toLowerCase(),
|
|
824
|
+
sessionId: policy.id,
|
|
825
|
+
sessionPublicKey: policy.publicKey.toLowerCase(),
|
|
826
|
+
expiresAt: new Date(policy.expiresAt).toISOString(),
|
|
827
|
+
mode: policy.scope.mode,
|
|
828
|
+
limits: {
|
|
829
|
+
maxTotalGas: policy.scope.maxTotalGas?.toString() ?? null,
|
|
830
|
+
maxGasPerTx: policy.scope.maxGasPerTx?.toString() ?? null,
|
|
831
|
+
maxTotalValue: policy.scope.maxTotalValue?.toString() ?? null,
|
|
832
|
+
maxValuePerTx: policy.scope.maxValuePerTx?.toString() ?? null,
|
|
833
|
+
maxTxCount: policy.scope.maxTxCount ?? null
|
|
834
|
+
},
|
|
835
|
+
allowedContracts: sorted(policy.scope.allowedContracts),
|
|
836
|
+
allowedMethods: sorted(policy.scope.allowedMethods),
|
|
837
|
+
allowedChainIds: policy.scope.allowedChainIds ? [...policy.scope.allowedChainIds].sort((left, right) => left - right) : null,
|
|
838
|
+
tokenAllowances
|
|
839
|
+
};
|
|
840
|
+
return `Naculus Session Policy v1
|
|
841
|
+
${JSON.stringify(payload)}`;
|
|
842
|
+
}
|
|
843
|
+
function isVerifiablePolicy(policy) {
|
|
844
|
+
return policy.status === "active" && Boolean(policy.authorized) && policy.scope.mode === "offchain";
|
|
845
|
+
}
|
|
846
|
+
function createDelegationPolicyFlow(deps) {
|
|
847
|
+
const { manager, origin } = deps;
|
|
848
|
+
const verifyStoredPolicy = async (policy) => {
|
|
849
|
+
if (!isVerifiablePolicy(policy)) return false;
|
|
850
|
+
return manager.verifyOffchainAuthorization(
|
|
851
|
+
policy.id,
|
|
852
|
+
buildDelegationPolicyMessage(policy, origin),
|
|
853
|
+
deps.verifySignature
|
|
854
|
+
);
|
|
855
|
+
};
|
|
856
|
+
const createPolicy = async (scope) => {
|
|
857
|
+
const signer = deps.signer();
|
|
858
|
+
if (!signer) {
|
|
859
|
+
throw new WalletError7("wallet_unavailable", "No active EVM account");
|
|
860
|
+
}
|
|
861
|
+
if (scope.mode && scope.mode !== "offchain") {
|
|
862
|
+
throw new WalletError7(
|
|
863
|
+
"method_not_allowed",
|
|
864
|
+
"This flow creates signed off-chain policies only. EIP-7702 and AA-module policies require a configured on-chain execution adapter."
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
if (!deps.encryptionKeyConfigured) {
|
|
868
|
+
throw new WalletError7(
|
|
869
|
+
"method_not_allowed",
|
|
870
|
+
"Persistent delegation policies require a host-provided encryptionKey. The deterministic compatibility fallback is not a production key boundary."
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
if (!deps.storageAvailable()) {
|
|
874
|
+
throw new WalletError7(
|
|
875
|
+
"storage_unavailable",
|
|
876
|
+
"Persistent delegation policy storage is unavailable in this browser"
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
let draft = null;
|
|
880
|
+
try {
|
|
881
|
+
draft = await manager.createSessionKey(
|
|
882
|
+
{ ...scope, mode: "offchain" },
|
|
883
|
+
signer
|
|
884
|
+
);
|
|
885
|
+
const message = buildDelegationPolicyMessage(draft, origin);
|
|
886
|
+
const signature = await deps.signMessage(message);
|
|
887
|
+
if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) {
|
|
888
|
+
throw new WalletError7(
|
|
889
|
+
"invalid_input",
|
|
890
|
+
"Wallet did not return a valid 65-byte EVM policy signature"
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
const recovers = await deps.verifySignature({
|
|
894
|
+
message,
|
|
895
|
+
signature,
|
|
896
|
+
signerAddress: signer
|
|
897
|
+
});
|
|
898
|
+
if (!recovers) {
|
|
899
|
+
throw new WalletError7(
|
|
900
|
+
"invalid_input",
|
|
901
|
+
"Policy signature does not recover to the connected EVM account"
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
await manager.setAuthorization(draft.id, {
|
|
905
|
+
signerAddress: signer,
|
|
906
|
+
type: "offchain",
|
|
907
|
+
rawSignature: signature,
|
|
908
|
+
message
|
|
909
|
+
});
|
|
910
|
+
const authorized = (await manager.listSessions()).find(
|
|
911
|
+
(policy) => policy.id === draft?.id
|
|
912
|
+
);
|
|
913
|
+
if (!authorized?.authorized) {
|
|
914
|
+
throw new WalletError7(
|
|
915
|
+
"invalid_input",
|
|
916
|
+
"Policy authorization was not persisted"
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (!await verifyStoredPolicy(authorized)) {
|
|
920
|
+
throw new WalletError7(
|
|
921
|
+
"invalid_input",
|
|
922
|
+
"Persisted policy signature did not verify against its stored scope and origin"
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
await deps.refresh();
|
|
926
|
+
return authorized;
|
|
927
|
+
} catch (cause) {
|
|
928
|
+
if (draft) {
|
|
929
|
+
try {
|
|
930
|
+
await manager.revokeSession(draft.id);
|
|
931
|
+
} catch {
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
throw cause instanceof Error ? cause : new Error("Policy creation failed");
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
const revokePolicy = async (policyId) => {
|
|
938
|
+
await manager.revokeSession(policyId);
|
|
939
|
+
await deps.refresh();
|
|
940
|
+
};
|
|
941
|
+
const previewPolicyExecution = async (policyId, tx, callCount = 1, atomicity = "preferred") => {
|
|
942
|
+
const policy = (await manager.listSessions()).find((item) => item.id === policyId) ?? null;
|
|
943
|
+
const scope = await manager.checkSessionScope(policyId, tx);
|
|
944
|
+
const plan = deps.previewExecution(callCount, atomicity);
|
|
945
|
+
const signer = deps.signer();
|
|
946
|
+
const authorizationVerified = policy ? await verifyStoredPolicy(policy) : false;
|
|
947
|
+
const signerMatches = Boolean(
|
|
948
|
+
policy && signer && policy.signerAddress.toLowerCase() === signer.toLowerCase()
|
|
949
|
+
);
|
|
950
|
+
const adapter = deps.adapter;
|
|
951
|
+
let adapterCheck = null;
|
|
952
|
+
if (policy && adapter && authorizationVerified && signerMatches && scope.valid) {
|
|
953
|
+
try {
|
|
954
|
+
adapterCheck = await adapter.check({ policy, transaction: tx });
|
|
955
|
+
} catch (cause) {
|
|
956
|
+
adapterCheck = {
|
|
957
|
+
ready: false,
|
|
958
|
+
authorizationInstalled: false,
|
|
959
|
+
sponsored: false,
|
|
960
|
+
promptless: false,
|
|
961
|
+
reason: cause instanceof Error ? `Execution adapter check failed: ${cause.message}` : "Execution adapter check failed."
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
const delegation = deps.delegation();
|
|
966
|
+
const delegationMatches = Boolean(
|
|
967
|
+
adapter?.route !== "eip7702" || delegation.delegated === true && adapterCheck?.executorAddress && delegation.delegate?.toLowerCase() === adapterCheck.executorAddress.toLowerCase()
|
|
968
|
+
);
|
|
969
|
+
const policyReady = Boolean(
|
|
970
|
+
policy?.status === "active" && policy.authorized && authorizationVerified && policy.scope.mode === "offchain" && signerMatches && scope.valid
|
|
971
|
+
);
|
|
972
|
+
const adapterSponsorshipReady = Boolean(
|
|
973
|
+
deps.sponsorship !== "required" || adapterCheck?.sponsored
|
|
974
|
+
);
|
|
975
|
+
const broadcastReady = Boolean(
|
|
976
|
+
policyReady && callCount === 1 && adapterCheck?.ready && adapterCheck.authorizationInstalled && adapterCheck.promptless && adapterSponsorshipReady && delegationMatches
|
|
977
|
+
);
|
|
978
|
+
const ready = Boolean(policyReady && (plan.route || broadcastReady));
|
|
979
|
+
const reason = !policy ? "Policy not found." : policy.status !== "active" ? `Policy is ${policy.status}.` : !policy.authorized ? "The main wallet has not signed this policy." : !authorizationVerified ? "The persisted policy signature no longer matches its scope, signer, or origin." : !signerMatches ? "The connected account is not the policy signer." : !scope.valid ? scope.reason ?? "The transaction is outside the policy scope." : broadcastReady ? `The configured ${adapter?.route ?? "AA/7702"} adapter can submit this policy-authorized operation without another main-wallet prompt.${adapterCheck?.sponsored ? " Gas sponsorship is guaranteed." : ""} ${adapterCheck?.reason ?? "The on-chain execution adapter is ready."}` : !adapter ? `${plan.reason} The signed off-chain policy permits this request; an AA/7702 adapter is still required to broadcast without another wallet prompt.` : !adapterCheck?.ready || !adapterCheck.authorizationInstalled ? `${plan.reason} ${adapterCheck?.reason ?? "The execution adapter is not ready."}` : !adapterCheck.promptless ? `${plan.reason} The configured adapter would invoke the main wallet again, so promptless broadcast is not ready.` : !adapterSponsorshipReady ? `${plan.reason} Sponsored gas is required, but the configured adapter does not guarantee sponsorship.` : callCount !== 1 ? `${plan.reason} This policy adapter prepares one execution at a time and cannot claim a multi-call broadcast.` : !delegationMatches ? `${plan.reason} The configured EIP-7702 executor does not match the account's on-chain delegation.` : !plan.route ? plan.reason : `${plan.reason} ${adapterCheck.reason}`;
|
|
980
|
+
return {
|
|
981
|
+
policy,
|
|
982
|
+
scope,
|
|
983
|
+
execution: plan,
|
|
984
|
+
signerMatches,
|
|
985
|
+
authorizationVerified,
|
|
986
|
+
ready,
|
|
987
|
+
broadcastReady,
|
|
988
|
+
broadcastRoute: broadcastReady ? adapter?.route ?? null : null,
|
|
989
|
+
onchainAuthorizationVerified: Boolean(
|
|
990
|
+
adapterCheck?.authorizationInstalled && delegationMatches
|
|
991
|
+
),
|
|
992
|
+
broadcastSponsored: broadcastReady ? adapterCheck?.sponsored ?? false : null,
|
|
993
|
+
broadcastPromptless: Boolean(broadcastReady && adapterCheck?.promptless),
|
|
994
|
+
reason
|
|
995
|
+
};
|
|
996
|
+
};
|
|
997
|
+
const signVerified = (policyId, digest, tx) => manager.signWithVerifiedOffchainAuthorization(
|
|
998
|
+
policyId,
|
|
999
|
+
(authoritativePolicy) => buildDelegationPolicyMessage(authoritativePolicy, origin),
|
|
1000
|
+
deps.verifySignature,
|
|
1001
|
+
digest,
|
|
1002
|
+
tx
|
|
1003
|
+
);
|
|
1004
|
+
const signPolicyDigest = async (policyId, digest, tx) => {
|
|
1005
|
+
const preview = await previewPolicyExecution(policyId, tx, 1, "any");
|
|
1006
|
+
if (!preview.ready) {
|
|
1007
|
+
throw new WalletError7("method_not_allowed", preview.reason);
|
|
1008
|
+
}
|
|
1009
|
+
return signVerified(policyId, digest, tx);
|
|
1010
|
+
};
|
|
1011
|
+
const executePolicy = async (policyId, tx) => {
|
|
1012
|
+
const adapter = deps.adapter;
|
|
1013
|
+
if (!adapter) {
|
|
1014
|
+
throw new WalletError7(
|
|
1015
|
+
"method_not_allowed",
|
|
1016
|
+
"No session-policy-aware AA/7702 execution adapter is configured."
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const preview = await previewPolicyExecution(policyId, tx, 1, "any");
|
|
1020
|
+
if (!preview.broadcastReady || !preview.policy) {
|
|
1021
|
+
throw new WalletError7("method_not_allowed", preview.reason);
|
|
1022
|
+
}
|
|
1023
|
+
const prepared = await adapter.prepare({
|
|
1024
|
+
policy: preview.policy,
|
|
1025
|
+
transaction: tx
|
|
1026
|
+
});
|
|
1027
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(prepared.digest)) {
|
|
1028
|
+
throw new WalletError7(
|
|
1029
|
+
"invalid_input",
|
|
1030
|
+
"Execution adapter returned a digest that is not 32 bytes."
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
if (!sameExecutionIntent(tx, prepared.transaction)) {
|
|
1034
|
+
throw new WalletError7(
|
|
1035
|
+
"invalid_input",
|
|
1036
|
+
"Execution adapter changed the requested target, value, data, or chain."
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
const currentCheck = await adapter.check({
|
|
1040
|
+
policy: preview.policy,
|
|
1041
|
+
transaction: prepared.transaction
|
|
1042
|
+
});
|
|
1043
|
+
const delegation = deps.delegation();
|
|
1044
|
+
const currentDelegationMatches = adapter.route !== "eip7702" || delegation.delegated === true && currentCheck.executorAddress !== void 0 && delegation.delegate?.toLowerCase() === currentCheck.executorAddress.toLowerCase();
|
|
1045
|
+
if (!currentCheck.ready || !currentCheck.authorizationInstalled || !currentCheck.promptless || deps.sponsorship === "required" && !currentCheck.sponsored || !currentDelegationMatches) {
|
|
1046
|
+
throw new WalletError7(
|
|
1047
|
+
"method_not_allowed",
|
|
1048
|
+
currentCheck.reason || "The on-chain policy authorization is not ready."
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
const signature = await signVerified(
|
|
1052
|
+
policyId,
|
|
1053
|
+
prepared.digest,
|
|
1054
|
+
prepared.transaction
|
|
1055
|
+
);
|
|
1056
|
+
const submission = await adapter.broadcast({
|
|
1057
|
+
policy: preview.policy,
|
|
1058
|
+
prepared,
|
|
1059
|
+
signature
|
|
1060
|
+
});
|
|
1061
|
+
if (submission.route !== adapter.route || submission.status !== "submitted" || !/^0x[0-9a-fA-F]{64}$/.test(submission.hash)) {
|
|
1062
|
+
throw new WalletError7(
|
|
1063
|
+
"invalid_input",
|
|
1064
|
+
"Execution adapter returned an invalid submission result."
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
await deps.refresh();
|
|
1068
|
+
return submission;
|
|
1069
|
+
};
|
|
1070
|
+
return {
|
|
1071
|
+
verifyStoredPolicy,
|
|
1072
|
+
createPolicy,
|
|
1073
|
+
revokePolicy,
|
|
1074
|
+
previewPolicyExecution,
|
|
1075
|
+
signPolicyDigest,
|
|
1076
|
+
executePolicy
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
395
1080
|
// src/web3-store.ts
|
|
396
1081
|
import { logger } from "@naculus/connect-core";
|
|
397
1082
|
function withTimeout(promise, ms, label = "Operation") {
|
|
@@ -480,13 +1165,57 @@ function web3Reducer(state, action) {
|
|
|
480
1165
|
return state;
|
|
481
1166
|
}
|
|
482
1167
|
}
|
|
1168
|
+
|
|
1169
|
+
// src/siwx-policy.ts
|
|
1170
|
+
function defaultSiwxDomain(location) {
|
|
1171
|
+
return location?.host ?? "localhost";
|
|
1172
|
+
}
|
|
1173
|
+
function defaultSiwxUri(location) {
|
|
1174
|
+
return location?.origin ?? "http://localhost";
|
|
1175
|
+
}
|
|
1176
|
+
function isSiwxExpired(result, now) {
|
|
1177
|
+
const value = result.message.expirationTime;
|
|
1178
|
+
if (!value) return false;
|
|
1179
|
+
const timestamp = new Date(value).getTime();
|
|
1180
|
+
return !Number.isFinite(timestamp) || timestamp <= now.getTime();
|
|
1181
|
+
}
|
|
1182
|
+
function isSiwxNotBeforeValid(result, now) {
|
|
1183
|
+
const value = result.message.notBefore;
|
|
1184
|
+
if (!value) return true;
|
|
1185
|
+
const timestamp = new Date(value).getTime();
|
|
1186
|
+
return Number.isFinite(timestamp) && timestamp <= now.getTime();
|
|
1187
|
+
}
|
|
1188
|
+
function randomSuffix() {
|
|
1189
|
+
const bytes = new Uint8Array(8);
|
|
1190
|
+
crypto.getRandomValues(bytes);
|
|
1191
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
1192
|
+
""
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
|
|
1196
|
+
return {
|
|
1197
|
+
id: `siwx_${now.getTime().toString(36)}_${randomSuffix()}`,
|
|
1198
|
+
chainId: result.message.chainId,
|
|
1199
|
+
address: result.message.address,
|
|
1200
|
+
domain: result.message.domain,
|
|
1201
|
+
message: result.message,
|
|
1202
|
+
signature: result.signature,
|
|
1203
|
+
issuedAt: result.message.issuedAt ?? now.toISOString(),
|
|
1204
|
+
expiresAt: result.message.expirationTime ?? null,
|
|
1205
|
+
refreshedAt: null
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
483
1208
|
export {
|
|
484
1209
|
DEFAULT_NOTIFICATION_SETTINGS,
|
|
1210
|
+
InvalidUserOperationReceiptError,
|
|
485
1211
|
NOTIFICATION_SETTINGS_KEY,
|
|
486
1212
|
__resetResolverForTests,
|
|
487
1213
|
addInAppNotification,
|
|
1214
|
+
assertHexSignature,
|
|
488
1215
|
atomicSupportFor,
|
|
489
1216
|
bareEvmAddress,
|
|
1217
|
+
buildDelegationPolicyMessage,
|
|
1218
|
+
buildSmartAccountConfig,
|
|
490
1219
|
chainNamespace,
|
|
491
1220
|
chainNumber,
|
|
492
1221
|
chainsForNamespace,
|
|
@@ -494,28 +1223,48 @@ export {
|
|
|
494
1223
|
clearOneInAppNotification,
|
|
495
1224
|
compareCostsKey,
|
|
496
1225
|
createDecimalsCache,
|
|
1226
|
+
createDelegationPolicyFlow,
|
|
497
1227
|
createNotification,
|
|
498
1228
|
createNotificationSettingsStorage,
|
|
499
1229
|
decodeRevertReason,
|
|
1230
|
+
defaultSiwxDomain,
|
|
1231
|
+
defaultSiwxUri,
|
|
500
1232
|
describeChain,
|
|
501
1233
|
eip155ChainIdToNumber,
|
|
1234
|
+
encodeErc20Approve,
|
|
1235
|
+
encodeErc20Transfer,
|
|
1236
|
+
encodeErc20TransferFrom,
|
|
1237
|
+
fetchUserOperationReceipt,
|
|
502
1238
|
getResolver,
|
|
1239
|
+
getSharedSessionKeyManager,
|
|
503
1240
|
initialWeb3State,
|
|
504
1241
|
isBurnDestination,
|
|
505
1242
|
isEvmChainId,
|
|
506
1243
|
isPositiveIntegerAmount,
|
|
507
1244
|
isQuotableInput,
|
|
1245
|
+
isSiwxExpired,
|
|
1246
|
+
isSiwxNotBeforeValid,
|
|
1247
|
+
isVerifiablePolicy,
|
|
508
1248
|
loadNotificationSettings,
|
|
509
1249
|
mutedChainSettings,
|
|
510
1250
|
normalizeCapabilities,
|
|
511
1251
|
normalizeEip155ChainId,
|
|
1252
|
+
parseUserOperationReceipt,
|
|
512
1253
|
readDecimals,
|
|
513
1254
|
readSolanaRoles,
|
|
1255
|
+
resetSharedSessionKeyManager,
|
|
514
1256
|
resolveChain,
|
|
1257
|
+
resolveContextSimulationEndpoint,
|
|
515
1258
|
resolveEvmChainId,
|
|
516
1259
|
resolveRevertReason,
|
|
1260
|
+
resolveSimulationEndpoint,
|
|
1261
|
+
resolveUserOpChain,
|
|
1262
|
+
sameExecutionIntent,
|
|
517
1263
|
saveNotificationSettings,
|
|
518
1264
|
selectChainCapabilities,
|
|
1265
|
+
sessionKeyConfigFingerprint,
|
|
1266
|
+
simulateTransactionPreview,
|
|
1267
|
+
siwxResultToSession,
|
|
519
1268
|
toChainSwitchError,
|
|
520
1269
|
toEip155Accounts,
|
|
521
1270
|
toExecuteRouteError,
|