@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.cjs
CHANGED
|
@@ -21,11 +21,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
DEFAULT_NOTIFICATION_SETTINGS: () => DEFAULT_NOTIFICATION_SETTINGS,
|
|
24
|
+
InvalidUserOperationReceiptError: () => InvalidUserOperationReceiptError,
|
|
24
25
|
NOTIFICATION_SETTINGS_KEY: () => NOTIFICATION_SETTINGS_KEY,
|
|
25
26
|
__resetResolverForTests: () => __resetResolverForTests,
|
|
26
27
|
addInAppNotification: () => addInAppNotification,
|
|
28
|
+
assertHexSignature: () => assertHexSignature,
|
|
27
29
|
atomicSupportFor: () => atomicSupportFor,
|
|
28
30
|
bareEvmAddress: () => bareEvmAddress,
|
|
31
|
+
buildDelegationPolicyMessage: () => buildDelegationPolicyMessage,
|
|
32
|
+
buildSmartAccountConfig: () => buildSmartAccountConfig,
|
|
29
33
|
chainNamespace: () => chainNamespace,
|
|
30
34
|
chainNumber: () => chainNumber,
|
|
31
35
|
chainsForNamespace: () => chainsForNamespace,
|
|
@@ -33,28 +37,48 @@ __export(src_exports, {
|
|
|
33
37
|
clearOneInAppNotification: () => clearOneInAppNotification,
|
|
34
38
|
compareCostsKey: () => compareCostsKey,
|
|
35
39
|
createDecimalsCache: () => createDecimalsCache,
|
|
40
|
+
createDelegationPolicyFlow: () => createDelegationPolicyFlow,
|
|
36
41
|
createNotification: () => createNotification,
|
|
37
42
|
createNotificationSettingsStorage: () => createNotificationSettingsStorage,
|
|
38
43
|
decodeRevertReason: () => decodeRevertReason,
|
|
44
|
+
defaultSiwxDomain: () => defaultSiwxDomain,
|
|
45
|
+
defaultSiwxUri: () => defaultSiwxUri,
|
|
39
46
|
describeChain: () => describeChain,
|
|
40
47
|
eip155ChainIdToNumber: () => eip155ChainIdToNumber,
|
|
48
|
+
encodeErc20Approve: () => encodeErc20Approve,
|
|
49
|
+
encodeErc20Transfer: () => encodeErc20Transfer,
|
|
50
|
+
encodeErc20TransferFrom: () => encodeErc20TransferFrom,
|
|
51
|
+
fetchUserOperationReceipt: () => fetchUserOperationReceipt,
|
|
41
52
|
getResolver: () => getResolver,
|
|
53
|
+
getSharedSessionKeyManager: () => getSharedSessionKeyManager,
|
|
42
54
|
initialWeb3State: () => initialWeb3State,
|
|
43
55
|
isBurnDestination: () => isBurnDestination,
|
|
44
56
|
isEvmChainId: () => isEvmChainId,
|
|
45
57
|
isPositiveIntegerAmount: () => isPositiveIntegerAmount,
|
|
46
58
|
isQuotableInput: () => isQuotableInput,
|
|
59
|
+
isSiwxExpired: () => isSiwxExpired,
|
|
60
|
+
isSiwxNotBeforeValid: () => isSiwxNotBeforeValid,
|
|
61
|
+
isVerifiablePolicy: () => isVerifiablePolicy,
|
|
47
62
|
loadNotificationSettings: () => loadNotificationSettings,
|
|
48
63
|
mutedChainSettings: () => mutedChainSettings,
|
|
49
64
|
normalizeCapabilities: () => normalizeCapabilities,
|
|
50
65
|
normalizeEip155ChainId: () => normalizeEip155ChainId,
|
|
66
|
+
parseUserOperationReceipt: () => parseUserOperationReceipt,
|
|
51
67
|
readDecimals: () => readDecimals,
|
|
52
68
|
readSolanaRoles: () => readSolanaRoles,
|
|
69
|
+
resetSharedSessionKeyManager: () => resetSharedSessionKeyManager,
|
|
53
70
|
resolveChain: () => resolveChain,
|
|
71
|
+
resolveContextSimulationEndpoint: () => resolveContextSimulationEndpoint,
|
|
54
72
|
resolveEvmChainId: () => resolveEvmChainId,
|
|
55
73
|
resolveRevertReason: () => resolveRevertReason,
|
|
74
|
+
resolveSimulationEndpoint: () => resolveSimulationEndpoint,
|
|
75
|
+
resolveUserOpChain: () => resolveUserOpChain,
|
|
76
|
+
sameExecutionIntent: () => sameExecutionIntent,
|
|
56
77
|
saveNotificationSettings: () => saveNotificationSettings,
|
|
57
78
|
selectChainCapabilities: () => selectChainCapabilities,
|
|
79
|
+
sessionKeyConfigFingerprint: () => sessionKeyConfigFingerprint,
|
|
80
|
+
simulateTransactionPreview: () => simulateTransactionPreview,
|
|
81
|
+
siwxResultToSession: () => siwxResultToSession,
|
|
58
82
|
toChainSwitchError: () => toChainSwitchError,
|
|
59
83
|
toEip155Accounts: () => toEip155Accounts,
|
|
60
84
|
toExecuteRouteError: () => toExecuteRouteError,
|
|
@@ -211,8 +235,34 @@ function writeDecimals(cache, token, value) {
|
|
|
211
235
|
cache.value = value;
|
|
212
236
|
}
|
|
213
237
|
|
|
214
|
-
// src/
|
|
238
|
+
// src/erc20-calldata.ts
|
|
215
239
|
var import_connect_core5 = require("@naculus/connect-core");
|
|
240
|
+
var UINT256_MAX = (1n << 256n) - 1n;
|
|
241
|
+
var EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
242
|
+
function addressWord(address) {
|
|
243
|
+
if (typeof address !== "string" || !EVM_ADDRESS.test(address) || address !== address.toLowerCase() && !(0, import_connect_core5.isChecksumAddress)(address)) {
|
|
244
|
+
throw new TypeError("Invalid EVM address checksum");
|
|
245
|
+
}
|
|
246
|
+
return address.slice(2).toLowerCase().padStart(64, "0");
|
|
247
|
+
}
|
|
248
|
+
function uint256Word(value) {
|
|
249
|
+
if (typeof value !== "bigint" || value < 0n || value > UINT256_MAX) {
|
|
250
|
+
throw new RangeError("Amount must be a uint256 bigint");
|
|
251
|
+
}
|
|
252
|
+
return value.toString(16).padStart(64, "0");
|
|
253
|
+
}
|
|
254
|
+
function encodeErc20Transfer(to, amount) {
|
|
255
|
+
return `0xa9059cbb${addressWord(to)}${uint256Word(amount)}`;
|
|
256
|
+
}
|
|
257
|
+
function encodeErc20Approve(spender, amount) {
|
|
258
|
+
return `0x095ea7b3${addressWord(spender)}${uint256Word(amount)}`;
|
|
259
|
+
}
|
|
260
|
+
function encodeErc20TransferFrom(from, to, amount) {
|
|
261
|
+
return `0x23b872dd${addressWord(from)}${addressWord(to)}${uint256Word(amount)}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/provider-errors.ts
|
|
265
|
+
var import_connect_core6 = require("@naculus/connect-core");
|
|
216
266
|
var USER_REJECTED = 4001;
|
|
217
267
|
var UNRECOGNIZED_CHAIN = 4902;
|
|
218
268
|
function providerCode(error) {
|
|
@@ -224,19 +274,19 @@ function providerCode(error) {
|
|
|
224
274
|
return nested ? providerCode(nested) : void 0;
|
|
225
275
|
}
|
|
226
276
|
function toChainSwitchError(error, fallback = "chain_unsupported") {
|
|
227
|
-
if (error instanceof
|
|
277
|
+
if (error instanceof import_connect_core6.WalletError) return error;
|
|
228
278
|
const message = error instanceof Error ? error.message : "Unknown error during chain switch";
|
|
229
279
|
switch (providerCode(error)) {
|
|
230
280
|
case USER_REJECTED:
|
|
231
|
-
return new
|
|
281
|
+
return new import_connect_core6.WalletError("chain_switch_rejected", message, error);
|
|
232
282
|
case UNRECOGNIZED_CHAIN:
|
|
233
|
-
return new
|
|
283
|
+
return new import_connect_core6.WalletError(
|
|
234
284
|
"chain_unsupported",
|
|
235
285
|
`${message} (the wallet does not have this chain configured)`,
|
|
236
286
|
error
|
|
237
287
|
);
|
|
238
288
|
default:
|
|
239
|
-
return new
|
|
289
|
+
return new import_connect_core6.WalletError(fallback, message, error);
|
|
240
290
|
}
|
|
241
291
|
}
|
|
242
292
|
|
|
@@ -284,12 +334,179 @@ function resolveRevertReason(data, fallbackMessage) {
|
|
|
284
334
|
return decodeRevertReason(data) ?? (fallbackMessage.includes("revert") ? fallbackMessage : "Transaction reverted");
|
|
285
335
|
}
|
|
286
336
|
|
|
337
|
+
// src/simulation-endpoint.ts
|
|
338
|
+
var import_connect_core7 = require("@naculus/connect-core");
|
|
339
|
+
function resolveSimulationEndpoint(requestedChainId, fallbackChainId, requestedRpcUrl, fallbackRpcUrl) {
|
|
340
|
+
const chainId = requestedChainId ?? fallbackChainId;
|
|
341
|
+
if (chainId === void 0) {
|
|
342
|
+
throw new import_connect_core7.WalletError(
|
|
343
|
+
"invalid_chain",
|
|
344
|
+
"No chain to simulate on. Connect a wallet or pass a chainId."
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (requestedChainId !== void 0 && fallbackChainId !== void 0 && requestedChainId !== fallbackChainId && requestedRpcUrl === void 0) {
|
|
348
|
+
throw new import_connect_core7.WalletError(
|
|
349
|
+
"invalid_chain",
|
|
350
|
+
"Simulation chain override requires an RPC URL for that chain."
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return { chainId, rpcUrl: requestedRpcUrl ?? fallbackRpcUrl };
|
|
354
|
+
}
|
|
355
|
+
function resolveContextSimulationEndpoint(requestedChainId, currentChain) {
|
|
356
|
+
const contextChainId = currentChain ? chainNumber(currentChain) ?? void 0 : void 0;
|
|
357
|
+
if (requestedChainId !== void 0 && currentChain && requestedChainId !== contextChainId) {
|
|
358
|
+
throw new import_connect_core7.WalletError(
|
|
359
|
+
"invalid_chain",
|
|
360
|
+
"Simulation chain does not match the connected chain endpoint."
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return resolveSimulationEndpoint(
|
|
364
|
+
requestedChainId,
|
|
365
|
+
contextChainId,
|
|
366
|
+
void 0,
|
|
367
|
+
currentChain?.rpcUrl
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/transaction-simulation.ts
|
|
372
|
+
var import_connect_core8 = require("@naculus/connect-core");
|
|
373
|
+
var coverage = {
|
|
374
|
+
balanceChanges: false,
|
|
375
|
+
approvalChanges: false,
|
|
376
|
+
risk: false
|
|
377
|
+
};
|
|
378
|
+
function baseResult(status, summary, changesDetected, warnings = []) {
|
|
379
|
+
return {
|
|
380
|
+
status,
|
|
381
|
+
coverage,
|
|
382
|
+
balanceChanges: [],
|
|
383
|
+
approvalChanges: [],
|
|
384
|
+
riskAssessment: { level: "unknown", score: 0, warnings },
|
|
385
|
+
provider: "eth_call",
|
|
386
|
+
summary,
|
|
387
|
+
changesDetected
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async function simulateTransactionPreview(tx, context) {
|
|
391
|
+
if (!tx)
|
|
392
|
+
return baseResult("unavailable", "No transaction to simulate", false);
|
|
393
|
+
const { chainId, rpcUrl } = resolveContextSimulationEndpoint(
|
|
394
|
+
context.chainId,
|
|
395
|
+
context.currentChain
|
|
396
|
+
);
|
|
397
|
+
const from = context.evmAccount?.includes(":") ? context.evmAccount.split(":").pop() : context.evmAccount;
|
|
398
|
+
if (context.publicClient) {
|
|
399
|
+
if (context.publicClient.chain?.id !== chainId) {
|
|
400
|
+
throw new import_connect_core8.WalletError(
|
|
401
|
+
"invalid_chain",
|
|
402
|
+
"Simulation client chain does not match the requested chain."
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
await context.publicClient.call({
|
|
407
|
+
to: tx.to,
|
|
408
|
+
data: tx.data ?? "0x",
|
|
409
|
+
value: tx.value ? BigInt(tx.value) : 0n,
|
|
410
|
+
...from ? { from } : {}
|
|
411
|
+
});
|
|
412
|
+
return {
|
|
413
|
+
...baseResult(
|
|
414
|
+
"success",
|
|
415
|
+
"Transaction simulation succeeded (basic revert check only)",
|
|
416
|
+
true
|
|
417
|
+
),
|
|
418
|
+
gasInfo: tx.gas ? { gasLimit: BigInt(tx.gas), estimatedFeeEth: "0" } : void 0
|
|
419
|
+
};
|
|
420
|
+
} catch (cause) {
|
|
421
|
+
const error = cause;
|
|
422
|
+
const reason = resolveRevertReason(
|
|
423
|
+
error?.data ?? error?.cause?.data,
|
|
424
|
+
error?.message ?? String(cause)
|
|
425
|
+
);
|
|
426
|
+
return {
|
|
427
|
+
...baseResult(
|
|
428
|
+
"reverted",
|
|
429
|
+
reason ? `Transaction reverted: ${reason}` : "Transaction reverted",
|
|
430
|
+
false,
|
|
431
|
+
[
|
|
432
|
+
{
|
|
433
|
+
category: "simulation_failed",
|
|
434
|
+
severity: "high",
|
|
435
|
+
message: reason ? `Transaction would revert: ${reason}` : "Transaction would revert"
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
),
|
|
439
|
+
revertReason: reason
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!rpcUrl) {
|
|
444
|
+
return baseResult(
|
|
445
|
+
"unavailable",
|
|
446
|
+
"Simulation unavailable: no RPC URL",
|
|
447
|
+
false,
|
|
448
|
+
[
|
|
449
|
+
{
|
|
450
|
+
category: "simulation_failed",
|
|
451
|
+
severity: "low",
|
|
452
|
+
message: "No RPC URL or public client available for simulation"
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const response = await (context.fetcher ?? fetch)(rpcUrl, {
|
|
459
|
+
method: "POST",
|
|
460
|
+
headers: { "Content-Type": "application/json" },
|
|
461
|
+
body: JSON.stringify({
|
|
462
|
+
jsonrpc: "2.0",
|
|
463
|
+
id: Date.now(),
|
|
464
|
+
method: "eth_call",
|
|
465
|
+
params: [
|
|
466
|
+
{
|
|
467
|
+
to: tx.to,
|
|
468
|
+
from,
|
|
469
|
+
data: tx.data ?? "0x",
|
|
470
|
+
value: tx.value ?? "0x0"
|
|
471
|
+
},
|
|
472
|
+
"latest"
|
|
473
|
+
]
|
|
474
|
+
})
|
|
475
|
+
});
|
|
476
|
+
const json = await response.json();
|
|
477
|
+
if (json.error) {
|
|
478
|
+
return baseResult("reverted", "Transaction reverted", false, [
|
|
479
|
+
{
|
|
480
|
+
category: "simulation_failed",
|
|
481
|
+
severity: "high",
|
|
482
|
+
message: `Transaction would revert: ${json.error.message}`
|
|
483
|
+
}
|
|
484
|
+
]);
|
|
485
|
+
}
|
|
486
|
+
return baseResult("success", "Transaction simulation succeeded", true);
|
|
487
|
+
} catch (cause) {
|
|
488
|
+
const error = cause;
|
|
489
|
+
return baseResult(
|
|
490
|
+
"unavailable",
|
|
491
|
+
"Simulation unavailable due to network error",
|
|
492
|
+
false,
|
|
493
|
+
[
|
|
494
|
+
{
|
|
495
|
+
category: "simulation_failed",
|
|
496
|
+
severity: "medium",
|
|
497
|
+
message: `Network error: ${error?.message ?? "Unknown"}`
|
|
498
|
+
}
|
|
499
|
+
]
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
287
504
|
// src/name-resolver.ts
|
|
288
|
-
var
|
|
505
|
+
var import_connect_core9 = require("@naculus/connect-core");
|
|
289
506
|
var sharedResolver = null;
|
|
290
507
|
function getResolver(config) {
|
|
291
|
-
if (config) return new
|
|
292
|
-
if (!sharedResolver) sharedResolver = new
|
|
508
|
+
if (config) return new import_connect_core9.NameResolver(config);
|
|
509
|
+
if (!sharedResolver) sharedResolver = new import_connect_core9.NameResolver();
|
|
293
510
|
return sharedResolver;
|
|
294
511
|
}
|
|
295
512
|
function __resetResolverForTests() {
|
|
@@ -297,7 +514,7 @@ function __resetResolverForTests() {
|
|
|
297
514
|
}
|
|
298
515
|
|
|
299
516
|
// src/notification.ts
|
|
300
|
-
var
|
|
517
|
+
var import_connect_core10 = require("@naculus/connect-core");
|
|
301
518
|
var NOTIFICATION_SETTINGS_KEY = "naculus_notif_react_settings";
|
|
302
519
|
var DEFAULT_NOTIFICATION_SETTINGS = {
|
|
303
520
|
telegram: false,
|
|
@@ -308,13 +525,16 @@ var DEFAULT_NOTIFICATION_SETTINGS = {
|
|
|
308
525
|
mutedTypes: []
|
|
309
526
|
};
|
|
310
527
|
function createNotificationSettingsStorage() {
|
|
311
|
-
const storage = (0,
|
|
528
|
+
const storage = (0, import_connect_core10.createStorageAdapter)("local", "naculus_notif_");
|
|
312
529
|
return {
|
|
313
530
|
getItem: async (key) => {
|
|
314
531
|
try {
|
|
315
532
|
return await storage.get(key);
|
|
316
533
|
} catch (cause) {
|
|
317
|
-
console.warn(
|
|
534
|
+
console.warn(
|
|
535
|
+
"notification settings: failed to read from storage:",
|
|
536
|
+
cause
|
|
537
|
+
);
|
|
318
538
|
return null;
|
|
319
539
|
}
|
|
320
540
|
},
|
|
@@ -322,14 +542,20 @@ function createNotificationSettingsStorage() {
|
|
|
322
542
|
try {
|
|
323
543
|
await storage.set(key, value);
|
|
324
544
|
} catch (cause) {
|
|
325
|
-
console.warn(
|
|
545
|
+
console.warn(
|
|
546
|
+
"notification settings: failed to write to storage:",
|
|
547
|
+
cause
|
|
548
|
+
);
|
|
326
549
|
}
|
|
327
550
|
},
|
|
328
551
|
removeItem: async (key) => {
|
|
329
552
|
try {
|
|
330
553
|
await storage.remove(key);
|
|
331
554
|
} catch (cause) {
|
|
332
|
-
console.warn(
|
|
555
|
+
console.warn(
|
|
556
|
+
"notification settings: failed to remove from storage:",
|
|
557
|
+
cause
|
|
558
|
+
);
|
|
333
559
|
}
|
|
334
560
|
}
|
|
335
561
|
};
|
|
@@ -389,21 +615,16 @@ async function clearOneInAppNotification(channel, id) {
|
|
|
389
615
|
}
|
|
390
616
|
|
|
391
617
|
// src/destination.ts
|
|
392
|
-
var
|
|
393
|
-
var DEAD_ADDRESSES = /* @__PURE__ */ new Set([
|
|
394
|
-
"0x0000000000000000000000000000000000000001",
|
|
395
|
-
"0x000000000000000000000000000000000000dead"
|
|
396
|
-
]);
|
|
618
|
+
var import_connect_core11 = require("@naculus/connect-core");
|
|
397
619
|
function isBurnDestination(address) {
|
|
398
|
-
|
|
399
|
-
return DEAD_ADDRESSES.has(lower) || lower.includes("dead");
|
|
620
|
+
return (0, import_connect_core11.isBurnAddress)(address);
|
|
400
621
|
}
|
|
401
622
|
function validateDestination(address) {
|
|
402
623
|
if (!address) return { isValid: false, level: "blocked", issue: "empty" };
|
|
403
|
-
if (!(0,
|
|
624
|
+
if (!(0, import_connect_core11.isEvmAddress)(address)) {
|
|
404
625
|
return { isValid: false, level: "blocked", issue: "invalid_format" };
|
|
405
626
|
}
|
|
406
|
-
if ((0,
|
|
627
|
+
if ((0, import_connect_core11.isZeroAddress)(address)) {
|
|
407
628
|
return { isValid: false, level: "blocked", issue: "zero_address" };
|
|
408
629
|
}
|
|
409
630
|
if (isBurnDestination(address)) {
|
|
@@ -414,11 +635,11 @@ function validateDestination(address) {
|
|
|
414
635
|
function bareEvmAddress(account) {
|
|
415
636
|
if (!account) return null;
|
|
416
637
|
const value = account.includes(":") ? account.split(":").pop() : account;
|
|
417
|
-
return value && (0,
|
|
638
|
+
return value && (0, import_connect_core11.isEvmAddress)(value) ? value : null;
|
|
418
639
|
}
|
|
419
640
|
|
|
420
641
|
// src/routing.ts
|
|
421
|
-
var
|
|
642
|
+
var import_connect_core12 = require("@naculus/connect-core");
|
|
422
643
|
function isPositiveIntegerAmount(amount) {
|
|
423
644
|
return /^(?:0|[1-9][0-9]*)$/.test(amount) && BigInt(amount) > 0n;
|
|
424
645
|
}
|
|
@@ -443,7 +664,7 @@ function validateRouteRecipient(quote, recipient) {
|
|
|
443
664
|
};
|
|
444
665
|
}
|
|
445
666
|
const namespace = quote?.toChain?.split(":")[0];
|
|
446
|
-
if (namespace && !(0,
|
|
667
|
+
if (namespace && !(0, import_connect_core12.isValidAddress)(recipient, namespace)) {
|
|
447
668
|
return {
|
|
448
669
|
code: "invalid_recipient",
|
|
449
670
|
message: `"${recipient}" is not a valid ${namespace} address for the destination chain ${quote?.toChain}`
|
|
@@ -460,8 +681,482 @@ function toExecuteRouteError(cause) {
|
|
|
460
681
|
};
|
|
461
682
|
}
|
|
462
683
|
|
|
684
|
+
// src/session-key-manager.ts
|
|
685
|
+
var import_connect_core13 = require("@naculus/connect-core");
|
|
686
|
+
var sharedManager = null;
|
|
687
|
+
var sharedFingerprint = null;
|
|
688
|
+
function sessionKeyConfigFingerprint(config) {
|
|
689
|
+
if (!config) return "";
|
|
690
|
+
return JSON.stringify({
|
|
691
|
+
defaultMaxTotalValue: config.defaultMaxTotalValue?.toString(),
|
|
692
|
+
defaultMaxTxCount: config.defaultMaxTxCount,
|
|
693
|
+
defaultExpiryMs: config.defaultExpiryMs,
|
|
694
|
+
requireAllowedContracts: config.requireAllowedContracts,
|
|
695
|
+
forbiddenMethods: config.forbiddenMethods,
|
|
696
|
+
pbkdf2Iterations: config.pbkdf2Iterations,
|
|
697
|
+
unsafeAllowWeakKdf: config.unsafeAllowWeakKdf,
|
|
698
|
+
storagePrefix: config.storagePrefix,
|
|
699
|
+
encryptionKey: config.encryptionKey,
|
|
700
|
+
encryptionSalt: config.encryptionSalt
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
function getSharedSessionKeyManager(config) {
|
|
704
|
+
const fingerprint = sessionKeyConfigFingerprint(config);
|
|
705
|
+
if (!sharedManager) {
|
|
706
|
+
const storagePrefix = config?.storagePrefix ?? import_connect_core13.DEFAULT_SESSION_KEY_CONFIG.storagePrefix;
|
|
707
|
+
sharedManager = new import_connect_core13.SessionKeyManager(
|
|
708
|
+
config,
|
|
709
|
+
typeof window !== "undefined" ? new import_connect_core13.LocalStorageAdapter(`${storagePrefix}:`) : new import_connect_core13.MemoryStorageAdapter()
|
|
710
|
+
);
|
|
711
|
+
sharedFingerprint = fingerprint;
|
|
712
|
+
return sharedManager;
|
|
713
|
+
}
|
|
714
|
+
if (fingerprint !== "" && fingerprint !== sharedFingerprint) {
|
|
715
|
+
throw new import_connect_core13.WalletError(
|
|
716
|
+
"invalid_input",
|
|
717
|
+
"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."
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
return sharedManager;
|
|
721
|
+
}
|
|
722
|
+
function resetSharedSessionKeyManager() {
|
|
723
|
+
sharedManager = null;
|
|
724
|
+
sharedFingerprint = null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/user-op-receipt.ts
|
|
728
|
+
var InvalidUserOperationReceiptError = class extends Error {
|
|
729
|
+
constructor(message) {
|
|
730
|
+
super(message);
|
|
731
|
+
this.name = "InvalidUserOperationReceiptError";
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
function isRecord(value) {
|
|
735
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
736
|
+
}
|
|
737
|
+
function isAddress(value) {
|
|
738
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value);
|
|
739
|
+
}
|
|
740
|
+
function isHash(value) {
|
|
741
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
742
|
+
}
|
|
743
|
+
function isHexData(value) {
|
|
744
|
+
return typeof value === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(value);
|
|
745
|
+
}
|
|
746
|
+
function parseQuantity(value, field) {
|
|
747
|
+
if (typeof value !== "string" || !/^(?:0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)|(?:0|[1-9][0-9]*))$/.test(value)) {
|
|
748
|
+
throw new InvalidUserOperationReceiptError(
|
|
749
|
+
`Bundler returned an invalid ${field}.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
return BigInt(value);
|
|
753
|
+
}
|
|
754
|
+
function parseUserOperationReceipt(value, expectedHash) {
|
|
755
|
+
if (!isRecord(value)) {
|
|
756
|
+
throw new InvalidUserOperationReceiptError(
|
|
757
|
+
"Bundler returned an invalid UserOperation receipt."
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
if (!isHash(value.userOpHash) || value.userOpHash.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
761
|
+
throw new InvalidUserOperationReceiptError(
|
|
762
|
+
"Bundler receipt does not match the requested UserOperation hash."
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
if (!isAddress(value.entryPoint) || !isAddress(value.sender)) {
|
|
766
|
+
throw new InvalidUserOperationReceiptError(
|
|
767
|
+
"Bundler receipt contains an invalid account address."
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (value.paymaster !== void 0 && value.paymaster !== null && !isAddress(value.paymaster)) {
|
|
771
|
+
throw new InvalidUserOperationReceiptError(
|
|
772
|
+
"Bundler receipt contains an invalid paymaster address."
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (typeof value.success !== "boolean" || !isHash(value.transactionHash)) {
|
|
776
|
+
throw new InvalidUserOperationReceiptError(
|
|
777
|
+
"Bundler receipt contains an invalid execution result."
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
if (!Array.isArray(value.logs)) {
|
|
781
|
+
throw new InvalidUserOperationReceiptError(
|
|
782
|
+
"Bundler receipt contains invalid logs."
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
const logs = value.logs.map((candidate) => {
|
|
786
|
+
if (!isRecord(candidate) || !isAddress(candidate.address) || !Array.isArray(candidate.topics) || !candidate.topics.every(isHash) || !isHexData(candidate.data)) {
|
|
787
|
+
throw new InvalidUserOperationReceiptError(
|
|
788
|
+
"Bundler receipt contains an invalid log entry."
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
address: candidate.address,
|
|
793
|
+
topics: candidate.topics,
|
|
794
|
+
data: candidate.data
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
return {
|
|
798
|
+
userOpHash: value.userOpHash,
|
|
799
|
+
entryPoint: value.entryPoint,
|
|
800
|
+
sender: value.sender,
|
|
801
|
+
nonce: parseQuantity(value.nonce, "nonce"),
|
|
802
|
+
...value.paymaster === void 0 || value.paymaster === null ? {} : { paymaster: value.paymaster },
|
|
803
|
+
actualGasUsed: parseQuantity(value.actualGasUsed, "actualGasUsed"),
|
|
804
|
+
actualGasCost: parseQuantity(value.actualGasCost, "actualGasCost"),
|
|
805
|
+
success: value.success,
|
|
806
|
+
transactionHash: value.transactionHash,
|
|
807
|
+
logs
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
async function fetchUserOperationReceipt(bundlerUrl, hash, signal) {
|
|
811
|
+
const response = await fetch(bundlerUrl, {
|
|
812
|
+
method: "POST",
|
|
813
|
+
headers: { "Content-Type": "application/json" },
|
|
814
|
+
body: JSON.stringify({
|
|
815
|
+
jsonrpc: "2.0",
|
|
816
|
+
id: 1,
|
|
817
|
+
method: "eth_getUserOperationReceipt",
|
|
818
|
+
params: [hash]
|
|
819
|
+
}),
|
|
820
|
+
signal
|
|
821
|
+
});
|
|
822
|
+
if (!response.ok) {
|
|
823
|
+
throw new Error(
|
|
824
|
+
`Bundler returned HTTP ${response.status} while reading UserOperation status.`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
const json = await response.json();
|
|
828
|
+
if (json.error) {
|
|
829
|
+
throw new Error(
|
|
830
|
+
typeof json.error.message === "string" ? json.error.message : "Bundler returned a JSON-RPC error."
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
if (json.result === void 0 || json.result === null) return null;
|
|
834
|
+
return parseUserOperationReceipt(json.result, hash);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/smart-account.ts
|
|
838
|
+
var import_connect_core14 = require("@naculus/connect-core");
|
|
839
|
+
function resolveUserOpChain(optionsChainId, connectedChainId) {
|
|
840
|
+
const chainId = resolveEvmChainId(optionsChainId, connectedChainId);
|
|
841
|
+
if (!chainId) {
|
|
842
|
+
throw new import_connect_core14.WalletError(
|
|
843
|
+
"invalid_chain",
|
|
844
|
+
optionsChainId || connectedChainId ? `Invalid EVM chain ID: ${optionsChainId ?? connectedChainId}` : "No EVM chain to send on. Connect a wallet or pass options.chainId."
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (connectedChainId && connectedChainId !== chainId) {
|
|
848
|
+
throw new import_connect_core14.WalletError(
|
|
849
|
+
"chain_mismatch",
|
|
850
|
+
`Connected chain ${connectedChainId} does not match UserOperation chain ${chainId}.`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
return chainId;
|
|
854
|
+
}
|
|
855
|
+
function buildSmartAccountConfig(owner, chainId, options = {}) {
|
|
856
|
+
return {
|
|
857
|
+
owner,
|
|
858
|
+
accountType: options.accountType ?? "simple",
|
|
859
|
+
entryPoint: options.entryPoint ?? import_connect_core14.AA_SUPPORTED_CHAINS[chainId]?.entryPoint ?? import_connect_core14.DEFAULT_ENTRY_POINT,
|
|
860
|
+
chainId,
|
|
861
|
+
salt: options.salt
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function assertHexSignature(value) {
|
|
865
|
+
if (typeof value !== "string" || !/^0x(?:[0-9a-fA-F]{2})+$/.test(value)) {
|
|
866
|
+
throw new import_connect_core14.WalletError(
|
|
867
|
+
"signature_rejected",
|
|
868
|
+
"Wallet returned an invalid UserOperation signature."
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return value;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/delegation-policy.ts
|
|
875
|
+
var import_connect_core15 = require("@naculus/connect-core");
|
|
876
|
+
function sorted(values) {
|
|
877
|
+
return values ? [...values].map((value) => value.toLowerCase()).sort() : null;
|
|
878
|
+
}
|
|
879
|
+
function sameExecutionIntent(requested, prepared) {
|
|
880
|
+
const sameHex = (left, right, empty) => {
|
|
881
|
+
const l = left === void 0 ? empty : left.toLowerCase();
|
|
882
|
+
const r = right === void 0 ? empty : right.toLowerCase();
|
|
883
|
+
return l === r;
|
|
884
|
+
};
|
|
885
|
+
const sameQuantity = (left, right) => {
|
|
886
|
+
try {
|
|
887
|
+
return BigInt(left ?? "0") === BigInt(right ?? "0");
|
|
888
|
+
} catch {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
return sameHex(requested.to, prepared.to) && sameHex(requested.data, prepared.data, "0x") && sameQuantity(requested.value, prepared.value) && requested.chainId === prepared.chainId;
|
|
893
|
+
}
|
|
894
|
+
function buildDelegationPolicyMessage(policy, origin) {
|
|
895
|
+
const tokenAllowances = policy.scope.tokenAllowances ? Object.entries(policy.scope.tokenAllowances).map(
|
|
896
|
+
([token, value]) => [token.toLowerCase(), value.toString()]
|
|
897
|
+
).sort(([left], [right]) => left.localeCompare(right)) : null;
|
|
898
|
+
const payload = {
|
|
899
|
+
version: 1,
|
|
900
|
+
origin,
|
|
901
|
+
signer: policy.signerAddress.toLowerCase(),
|
|
902
|
+
sessionId: policy.id,
|
|
903
|
+
sessionPublicKey: policy.publicKey.toLowerCase(),
|
|
904
|
+
expiresAt: new Date(policy.expiresAt).toISOString(),
|
|
905
|
+
mode: policy.scope.mode,
|
|
906
|
+
limits: {
|
|
907
|
+
maxTotalGas: policy.scope.maxTotalGas?.toString() ?? null,
|
|
908
|
+
maxGasPerTx: policy.scope.maxGasPerTx?.toString() ?? null,
|
|
909
|
+
maxTotalValue: policy.scope.maxTotalValue?.toString() ?? null,
|
|
910
|
+
maxValuePerTx: policy.scope.maxValuePerTx?.toString() ?? null,
|
|
911
|
+
maxTxCount: policy.scope.maxTxCount ?? null
|
|
912
|
+
},
|
|
913
|
+
allowedContracts: sorted(policy.scope.allowedContracts),
|
|
914
|
+
allowedMethods: sorted(policy.scope.allowedMethods),
|
|
915
|
+
allowedChainIds: policy.scope.allowedChainIds ? [...policy.scope.allowedChainIds].sort((left, right) => left - right) : null,
|
|
916
|
+
tokenAllowances
|
|
917
|
+
};
|
|
918
|
+
return `Naculus Session Policy v1
|
|
919
|
+
${JSON.stringify(payload)}`;
|
|
920
|
+
}
|
|
921
|
+
function isVerifiablePolicy(policy) {
|
|
922
|
+
return policy.status === "active" && Boolean(policy.authorized) && policy.scope.mode === "offchain";
|
|
923
|
+
}
|
|
924
|
+
function createDelegationPolicyFlow(deps) {
|
|
925
|
+
const { manager, origin } = deps;
|
|
926
|
+
const verifyStoredPolicy = async (policy) => {
|
|
927
|
+
if (!isVerifiablePolicy(policy)) return false;
|
|
928
|
+
return manager.verifyOffchainAuthorization(
|
|
929
|
+
policy.id,
|
|
930
|
+
buildDelegationPolicyMessage(policy, origin),
|
|
931
|
+
deps.verifySignature
|
|
932
|
+
);
|
|
933
|
+
};
|
|
934
|
+
const createPolicy = async (scope) => {
|
|
935
|
+
const signer = deps.signer();
|
|
936
|
+
if (!signer) {
|
|
937
|
+
throw new import_connect_core15.WalletError("wallet_unavailable", "No active EVM account");
|
|
938
|
+
}
|
|
939
|
+
if (scope.mode && scope.mode !== "offchain") {
|
|
940
|
+
throw new import_connect_core15.WalletError(
|
|
941
|
+
"method_not_allowed",
|
|
942
|
+
"This flow creates signed off-chain policies only. EIP-7702 and AA-module policies require a configured on-chain execution adapter."
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (!deps.encryptionKeyConfigured) {
|
|
946
|
+
throw new import_connect_core15.WalletError(
|
|
947
|
+
"method_not_allowed",
|
|
948
|
+
"Persistent delegation policies require a host-provided encryptionKey. The deterministic compatibility fallback is not a production key boundary."
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (!deps.storageAvailable()) {
|
|
952
|
+
throw new import_connect_core15.WalletError(
|
|
953
|
+
"storage_unavailable",
|
|
954
|
+
"Persistent delegation policy storage is unavailable in this browser"
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
let draft = null;
|
|
958
|
+
try {
|
|
959
|
+
draft = await manager.createSessionKey(
|
|
960
|
+
{ ...scope, mode: "offchain" },
|
|
961
|
+
signer
|
|
962
|
+
);
|
|
963
|
+
const message = buildDelegationPolicyMessage(draft, origin);
|
|
964
|
+
const signature = await deps.signMessage(message);
|
|
965
|
+
if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) {
|
|
966
|
+
throw new import_connect_core15.WalletError(
|
|
967
|
+
"invalid_input",
|
|
968
|
+
"Wallet did not return a valid 65-byte EVM policy signature"
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
const recovers = await deps.verifySignature({
|
|
972
|
+
message,
|
|
973
|
+
signature,
|
|
974
|
+
signerAddress: signer
|
|
975
|
+
});
|
|
976
|
+
if (!recovers) {
|
|
977
|
+
throw new import_connect_core15.WalletError(
|
|
978
|
+
"invalid_input",
|
|
979
|
+
"Policy signature does not recover to the connected EVM account"
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
await manager.setAuthorization(draft.id, {
|
|
983
|
+
signerAddress: signer,
|
|
984
|
+
type: "offchain",
|
|
985
|
+
rawSignature: signature,
|
|
986
|
+
message
|
|
987
|
+
});
|
|
988
|
+
const authorized = (await manager.listSessions()).find(
|
|
989
|
+
(policy) => policy.id === draft?.id
|
|
990
|
+
);
|
|
991
|
+
if (!authorized?.authorized) {
|
|
992
|
+
throw new import_connect_core15.WalletError(
|
|
993
|
+
"invalid_input",
|
|
994
|
+
"Policy authorization was not persisted"
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
if (!await verifyStoredPolicy(authorized)) {
|
|
998
|
+
throw new import_connect_core15.WalletError(
|
|
999
|
+
"invalid_input",
|
|
1000
|
+
"Persisted policy signature did not verify against its stored scope and origin"
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
await deps.refresh();
|
|
1004
|
+
return authorized;
|
|
1005
|
+
} catch (cause) {
|
|
1006
|
+
if (draft) {
|
|
1007
|
+
try {
|
|
1008
|
+
await manager.revokeSession(draft.id);
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
throw cause instanceof Error ? cause : new Error("Policy creation failed");
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
const revokePolicy = async (policyId) => {
|
|
1016
|
+
await manager.revokeSession(policyId);
|
|
1017
|
+
await deps.refresh();
|
|
1018
|
+
};
|
|
1019
|
+
const previewPolicyExecution = async (policyId, tx, callCount = 1, atomicity = "preferred") => {
|
|
1020
|
+
const policy = (await manager.listSessions()).find((item) => item.id === policyId) ?? null;
|
|
1021
|
+
const scope = await manager.checkSessionScope(policyId, tx);
|
|
1022
|
+
const plan = deps.previewExecution(callCount, atomicity);
|
|
1023
|
+
const signer = deps.signer();
|
|
1024
|
+
const authorizationVerified = policy ? await verifyStoredPolicy(policy) : false;
|
|
1025
|
+
const signerMatches = Boolean(
|
|
1026
|
+
policy && signer && policy.signerAddress.toLowerCase() === signer.toLowerCase()
|
|
1027
|
+
);
|
|
1028
|
+
const adapter = deps.adapter;
|
|
1029
|
+
let adapterCheck = null;
|
|
1030
|
+
if (policy && adapter && authorizationVerified && signerMatches && scope.valid) {
|
|
1031
|
+
try {
|
|
1032
|
+
adapterCheck = await adapter.check({ policy, transaction: tx });
|
|
1033
|
+
} catch (cause) {
|
|
1034
|
+
adapterCheck = {
|
|
1035
|
+
ready: false,
|
|
1036
|
+
authorizationInstalled: false,
|
|
1037
|
+
sponsored: false,
|
|
1038
|
+
promptless: false,
|
|
1039
|
+
reason: cause instanceof Error ? `Execution adapter check failed: ${cause.message}` : "Execution adapter check failed."
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const delegation = deps.delegation();
|
|
1044
|
+
const delegationMatches = Boolean(
|
|
1045
|
+
adapter?.route !== "eip7702" || delegation.delegated === true && adapterCheck?.executorAddress && delegation.delegate?.toLowerCase() === adapterCheck.executorAddress.toLowerCase()
|
|
1046
|
+
);
|
|
1047
|
+
const policyReady = Boolean(
|
|
1048
|
+
policy?.status === "active" && policy.authorized && authorizationVerified && policy.scope.mode === "offchain" && signerMatches && scope.valid
|
|
1049
|
+
);
|
|
1050
|
+
const adapterSponsorshipReady = Boolean(
|
|
1051
|
+
deps.sponsorship !== "required" || adapterCheck?.sponsored
|
|
1052
|
+
);
|
|
1053
|
+
const broadcastReady = Boolean(
|
|
1054
|
+
policyReady && callCount === 1 && adapterCheck?.ready && adapterCheck.authorizationInstalled && adapterCheck.promptless && adapterSponsorshipReady && delegationMatches
|
|
1055
|
+
);
|
|
1056
|
+
const ready = Boolean(policyReady && (plan.route || broadcastReady));
|
|
1057
|
+
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}`;
|
|
1058
|
+
return {
|
|
1059
|
+
policy,
|
|
1060
|
+
scope,
|
|
1061
|
+
execution: plan,
|
|
1062
|
+
signerMatches,
|
|
1063
|
+
authorizationVerified,
|
|
1064
|
+
ready,
|
|
1065
|
+
broadcastReady,
|
|
1066
|
+
broadcastRoute: broadcastReady ? adapter?.route ?? null : null,
|
|
1067
|
+
onchainAuthorizationVerified: Boolean(
|
|
1068
|
+
adapterCheck?.authorizationInstalled && delegationMatches
|
|
1069
|
+
),
|
|
1070
|
+
broadcastSponsored: broadcastReady ? adapterCheck?.sponsored ?? false : null,
|
|
1071
|
+
broadcastPromptless: Boolean(broadcastReady && adapterCheck?.promptless),
|
|
1072
|
+
reason
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
1075
|
+
const signVerified = (policyId, digest, tx) => manager.signWithVerifiedOffchainAuthorization(
|
|
1076
|
+
policyId,
|
|
1077
|
+
(authoritativePolicy) => buildDelegationPolicyMessage(authoritativePolicy, origin),
|
|
1078
|
+
deps.verifySignature,
|
|
1079
|
+
digest,
|
|
1080
|
+
tx
|
|
1081
|
+
);
|
|
1082
|
+
const signPolicyDigest = async (policyId, digest, tx) => {
|
|
1083
|
+
const preview = await previewPolicyExecution(policyId, tx, 1, "any");
|
|
1084
|
+
if (!preview.ready) {
|
|
1085
|
+
throw new import_connect_core15.WalletError("method_not_allowed", preview.reason);
|
|
1086
|
+
}
|
|
1087
|
+
return signVerified(policyId, digest, tx);
|
|
1088
|
+
};
|
|
1089
|
+
const executePolicy = async (policyId, tx) => {
|
|
1090
|
+
const adapter = deps.adapter;
|
|
1091
|
+
if (!adapter) {
|
|
1092
|
+
throw new import_connect_core15.WalletError(
|
|
1093
|
+
"method_not_allowed",
|
|
1094
|
+
"No session-policy-aware AA/7702 execution adapter is configured."
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const preview = await previewPolicyExecution(policyId, tx, 1, "any");
|
|
1098
|
+
if (!preview.broadcastReady || !preview.policy) {
|
|
1099
|
+
throw new import_connect_core15.WalletError("method_not_allowed", preview.reason);
|
|
1100
|
+
}
|
|
1101
|
+
const prepared = await adapter.prepare({
|
|
1102
|
+
policy: preview.policy,
|
|
1103
|
+
transaction: tx
|
|
1104
|
+
});
|
|
1105
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(prepared.digest)) {
|
|
1106
|
+
throw new import_connect_core15.WalletError(
|
|
1107
|
+
"invalid_input",
|
|
1108
|
+
"Execution adapter returned a digest that is not 32 bytes."
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
if (!sameExecutionIntent(tx, prepared.transaction)) {
|
|
1112
|
+
throw new import_connect_core15.WalletError(
|
|
1113
|
+
"invalid_input",
|
|
1114
|
+
"Execution adapter changed the requested target, value, data, or chain."
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
const currentCheck = await adapter.check({
|
|
1118
|
+
policy: preview.policy,
|
|
1119
|
+
transaction: prepared.transaction
|
|
1120
|
+
});
|
|
1121
|
+
const delegation = deps.delegation();
|
|
1122
|
+
const currentDelegationMatches = adapter.route !== "eip7702" || delegation.delegated === true && currentCheck.executorAddress !== void 0 && delegation.delegate?.toLowerCase() === currentCheck.executorAddress.toLowerCase();
|
|
1123
|
+
if (!currentCheck.ready || !currentCheck.authorizationInstalled || !currentCheck.promptless || deps.sponsorship === "required" && !currentCheck.sponsored || !currentDelegationMatches) {
|
|
1124
|
+
throw new import_connect_core15.WalletError(
|
|
1125
|
+
"method_not_allowed",
|
|
1126
|
+
currentCheck.reason || "The on-chain policy authorization is not ready."
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
const signature = await signVerified(
|
|
1130
|
+
policyId,
|
|
1131
|
+
prepared.digest,
|
|
1132
|
+
prepared.transaction
|
|
1133
|
+
);
|
|
1134
|
+
const submission = await adapter.broadcast({
|
|
1135
|
+
policy: preview.policy,
|
|
1136
|
+
prepared,
|
|
1137
|
+
signature
|
|
1138
|
+
});
|
|
1139
|
+
if (submission.route !== adapter.route || submission.status !== "submitted" || !/^0x[0-9a-fA-F]{64}$/.test(submission.hash)) {
|
|
1140
|
+
throw new import_connect_core15.WalletError(
|
|
1141
|
+
"invalid_input",
|
|
1142
|
+
"Execution adapter returned an invalid submission result."
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
await deps.refresh();
|
|
1146
|
+
return submission;
|
|
1147
|
+
};
|
|
1148
|
+
return {
|
|
1149
|
+
verifyStoredPolicy,
|
|
1150
|
+
createPolicy,
|
|
1151
|
+
revokePolicy,
|
|
1152
|
+
previewPolicyExecution,
|
|
1153
|
+
signPolicyDigest,
|
|
1154
|
+
executePolicy
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
463
1158
|
// src/web3-store.ts
|
|
464
|
-
var
|
|
1159
|
+
var import_connect_core16 = require("@naculus/connect-core");
|
|
465
1160
|
function withTimeout(promise, ms, label = "Operation") {
|
|
466
1161
|
let timeoutId;
|
|
467
1162
|
const timeout = new Promise((_, reject) => {
|
|
@@ -483,7 +1178,7 @@ async function withRetry(fn, options) {
|
|
|
483
1178
|
lastError = error;
|
|
484
1179
|
if (attempt < options.maxRetries) {
|
|
485
1180
|
const delay = options.baseDelay * 2 ** attempt;
|
|
486
|
-
|
|
1181
|
+
import_connect_core16.logger.warn(
|
|
487
1182
|
"appkit/core",
|
|
488
1183
|
`Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`
|
|
489
1184
|
);
|
|
@@ -548,14 +1243,58 @@ function web3Reducer(state, action) {
|
|
|
548
1243
|
return state;
|
|
549
1244
|
}
|
|
550
1245
|
}
|
|
1246
|
+
|
|
1247
|
+
// src/siwx-policy.ts
|
|
1248
|
+
function defaultSiwxDomain(location) {
|
|
1249
|
+
return location?.host ?? "localhost";
|
|
1250
|
+
}
|
|
1251
|
+
function defaultSiwxUri(location) {
|
|
1252
|
+
return location?.origin ?? "http://localhost";
|
|
1253
|
+
}
|
|
1254
|
+
function isSiwxExpired(result, now) {
|
|
1255
|
+
const value = result.message.expirationTime;
|
|
1256
|
+
if (!value) return false;
|
|
1257
|
+
const timestamp = new Date(value).getTime();
|
|
1258
|
+
return !Number.isFinite(timestamp) || timestamp <= now.getTime();
|
|
1259
|
+
}
|
|
1260
|
+
function isSiwxNotBeforeValid(result, now) {
|
|
1261
|
+
const value = result.message.notBefore;
|
|
1262
|
+
if (!value) return true;
|
|
1263
|
+
const timestamp = new Date(value).getTime();
|
|
1264
|
+
return Number.isFinite(timestamp) && timestamp <= now.getTime();
|
|
1265
|
+
}
|
|
1266
|
+
function randomSuffix() {
|
|
1267
|
+
const bytes = new Uint8Array(8);
|
|
1268
|
+
crypto.getRandomValues(bytes);
|
|
1269
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
1270
|
+
""
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
|
|
1274
|
+
return {
|
|
1275
|
+
id: `siwx_${now.getTime().toString(36)}_${randomSuffix()}`,
|
|
1276
|
+
chainId: result.message.chainId,
|
|
1277
|
+
address: result.message.address,
|
|
1278
|
+
domain: result.message.domain,
|
|
1279
|
+
message: result.message,
|
|
1280
|
+
signature: result.signature,
|
|
1281
|
+
issuedAt: result.message.issuedAt ?? now.toISOString(),
|
|
1282
|
+
expiresAt: result.message.expirationTime ?? null,
|
|
1283
|
+
refreshedAt: null
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
551
1286
|
// Annotate the CommonJS export names for ESM import in node:
|
|
552
1287
|
0 && (module.exports = {
|
|
553
1288
|
DEFAULT_NOTIFICATION_SETTINGS,
|
|
1289
|
+
InvalidUserOperationReceiptError,
|
|
554
1290
|
NOTIFICATION_SETTINGS_KEY,
|
|
555
1291
|
__resetResolverForTests,
|
|
556
1292
|
addInAppNotification,
|
|
1293
|
+
assertHexSignature,
|
|
557
1294
|
atomicSupportFor,
|
|
558
1295
|
bareEvmAddress,
|
|
1296
|
+
buildDelegationPolicyMessage,
|
|
1297
|
+
buildSmartAccountConfig,
|
|
559
1298
|
chainNamespace,
|
|
560
1299
|
chainNumber,
|
|
561
1300
|
chainsForNamespace,
|
|
@@ -563,28 +1302,48 @@ function web3Reducer(state, action) {
|
|
|
563
1302
|
clearOneInAppNotification,
|
|
564
1303
|
compareCostsKey,
|
|
565
1304
|
createDecimalsCache,
|
|
1305
|
+
createDelegationPolicyFlow,
|
|
566
1306
|
createNotification,
|
|
567
1307
|
createNotificationSettingsStorage,
|
|
568
1308
|
decodeRevertReason,
|
|
1309
|
+
defaultSiwxDomain,
|
|
1310
|
+
defaultSiwxUri,
|
|
569
1311
|
describeChain,
|
|
570
1312
|
eip155ChainIdToNumber,
|
|
1313
|
+
encodeErc20Approve,
|
|
1314
|
+
encodeErc20Transfer,
|
|
1315
|
+
encodeErc20TransferFrom,
|
|
1316
|
+
fetchUserOperationReceipt,
|
|
571
1317
|
getResolver,
|
|
1318
|
+
getSharedSessionKeyManager,
|
|
572
1319
|
initialWeb3State,
|
|
573
1320
|
isBurnDestination,
|
|
574
1321
|
isEvmChainId,
|
|
575
1322
|
isPositiveIntegerAmount,
|
|
576
1323
|
isQuotableInput,
|
|
1324
|
+
isSiwxExpired,
|
|
1325
|
+
isSiwxNotBeforeValid,
|
|
1326
|
+
isVerifiablePolicy,
|
|
577
1327
|
loadNotificationSettings,
|
|
578
1328
|
mutedChainSettings,
|
|
579
1329
|
normalizeCapabilities,
|
|
580
1330
|
normalizeEip155ChainId,
|
|
1331
|
+
parseUserOperationReceipt,
|
|
581
1332
|
readDecimals,
|
|
582
1333
|
readSolanaRoles,
|
|
1334
|
+
resetSharedSessionKeyManager,
|
|
583
1335
|
resolveChain,
|
|
1336
|
+
resolveContextSimulationEndpoint,
|
|
584
1337
|
resolveEvmChainId,
|
|
585
1338
|
resolveRevertReason,
|
|
1339
|
+
resolveSimulationEndpoint,
|
|
1340
|
+
resolveUserOpChain,
|
|
1341
|
+
sameExecutionIntent,
|
|
586
1342
|
saveNotificationSettings,
|
|
587
1343
|
selectChainCapabilities,
|
|
1344
|
+
sessionKeyConfigFingerprint,
|
|
1345
|
+
simulateTransactionPreview,
|
|
1346
|
+
siwxResultToSession,
|
|
588
1347
|
toChainSwitchError,
|
|
589
1348
|
toEip155Accounts,
|
|
590
1349
|
toExecuteRouteError,
|