@rhinestone/sdk 2.0.0-beta.4 → 2.0.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/src/accounts/error.d.ts +2 -9
  2. package/dist/src/accounts/error.d.ts.map +1 -1
  3. package/dist/src/accounts/error.js +5 -11
  4. package/dist/src/accounts/hca.d.ts +25 -0
  5. package/dist/src/accounts/hca.d.ts.map +1 -0
  6. package/dist/src/accounts/hca.js +148 -0
  7. package/dist/src/accounts/index.d.ts +23 -10
  8. package/dist/src/accounts/index.d.ts.map +1 -1
  9. package/dist/src/accounts/index.js +170 -79
  10. package/dist/src/accounts/kernel.d.ts +1 -2
  11. package/dist/src/accounts/kernel.d.ts.map +1 -1
  12. package/dist/src/accounts/kernel.js +1 -8
  13. package/dist/src/accounts/nexus.d.ts +3 -2
  14. package/dist/src/accounts/nexus.d.ts.map +1 -1
  15. package/dist/src/accounts/nexus.js +33 -8
  16. package/dist/src/accounts/safe.d.ts +1 -2
  17. package/dist/src/accounts/safe.d.ts.map +1 -1
  18. package/dist/src/accounts/safe.js +3 -9
  19. package/dist/src/accounts/signing/common.d.ts +10 -4
  20. package/dist/src/accounts/signing/common.d.ts.map +1 -1
  21. package/dist/src/accounts/signing/common.js +48 -46
  22. package/dist/src/accounts/signing/message.d.ts.map +1 -1
  23. package/dist/src/accounts/signing/message.js +1 -4
  24. package/dist/src/accounts/signing/typedData.d.ts.map +1 -1
  25. package/dist/src/accounts/signing/typedData.js +1 -4
  26. package/dist/src/accounts/startale.d.ts +1 -2
  27. package/dist/src/accounts/startale.d.ts.map +1 -1
  28. package/dist/src/accounts/startale.js +6 -9
  29. package/dist/src/actions/ecdsa.js +5 -5
  30. package/dist/src/actions/index.js +2 -2
  31. package/dist/src/actions/mfa.js +2 -2
  32. package/dist/src/actions/passkeys.js +2 -2
  33. package/dist/src/actions/smart-sessions.d.ts +29 -5
  34. package/dist/src/actions/smart-sessions.d.ts.map +1 -1
  35. package/dist/src/actions/smart-sessions.js +38 -10
  36. package/dist/src/errors/index.d.ts +6 -4
  37. package/dist/src/errors/index.d.ts.map +1 -1
  38. package/dist/src/errors/index.js +6 -6
  39. package/dist/src/execution/error.d.ts +42 -11
  40. package/dist/src/execution/error.d.ts.map +1 -1
  41. package/dist/src/execution/error.js +53 -15
  42. package/dist/src/execution/index.d.ts +18 -18
  43. package/dist/src/execution/index.d.ts.map +1 -1
  44. package/dist/src/execution/index.js +27 -42
  45. package/dist/src/execution/utils.d.ts +55 -14
  46. package/dist/src/execution/utils.d.ts.map +1 -1
  47. package/dist/src/execution/utils.js +629 -82
  48. package/dist/src/index.d.ts +270 -38
  49. package/dist/src/index.d.ts.map +1 -1
  50. package/dist/src/index.js +89 -116
  51. package/dist/src/jwt-server/jcs.d.ts +1 -1
  52. package/dist/src/jwt-server/jcs.js +4 -2
  53. package/dist/src/modules/index.d.ts.map +1 -1
  54. package/dist/src/modules/index.js +0 -5
  55. package/dist/src/modules/read.d.ts +3 -2
  56. package/dist/src/modules/read.d.ts.map +1 -1
  57. package/dist/src/modules/read.js +52 -4
  58. package/dist/src/modules/validators/core.d.ts +6 -6
  59. package/dist/src/modules/validators/core.d.ts.map +1 -1
  60. package/dist/src/modules/validators/core.js +42 -39
  61. package/dist/src/modules/validators/cross-chain-permits.d.ts +21 -0
  62. package/dist/src/modules/validators/cross-chain-permits.d.ts.map +1 -0
  63. package/dist/src/modules/validators/cross-chain-permits.js +111 -0
  64. package/dist/src/modules/validators/permissions.d.ts +1 -0
  65. package/dist/src/modules/validators/permissions.d.ts.map +1 -1
  66. package/dist/src/modules/validators/permissions.js +192 -18
  67. package/dist/src/modules/validators/policies/claim/arbiters.d.ts +22 -0
  68. package/dist/src/modules/validators/policies/claim/arbiters.d.ts.map +1 -0
  69. package/dist/src/modules/validators/policies/claim/arbiters.js +57 -0
  70. package/dist/src/modules/validators/smart-sessions.d.ts +38 -9
  71. package/dist/src/modules/validators/smart-sessions.d.ts.map +1 -1
  72. package/dist/src/modules/validators/smart-sessions.js +509 -92
  73. package/dist/src/orchestrator/caip2.d.ts +16 -3
  74. package/dist/src/orchestrator/caip2.d.ts.map +1 -1
  75. package/dist/src/orchestrator/caip2.js +38 -6
  76. package/dist/src/orchestrator/client.d.ts +2 -1
  77. package/dist/src/orchestrator/client.d.ts.map +1 -1
  78. package/dist/src/orchestrator/client.js +50 -24
  79. package/dist/src/orchestrator/consts.d.ts +1 -1
  80. package/dist/src/orchestrator/consts.d.ts.map +1 -1
  81. package/dist/src/orchestrator/consts.js +1 -1
  82. package/dist/src/orchestrator/destinations.d.ts +24 -0
  83. package/dist/src/orchestrator/destinations.d.ts.map +1 -0
  84. package/dist/src/orchestrator/destinations.js +59 -0
  85. package/dist/src/orchestrator/error.d.ts +95 -5
  86. package/dist/src/orchestrator/error.d.ts.map +1 -1
  87. package/dist/src/orchestrator/error.js +260 -4
  88. package/dist/src/orchestrator/index.d.ts +7 -5
  89. package/dist/src/orchestrator/index.d.ts.map +1 -1
  90. package/dist/src/orchestrator/index.js +4 -3
  91. package/dist/src/orchestrator/registry.d.ts +2 -1
  92. package/dist/src/orchestrator/registry.d.ts.map +1 -1
  93. package/dist/src/orchestrator/registry.js +13 -0
  94. package/dist/src/orchestrator/types.d.ts +92 -29
  95. package/dist/src/orchestrator/types.d.ts.map +1 -1
  96. package/dist/src/orchestrator/types.js +1 -5
  97. package/dist/src/orchestrator/wire.d.ts +30 -0
  98. package/dist/src/orchestrator/wire.d.ts.map +1 -0
  99. package/dist/src/orchestrator/wire.gen.d.ts +4290 -0
  100. package/dist/src/orchestrator/wire.gen.d.ts.map +1 -0
  101. package/dist/src/orchestrator/wire.gen.js +1 -0
  102. package/dist/src/orchestrator/wire.js +1 -0
  103. package/dist/src/smart-sessions/index.d.ts +6 -0
  104. package/dist/src/smart-sessions/index.d.ts.map +1 -0
  105. package/dist/src/smart-sessions/index.js +7 -0
  106. package/dist/src/types.d.ts +299 -31
  107. package/dist/src/types.d.ts.map +1 -1
  108. package/dist/src/utils/index.d.ts +59 -0
  109. package/dist/src/utils/index.d.ts.map +1 -1
  110. package/dist/src/utils/index.js +59 -0
  111. package/package.json +5 -9
  112. package/dist/src/actions/recovery.d.ts +0 -33
  113. package/dist/src/actions/recovery.d.ts.map +0 -1
  114. package/dist/src/actions/recovery.js +0 -189
@@ -1,12 +1,11 @@
1
1
  import { bytesToHex, concat, encodeAbiParameters, encodePacked, hexToBytes, maxUint48, pad, toHex, } from 'viem';
2
- import { OwnersFieldRequiredError } from '../../accounts/error.js';
2
+ import { AccountConfigurationNotSupportedError, OwnersFieldRequiredError, } from '../../accounts/error.js';
3
3
  import { MODULE_TYPE_ID_VALIDATOR } from '../common.js';
4
4
  const SMART_SESSION_EMISSARY_ADDRESS_DEV = '0x60731de80d78548875f8a67c4fec2a8660194e0c';
5
5
  const SMART_SESSION_EMISSARY_ADDRESS = '0xad568b3f825a8d5ffc06dd3253526b64d810ae89';
6
6
  const OWNABLE_VALIDATOR_ADDRESS = '0x000000000013fdb5234e4e3162a810f54d9f7e98';
7
- const ENS_VALIDATOR_ADDRESS = '0xdc38f07b060374b6480c4bf06231e7d10955bca4';
7
+ const ENS_HCA_MODULE = '0x5049ecBd4d961aE6DFEED9b7ccCe7f026454970E';
8
8
  const WEBAUTHN_VALIDATOR_ADDRESS = '0x0000000000578c4cb0e472a5462da43c495c3f33';
9
- const SOCIAL_RECOVERY_VALIDATOR_ADDRESS = '0xa04d053b3c8021e8d5bf641816c42daa75d8b597';
10
9
  const MULTI_FACTOR_VALIDATOR_ADDRESS = '0xf6bdf42c9be18ceca5c06c42a43daf7fbbe7896b';
11
10
  // Legacy
12
11
  const OWNABLE_V0_VALIDATOR_ADDRESS = '0x2483da3a338895199e5e538530213157e931bf06';
@@ -14,21 +13,37 @@ const OWNABLE_BETA_VALIDATOR_ADDRESS = '0x0000000000e9e6e96bcaa3c113187cdb7e38ae
14
13
  const WEBAUTHN_V0_VALIDATOR_ADDRESS = '0x0000000000578c4cb0e472a5462da43c495c3f33';
15
14
  const ECDSA_MOCK_SIGNATURE = '0x81d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b';
16
15
  const WEBAUTHN_MOCK_SIGNATURE = '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001b9b86eb98fda3ed4d797d9e690588dfadf17b329a76a47cec935bebf92d7ddc80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000019b2e9410bb6850f9f660a03d609d5a844fb96bcdc87a15139b03ee22c70f469100d2b865a215c3bf786387064effa8fcedcb1d625b5148f8a1236d5e3ff11acf000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22396a4546696a75684557724d34534f572d7443684a625545484550343456636a634a2d42716f3166544d38222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a38303830222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000';
16
+ // ENS validation is only available on HCA accounts (its validator default is
17
+ // the HCA module). True for a direct ENS owner set or an ENS sub-validator
18
+ // nested in a multi-factor config.
19
+ function ownerSetUsesEns(owners) {
20
+ return (owners.type === 'ens' ||
21
+ (owners.type === 'multi-factor' &&
22
+ owners.validators.some((validator) => validator.type === 'ens')));
23
+ }
17
24
  function getOwnerValidator(config) {
18
25
  if (!config.owners) {
19
26
  throw new OwnersFieldRequiredError();
20
27
  }
28
+ // ENS owners resolve to the HCA module, which is only valid for HCA accounts.
29
+ // Other account types would silently install/sign against the wrong validator.
30
+ if (ownerSetUsesEns(config.owners) && config.account?.type !== 'hca') {
31
+ throw new AccountConfigurationNotSupportedError('ENS owners are only supported on HCA accounts', config.account?.type ?? 'nexus');
32
+ }
21
33
  return getValidator(config.owners);
22
34
  }
23
35
  function getMockSignature(ownerSet) {
24
36
  switch (ownerSet.type) {
25
- case 'ecdsa':
26
- case 'ens': {
27
- // ENS validator uses same mock signature format as ECDSA for UserOps
37
+ case 'ecdsa': {
28
38
  const owners = ownerSet.accounts.map((account) => account.address);
29
39
  const signatures = owners.map(() => ECDSA_MOCK_SIGNATURE);
30
40
  return concat(signatures);
31
41
  }
42
+ case 'ens': {
43
+ // ENS validator uses same mock signature format as ECDSA for UserOps
44
+ const signatures = ownerSet.owners.map(() => ECDSA_MOCK_SIGNATURE);
45
+ return concat(signatures);
46
+ }
32
47
  case 'passkey':
33
48
  return WEBAUTHN_MOCK_SIGNATURE;
34
49
  case 'multi-factor': {
@@ -67,14 +82,16 @@ function getValidator(owners) {
67
82
  case 'ecdsa':
68
83
  return getOwnableValidator(owners.threshold ?? 1, owners.accounts.map((account) => account.address), owners.module);
69
84
  case 'ens':
70
- return getENSValidator(owners.threshold ?? 1, owners.accounts.map((account) => account.address), owners.ownerExpirations, owners.module);
85
+ // ENS validation is baked into the HCA account implementation, so the
86
+ // module address is fixed (no override).
87
+ return getENSValidator(owners.threshold ?? 1, owners.owners.map((o) => o.account.address), owners.owners.map((o) => o.expiration));
71
88
  case 'passkey':
72
89
  return getWebAuthnValidator(owners.threshold ?? 1, owners.accounts.map((account) => ({
73
90
  pubKey: account.publicKey,
74
91
  authenticatorId: account.id,
75
- })));
92
+ })), owners.module);
76
93
  case 'multi-factor': {
77
- return getMultiFactorValidator(owners.threshold ?? 1, owners.validators);
94
+ return getMultiFactorValidator(owners.threshold ?? 1, owners.validators, owners.module);
78
95
  }
79
96
  }
80
97
  }
@@ -93,13 +110,20 @@ function getOwnableValidator(threshold, owners, address) {
93
110
  type: MODULE_TYPE_ID_VALIDATOR,
94
111
  };
95
112
  }
96
- function getENSValidator(threshold, owners, ownerExpirations, address) {
113
+ function getENSValidator(threshold, owners, expirations) {
97
114
  // format: (uint256 threshold, Owner[] owners)
98
115
  // where Owner is a tuple of (address addr, uint48 expiration)
99
- const ownerPairs = owners.map((owner, index) => ({
100
- addr: owner.toLowerCase(),
101
- expiration: ownerExpirations[index] ?? maxUint48,
102
- }));
116
+ const ownerPairs = owners.map((owner, index) => {
117
+ const expiration = expirations[index];
118
+ return {
119
+ addr: owner.toLowerCase(),
120
+ // maxUint48 is the on-chain "never expires" sentinel; it's outside the
121
+ // representable Date range, so an omitted expiry maps to it directly.
122
+ expiration: expiration !== undefined
123
+ ? Math.floor(expiration.getTime() / 1000)
124
+ : Number(maxUint48),
125
+ };
126
+ });
103
127
  // Sort by address to match ENS validator's expectations
104
128
  const sortedPairs = ownerPairs.sort((a, b) => a.addr.localeCompare(b.addr));
105
129
  const ownersWithExpiration = sortedPairs;
@@ -114,9 +138,8 @@ function getENSValidator(threshold, owners, ownerExpirations, address) {
114
138
  ],
115
139
  },
116
140
  ], [BigInt(threshold), ownersWithExpiration]);
117
- const moduleAddress = address ?? ENS_VALIDATOR_ADDRESS;
118
141
  return {
119
- address: moduleAddress,
142
+ address: ENS_HCA_MODULE,
120
143
  initData,
121
144
  deInitData: '0x',
122
145
  additionalContext: '0x',
@@ -178,9 +201,9 @@ function getWebAuthnValidator(threshold, webAuthnCredentials, address) {
178
201
  type: MODULE_TYPE_ID_VALIDATOR,
179
202
  };
180
203
  }
181
- function getMultiFactorValidator(threshold, validators) {
204
+ function getMultiFactorValidator(threshold, validators, address) {
182
205
  return {
183
- address: MULTI_FACTOR_VALIDATOR_ADDRESS,
206
+ address: address ?? MULTI_FACTOR_VALIDATOR_ADDRESS,
184
207
  initData: encodePacked(['uint8', 'bytes'], [
185
208
  threshold,
186
209
  encodeAbiParameters([
@@ -221,26 +244,6 @@ function getMultiFactorValidator(threshold, validators) {
221
244
  type: MODULE_TYPE_ID_VALIDATOR,
222
245
  };
223
246
  }
224
- function getSocialRecoveryValidator(guardians, threshold = 1) {
225
- const guardianAddresses = guardians.map((guardian) => guardian.address);
226
- guardianAddresses.sort();
227
- return {
228
- type: MODULE_TYPE_ID_VALIDATOR,
229
- address: SOCIAL_RECOVERY_VALIDATOR_ADDRESS,
230
- initData: encodeAbiParameters([
231
- {
232
- type: 'uint256',
233
- name: 'threshold',
234
- },
235
- {
236
- type: 'address[]',
237
- name: 'guardians',
238
- },
239
- ], [BigInt(threshold), guardianAddresses]),
240
- deInitData: '0x',
241
- additionalContext: '0x',
242
- };
243
- }
244
247
  function parsePublicKey(publicKey) {
245
248
  const bytes = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey;
246
249
  const offset = bytes.length === 65 ? 1 : 0;
@@ -263,4 +266,4 @@ function supportsEip712(validator) {
263
266
  return true;
264
267
  }
265
268
  }
266
- export { OWNABLE_VALIDATOR_ADDRESS, ENS_VALIDATOR_ADDRESS, WEBAUTHN_VALIDATOR_ADDRESS, MULTI_FACTOR_VALIDATOR_ADDRESS, WEBAUTHN_V0_VALIDATOR_ADDRESS, SMART_SESSION_EMISSARY_ADDRESS, SMART_SESSION_EMISSARY_ADDRESS_DEV, getOwnerValidator, getOwnableValidator, getENSValidator, getWebAuthnValidator, getMultiFactorValidator, getSocialRecoveryValidator, getValidator, getMockSignature, supportsEip712, };
269
+ export { OWNABLE_VALIDATOR_ADDRESS, ENS_HCA_MODULE, WEBAUTHN_VALIDATOR_ADDRESS, MULTI_FACTOR_VALIDATOR_ADDRESS, WEBAUTHN_V0_VALIDATOR_ADDRESS, SMART_SESSION_EMISSARY_ADDRESS, SMART_SESSION_EMISSARY_ADDRESS_DEV, getOwnerValidator, getOwnableValidator, getENSValidator, getWebAuthnValidator, getMultiFactorValidator, getValidator, getMockSignature, supportsEip712, ownerSetUsesEns, };
@@ -0,0 +1,21 @@
1
+ import type { CrossChainPermissionInput, CrossChainPermit } from '../../types.js';
2
+ /**
3
+ * Resolve a {@link CrossChainPermissionInput} into a {@link CrossChainPermit}:
4
+ * normalise single/array legs, resolve `TokenSymbol`s to per-chain ERC-20
5
+ * addresses, and convert `Date` validity bounds to unix-seconds.
6
+ *
7
+ * @internal Used by `expandCrossChainPermit`; not part of the public API.
8
+ */
9
+ declare function resolveCrossChainPermission(input: CrossChainPermissionInput): CrossChainPermit;
10
+ /**
11
+ * Convert a resolved {@link CrossChainPermit} back into the
12
+ * {@link CrossChainPermissionInput} shape accepted by
13
+ * `SessionDefinition.crossChainPermits`.
14
+ *
15
+ * Useful when a permit was persisted in its resolved (unix-seconds) form and
16
+ * has to be re-supplied as session input: converts `bigint` deadlines to
17
+ * `Date` and maps `recipientIsAccount` to the inverse `allowRecipientNotAccount`.
18
+ */
19
+ declare function toCrossChainPermissionInput(permit: CrossChainPermit): CrossChainPermissionInput;
20
+ export { resolveCrossChainPermission, toCrossChainPermissionInput };
21
+ //# sourceMappingURL=cross-chain-permits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-chain-permits.d.ts","sourceRoot":"","sources":["../../../../modules/validators/cross-chain-permits.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,gBAAgB,EAEjB,MAAM,aAAa,CAAA;AAyBpB;;;;;;GAMG;AACH,iBAAS,2BAA2B,CAClC,KAAK,EAAE,yBAAyB,GAC/B,gBAAgB,CA0DlB;AAQD;;;;;;;;GAQG;AACH,iBAAS,2BAA2B,CAClC,MAAM,EAAE,gBAAgB,GACvB,yBAAyB,CAuB3B;AAED,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,CAAA"}
@@ -0,0 +1,111 @@
1
+ import { isAddress } from 'viem';
2
+ import { getTokenAddress } from '../../orchestrator/registry.js';
3
+ function dateToUnixSeconds(input) {
4
+ // Date.getTime() returns milliseconds since epoch; the on-chain policy
5
+ // expects unix-seconds. Integer division matches the Permit2 deadline
6
+ // convention.
7
+ return BigInt(Math.floor(input.getTime() / 1000));
8
+ }
9
+ function resolveTokenForChain(token, chainId) {
10
+ return isAddress(token) ? token : getTokenAddress(token, chainId);
11
+ }
12
+ // Normalise the single-object / array / omitted shapes into arrays. An
13
+ // omitted side stays `undefined` (no restriction), mirroring how the
14
+ // underlying Permit2 policy treats an absent token list.
15
+ function normalizeLegs(legs) {
16
+ if (legs === undefined)
17
+ return undefined;
18
+ const arr = Array.isArray(legs) ? legs : [legs];
19
+ return arr.length ? arr : undefined;
20
+ }
21
+ /**
22
+ * Resolve a {@link CrossChainPermissionInput} into a {@link CrossChainPermit}:
23
+ * normalise single/array legs, resolve `TokenSymbol`s to per-chain ERC-20
24
+ * addresses, and convert `Date` validity bounds to unix-seconds.
25
+ *
26
+ * @internal Used by `expandCrossChainPermit`; not part of the public API.
27
+ */
28
+ function resolveCrossChainPermission(input) {
29
+ const fromLegs = normalizeLegs(input.from);
30
+ const toLegs = normalizeLegs(input.to);
31
+ const from = fromLegs?.map((leg) => ({
32
+ chain: leg.chain,
33
+ token: resolveTokenForChain(leg.token, leg.chain.id),
34
+ maxAmount: leg.maxAmount,
35
+ }));
36
+ const to = toLegs?.map((leg) => ({
37
+ chain: leg.chain,
38
+ token: resolveTokenForChain(leg.token, leg.chain.id),
39
+ recipient: leg.recipient,
40
+ }));
41
+ const validUntil = input.validUntil !== undefined
42
+ ? dateToUnixSeconds(input.validUntil)
43
+ : undefined;
44
+ const validAfter = input.validAfter !== undefined
45
+ ? dateToUnixSeconds(input.validAfter)
46
+ : undefined;
47
+ // Detect an obviously-broken time window early. The on-chain policy
48
+ // would reject any intent in this state anyway; surfacing it at build
49
+ // time saves a round-trip to chain.
50
+ if (validUntil !== undefined &&
51
+ validAfter !== undefined &&
52
+ validAfter > validUntil) {
53
+ throw new Error(`crossChainPermits: validAfter (${validAfter}) is greater than validUntil (${validUntil})`);
54
+ }
55
+ const fillDeadline = input.fillDeadline?.map(({ chain, min, max }) => ({
56
+ chain,
57
+ min: min !== undefined ? dateToUnixSeconds(min) : undefined,
58
+ max: max !== undefined ? dateToUnixSeconds(max) : undefined,
59
+ }));
60
+ return {
61
+ from,
62
+ to,
63
+ validUntil,
64
+ validAfter,
65
+ fillDeadline,
66
+ // Inverted default: callers must opt out of bridge-to-self
67
+ // explicitly. This stops a session key from quietly bridging to an
68
+ // attacker-controlled recipient.
69
+ recipientIsAccount: !input.allowRecipientNotAccount,
70
+ // Passed through verbatim. `getArbitersForSettlementLayers` expands
71
+ // undefined/empty to "all supported layers" at session-data build
72
+ // time.
73
+ settlementLayers: input.settlementLayers,
74
+ };
75
+ }
76
+ function unixSecondsToDate(seconds) {
77
+ // Inverse of dateToUnixSeconds: on-chain deadlines are unix-seconds; Date
78
+ // expects milliseconds.
79
+ return new Date(Number(seconds) * 1000);
80
+ }
81
+ /**
82
+ * Convert a resolved {@link CrossChainPermit} back into the
83
+ * {@link CrossChainPermissionInput} shape accepted by
84
+ * `SessionDefinition.crossChainPermits`.
85
+ *
86
+ * Useful when a permit was persisted in its resolved (unix-seconds) form and
87
+ * has to be re-supplied as session input: converts `bigint` deadlines to
88
+ * `Date` and maps `recipientIsAccount` to the inverse `allowRecipientNotAccount`.
89
+ */
90
+ function toCrossChainPermissionInput(permit) {
91
+ return {
92
+ from: permit.from,
93
+ to: permit.to,
94
+ validUntil: permit.validUntil !== undefined
95
+ ? unixSecondsToDate(permit.validUntil)
96
+ : undefined,
97
+ validAfter: permit.validAfter !== undefined
98
+ ? unixSecondsToDate(permit.validAfter)
99
+ : undefined,
100
+ fillDeadline: permit.fillDeadline?.map(({ chain, min, max }) => ({
101
+ chain,
102
+ min: min !== undefined ? unixSecondsToDate(min) : undefined,
103
+ max: max !== undefined ? unixSecondsToDate(max) : undefined,
104
+ })),
105
+ allowRecipientNotAccount: permit.recipientIsAccount !== undefined
106
+ ? !permit.recipientIsAccount
107
+ : undefined,
108
+ settlementLayers: permit.settlementLayers,
109
+ };
110
+ }
111
+ export { resolveCrossChainPermission, toCrossChainPermissionInput };
@@ -1,4 +1,5 @@
1
1
  import type { Permission, ScopedAction } from '../../types.js';
2
+ export declare const FAR_FUTURE_MS = 4102444800000;
2
3
  declare function resolvePermission(permission: Permission): ScopedAction[];
3
4
  declare function resolvePermissions(permissions: readonly Permission[]): ScopedAction[];
4
5
  export { resolvePermissions, resolvePermission };
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../modules/validators/permissions.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EAEV,YAAY,EAEb,MAAM,aAAa,CAAA;AA0CpB,iBAAS,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,YAAY,EAAE,CA+FjE;AAED,iBAAS,kBAAkB,CACzB,WAAW,EAAE,SAAS,UAAU,EAAE,GACjC,YAAY,EAAE,CAEhB;AAED,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../modules/validators/permissions.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,UAAU,EAEV,YAAY,EAEb,MAAM,aAAa,CAAA;AAiHpB,eAAO,MAAM,aAAa,gBAAoB,CAAA;AAE9C,iBAAS,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,YAAY,EAAE,CA0NjE;AAkBD,iBAAS,kBAAkB,CACzB,WAAW,EAAE,SAAS,UAAU,EAAE,GACjC,YAAY,EAAE,CAwBhB;AAED,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAA"}
@@ -1,4 +1,4 @@
1
- import { isAddress, isHex, size, toFunctionSelector, } from 'viem';
1
+ import { isAddress, isHex, padHex, size, toFunctionSelector, } from 'viem';
2
2
  function isStaticAbiType(type) {
3
3
  if (type === 'address' || type === 'bool')
4
4
  return true;
@@ -33,12 +33,53 @@ function toReferenceValue(value, abiType) {
33
33
  if (typeof value === 'string' &&
34
34
  isHex(value) &&
35
35
  size(value) === expectedSize) {
36
- return value;
36
+ // Solidity calldata encodes bytesN (N<32) left-aligned + right-padded
37
+ // inside its 32-byte word, whereas address/uint*/bool are right-aligned
38
+ // + left-padded. Downstream `encodeActionParamRule` unconditionally
39
+ // left-pads with `padHex`, which is correct for the right-aligned types
40
+ // but wrong for bytesN. Pre-encode here to the full 32-byte hex with
41
+ // the correct alignment so the policy's bytes32 == bytes32 comparison
42
+ // matches what's actually in calldata.
43
+ return padHex(value, { size: 32, dir: 'right' });
37
44
  }
38
45
  throw new Error(`Expected ${expectedSize}-byte hex string for ${abiType}`);
39
46
  }
40
47
  throw new Error(`Unsupported ABI type: ${abiType}`);
41
48
  }
49
+ // Right-fold an array of leaves into a right-leaning OR chain:
50
+ // [a, b, c] → OR(a, OR(b, c))
51
+ // Right-leaning is fine because ArgPolicy evaluates with short-circuit; any
52
+ // shape that uses every leaf and respects post-order produces the same result.
53
+ function orChain(leaves) {
54
+ let acc = leaves[leaves.length - 1];
55
+ for (let i = leaves.length - 2; i >= 0; i--) {
56
+ acc = { type: 'or', left: leaves[i], right: acc };
57
+ }
58
+ return acc;
59
+ }
60
+ function andChain(leaves) {
61
+ let acc = leaves[leaves.length - 1];
62
+ for (let i = leaves.length - 2; i >= 0; i--) {
63
+ acc = { type: 'and', left: leaves[i], right: acc };
64
+ }
65
+ return acc;
66
+ }
67
+ // On-chain ERC20SpendingLimitPolicy._isTokenTransferOrApprove matches by
68
+ // 4-byte selector, accepting only these four. Selector-gating here keeps the
69
+ // SDK's accepted set congruent with the contract's — a same-shaped function
70
+ // like `mint(address,uint256)` would otherwise pass argument-type checks and
71
+ // fail every call on-chain.
72
+ const ERC20_SPENDING_LIMIT_SELECTORS = new Set([
73
+ '0x095ea7b3', // approve(address,uint256)
74
+ '0x39509351', // increaseAllowance(address,uint256)
75
+ '0xa9059cbb', // transfer(address,uint256)
76
+ '0x23b872dd', // transferFrom(address,address,uint256)
77
+ ]);
78
+ // Year 2100 in ms — well within uint128 after the encoder's ms→s conversion.
79
+ // Used as the one-sided default for `validUntil` when only `validAfter` is set.
80
+ // Exported so the cross-chain permit expansion (smart-sessions.ts) applies the
81
+ // same always-passing upper bound instead of defaulting to 0 (already expired).
82
+ export const FAR_FUTURE_MS = 4_102_444_800_000;
42
83
  function resolvePermission(permission) {
43
84
  const { abi, address, functions } = permission;
44
85
  const actions = [];
@@ -58,11 +99,57 @@ function resolvePermission(permission) {
58
99
  }
59
100
  const abiEntry = abiEntries[0];
60
101
  const selector = toFunctionSelector(abiEntry);
61
- const policies = config.policies ? [...config.policies] : [];
62
- const params = config.params ?? {};
63
- const paramEntries = Object.entries(params).filter(([, v]) => v !== undefined);
102
+ if (Object.hasOwn(config, 'policies')) {
103
+ throw new Error(`Function "${fnName}": \`policies\` was removed from permission configs. ` +
104
+ 'Use params, maxUses, validUntil/validAfter, valueLimit, or spendingLimit instead.');
105
+ }
106
+ const policies = [];
107
+ // --- Sugar field expansion -----------------------------------------------
108
+ if (config.maxUses !== undefined) {
109
+ policies.push({ type: 'usage-limit', limit: config.maxUses });
110
+ }
111
+ if (config.validUntil !== undefined || config.validAfter !== undefined) {
112
+ const validUntil = config.validUntil !== undefined
113
+ ? config.validUntil.getTime()
114
+ : FAR_FUTURE_MS;
115
+ const validAfter = config.validAfter !== undefined ? config.validAfter.getTime() : 0;
116
+ if (validUntil < validAfter) {
117
+ throw new Error(`Function "${fnName}": validUntil (${validUntil}) is before validAfter (${validAfter}).`);
118
+ }
119
+ policies.push({ type: 'time-frame', validUntil, validAfter });
120
+ }
121
+ if (config.valueLimit !== undefined) {
122
+ // Runtime backstop: payable-gating is enforced at the type level, but
123
+ // users can bypass with `as` casts. valueLimit on a non-payable function
124
+ // is harmless on-chain (msg.value is always 0, the cap always passes)
125
+ // but it leaks intent — throw rather than encode dead weight.
126
+ if (abiEntry.stateMutability !== 'payable') {
127
+ throw new Error(`Function "${fnName}" is not payable — \`valueLimit\` only constrains native ETH ` +
128
+ 'attached to the call, which is always zero for non-payable functions. ' +
129
+ 'Remove `valueLimit`.');
130
+ }
131
+ policies.push({ type: 'value-limit', limit: config.valueLimit });
132
+ }
133
+ if (config.spendingLimit !== undefined) {
134
+ // Runtime backstop: type-level gate restricts shape, but ERC20SpendingLimitPolicy
135
+ // dispatches by selector on-chain — a same-shaped function like
136
+ // mint(address,uint256) would encode successfully and fail every call.
137
+ if (!ERC20_SPENDING_LIMIT_SELECTORS.has(selector)) {
138
+ throw new Error(`Function "${fnName}" (selector ${selector}) is not an ERC-20 transfer/approve ` +
139
+ 'selector; `spendingLimit` only works on approve, increaseAllowance, ' +
140
+ 'transfer, or transferFrom. The on-chain policy dispatches by selector ' +
141
+ 'and would fail every call for other functions.');
142
+ }
143
+ policies.push({
144
+ type: 'spending-limits',
145
+ limits: [config.spendingLimit],
146
+ });
147
+ }
148
+ // --- End sugar field expansion -------------------------------------------
149
+ const rawParams = config.params ?? {};
150
+ const paramEntries = Object.entries(rawParams).filter(([, v]) => v !== undefined);
64
151
  if (paramEntries.length > 0) {
65
- const rules = paramEntries.map(([paramName, rule]) => {
152
+ const normalized = paramEntries.map(([paramName, rule]) => {
66
153
  const paramIndex = abiEntry.inputs.findIndex((p) => p.name === paramName);
67
154
  if (paramIndex === -1) {
68
155
  throw new Error(`Parameter "${paramName}" not found in function "${fnName}". ` +
@@ -75,21 +162,79 @@ function resolvePermission(permission) {
75
162
  '(address, bool, uint*, int*, bytes1–bytes32).');
76
163
  }
77
164
  const calldataOffset = BigInt(paramIndex) * 32n;
78
- const referenceValue = toReferenceValue(rule.value, param.type);
165
+ if (rule.anyOf !== undefined) {
166
+ if (rule.anyOf.length === 0) {
167
+ throw new Error(`Parameter "${paramName}" has empty anyOf — provide at least one value.`);
168
+ }
169
+ return {
170
+ paramName,
171
+ calldataOffset,
172
+ abiType: param.type,
173
+ anyOf: rule.anyOf,
174
+ };
175
+ }
79
176
  return {
80
- condition: rule.condition,
177
+ paramName,
81
178
  calldataOffset,
82
- referenceValue,
83
- ...(rule.usageLimit !== undefined
84
- ? { usageLimit: rule.usageLimit }
85
- : {}),
179
+ abiType: param.type,
180
+ condition: rule.condition,
181
+ value: rule.value,
182
+ usageLimit: rule.usageLimit,
86
183
  };
87
184
  });
88
- policies.push({
89
- type: 'universal-action',
90
- valueLimitPerUse: config.valueLimitPerUse ?? 0n,
91
- rules: rules,
92
- });
185
+ const usesArgPolicy = normalized.some((n) => n.anyOf !== undefined);
186
+ // UniActionPolicy/ArgPolicy reject `msg.value > valueLimitPerUse`, so a
187
+ // default of 0 would block any non-zero msg.value before the standalone
188
+ // value-limit policy (sugar) could allow it. Inherit the sugar's cap so
189
+ // the per-use gate matches user intent; value-limit still enforces the
190
+ // cumulative cap on top.
191
+ const embeddedValueLimit = config.valueLimitPerUse ?? config.valueLimit ?? 0n;
192
+ if (usesArgPolicy) {
193
+ // One sub-expression per param, then AND across params.
194
+ const perParam = normalized.map((n) => {
195
+ if (n.anyOf !== undefined) {
196
+ const leaves = n.anyOf.map((v) => ({
197
+ type: 'rule',
198
+ rule: {
199
+ condition: 'equal',
200
+ calldataOffset: n.calldataOffset,
201
+ referenceValue: toReferenceValue(v, n.abiType),
202
+ },
203
+ }));
204
+ return leaves.length === 1 ? leaves[0] : orChain(leaves);
205
+ }
206
+ return {
207
+ type: 'rule',
208
+ rule: {
209
+ condition: n.condition,
210
+ calldataOffset: n.calldataOffset,
211
+ referenceValue: toReferenceValue(n.value, n.abiType),
212
+ ...(n.usageLimit !== undefined
213
+ ? { usageLimit: n.usageLimit }
214
+ : {}),
215
+ },
216
+ };
217
+ });
218
+ policies.push({
219
+ type: 'arg-policy',
220
+ valueLimitPerUse: embeddedValueLimit,
221
+ expression: perParam.length === 1 ? perParam[0] : andChain(perParam),
222
+ });
223
+ }
224
+ else {
225
+ // Flat AND-of-rules — cheaper to init via UniActionPolicy.
226
+ const rules = normalized.map((n) => ({
227
+ condition: n.condition,
228
+ calldataOffset: n.calldataOffset,
229
+ referenceValue: toReferenceValue(n.value, n.abiType),
230
+ ...(n.usageLimit !== undefined ? { usageLimit: n.usageLimit } : {}),
231
+ }));
232
+ policies.push({
233
+ type: 'universal-action',
234
+ valueLimitPerUse: embeddedValueLimit,
235
+ rules: rules,
236
+ });
237
+ }
93
238
  }
94
239
  else if (config.valueLimitPerUse !== undefined) {
95
240
  policies.push({
@@ -105,7 +250,36 @@ function resolvePermission(permission) {
105
250
  }
106
251
  return actions;
107
252
  }
253
+ // Error-path only: recover the function name behind a selector for the
254
+ // duplicate-permission message.
255
+ function findFunctionName(permission, selector) {
256
+ return Object.keys(permission.functions).find((name) => {
257
+ const entry = permission.abi.find((e) => e.type === 'function' &&
258
+ e.name === name);
259
+ return entry !== undefined && toFunctionSelector(entry) === selector;
260
+ });
261
+ }
108
262
  function resolvePermissions(permissions) {
109
- return permissions.flatMap(resolvePermission);
263
+ // On-chain, actions are keyed by keccak(target, selector): two permission
264
+ // entries for the same function on the same contract share one actionId,
265
+ // and the later entry's `initializeWithMultiplexer` silently overwrites the
266
+ // earlier one's policy config. Reject at build time instead of letting the
267
+ // collision surface as an opaque InvalidSignature() at execution.
268
+ const seen = new Set();
269
+ return permissions.flatMap((permission) => {
270
+ const actions = resolvePermission(permission);
271
+ for (const action of actions) {
272
+ const key = `${action.target.toLowerCase()}:${action.selector.toLowerCase()}`;
273
+ if (seen.has(key)) {
274
+ const fnName = findFunctionName(permission, action.selector);
275
+ throw new Error(`Duplicate permission for function "${fnName}" (selector ${action.selector}) ` +
276
+ `on ${action.target}: permission entries for the same function on the same ` +
277
+ "contract share one on-chain action, so the later entry's policies would " +
278
+ 'silently overwrite the earlier ones. Merge them into a single entry.');
279
+ }
280
+ seen.add(key);
281
+ }
282
+ return actions;
283
+ });
110
284
  }
111
285
  export { resolvePermissions, resolvePermission };
@@ -0,0 +1,22 @@
1
+ import type { Address } from 'viem';
2
+ import type { CrossChainSettlementLayer } from '../../../../types.js';
3
+ /**
4
+ * Collects every distinct arbiter address that any chain in the registry
5
+ * has deployed for the given settlement layers. The resulting whitelist
6
+ * is what the Permit2 claim policy enforces on-chain — a session signed
7
+ * once with this whitelist is valid against any of those arbiters.
8
+ *
9
+ * **"Any" means any of the *supported* settlement layers**, not any
10
+ * address on earth. When `layers` is empty or undefined, this resolver
11
+ * expands to the union of every layer in
12
+ * {@link SETTLEMENT_LAYER_CONTRACT_KEYS}, so the on-chain arbiter check
13
+ * stays meaningful (the worst case is still a Rhinestone-blessed
14
+ * arbiter). To get a truly unrestricted whitelist a caller would have
15
+ * to bypass this helper entirely.
16
+ *
17
+ * @param layers Settlement layers the session is permitted to use.
18
+ * Empty/undefined ⇒ all supported layers.
19
+ * @param useDevContracts Pull from the dev address book instead of mainnet.
20
+ */
21
+ export declare function getArbitersForSettlementLayers(layers: readonly CrossChainSettlementLayer[] | undefined, useDevContracts?: boolean): Address[] | undefined;
22
+ //# sourceMappingURL=arbiters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arbiters.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/policies/claim/arbiters.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAiBlE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,yBAAyB,EAAE,GAAG,SAAS,EACxD,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,EAAE,GAAG,SAAS,CA8BvB"}
@@ -0,0 +1,57 @@
1
+ import { contractAddresses, contractAddressesDev, } from '@rhinestone/shared-configs';
2
+ /**
3
+ * The contract registry keys (per `shared-configs/contracts.{,dev}.js`)
4
+ * that each settlement layer expands to. `ACROSS` whitelists both arbiter
5
+ * impls so a session is permissioned for the 7579 and multicall paths
6
+ * regardless of which one the orchestrator picks at intent time.
7
+ */
8
+ const SETTLEMENT_LAYER_CONTRACT_KEYS = {
9
+ SAME_CHAIN: ['samechainArbiter'],
10
+ ECO: ['ecoArbiter'],
11
+ ACROSS: ['across7579Arbiter', 'acrossMulticallArbiter'],
12
+ };
13
+ /**
14
+ * Collects every distinct arbiter address that any chain in the registry
15
+ * has deployed for the given settlement layers. The resulting whitelist
16
+ * is what the Permit2 claim policy enforces on-chain — a session signed
17
+ * once with this whitelist is valid against any of those arbiters.
18
+ *
19
+ * **"Any" means any of the *supported* settlement layers**, not any
20
+ * address on earth. When `layers` is empty or undefined, this resolver
21
+ * expands to the union of every layer in
22
+ * {@link SETTLEMENT_LAYER_CONTRACT_KEYS}, so the on-chain arbiter check
23
+ * stays meaningful (the worst case is still a Rhinestone-blessed
24
+ * arbiter). To get a truly unrestricted whitelist a caller would have
25
+ * to bypass this helper entirely.
26
+ *
27
+ * @param layers Settlement layers the session is permitted to use.
28
+ * Empty/undefined ⇒ all supported layers.
29
+ * @param useDevContracts Pull from the dev address book instead of mainnet.
30
+ */
31
+ export function getArbitersForSettlementLayers(layers, useDevContracts) {
32
+ const effectiveLayers = !layers || layers.length === 0
33
+ ? Object.keys(SETTLEMENT_LAYER_CONTRACT_KEYS)
34
+ : layers;
35
+ const registry = useDevContracts ? contractAddressesDev : contractAddresses;
36
+ const keys = effectiveLayers.flatMap((l) => SETTLEMENT_LAYER_CONTRACT_KEYS[l]);
37
+ const seen = new Set();
38
+ const addresses = [];
39
+ // shared-configs is shaped Record<chainId, Record<contractKey, Address>>.
40
+ // The same logical arbiter often shares an address across chains, but a
41
+ // given key may also have multiple addresses live in the wild (e.g.
42
+ // post-redeploy). We collect every unique address we see for the keys
43
+ // the caller asked for, deduplicated case-insensitively.
44
+ for (const chainContracts of Object.values(registry)) {
45
+ for (const key of keys) {
46
+ const addr = chainContracts[key];
47
+ if (!addr)
48
+ continue;
49
+ const norm = addr.toLowerCase();
50
+ if (seen.has(norm))
51
+ continue;
52
+ seen.add(norm);
53
+ addresses.push(addr);
54
+ }
55
+ }
56
+ return addresses.length ? addresses : undefined;
57
+ }