@naculus/connect-appkit-core 0.2.8 → 0.4.0

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.js CHANGED
@@ -793,8 +793,112 @@ function assertHexSignature(value) {
793
793
  return value;
794
794
  }
795
795
 
796
+ // src/delegation-framework-adapter.ts
797
+ import {
798
+ DELEGATION_FRAMEWORK,
799
+ sessionKeyAddress,
800
+ WalletError as WalletError7
801
+ } from "@naculus/connect-core";
802
+ function createDelegationFrameworkAdapter(options) {
803
+ const { manager, rpc, codec } = options;
804
+ return {
805
+ route: "eip7702",
806
+ async check({ policy }) {
807
+ const ready = policy.scope.mode === "eip7702" && policy.status === "active" && Boolean(policy.authorized);
808
+ return {
809
+ ready,
810
+ authorizationInstalled: ready,
811
+ sponsored: false,
812
+ promptless: true,
813
+ executorAddress: DELEGATION_FRAMEWORK.eip7702StatelessDeleGator,
814
+ reason: ready ? "The session key redeems its delegation; the chain enforces the caveats." : "This policy has no attached EIP-7702 delegation."
815
+ };
816
+ },
817
+ async prepare({ policy, transaction }) {
818
+ if (!transaction.to) {
819
+ throw new WalletError7(
820
+ "invalid_input",
821
+ "A delegated execution needs a target."
822
+ );
823
+ }
824
+ let value;
825
+ try {
826
+ value = BigInt(transaction.value ?? "0");
827
+ } catch {
828
+ throw new WalletError7(
829
+ "invalid_input",
830
+ "Transaction value is not a quantity."
831
+ );
832
+ }
833
+ const execution = {
834
+ target: transaction.to,
835
+ value,
836
+ callData: transaction.data ?? "0x"
837
+ };
838
+ const call = await manager.buildDelegationRedemption(
839
+ policy.id,
840
+ execution
841
+ );
842
+ const chainId = options.chainId();
843
+ if (chainId !== call.chainId) {
844
+ throw new WalletError7(
845
+ "chain_mismatch",
846
+ `The delegation is for chain ${call.chainId}, not ${String(chainId)}.`
847
+ );
848
+ }
849
+ const from = sessionKeyAddress(policy.publicKey);
850
+ const [nonce, gas, fees] = await Promise.all([
851
+ rpc.getTransactionCount(from),
852
+ rpc.estimateGas({ from, to: call.to, data: call.data }),
853
+ rpc.fees()
854
+ ]);
855
+ const unsigned = {
856
+ type: "eip1559",
857
+ chainId,
858
+ nonce,
859
+ to: call.to,
860
+ value: 0n,
861
+ data: call.data,
862
+ gas,
863
+ maxFeePerGas: fees.maxFeePerGas,
864
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas
865
+ };
866
+ const payload = {
867
+ outerTx: {
868
+ to: call.to,
869
+ value: "0x0",
870
+ data: call.data,
871
+ chainId,
872
+ gas: `0x${gas.toString(16)}`
873
+ },
874
+ execution,
875
+ unsigned
876
+ };
877
+ return {
878
+ digest: codec.signingHash(unsigned),
879
+ transaction,
880
+ payload
881
+ };
882
+ },
883
+ async broadcast({ prepared, signature }) {
884
+ const { unsigned } = prepared.payload;
885
+ const hash = await rpc.sendRawTransaction(
886
+ codec.serialize(unsigned, signature)
887
+ );
888
+ return { route: "eip7702", hash, status: "submitted" };
889
+ }
890
+ };
891
+ }
892
+
796
893
  // src/delegation-policy.ts
797
- import { WalletError as WalletError7 } from "@naculus/connect-core";
894
+ import {
895
+ DELEGATION_FRAMEWORK as DELEGATION_FRAMEWORK2,
896
+ delegationTypedData,
897
+ WalletError as WalletError8
898
+ } from "@naculus/connect-core";
899
+ function delegatesToStatelessDeleGator(delegation) {
900
+ return delegation.delegated === true && delegation.delegate?.toLowerCase() === DELEGATION_FRAMEWORK2.eip7702StatelessDeleGator.toLowerCase();
901
+ }
798
902
  function sorted(values) {
799
903
  return values ? [...values].map((value) => value.toLowerCase()).sort() : null;
800
904
  }
@@ -841,12 +945,13 @@ function buildDelegationPolicyMessage(policy, origin) {
841
945
  ${JSON.stringify(payload)}`;
842
946
  }
843
947
  function isVerifiablePolicy(policy) {
844
- return policy.status === "active" && Boolean(policy.authorized) && policy.scope.mode === "offchain";
948
+ return policy.status === "active" && Boolean(policy.authorized) && (policy.scope.mode === "offchain" || policy.scope.mode === "eip7702");
845
949
  }
846
950
  function createDelegationPolicyFlow(deps) {
847
951
  const { manager, origin } = deps;
848
952
  const verifyStoredPolicy = async (policy) => {
849
953
  if (!isVerifiablePolicy(policy)) return false;
954
+ if (policy.scope.mode === "eip7702") return true;
850
955
  return manager.verifyOffchainAuthorization(
851
956
  policy.id,
852
957
  buildDelegationPolicyMessage(policy, origin),
@@ -856,28 +961,29 @@ function createDelegationPolicyFlow(deps) {
856
961
  const createPolicy = async (scope) => {
857
962
  const signer = deps.signer();
858
963
  if (!signer) {
859
- throw new WalletError7("wallet_unavailable", "No active EVM account");
964
+ throw new WalletError8("wallet_unavailable", "No active EVM account");
860
965
  }
966
+ if (scope.mode === "eip7702") return createDelegatedPolicy(scope, signer);
861
967
  if (scope.mode && scope.mode !== "offchain") {
862
- throw new WalletError7(
968
+ throw new WalletError8(
863
969
  "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."
970
+ "This flow creates off-chain and EIP-7702 policies only; AA-module policies need an on-chain module."
865
971
  );
866
972
  }
867
973
  if (scope.allowedRecipients && scope.allowedRecipients.length > 0) {
868
- throw new WalletError7(
974
+ throw new WalletError8(
869
975
  "method_not_allowed",
870
976
  "Recipient-limited policies are not supported by this flow: its executions sign raw digests, which connect-core refuses while allowedRecipients is set."
871
977
  );
872
978
  }
873
979
  if (!deps.encryptionKeyConfigured) {
874
- throw new WalletError7(
980
+ throw new WalletError8(
875
981
  "method_not_allowed",
876
982
  "Persistent delegation policies require a host-provided encryptionKey. The deterministic compatibility fallback is not a production key boundary."
877
983
  );
878
984
  }
879
985
  if (!deps.storageAvailable()) {
880
- throw new WalletError7(
986
+ throw new WalletError8(
881
987
  "storage_unavailable",
882
988
  "Persistent delegation policy storage is unavailable in this browser"
883
989
  );
@@ -891,7 +997,7 @@ function createDelegationPolicyFlow(deps) {
891
997
  const message = buildDelegationPolicyMessage(draft, origin);
892
998
  const signature = await deps.signMessage(message);
893
999
  if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) {
894
- throw new WalletError7(
1000
+ throw new WalletError8(
895
1001
  "invalid_input",
896
1002
  "Wallet did not return a valid 65-byte EVM policy signature"
897
1003
  );
@@ -902,7 +1008,7 @@ function createDelegationPolicyFlow(deps) {
902
1008
  signerAddress: signer
903
1009
  });
904
1010
  if (!recovers) {
905
- throw new WalletError7(
1011
+ throw new WalletError8(
906
1012
  "invalid_input",
907
1013
  "Policy signature does not recover to the connected EVM account"
908
1014
  );
@@ -917,13 +1023,13 @@ function createDelegationPolicyFlow(deps) {
917
1023
  (policy) => policy.id === draft?.id
918
1024
  );
919
1025
  if (!authorized?.authorized) {
920
- throw new WalletError7(
1026
+ throw new WalletError8(
921
1027
  "invalid_input",
922
1028
  "Policy authorization was not persisted"
923
1029
  );
924
1030
  }
925
1031
  if (!await verifyStoredPolicy(authorized)) {
926
- throw new WalletError7(
1032
+ throw new WalletError8(
927
1033
  "invalid_input",
928
1034
  "Persisted policy signature did not verify against its stored scope and origin"
929
1035
  );
@@ -940,6 +1046,66 @@ function createDelegationPolicyFlow(deps) {
940
1046
  throw cause instanceof Error ? cause : new Error("Policy creation failed");
941
1047
  }
942
1048
  };
1049
+ const createDelegatedPolicy = async (scope, signer) => {
1050
+ if (!deps.signTypedData || !deps.chainId) {
1051
+ throw new WalletError8(
1052
+ "method_not_allowed",
1053
+ "EIP-7702 policies need the wallet's signTypedData and the current chain."
1054
+ );
1055
+ }
1056
+ if (!deps.encryptionKeyConfigured) {
1057
+ throw new WalletError8(
1058
+ "method_not_allowed",
1059
+ "Persistent delegation policies require a host-provided encryptionKey. The deterministic compatibility fallback is not a production key boundary."
1060
+ );
1061
+ }
1062
+ if (!deps.storageAvailable()) {
1063
+ throw new WalletError8(
1064
+ "storage_unavailable",
1065
+ "Persistent delegation policy storage is unavailable in this browser"
1066
+ );
1067
+ }
1068
+ if (!delegatesToStatelessDeleGator(deps.delegation())) {
1069
+ throw new WalletError8(
1070
+ "method_not_allowed",
1071
+ `The account must first delegate to EIP7702StatelessDeleGatorImpl (${DELEGATION_FRAMEWORK2.eip7702StatelessDeleGator}).`
1072
+ );
1073
+ }
1074
+ const chainId = deps.chainId();
1075
+ if (chainId === null || !Number.isSafeInteger(chainId)) {
1076
+ throw new WalletError8("wallet_unavailable", "No connected EVM chain");
1077
+ }
1078
+ let draft = null;
1079
+ try {
1080
+ draft = await manager.createSessionKey(
1081
+ { ...scope, mode: "eip7702" },
1082
+ signer
1083
+ );
1084
+ const delegation = await manager.prepareDelegation(draft.id, chainId);
1085
+ const signature = await deps.signTypedData(
1086
+ delegationTypedData(delegation)
1087
+ );
1088
+ if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) {
1089
+ throw new WalletError8(
1090
+ "invalid_input",
1091
+ "Wallet did not return a valid 65-byte EVM signature"
1092
+ );
1093
+ }
1094
+ await manager.attachDelegation(
1095
+ draft.id,
1096
+ delegation,
1097
+ signature
1098
+ );
1099
+ await deps.refresh();
1100
+ return (await manager.listSessions()).find((s) => s.id === draft?.id) ?? draft;
1101
+ } catch (cause) {
1102
+ if (draft) await manager.revokeSession(draft.id).catch(() => {
1103
+ });
1104
+ await deps.refresh().catch(() => {
1105
+ });
1106
+ throw cause;
1107
+ }
1108
+ };
943
1109
  const revokePolicy = async (policyId) => {
944
1110
  await manager.revokeSession(policyId);
945
1111
  await deps.refresh();
@@ -972,16 +1138,21 @@ function createDelegationPolicyFlow(deps) {
972
1138
  const delegationMatches = Boolean(
973
1139
  adapter?.route !== "eip7702" || delegation.delegated === true && adapterCheck?.executorAddress && delegation.delegate?.toLowerCase() === adapterCheck.executorAddress.toLowerCase()
974
1140
  );
1141
+ const delegatedPolicy = policy?.scope.mode === "eip7702";
975
1142
  const policyReady = Boolean(
976
- policy?.status === "active" && policy.authorized && authorizationVerified && policy.scope.mode === "offchain" && signerMatches && scope.valid
1143
+ policy?.status === "active" && policy.authorized && authorizationVerified && (policy.scope.mode === "offchain" || delegatedPolicy) && signerMatches && scope.valid
977
1144
  );
978
1145
  const adapterSponsorshipReady = Boolean(
979
1146
  deps.sponsorship !== "required" || adapterCheck?.sponsored
980
1147
  );
981
1148
  const broadcastReady = Boolean(
982
- policyReady && callCount === 1 && adapterCheck?.ready && adapterCheck.authorizationInstalled && adapterCheck.promptless && adapterSponsorshipReady && delegationMatches
1149
+ policyReady && callCount === 1 && adapterCheck?.ready && adapterCheck.authorizationInstalled && adapterCheck.promptless && adapterSponsorshipReady && delegationMatches && // An eip7702 policy executes only through a redemption: the key
1150
+ // signs nothing else, so only an eip7702 adapter can carry it.
1151
+ (!delegatedPolicy || adapter?.route === "eip7702")
1152
+ );
1153
+ const ready = Boolean(
1154
+ policyReady && (delegatedPolicy ? broadcastReady : plan.route || broadcastReady)
983
1155
  );
984
- const ready = Boolean(policyReady && (plan.route || broadcastReady));
985
1156
  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}`;
986
1157
  return {
987
1158
  policy,
@@ -1007,37 +1178,76 @@ function createDelegationPolicyFlow(deps) {
1007
1178
  digest,
1008
1179
  tx
1009
1180
  );
1181
+ const signRedemption = (policyId, prepared) => {
1182
+ const payload = prepared.payload;
1183
+ if (!payload?.outerTx || !payload.execution) {
1184
+ throw new WalletError8(
1185
+ "invalid_input",
1186
+ "The EIP-7702 adapter did not return a redemption to sign."
1187
+ );
1188
+ }
1189
+ if (prepared.transaction.chainId !== void 0 && prepared.transaction.chainId !== payload.outerTx.chainId) {
1190
+ throw new WalletError8(
1191
+ "chain_mismatch",
1192
+ `The delegation is for chain ${String(payload.outerTx.chainId)}, not ${prepared.transaction.chainId}.`
1193
+ );
1194
+ }
1195
+ const { execution } = payload;
1196
+ if (typeof execution.value !== "bigint" || execution.value < 0n || !sameExecutionIntent(prepared.transaction, {
1197
+ to: execution.target,
1198
+ data: execution.callData,
1199
+ value: `0x${execution.value.toString(16)}`,
1200
+ chainId: prepared.transaction.chainId
1201
+ })) {
1202
+ throw new WalletError8(
1203
+ "invalid_input",
1204
+ "The EIP-7702 adapter's execution differs from the requested transaction."
1205
+ );
1206
+ }
1207
+ return manager.signDelegationRedemption(
1208
+ policyId,
1209
+ prepared.digest,
1210
+ payload.outerTx,
1211
+ payload.execution
1212
+ );
1213
+ };
1010
1214
  const signPolicyDigest = async (policyId, digest, tx) => {
1011
1215
  const preview = await previewPolicyExecution(policyId, tx, 1, "any");
1216
+ if (preview.policy?.scope.mode === "eip7702") {
1217
+ throw new WalletError8(
1218
+ "method_not_allowed",
1219
+ "An EIP-7702 policy signs only its delegation redemptions; use executePolicy."
1220
+ );
1221
+ }
1012
1222
  if (!preview.ready) {
1013
- throw new WalletError7("method_not_allowed", preview.reason);
1223
+ throw new WalletError8("method_not_allowed", preview.reason);
1014
1224
  }
1015
1225
  return signVerified(policyId, digest, tx);
1016
1226
  };
1017
1227
  const executePolicy = async (policyId, tx) => {
1018
1228
  const adapter = deps.adapter;
1019
1229
  if (!adapter) {
1020
- throw new WalletError7(
1230
+ throw new WalletError8(
1021
1231
  "method_not_allowed",
1022
1232
  "No session-policy-aware AA/7702 execution adapter is configured."
1023
1233
  );
1024
1234
  }
1025
1235
  const preview = await previewPolicyExecution(policyId, tx, 1, "any");
1026
1236
  if (!preview.broadcastReady || !preview.policy) {
1027
- throw new WalletError7("method_not_allowed", preview.reason);
1237
+ throw new WalletError8("method_not_allowed", preview.reason);
1028
1238
  }
1029
1239
  const prepared = await adapter.prepare({
1030
1240
  policy: preview.policy,
1031
1241
  transaction: tx
1032
1242
  });
1033
1243
  if (!/^0x[0-9a-fA-F]{64}$/.test(prepared.digest)) {
1034
- throw new WalletError7(
1244
+ throw new WalletError8(
1035
1245
  "invalid_input",
1036
1246
  "Execution adapter returned a digest that is not 32 bytes."
1037
1247
  );
1038
1248
  }
1039
1249
  if (!sameExecutionIntent(tx, prepared.transaction)) {
1040
- throw new WalletError7(
1250
+ throw new WalletError8(
1041
1251
  "invalid_input",
1042
1252
  "Execution adapter changed the requested target, value, data, or chain."
1043
1253
  );
@@ -1049,23 +1259,19 @@ function createDelegationPolicyFlow(deps) {
1049
1259
  const delegation = deps.delegation();
1050
1260
  const currentDelegationMatches = adapter.route !== "eip7702" || delegation.delegated === true && currentCheck.executorAddress !== void 0 && delegation.delegate?.toLowerCase() === currentCheck.executorAddress.toLowerCase();
1051
1261
  if (!currentCheck.ready || !currentCheck.authorizationInstalled || !currentCheck.promptless || deps.sponsorship === "required" && !currentCheck.sponsored || !currentDelegationMatches) {
1052
- throw new WalletError7(
1262
+ throw new WalletError8(
1053
1263
  "method_not_allowed",
1054
1264
  currentCheck.reason || "The on-chain policy authorization is not ready."
1055
1265
  );
1056
1266
  }
1057
- const signature = await signVerified(
1058
- policyId,
1059
- prepared.digest,
1060
- prepared.transaction
1061
- );
1267
+ const signature = preview.policy.scope.mode === "eip7702" ? await signRedemption(policyId, prepared) : await signVerified(policyId, prepared.digest, prepared.transaction);
1062
1268
  const submission = await adapter.broadcast({
1063
1269
  policy: preview.policy,
1064
1270
  prepared,
1065
1271
  signature
1066
1272
  });
1067
1273
  if (submission.route !== adapter.route || submission.status !== "submitted" || !/^0x[0-9a-fA-F]{64}$/.test(submission.hash)) {
1068
- throw new WalletError7(
1274
+ throw new WalletError8(
1069
1275
  "invalid_input",
1070
1276
  "Execution adapter returned an invalid submission result."
1071
1277
  );
@@ -1083,6 +1289,64 @@ function createDelegationPolicyFlow(deps) {
1083
1289
  };
1084
1290
  }
1085
1291
 
1292
+ // src/payment-fetch.ts
1293
+ function record(value) {
1294
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1295
+ }
1296
+ function text(value) {
1297
+ return typeof value === "string" && value !== "" ? value : null;
1298
+ }
1299
+ function requestUrl(input) {
1300
+ if (input instanceof URL) return input.href;
1301
+ if (typeof input !== "string") return input.url;
1302
+ try {
1303
+ return new Request(input).url;
1304
+ } catch {
1305
+ return input;
1306
+ }
1307
+ }
1308
+ function describePayment(input, result) {
1309
+ const paid = record(result.paid);
1310
+ if (!paid) return null;
1311
+ const resource = requestUrl(input);
1312
+ if (typeof paid.scheme === "string" && typeof paid.network === "string") {
1313
+ const settlement = record(result.settlement);
1314
+ return {
1315
+ protocol: "x402",
1316
+ resource,
1317
+ chainId: text(paid.network),
1318
+ asset: text(paid.asset),
1319
+ payTo: text(paid.payTo),
1320
+ amount: text(paid.amount),
1321
+ // A hash reported for another chain is not this payment's.
1322
+ reference: settlement?.success === true && settlement.network === paid.network ? text(settlement.transaction) : null
1323
+ };
1324
+ }
1325
+ const challenge = record(record(paid.challenge)?.params);
1326
+ const request = record(paid.request);
1327
+ if (challenge && request) {
1328
+ const chainId = challenge.method === "evm" && typeof request.chainId === "number" && Number.isSafeInteger(request.chainId) ? `eip155:${request.chainId}` : challenge.method === "solana" && typeof request.network === "string" && /^solana:[1-9A-HJ-NP-Za-km-z]{32}$/.test(request.network) ? request.network : null;
1329
+ return {
1330
+ protocol: "mpp",
1331
+ resource,
1332
+ chainId,
1333
+ asset: text(request.currency),
1334
+ payTo: text(request.recipient),
1335
+ amount: text(request.amount),
1336
+ reference: text(record(result.receipt)?.reference)
1337
+ };
1338
+ }
1339
+ return {
1340
+ protocol: "unknown",
1341
+ resource,
1342
+ chainId: null,
1343
+ asset: null,
1344
+ payTo: null,
1345
+ amount: null,
1346
+ reference: null
1347
+ };
1348
+ }
1349
+
1086
1350
  // src/web3-store.ts
1087
1351
  import { logger } from "@naculus/connect-core";
1088
1352
  function withTimeout(promise, ms, label = "Operation") {
@@ -1229,6 +1493,7 @@ export {
1229
1493
  clearOneInAppNotification,
1230
1494
  compareCostsKey,
1231
1495
  createDecimalsCache,
1496
+ createDelegationFrameworkAdapter,
1232
1497
  createDelegationPolicyFlow,
1233
1498
  createNotification,
1234
1499
  createNotificationSettingsStorage,
@@ -1236,6 +1501,7 @@ export {
1236
1501
  defaultSiwxDomain,
1237
1502
  defaultSiwxUri,
1238
1503
  describeChain,
1504
+ describePayment,
1239
1505
  eip155ChainIdToNumber,
1240
1506
  encodeErc20Approve,
1241
1507
  encodeErc20Transfer,