@rhinestone/sdk 2.4.0 → 2.6.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.
Files changed (70) hide show
  1. package/dist/src/actions/mfa.d.ts +5 -4
  2. package/dist/src/actions/mfa.d.ts.map +1 -1
  3. package/dist/src/actions/mfa.js +11 -4
  4. package/dist/src/api/account.d.ts.map +1 -1
  5. package/dist/src/api/account.js +118 -0
  6. package/dist/src/api/compose-types.d.ts +7 -0
  7. package/dist/src/api/compose-types.d.ts.map +1 -1
  8. package/dist/src/clients/orchestrator/client.js +1 -1
  9. package/dist/src/clients/orchestrator/mappers.js +40 -2
  10. package/dist/src/clients/orchestrator/public.d.ts +16 -1
  11. package/dist/src/clients/orchestrator/public.d.ts.map +1 -1
  12. package/dist/src/clients/orchestrator/types.d.ts +6 -0
  13. package/dist/src/clients/orchestrator/types.d.ts.map +1 -1
  14. package/dist/src/clients/orchestrator/wire.d.ts +15 -2
  15. package/dist/src/clients/orchestrator/wire.d.ts.map +1 -1
  16. package/dist/src/config/account.d.ts +140 -6
  17. package/dist/src/config/account.d.ts.map +1 -1
  18. package/dist/src/index.d.ts +2 -2
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/modules/validators/ens.d.ts +9 -0
  21. package/dist/src/modules/validators/ens.d.ts.map +1 -1
  22. package/dist/src/modules/validators/ens.js +21 -3
  23. package/dist/src/modules/validators/multi-factor.d.ts +4 -1
  24. package/dist/src/modules/validators/multi-factor.d.ts.map +1 -1
  25. package/dist/src/modules/validators/multi-factor.js +2 -2
  26. package/dist/src/modules/validators/ownable.d.ts +10 -0
  27. package/dist/src/modules/validators/ownable.d.ts.map +1 -1
  28. package/dist/src/modules/validators/ownable.js +26 -12
  29. package/dist/src/modules/validators/permissions.d.ts +13 -0
  30. package/dist/src/modules/validators/permissions.d.ts.map +1 -1
  31. package/dist/src/modules/validators/permissions.js +140 -2
  32. package/dist/src/modules/validators/policies/claim/arbiters.d.ts.map +1 -1
  33. package/dist/src/modules/validators/policies/claim/arbiters.js +8 -0
  34. package/dist/src/modules/validators/resolve.d.ts +6 -0
  35. package/dist/src/modules/validators/resolve.d.ts.map +1 -1
  36. package/dist/src/modules/validators/resolve.js +28 -6
  37. package/dist/src/modules/validators/smart-sessions/resolve.d.ts.map +1 -1
  38. package/dist/src/modules/validators/smart-sessions/resolve.js +112 -13
  39. package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts +92 -0
  40. package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts.map +1 -0
  41. package/dist/src/modules/validators/smart-sessions/swap/fynd.js +101 -0
  42. package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts +147 -0
  43. package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts.map +1 -0
  44. package/dist/src/modules/validators/smart-sessions/swap/rhinestone.js +305 -0
  45. package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts +73 -0
  46. package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts.map +1 -0
  47. package/dist/src/modules/validators/smart-sessions/swap/rules.js +105 -0
  48. package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts +31 -0
  49. package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts.map +1 -0
  50. package/dist/src/modules/validators/smart-sessions/swap/scope.js +168 -0
  51. package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts +143 -0
  52. package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts.map +1 -0
  53. package/dist/src/modules/validators/smart-sessions/swap/zero-ex.js +164 -0
  54. package/dist/src/modules/validators/smart-sessions/types.d.ts +78 -0
  55. package/dist/src/modules/validators/smart-sessions/types.d.ts.map +1 -1
  56. package/dist/src/modules/validators/types.d.ts +5 -1
  57. package/dist/src/modules/validators/types.d.ts.map +1 -1
  58. package/dist/src/modules/validators/webauthn.d.ts +33 -1
  59. package/dist/src/modules/validators/webauthn.d.ts.map +1 -1
  60. package/dist/src/modules/validators/webauthn.js +128 -13
  61. package/dist/src/signing/context.d.ts.map +1 -1
  62. package/dist/src/signing/context.js +2 -5
  63. package/dist/src/smart-sessions/index.d.ts +10 -5
  64. package/dist/src/smart-sessions/index.d.ts.map +1 -1
  65. package/dist/src/smart-sessions/index.js +6 -1
  66. package/dist/src/transactions/intents/session-signing.d.ts.map +1 -1
  67. package/dist/src/transactions/intents/session-signing.js +7 -1
  68. package/dist/src/transactions/user-operations/validator-account.d.ts.map +1 -1
  69. package/dist/src/transactions/user-operations/validator-account.js +2 -2
  70. package/package.json +1 -1
@@ -10,6 +10,88 @@ function isStaticAbiType(type) {
10
10
  }
11
11
  return false;
12
12
  }
13
+ /** Split `T[k]` / `T[]` into element type and length (`''` when unbounded). */
14
+ const ARRAY_SUFFIX = /^(.*)\[(\d*)\]$/;
15
+ function withType(param, type) {
16
+ return { ...param, type };
17
+ }
18
+ function tupleComponents(param) {
19
+ return param.components ?? [];
20
+ }
21
+ /**
22
+ * True when the param is encoded as a 32-byte offset pointer in the head rather
23
+ * than inline. Mirrors the ABI spec: `bytes`, `string`, unbounded arrays, and
24
+ * any tuple/fixed-array transitively containing one of those.
25
+ */
26
+ function isDynamicAbiParam(param) {
27
+ const { type } = param;
28
+ if (type === 'bytes' || type === 'string')
29
+ return true;
30
+ const array = ARRAY_SUFFIX.exec(type);
31
+ if (array) {
32
+ const [, elementType, length] = array;
33
+ if (length === '')
34
+ return true;
35
+ return isDynamicAbiParam(withType(param, elementType));
36
+ }
37
+ if (type === 'tuple')
38
+ return tupleComponents(param).some(isDynamicAbiParam);
39
+ return false;
40
+ }
41
+ /**
42
+ * Bytes this param occupies in the calldata HEAD.
43
+ *
44
+ * Not always 32: a static tuple is flattened inline (one word per leaf) and a
45
+ * fixed-size static array occupies `length` words. Computing offsets as
46
+ * `paramIndex * 32` silently mis-addresses every param that follows one of
47
+ * those — e.g. `f(uint256[3] a, address b)` would point at word 1 instead of
48
+ * word 3 and compare against array data, producing a policy that passes or
49
+ * fails for reasons unrelated to `b`.
50
+ */
51
+ function headSize(param) {
52
+ if (isDynamicAbiParam(param))
53
+ return 32;
54
+ const array = ARRAY_SUFFIX.exec(param.type);
55
+ if (array) {
56
+ const [, elementType, length] = array;
57
+ return Number.parseInt(length, 10) * headSize(withType(param, elementType));
58
+ }
59
+ if (param.type === 'tuple') {
60
+ return tupleComponents(param).reduce((sum, c) => sum + headSize(c), 0);
61
+ }
62
+ return 32;
63
+ }
64
+ /** Byte offset of `inputs[index]` within the calldata head (past the selector). */
65
+ function headOffset(inputs, index) {
66
+ let offset = 0;
67
+ for (let i = 0; i < index; i++)
68
+ offset += headSize(inputs[i]);
69
+ return BigInt(offset);
70
+ }
71
+ /**
72
+ * Calldata head offset of every named top-level parameter of `functionName`.
73
+ *
74
+ * Lets callers that build raw param rules (the swap venues) address arguments by
75
+ * name instead of hardcoding byte offsets — the offsets then follow the ABI
76
+ * automatically, and a signature change surfaces as a missing key rather than a
77
+ * rule silently pointing at the wrong word.
78
+ *
79
+ * Only single-word static params are included; anything dynamic or multi-word
80
+ * cannot be compared by a 32-byte reference value and is omitted.
81
+ */
82
+ export function namedParamOffsets(abi, functionName) {
83
+ const entry = abi.find((item) => item.type === 'function' && item.name === functionName);
84
+ if (!entry) {
85
+ throw new Error(`Function "${functionName}" not found in ABI`);
86
+ }
87
+ const offsets = {};
88
+ entry.inputs.forEach((param, index) => {
89
+ if (param.name && isStaticAbiType(param.type)) {
90
+ offsets[param.name] = headOffset(entry.inputs, index);
91
+ }
92
+ });
93
+ return offsets;
94
+ }
13
95
  function toReferenceValue(value, abiType) {
14
96
  if (abiType === 'address') {
15
97
  if (typeof value === 'string' && isAddress(value))
@@ -161,7 +243,22 @@ function resolvePermission(permission) {
161
243
  'Permission rules only support static types ' +
162
244
  '(address, bool, uint*, int*, bytes1–bytes32).');
163
245
  }
164
- const calldataOffset = BigInt(paramIndex) * 32n;
246
+ const calldataOffset = headOffset(abiEntry.inputs, paramIndex);
247
+ if (rule.min !== undefined || rule.max !== undefined) {
248
+ if (rule.min === undefined || rule.max === undefined) {
249
+ throw new Error(`Parameter "${paramName}" needs both "min" and "max" — ` +
250
+ 'provide the other bound, or use a single ' +
251
+ '{ condition, value } comparison.');
252
+ }
253
+ return {
254
+ paramName,
255
+ calldataOffset,
256
+ abiType: param.type,
257
+ min: rule.min,
258
+ max: rule.max,
259
+ usageLimit: rule.usageLimit,
260
+ };
261
+ }
165
262
  if (rule.anyOf !== undefined) {
166
263
  if (rule.anyOf.length === 0) {
167
264
  throw new Error(`Parameter "${paramName}" has empty anyOf — provide at least one value.`);
@@ -182,7 +279,9 @@ function resolvePermission(permission) {
182
279
  usageLimit: rule.usageLimit,
183
280
  };
184
281
  });
185
- const usesArgPolicy = normalized.some((n) => n.anyOf !== undefined);
282
+ // Both `anyOf` (OR chain) and `min`/`max` (AND of two rules on ONE param)
283
+ // need more than the flat one-rule-per-param shape UniActionPolicy offers.
284
+ const usesArgPolicy = normalized.some((n) => n.anyOf !== undefined || n.min !== undefined);
186
285
  // UniActionPolicy/ArgPolicy reject `msg.value > valueLimitPerUse`, so a
187
286
  // default of 0 would block any non-zero msg.value before the standalone
188
287
  // value-limit policy (sugar) could allow it. Inherit the sugar's cap so
@@ -203,6 +302,45 @@ function resolvePermission(permission) {
203
302
  }));
204
303
  return leaves.length === 1 ? leaves[0] : orChain(leaves);
205
304
  }
305
+ if (n.min !== undefined) {
306
+ // Inclusive bounds as AND(x >= min, x <= max). Deliberately NOT the
307
+ // policy's own `inRange` condition: that packs two uint128 bounds
308
+ // into one bytes32 `ref`, which this SDK has no verified encoding
309
+ // for. Two comparisons are exactly equivalent and use primitives
310
+ // already covered by tests.
311
+ //
312
+ // `usageLimit` rides on the lower-bound leaf only — putting it on
313
+ // both would double-count every call against the cumulative cap.
314
+ const minRef = toReferenceValue(n.min, n.abiType);
315
+ const maxRef = toReferenceValue(n.max, n.abiType);
316
+ if (typeof minRef === 'bigint' &&
317
+ typeof maxRef === 'bigint' &&
318
+ minRef > maxRef) {
319
+ throw new Error(`Parameter "${n.paramName}" has min (${minRef}) greater than ` +
320
+ `max (${maxRef}) — no value can satisfy this rule.`);
321
+ }
322
+ return andChain([
323
+ {
324
+ type: 'rule',
325
+ rule: {
326
+ condition: 'greaterThanOrEqual',
327
+ calldataOffset: n.calldataOffset,
328
+ referenceValue: minRef,
329
+ ...(n.usageLimit !== undefined
330
+ ? { usageLimit: n.usageLimit }
331
+ : {}),
332
+ },
333
+ },
334
+ {
335
+ type: 'rule',
336
+ rule: {
337
+ condition: 'lessThanOrEqual',
338
+ calldataOffset: n.calldataOffset,
339
+ referenceValue: maxRef,
340
+ },
341
+ },
342
+ ]);
343
+ }
206
344
  return {
207
345
  type: 'rule',
208
346
  rule: {
@@ -1 +1 @@
1
- {"version":3,"file":"arbiters.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/policies/claim/arbiters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AAoD3E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,yBAAyB,EAAE,GAAG,SAAS,EACxD,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,EAAE,GAAG,SAAS,CA6BvB"}
1
+ {"version":3,"file":"arbiters.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/policies/claim/arbiters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AA4D3E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,yBAAyB,EAAE,GAAG,SAAS,EACxD,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,EAAE,GAAG,SAAS,CA6BvB"}
@@ -2,6 +2,14 @@
2
2
  * The arbiter contract keys that each settlement layer expands to. `ACROSS`
3
3
  * whitelists both arbiter impls so a session is permissioned for the 7579 and
4
4
  * multicall paths regardless of which one the orchestrator picks at intent time.
5
+ *
6
+ * `ECO` intentionally remains mapped to the legacy Standard ECO arbiters. The
7
+ * solver-network ECO route uses `intentExecutorAdapter`, but the current claim
8
+ * policy only inspects the outer origin mandate. Authorizing that generic
9
+ * adapter would also authorize unrelated IntentExecutor-backed routes without
10
+ * enforcing the encoded Eco destination, token, recipient, or deadline. Keep
11
+ * solver-network ECO blocked by this built-in permission until a route-aware
12
+ * policy is available.
5
13
  */
6
14
  const SETTLEMENT_LAYER_CONTRACT_KEYS = {
7
15
  SAME_CHAIN: ['samechainArbiter'],
@@ -1,6 +1,12 @@
1
+ import type { Hex } from 'viem';
1
2
  import type { ResolvedModule } from '../types.js';
2
3
  import type { AtomicValidatorDefinition, ResolvedValidatorDefinition } from './types.js';
3
4
  import { type WebauthnCredentialOrdering } from './webauthn.js';
4
5
  export declare function resolveAtomicValidator(definition: AtomicValidatorDefinition, webauthnOrdering?: WebauthnCredentialOrdering): ResolvedModule;
6
+ export declare function resolveAtomicValidatorStatelessData(definition: AtomicValidatorDefinition): Hex;
7
+ export declare function resolveAtomicValidatorFactor(definition: AtomicValidatorDefinition): {
8
+ module: ResolvedModule;
9
+ statelessData: `0x${string}`;
10
+ };
5
11
  export declare function resolveValidator(definition: ResolvedValidatorDefinition, webauthnOrdering?: WebauthnCredentialOrdering): ResolvedModule;
6
12
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../modules/validators/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAI9C,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC5B,MAAM,SAAS,CAAA;AAChB,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAA;AAEnB,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,yBAAyB,EACrC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAYhB;AAKD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,2BAA2B,EACvC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAIhB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../modules/validators/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAI9C,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC5B,MAAM,SAAS,CAAA;AAChB,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAA;AAEnB,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,yBAAyB,EACrC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAYhB;AAKD,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,yBAAyB,GACpC,GAAG,CAYL;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,yBAAyB;;;EAMtC;AAKD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,2BAA2B,EACvC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAIhB"}
@@ -1,7 +1,7 @@
1
- import { resolveEnsValidator } from './ens.js';
1
+ import { encodeEnsStatelessData, resolveEnsValidator } from './ens.js';
2
2
  import { resolveMultiFactorValidator } from './multi-factor.js';
3
- import { resolveOwnableValidator } from './ownable.js';
4
- import { resolveWebauthnValidator, } from './webauthn.js';
3
+ import { resolveOwnableStatelessData, resolveOwnableValidator } from './ownable.js';
4
+ import { resolveWebauthnStatelessData, resolveWebauthnValidator, } from './webauthn.js';
5
5
  export function resolveAtomicValidator(definition, webauthnOrdering) {
6
6
  switch (definition.kind) {
7
7
  case 'ecdsa':
@@ -15,11 +15,33 @@ export function resolveAtomicValidator(definition, webauthnOrdering) {
15
15
  throw new Error(`Validator ${definition.kind} requires feature input`);
16
16
  }
17
17
  }
18
+ // What a validator's `validateSignatureWithData` expects as its `data`. Only
19
+ // Ownable's happens to equal its install data; for WebAuthn and ENS the two
20
+ // differ, and multi-factor factor slots need this one.
21
+ export function resolveAtomicValidatorStatelessData(definition) {
22
+ switch (definition.kind) {
23
+ case 'ecdsa':
24
+ return resolveOwnableStatelessData(definition);
25
+ case 'ens':
26
+ return encodeEnsStatelessData(definition);
27
+ case 'passkey':
28
+ return resolveWebauthnStatelessData(definition);
29
+ case 'k1':
30
+ case 'smart-session':
31
+ throw new Error(`Validator ${definition.kind} requires feature input`);
32
+ }
33
+ }
34
+ export function resolveAtomicValidatorFactor(definition) {
35
+ return {
36
+ module: resolveAtomicValidator(definition),
37
+ statelessData: resolveAtomicValidatorStatelessData(definition),
38
+ };
39
+ }
18
40
  // `webauthnOrdering` only applies to a top-level passkey validator: multi-factor
19
- // stores its sub-validator data without ever calling the sub-validator's
20
- // `onInstall`, so the ascending rule never runs and its bytes must not move.
41
+ // stores the stateless configuration for its factors, whose credential order is
42
+ // fixed by the pinned account the IDs are derived from.
21
43
  export function resolveValidator(definition, webauthnOrdering) {
22
44
  return definition.kind === 'multi-factor'
23
- ? resolveMultiFactorValidator(definition, resolveAtomicValidator)
45
+ ? resolveMultiFactorValidator(definition, resolveAtomicValidatorFactor)
24
46
  : resolveAtomicValidator(definition, webauthnOrdering);
25
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../modules/validators/smart-sessions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAgC,MAAM,MAAM,CAAA;AAU3E,OAAO,EACL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;AAO7B,OAAO,KAAK,EAEV,OAAO,EAEP,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAA;AAEhB,eAAO,MAAM,mCAAmC,EAAE,OACJ,CAAA;AAC9C,eAAO,MAAM,4CAA4C,EAAE,GAAkB,CAAA;AAC7E,eAAO,MAAM,2EAA2E,EACtF,YAAqB,CAAA;AACvB,eAAO,MAAM,uBAAuB,EAClC,4CAAqD,CAAA;AACvD,eAAO,MAAM,yBAAyB,EAAG,YAAqB,CAAA;AAU9D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,GAAG,aAAa,CAAA;IAGnD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CACtC;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,WAAW,CA0Fb;AAED,wBAAgB,SAAS,CACvB,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAyBT;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../modules/validators/smart-sessions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAgC,MAAM,MAAM,CAAA;AAU3E,OAAO,EACL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;AAQ7B,OAAO,KAAK,EAGV,OAAO,EAEP,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAA;AAEhB,eAAO,MAAM,mCAAmC,EAAE,OACJ,CAAA;AAC9C,eAAO,MAAM,4CAA4C,EAAE,GAAkB,CAAA;AAC7E,eAAO,MAAM,2EAA2E,EACtF,YAAqB,CAAA;AACvB,eAAO,MAAM,uBAAuB,EAClC,4CAAqD,CAAA;AACvD,eAAO,MAAM,yBAAyB,EAAG,YAAqB,CAAA;AAU9D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,GAAG,aAAa,CAAA;IAGnD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CACtC;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,WAAW,CAqMb;AAED,wBAAgB,SAAS,CACvB,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAoCT;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
@@ -9,6 +9,7 @@ import { DEFAULT_POLICY_ADDRESSES, resolvePolicyAddresses, } from './policies/ad
9
9
  import { expandCrossChainPermit, resolvePermit2ClaimPolicy, } from './policies/claim.js';
10
10
  import { encodeSessionPolicy } from './policies/encode.js';
11
11
  import { resolveSessionSigning } from './signing.js';
12
+ import { resolveSwapScope } from './swap/scope.js';
12
13
  export const SMART_SESSIONS_FALLBACK_TARGET_FLAG = '0x0000000000000000000000000000000000000001';
13
14
  export const SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG = '0x00000001';
14
15
  export const SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG_PERMITTED_TO_CALL_SMARTSESSION = '0x00000002';
@@ -31,16 +32,73 @@ export function resolveSessionData(definition, options = {}) {
31
32
  actionTarget: SMART_SESSIONS_FALLBACK_TARGET_FLAG,
32
33
  actionPolicies: [{ policy: addresses.sudo, initData: '0x' }],
33
34
  };
34
- const userActions = definition.permissions?.length
35
- ? resolvePermissions(definition.permissions)
36
- : [];
35
+ // `swap` is sugar over permissions + actions: it compiles to one merged
36
+ // approve plus one scoped swap action per venue, then rides the same
37
+ // resolution path. Venue routers, selectors and calldata offsets stay inside
38
+ // swap-venues.ts so they never reach the public surface (RHI-6286).
39
+ const swapScope = definition.swap
40
+ ? resolveSwapScope(definition.swap, definition.chain.id, environment)
41
+ : undefined;
42
+ // Declaring `swap` IS the restriction — a swap-scoped session that still
43
+ // carried the wildcard fallback would let the session key call anything the
44
+ // global intent-execution whitelist allows, which is the opposite of what the
45
+ // caller asked for. `restrictToActions` stays as the explicit spelling for
46
+ // sessions scoped by hand.
47
+ const restricted = definition.restrictToActions === true || swapScope !== undefined;
48
+ const permissions = [
49
+ ...(definition.permissions ?? []),
50
+ ...(swapScope?.permissions ?? []),
51
+ ];
52
+ const userActions = permissions.length ? resolvePermissions(permissions) : [];
53
+ // Raw scoped actions (target + selector + policies) for calls that can't be
54
+ // addressed by the ABI-name `permissions` sugar — e.g. a fynd swap scoped by
55
+ // its raw selector with no ABI (RHI-6286).
56
+ const rawActions = [
57
+ ...(definition.actions ?? []),
58
+ ...(swapScope?.actions ?? []),
59
+ ];
60
+ // A restricted session drops the fallback action, which is also where a
61
+ // cross-chain permit's spending-limit / time-frame guardrails live — so a
62
+ // restricted session combined with a permit would keep claim signing but lose
63
+ // maxAmount/deadline enforcement. These are different authorization surfaces;
64
+ // reject the combination rather than silently drop the guardrails.
65
+ if (restricted &&
66
+ (definition.crossChainPermits?.length || definition.claimPolicies?.length)) {
67
+ throw new Error('restrictToActions is incompatible with crossChainPermits/claimPolicies: ' +
68
+ 'dropping the fallback also drops the permit guardrails (spending/time ' +
69
+ 'limits). Use a restricted scoped-action session or a permit session, ' +
70
+ 'not both.');
71
+ }
72
+ // Guard raw actions from reintroducing the wildcard: reject one without
73
+ // target+selector (would map to the fallback flags), or one that targets the
74
+ // fallback sentinel outright — either would re-add the wildcard action that
75
+ // restrictToActions drops.
76
+ for (const a of rawActions) {
77
+ if (!('target' in a) || !('selector' in a)) {
78
+ throw new Error('definition.actions entries must be scoped (target + selector); a ' +
79
+ 'fallback-shaped action would map to the wildcard fallback target');
80
+ }
81
+ if (a.target.toLowerCase() ===
82
+ SMART_SESSIONS_FALLBACK_TARGET_FLAG.toLowerCase()) {
83
+ throw new Error('definition.actions must not target the fallback sentinel ' +
84
+ `(${SMART_SESSIONS_FALLBACK_TARGET_FLAG}) — it reintroduces the wildcard action`);
85
+ }
86
+ }
37
87
  const expandedPermits = (definition.crossChainPermits ?? []).map((input) => expandCrossChainPermit(resolveCrossChainPermission(input), environment));
38
88
  const permitFallbackPolicies = expandedPermits.flatMap(({ fallbackPolicies }) => fallbackPolicies);
89
+ // The wildcard intent-execution fallback. Dropped for a restricted session so
90
+ // the explicit permissions are the ONLY authorized ops — a non-listed selector
91
+ // then reverts instead of escaping via the global intent-execution target
92
+ // whitelist (RHI-6286).
93
+ const fallbackAction = {
94
+ policies: [{ type: 'intent-execution' }, ...permitFallbackPolicies],
95
+ };
39
96
  const injectedActions = [
40
97
  // Native-wrap `deposit()` is only permitted when the caller supplies the
41
98
  // chain's wrapped-native token (e.g. via `RhinestoneSDK.createSession`,
42
- // which resolves it from `/chains`).
43
- ...(options.wrappedNativeToken
99
+ // which resolves it from `/chains`). Dropped for a restricted session so it
100
+ // can't add an unrequested sudo action beyond the caller's permissions.
101
+ ...(options.wrappedNativeToken && !restricted
44
102
  ? [
45
103
  {
46
104
  target: options.wrappedNativeToken,
@@ -54,17 +112,45 @@ export function resolveSessionData(definition, options = {}) {
54
112
  },
55
113
  ]
56
114
  : []),
57
- {
58
- policies: [{ type: 'intent-execution' }, ...permitFallbackPolicies],
59
- },
115
+ ...(restricted ? [] : [fallbackAction]),
60
116
  {
61
117
  target: DUMMY_PRECLAIMOP_TARGET,
62
118
  selector: DUMMY_PRECLAIMOP_SELECTOR,
63
- policies: [{ type: 'sudo' }],
119
+ // The real pre-claim op carries no value, so cap it for a restricted
120
+ // session rather than granting sudo, which would let this injected action
121
+ // send native value to the dummy target.
122
+ //
123
+ // 1 wei, NOT 0: `ValueLimitPolicy.initializeWithMultiplexer` does
124
+ // `require(valueLimit != 0)`, so a zero limit reverts while the policy is
125
+ // being installed. That made every restricted session impossible to
126
+ // enable — the revert surfaces as `InvalidSignature()` from the emissary,
127
+ // which reads as a signature problem rather than a policy-init one.
128
+ // 1 wei is the smallest limit that installs, and the op carries no value.
129
+ policies: restricted
130
+ ? [{ type: 'value-limit', limit: 1n }]
131
+ : [{ type: 'sudo' }],
64
132
  },
65
133
  ];
66
- const actions = userActions.length || permitFallbackPolicies.length
67
- ? [...userActions, ...injectedActions].map((action) => ({
134
+ if (restricted && !userActions.length && !rawActions.length) {
135
+ throw new Error('restrictToActions drops the fallback, so the session must supply at ' +
136
+ 'least one permission or action — none were given');
137
+ }
138
+ // Raw actions bypass resolvePermissions' duplicate guard, so a raw action that
139
+ // collides with an ABI permission (or another raw action) on the same
140
+ // (target, selector) would map to the same on-chain action id and silently
141
+ // overwrite policy config — reject it instead.
142
+ const scoped = [...userActions, ...rawActions].filter((a) => 'target' in a && 'selector' in a);
143
+ const seen = new Set();
144
+ for (const a of scoped) {
145
+ const key = `${a.target.toLowerCase()}:${a.selector.toLowerCase()}`;
146
+ if (seen.has(key)) {
147
+ throw new Error(`Duplicate scoped action for (${a.target}, ${a.selector}) — permissions ` +
148
+ 'and actions share one on-chain action id; merge them into a single entry');
149
+ }
150
+ seen.add(key);
151
+ }
152
+ const actions = userActions.length || rawActions.length || permitFallbackPolicies.length
153
+ ? [...userActions, ...rawActions, ...injectedActions].map((action) => ({
68
154
  actionTargetSelector: 'selector' in action
69
155
  ? action.selector
70
156
  : SMART_SESSIONS_FALLBACK_TARGET_SELECTOR_FLAG,
@@ -81,8 +167,12 @@ export function resolveSessionData(definition, options = {}) {
81
167
  policy: PERMIT2_CLAIM_POLICY_ADDRESS,
82
168
  initData: encodePermit2ClaimPolicyInitData(resolvePermit2ClaimPolicy(policy)),
83
169
  }));
170
+ // A restricted session must not leave an open ERC-1271 signing surface: with
171
+ // signing defaulting to unrestricted, a session key limited to swap/approve
172
+ // could still sign e.g. a Permit2 approval off-chain and move funds. Default it
173
+ // to `disabled` when restricting; the caller can still opt into a signing policy.
84
174
  const erc7739Policies = resolveSessionSigning({
85
- signing: definition.signing,
175
+ signing: definition.signing ?? (restricted ? { mode: 'disabled' } : undefined),
86
176
  environment,
87
177
  addresses,
88
178
  });
@@ -108,7 +198,15 @@ export function toSession(definition, options = {}) {
108
198
  return {
109
199
  chain: definition.chain,
110
200
  owners: definition.owners,
111
- hasExplicitPermissions: Boolean(definition.permissions?.length),
201
+ // Drives `verifyExecutions`, which selects the signature mode: false lets an
202
+ // already-enabled session sign in pure ERC-1271 mode, and that mode never
203
+ // reaches the emissary's `verifyExecution` — so the action policies are
204
+ // never consulted. `swap` compiles to actions, so it MUST count here, or a
205
+ // swap-only session would silently stop being action-checked the moment it
206
+ // is enabled.
207
+ hasExplicitPermissions: Boolean(definition.permissions?.length ||
208
+ definition.actions?.length ||
209
+ definition.swap),
112
210
  permissionId: getPermissionIdFromData(data),
113
211
  sessionValidator: data.sessionValidator,
114
212
  sessionValidatorInitData: data.sessionValidatorInitData,
@@ -116,6 +214,7 @@ export function toSession(definition, options = {}) {
116
214
  erc7739Policies: data.erc7739Policies,
117
215
  actions: data.actions,
118
216
  claimPolicies: [...(definition.claimPolicies ?? []), ...expandedClaims],
217
+ ...(definition.swap ? { swap: definition.swap } : {}),
119
218
  };
120
219
  }
121
220
  export { DEFAULT_POLICY_ADDRESSES };
@@ -0,0 +1,92 @@
1
+ import { type Address } from 'viem';
2
+ import type { FyndVenue } from '../types.js';
3
+ import type { VenueContext, VenueScoping } from './rules.js';
4
+ /**
5
+ * fynd — Rhinestone's self-hosted Tycho aggregator.
6
+ *
7
+ * Unlike 0x there is no allowance-holder indirection: the TychoRouter is both
8
+ * the swap target and the ERC-20 approval spender, and every field we care about
9
+ * is a named static argument in the calldata head. That means nothing here is a
10
+ * magic offset — the rules are addressed by ABI parameter name.
11
+ */
12
+ /** Chains with a deployed, whitelisted TychoRouter. */
13
+ export declare const FYND_CHAIN_IDS: readonly [1, 56, 130, 137, 8453, 9745, 42161];
14
+ export type FyndChainId = (typeof FYND_CHAIN_IDS)[number];
15
+ /**
16
+ * TychoRouter per chain — the contract fynd's encoded fills target.
17
+ *
18
+ * Mirrors `fynd.routers` in the orchestrator's quoters.jsonnet. A chain is
19
+ * listed only when the router is whitelisted in `IntentExecutionPolicy`, since a
20
+ * quoter enabled without a whitelisted target produces quotes that revert.
21
+ */
22
+ export declare const FYND_ROUTERS: Record<FyndChainId, Address>;
23
+ /**
24
+ * TychoRouter `singleSwap`, the entrypoint fynd's encoded quotes call.
25
+ *
26
+ * Only the argument TYPES determine the selector and the head layout, and those
27
+ * are pinned by {@link FYND_SWAP_SELECTOR}'s assertion in the test suite. The
28
+ * NAMES of the first five are corroborated by observed fynd calldata; the
29
+ * trailing tuple's component names are our own labels and carry no guarantee —
30
+ * we never address them, since a tuple containing `bytes` is dynamic and sits
31
+ * behind a pointer.
32
+ */
33
+ export declare const tychoRouterAbi: readonly [{
34
+ readonly type: "function";
35
+ readonly name: "singleSwap";
36
+ readonly stateMutability: "payable";
37
+ readonly inputs: readonly [{
38
+ readonly name: "amountIn";
39
+ readonly type: "uint256";
40
+ }, {
41
+ readonly name: "tokenIn";
42
+ readonly type: "address";
43
+ }, {
44
+ readonly name: "tokenOut";
45
+ readonly type: "address";
46
+ }, {
47
+ readonly name: "minAmountOut";
48
+ readonly type: "uint256";
49
+ }, {
50
+ readonly name: "receiver";
51
+ readonly type: "address";
52
+ }, {
53
+ readonly name: "permit";
54
+ readonly type: "tuple";
55
+ readonly components: readonly [{
56
+ readonly name: "nonce";
57
+ readonly type: "uint16";
58
+ }, {
59
+ readonly name: "token";
60
+ readonly type: "address";
61
+ }, {
62
+ readonly name: "amount";
63
+ readonly type: "uint256";
64
+ }, {
65
+ readonly name: "deadline";
66
+ readonly type: "uint256";
67
+ }, {
68
+ readonly name: "signature";
69
+ readonly type: "bytes";
70
+ }];
71
+ }, {
72
+ readonly name: "swap";
73
+ readonly type: "bytes";
74
+ }];
75
+ readonly outputs: readonly [{
76
+ readonly name: "amountOut";
77
+ readonly type: "uint256";
78
+ }];
79
+ }];
80
+ export declare const FYND_SWAP_SELECTOR: `0x${string}`;
81
+ /**
82
+ * Scope a session to fynd swaps.
83
+ *
84
+ * Needs no addresses: the router is a per-chain deployment the SDK knows, and
85
+ * because it is also the approval spender there is no separate allowance target
86
+ * to reason about.
87
+ */
88
+ export declare function fynd(options?: {
89
+ maxSpend?: bigint;
90
+ }): FyndVenue;
91
+ export declare function scopeFynd(ctx: VenueContext): VenueScoping;
92
+ //# sourceMappingURL=fynd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fynd.d.ts","sourceRoot":"","sources":["../../../../../../modules/validators/smart-sessions/swap/fynd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAsB,MAAM,MAAM,CAAA;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAkC,MAAM,UAAU,CAAA;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGzD;;;;;;;GAOG;AAEH,uDAAuD;AACvD,eAAO,MAAM,cAAc,+CAAgD,CAAA;AAE3E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAQrD,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BH,CAAA;AAExB,eAAO,MAAM,kBAAkB,eAAwC,CAAA;AAQvE;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,SAAS,CAKnE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,YAAY,CAoBzD"}
@@ -0,0 +1,101 @@
1
+ import { toFunctionSelector } from 'viem';
2
+ import { namedParamOffsets } from '../../permissions.js';
3
+ import { cumulativeCap, pin, swapAction } from './rules.js';
4
+ /**
5
+ * fynd — Rhinestone's self-hosted Tycho aggregator.
6
+ *
7
+ * Unlike 0x there is no allowance-holder indirection: the TychoRouter is both
8
+ * the swap target and the ERC-20 approval spender, and every field we care about
9
+ * is a named static argument in the calldata head. That means nothing here is a
10
+ * magic offset — the rules are addressed by ABI parameter name.
11
+ */
12
+ /** Chains with a deployed, whitelisted TychoRouter. */
13
+ export const FYND_CHAIN_IDS = [1, 56, 130, 137, 8453, 9745, 42161];
14
+ /**
15
+ * TychoRouter per chain — the contract fynd's encoded fills target.
16
+ *
17
+ * Mirrors `fynd.routers` in the orchestrator's quoters.jsonnet. A chain is
18
+ * listed only when the router is whitelisted in `IntentExecutionPolicy`, since a
19
+ * quoter enabled without a whitelisted target produces quotes that revert.
20
+ */
21
+ export const FYND_ROUTERS = {
22
+ 1: '0xda892c989d07a18b5dd3f392d949f00df15c5736',
23
+ 56: '0x99748cbd931cb367dad265c5b2b4bd306d448e99',
24
+ 130: '0x764bc67b1036b00bc91221e988261f971a1c7ce4',
25
+ 137: '0x7cb3e87095f6cf95982dc6f57445171a6d3b511c',
26
+ 8453: '0x2d3524b9b5dae34b646614eebb1e038d403e4cac',
27
+ 9745: '0x8f9b3b0451efff0ae8100428aee35fa3cbc0b769',
28
+ 42161: '0xc1f838a5382bbb5729a0801c8ba73dfc861c4d34',
29
+ };
30
+ /**
31
+ * TychoRouter `singleSwap`, the entrypoint fynd's encoded quotes call.
32
+ *
33
+ * Only the argument TYPES determine the selector and the head layout, and those
34
+ * are pinned by {@link FYND_SWAP_SELECTOR}'s assertion in the test suite. The
35
+ * NAMES of the first five are corroborated by observed fynd calldata; the
36
+ * trailing tuple's component names are our own labels and carry no guarantee —
37
+ * we never address them, since a tuple containing `bytes` is dynamic and sits
38
+ * behind a pointer.
39
+ */
40
+ export const tychoRouterAbi = [
41
+ {
42
+ type: 'function',
43
+ name: 'singleSwap',
44
+ stateMutability: 'payable',
45
+ inputs: [
46
+ { name: 'amountIn', type: 'uint256' },
47
+ { name: 'tokenIn', type: 'address' },
48
+ { name: 'tokenOut', type: 'address' },
49
+ { name: 'minAmountOut', type: 'uint256' },
50
+ { name: 'receiver', type: 'address' },
51
+ {
52
+ name: 'permit',
53
+ type: 'tuple',
54
+ components: [
55
+ { name: 'nonce', type: 'uint16' },
56
+ { name: 'token', type: 'address' },
57
+ { name: 'amount', type: 'uint256' },
58
+ { name: 'deadline', type: 'uint256' },
59
+ { name: 'signature', type: 'bytes' },
60
+ ],
61
+ },
62
+ { name: 'swap', type: 'bytes' },
63
+ ],
64
+ outputs: [{ name: 'amountOut', type: 'uint256' }],
65
+ },
66
+ ];
67
+ export const FYND_SWAP_SELECTOR = toFunctionSelector(tychoRouterAbi[0]);
68
+ /** Head offsets derived from the ABI — never hardcoded. */
69
+ const OFFSETS = namedParamOffsets(tychoRouterAbi, 'singleSwap');
70
+ /**
71
+ * Scope a session to fynd swaps.
72
+ *
73
+ * Needs no addresses: the router is a per-chain deployment the SDK knows, and
74
+ * because it is also the approval spender there is no separate allowance target
75
+ * to reason about.
76
+ */
77
+ export function fynd(options = {}) {
78
+ return {
79
+ id: 'fynd',
80
+ ...(options.maxSpend !== undefined ? { maxSpend: options.maxSpend } : {}),
81
+ };
82
+ }
83
+ export function scopeFynd(ctx) {
84
+ const router = FYND_ROUTERS[ctx.chainId];
85
+ if (router === undefined) {
86
+ throw new Error(`fynd is not available on chain ${ctx.chainId}. ` +
87
+ `Supported: ${FYND_CHAIN_IDS.join(', ')}.`);
88
+ }
89
+ const rules = [
90
+ pin(OFFSETS.tokenIn, ctx.sellToken),
91
+ pin(OFFSETS.tokenOut, ctx.buyToken),
92
+ pin(OFFSETS.receiver, ctx.recipient),
93
+ ];
94
+ if (ctx.cap !== undefined) {
95
+ rules.push(cumulativeCap(OFFSETS.amountIn, ctx.cap));
96
+ }
97
+ return {
98
+ approveSpenders: [router],
99
+ actions: [swapAction(router, FYND_SWAP_SELECTOR, rules)],
100
+ };
101
+ }