@agenticprimitives/contracts 0.1.0-alpha.3 → 1.0.0-alpha.5

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 (57) hide show
  1. package/AUDIT.md +304 -47
  2. package/README.md +19 -8
  3. package/deployments-base-sepolia.json +1 -0
  4. package/dist/abi/AgentAccount.json +1647 -0
  5. package/dist/abi/AgentAccountFactory.json +472 -0
  6. package/dist/abi/AgentNameRegistry.json +46 -0
  7. package/dist/abi/AgreementRegistry.json +432 -0
  8. package/dist/abi/AttestationRegistry.json +436 -0
  9. package/dist/abi/CallDataHashEnforcer.json +119 -0
  10. package/dist/abi/CustodyPolicy.json +1111 -0
  11. package/dist/abi/DelegationManager.json +645 -0
  12. package/dist/abi/GeoFeatureRegistry.json +625 -0
  13. package/dist/abi/IAgentAccount.json +262 -0
  14. package/dist/abi/IAgentAccountFactoryAccessor.json +15 -0
  15. package/dist/abi/IAgentAccountFactoryView.json +41 -0
  16. package/dist/abi/IAgentAccountPauseView.json +15 -0
  17. package/dist/abi/IAgenticPrimitivesAgentAccount.json +15 -0
  18. package/dist/abi/IApprovedHashRegistryView.json +26 -0
  19. package/dist/abi/ICustodyPolicyFactoryView.json +15 -0
  20. package/dist/abi/ICustodyPolicyPauseView.json +15 -0
  21. package/dist/abi/IDelegationManager.json +171 -0
  22. package/dist/abi/IERC7579HookLike.json +44 -0
  23. package/dist/abi/IERC7579ModuleLike.json +28 -0
  24. package/dist/abi/PermissionlessSubregistry.json +5 -0
  25. package/dist/abi/QuorumEnforcer.json +295 -0
  26. package/dist/abi/SignatureSlotRecovery.json +88 -0
  27. package/dist/abi/SkillDefinitionRegistry.json +491 -0
  28. package/dist/abi/SmartAgentPaymaster.json +664 -0
  29. package/dist/abi/UniversalSignatureValidator.json +65 -0
  30. package/dist/abi/index.d.ts +23 -0
  31. package/dist/abi/index.js +23 -0
  32. package/dist/deployments/base-sepolia.d.ts +37 -0
  33. package/dist/deployments/base-sepolia.js +35 -0
  34. package/dist/deployments/index.d.ts +8 -0
  35. package/dist/deployments/index.js +13 -0
  36. package/package.json +20 -3
  37. package/src/AgentAccount.sol +130 -9
  38. package/src/AgentAccountFactory.sol +34 -5
  39. package/src/SmartAgentPaymaster.sol +37 -8
  40. package/src/UniversalSignatureValidator.sol +3 -0
  41. package/src/agency/DelegationManager.sol +155 -0
  42. package/src/agency/IDelegationManager.sol +35 -0
  43. package/src/agreement/AgreementRegistry.sol +306 -0
  44. package/src/attestation/AttestationRegistry.sol +320 -0
  45. package/src/custody/CustodyPolicy.sol +79 -2
  46. package/src/enforcers/AllowedMethodsEnforcer.sol +6 -0
  47. package/src/enforcers/AllowedTargetsEnforcer.sol +6 -0
  48. package/src/enforcers/CallDataHashEnforcer.sol +61 -0
  49. package/src/enforcers/QuorumEnforcer.sol +8 -0
  50. package/src/enforcers/TimestampEnforcer.sol +7 -0
  51. package/src/enforcers/ValueEnforcer.sol +6 -0
  52. package/src/geo/GeoFeatureRegistry.sol +190 -0
  53. package/src/naming/AgentNameRegistry.sol +25 -13
  54. package/src/naming/AgentNameUniversalResolver.sol +1 -1
  55. package/src/naming/PermissionlessSubregistry.sol +19 -2
  56. package/src/relationships/AgentRelationship.sol +8 -0
  57. package/src/skills/SkillDefinitionRegistry.sol +181 -0
@@ -10,7 +10,7 @@ import "./governance/GovernanceManaged.sol";
10
10
  /**
11
11
  * @title AgentAccountFactory
12
12
  * @notice Single-entry factory for AgentAccount proxies with
13
- * deterministic CREATE2 addresses. Phase 6f.5 — collapsed
13
+ * deterministic CREATE2 addresses. Phase 6f.5 -- collapsed
14
14
  * createPersonAgent + createMultiSigSmartAgent into one
15
15
  * entry point so every account on chain has the same shape;
16
16
  * the only axis is `mode` on the init params.
@@ -32,7 +32,7 @@ import "./governance/GovernanceManaged.sol";
32
32
  * marker check).
33
33
  *
34
34
  * The canonical CustodyPolicy address is wired at construction
35
- * (factory-immutable) — there is no per-call validator
35
+ * (factory-immutable) -- there is no per-call validator
36
36
  * override. Swapping CustodyPolicy versions means redeploying
37
37
  * the factory, which is the same blast radius as a CREATE2
38
38
  * salt bump.
@@ -41,7 +41,7 @@ import "./governance/GovernanceManaged.sol";
41
41
  * `bundlerSigner` and `sessionIssuer` are factory-level capability
42
42
  * addresses. They are NEVER custodians of any deployed account; each
43
43
  * account reads them off this factory on demand. Phase A.5 made both
44
- * mutable under governance — rotation flows through a governance
44
+ * mutable under governance -- rotation flows through a governance
45
45
  * proposal + 48h timelock and propagates automatically to every
46
46
  * existing AgentAccount.
47
47
  */
@@ -49,6 +49,11 @@ contract AgentAccountFactory is GovernanceManaged {
49
49
  AgentAccount public immutable accountImplementation;
50
50
  address public immutable delegationManager;
51
51
  address public immutable custodyPolicy;
52
+ /// @dev Spec 253 — the ApprovedHashRegistry baked (immutable) into the
53
+ /// AgentAccount impl this factory deploys; surfaced here for
54
+ /// deploy-time auditing. The account's `isValidSignature` 0x03
55
+ /// sentinel consults it; it is NOT a mutable factory-view.
56
+ address public immutable approvedHashRegistry;
52
57
 
53
58
  address public bundlerSigner;
54
59
  address public sessionIssuer;
@@ -73,6 +78,21 @@ contract AgentAccountFactory is GovernanceManaged {
73
78
  error InsufficientTrusteesForMode(uint8 mode, uint256 actual, uint256 required);
74
79
  error TrusteesRequiredForRecoverableMode(uint8 mode);
75
80
  error ZeroAddress();
81
+ /// @notice R9.6 / ATL-SEC-05 -- thrown when `params.custodians.length`
82
+ /// exceeds `MAX_INITIAL_CUSTODIANS`. Without this cap,
83
+ /// `_buildValidatorInitData` casts `nSigners` to `uint8`
84
+ /// and silently truncates at 256, computing a wrong default
85
+ /// threshold for a misconfigured account. A self-hurt
86
+ /// footgun, not a cross-account vulnerability -- capped so
87
+ /// the misconfiguration fails closed at construction.
88
+ error TooManyInitialCustodians(uint256 actual, uint256 max);
89
+
90
+ /// @notice R9.6 / ATL-SEC-05 -- upper bound on initial custodians
91
+ /// passed via `AgentAccountInitParams`. 32 is deliberately
92
+ /// generous (every smart-account substrate we surveyed
93
+ /// caps well below 16) but well under the `uint8` truncation
94
+ /// threshold at `_buildValidatorInitData:252`.
95
+ uint256 public constant MAX_INITIAL_CUSTODIANS = 32;
76
96
 
77
97
  constructor(
78
98
  IEntryPoint entryPoint_,
@@ -80,10 +100,12 @@ contract AgentAccountFactory is GovernanceManaged {
80
100
  address custodyPolicy_,
81
101
  address bundlerSigner_,
82
102
  address sessionIssuer_,
83
- address governance_
103
+ address governance_,
104
+ address approvedHashRegistry_
84
105
  ) GovernanceManaged(governance_) {
85
106
  if (custodyPolicy_ == address(0)) revert ZeroAddress();
86
- accountImplementation = new AgentAccount(entryPoint_);
107
+ accountImplementation = new AgentAccount(entryPoint_, approvedHashRegistry_);
108
+ approvedHashRegistry = approvedHashRegistry_;
87
109
  delegationManager = delegationManager_;
88
110
  custodyPolicy = custodyPolicy_;
89
111
  bundlerSigner = bundlerSigner_;
@@ -219,6 +241,13 @@ contract AgentAccountFactory is GovernanceManaged {
219
241
 
220
242
  bool hasPasskey = params.initialPasskeyX != 0 && params.initialPasskeyY != 0;
221
243
  if (params.custodians.length == 0 && !hasPasskey) revert NoInitialSigner();
244
+ // R9.6 / ATL-SEC-05: cap the initial custodian set so
245
+ // `_buildValidatorInitData`'s `uint8(nSigners)` cast can't
246
+ // silently truncate. 32 is well above any rational config and
247
+ // well below the uint8 boundary.
248
+ if (params.custodians.length > MAX_INITIAL_CUSTODIANS) {
249
+ revert TooManyInitialCustodians(params.custodians.length, MAX_INITIAL_CUSTODIANS);
250
+ }
222
251
 
223
252
  if (params.mode == 0) return;
224
253
 
@@ -51,12 +51,26 @@ import "./governance/IGovernance.sol";
51
51
  * The hash deliberately omits `paymasterAndData` from the userOp
52
52
  * because the signature lives there.
53
53
  *
54
+ * Construction modes (R5.7 / PKG-PAYMASTER-002 closure — external audit P0-2):
55
+ * The constructor takes `bool devMode_` + `address verifyingSigner_`
56
+ * EXPLICITLY. There is no implicit fail-open default; pre-R5.7 the
57
+ * constructor forcibly set `_dev = true` and production deploys had to
58
+ * remember to call `setDevMode(false) + setVerifyingSigner(...)` AFTER
59
+ * the broadcast, which left a window where the paymaster would sponsor
60
+ * any userOp on the freshly-deployed network. Now: testnet deploys pass
61
+ * `devMode_=true`; production deploys pass `devMode_=false` with a
62
+ * verifying signer (or allowlist seed). See `Deploy.s.sol`.
63
+ *
54
64
  * Production checklist:
55
- * 1. Call `setDevMode(false)` (governance only) to leave dev mode.
56
- * 2. Call `setVerifyingSigner(<KMS-backed signer addr>)` to enable
57
- * verifying-paymaster mode (preferred). OR populate `_acceptList`
58
- * via `setAccepted` if you want allowlist mode.
59
- * 3. Monitor `getDeposit()` and alert below a runway threshold.
65
+ * 1. Construct with `devMode_=false` AND either:
66
+ * - a non-zero `verifyingSigner_` for verifying-paymaster mode
67
+ * (preferred — Pimlico/Stackup/Alchemy pattern), OR
68
+ * - `verifyingSigner_=address(0)` to start in allowlist mode (no
69
+ * sender is sponsored until `setAccepted` runs). The fall-back
70
+ * to allowlist is fail-closed: every userOp reverts with
71
+ * `SenderNotAccepted` until governance explicitly opts a sender
72
+ * in. That is the documented safe state.
73
+ * 2. Monitor `getDeposit()` and alert below a runway threshold.
60
74
  *
61
75
  * @dev Inherits `addStake`, `unlockStake`, `withdrawStake`, `deposit`,
62
76
  * and `withdrawTo` from `BasePaymaster`. Ownable owner is set in
@@ -102,15 +116,28 @@ contract SmartAgentPaymaster is BasePaymaster {
102
116
  /// begins: 20 (paymaster addr) + 16 (verifGas) + 16 (postOpGas).
103
117
  uint256 private constant PM_DATA_OFFSET = 52;
104
118
 
119
+ /// @param devMode_ true → accept-all (dev/anvil); false → require
120
+ /// verifying-signer or allowlist. R5.7 removed
121
+ /// the implicit fail-open default.
122
+ /// @param verifyingSigner_ EOA that signs paymaster envelopes when
123
+ /// `devMode_=false`. Pass `address(0)` to start
124
+ /// in allowlist mode (fail-closed until
125
+ /// `setAccepted` runs).
105
126
  constructor(
106
127
  IEntryPoint entryPointAddr,
107
128
  address initialOwner,
108
- address governance_
129
+ address governance_,
130
+ bool devMode_,
131
+ address verifyingSigner_
109
132
  ) BasePaymaster(entryPointAddr, initialOwner) {
110
133
  if (governance_ == address(0)) revert ZeroGovernance();
111
134
  governance = governance_;
112
- _dev = true;
113
- emit DevModeSet(true);
135
+ _dev = devMode_;
136
+ emit DevModeSet(devMode_);
137
+ if (verifyingSigner_ != address(0)) {
138
+ verifyingSigner = verifyingSigner_;
139
+ emit VerifyingSignerSet(address(0), verifyingSigner_);
140
+ }
114
141
  }
115
142
 
116
143
  // ─── Admin (governance-only) ────────────────────────────────────────
@@ -236,6 +263,8 @@ contract SmartAgentPaymaster is BasePaymaster {
236
263
  // EIP-191 wrap matches what KMS-backed `signMessage({raw})`
237
264
  // produces via the v0.7 reference paymaster convention.
238
265
  bytes32 ethHash = hash.toEthSignedMessageHash();
266
+ // R6.3: third return (sigVersion) intentionally discarded; the explicit `err` + `recovered != verifyingSigner` revert IS the auth.
267
+ // slither-disable-next-line unused-return
239
268
  (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(ethHash, signature);
240
269
  if (err != ECDSA.RecoverError.NoError || recovered != verifyingSigner) {
241
270
  // Returning sigFailed=true (per EntryPoint convention)
@@ -125,11 +125,14 @@ contract UniversalSignatureValidator {
125
125
  ) private pure returns (bool) {
126
126
  if (sig.length != 65) return false;
127
127
  // Try raw hash first.
128
+ // R6.3: third return (sigVersion) intentionally discarded; the `err` + `recovered == signer` is the auth.
129
+ // slither-disable-next-line unused-return
128
130
  (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, sig);
129
131
  if (err == ECDSA.RecoverError.NoError && recovered == signer) return true;
130
132
  // Then try the eth-signed prefix variant — many wallets sign this
131
133
  // form even when the caller passes a raw digest.
132
134
  bytes32 prefixed = hash.toEthSignedMessageHash();
135
+ // slither-disable-next-line unused-return
133
136
  (recovered, err, ) = ECDSA.tryRecover(prefixed, sig);
134
137
  return err == ECDSA.RecoverError.NoError && recovered == signer;
135
138
  }
@@ -86,6 +86,11 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
86
86
  "Caveat(address enforcer,bytes terms)"
87
87
  );
88
88
 
89
+ /// @dev Gas cap for the read-only caveat evaluation in `verifyAuthorizationForCall`.
90
+ /// Generous for any pure/view enforcer; bounds the gas a stateful enforcer would
91
+ /// burn before reverting under staticcall, so on-chain callers get a cheap `false`.
92
+ uint256 private constant CAVEAT_VIEW_GAS = 1_000_000;
93
+
89
94
  /// @dev Revoked delegation hashes
90
95
  mapping(bytes32 => bool) private _revoked;
91
96
 
@@ -242,6 +247,156 @@ contract DelegationManager is IDelegationManager, ReentrancyGuard {
242
247
  return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
243
248
  }
244
249
 
250
+ /// @notice View-only verification of a delegation chain. Returns whether
251
+ /// the chain is well-formed (signatures, authority chain, not
252
+ /// revoked) WITHOUT executing it.
253
+ ///
254
+ /// Spec 242 §6 (PD-9): the `attestations` package uses this to verify
255
+ /// bilateral-consent delegations as signed authorization predicates,
256
+ /// rather than redeeming them as cross-account execution. Returning
257
+ /// `(true, "")` means the delegation chain authorizes the named call from
258
+ /// `sender`. Caveat predicates (CalldataHashEnforcer, AllowedTargets,
259
+ /// AllowedMethods, Timestamp) are NOT executed here — callers are expected
260
+ /// to pin those into the delegation's caveats at issuance time, where
261
+ /// they're hash-bound to the delegation signature.
262
+ ///
263
+ /// @param delegations Chain (leaf first; root authority last)
264
+ /// @param sender The address that would be the redeemer
265
+ /// @return ok True if every link verifies
266
+ /// @return reason Empty if ok; otherwise a short rejection reason
267
+ function verifyAuthorization(
268
+ Delegation[] calldata delegations,
269
+ address sender
270
+ ) external view returns (bool ok, string memory reason) {
271
+ if (delegations.length == 0) return (false, "empty-chain");
272
+
273
+ for (uint256 i = 0; i < delegations.length; i++) {
274
+ (bool valid, string memory r) = _validateDelegationView(delegations, i, sender);
275
+ if (!valid) return (false, r);
276
+ }
277
+ return (true, "");
278
+ }
279
+
280
+ /// @notice View verifier for a SPECIFIC call (ADR-0027 / spec 249, RW1-4). Validates the
281
+ /// delegation chain AND evaluates every caveat's `beforeHook` against the exact
282
+ /// `(target, value, data)` — read-only, FAIL-CLOSED.
283
+ /// @dev `verifyAuthorization` checks chain/signature/authority/revocation but NOT the
284
+ /// caveats — too weak to authorize a specific call. This evaluates each caveat via
285
+ /// `staticcall` (the hook is `external`/non-view because stateful enforcers like
286
+ /// Quorum exist). Returns `true` ONLY if the chain validates and EVERY caveat passes
287
+ /// read-only. A caveat that reverts (denied) OR cannot be evaluated read-only (a
288
+ /// stateful enforcer attempting a write under staticcall) yields `false` — so `true`
289
+ /// is a genuine authorization guarantee for this exact call, and `false` means "use
290
+ /// live redemption" (which evaluates stateful caveats properly). The caveat
291
+ /// evaluation mirrors `_runBeforeHooks` exactly (same args, same `sender`-as-redeemer),
292
+ /// so an unknown/no-code enforcer is a no-op here just as it is in redemption. Pure
293
+ /// addition — `redeemDelegation` is untouched.
294
+ /// @param delegations Chain (leaf first; root authority last)
295
+ /// @param sender The address that would be the redeemer
296
+ /// @param target The exact target the delegated call would hit
297
+ /// @param value The exact ETH value
298
+ /// @param data The exact calldata
299
+ function verifyAuthorizationForCall(
300
+ Delegation[] calldata delegations,
301
+ address sender,
302
+ address target,
303
+ uint256 value,
304
+ bytes calldata data
305
+ ) external view returns (bool ok, string memory reason) {
306
+ if (delegations.length == 0) return (false, "empty-chain");
307
+
308
+ for (uint256 i = 0; i < delegations.length; i++) {
309
+ (bool valid, string memory r) = _validateDelegationView(delegations, i, sender);
310
+ if (!valid) return (false, r);
311
+
312
+ Delegation calldata d = delegations[i];
313
+ bytes32 dHash = hashDelegation(d);
314
+ for (uint256 j = 0; j < d.caveats.length; j++) {
315
+ // staticcall the same hook redemption would call; success == satisfied read-only.
316
+ // Gas-bounded: a stateful enforcer (which would revert on its SSTORE under
317
+ // staticcall) is capped so an on-chain caller (e.g. RW1-1) sees a cheap, predictable
318
+ // `false`, not an out-of-gas of the whole call. CAVEAT_VIEW_GAS is generous for any
319
+ // pure/view enforcer (timestamp/value/targets/methods are all far below it).
320
+ (bool passed, ) = d.caveats[j].enforcer.staticcall{gas: CAVEAT_VIEW_GAS}(
321
+ abi.encodeWithSelector(
322
+ ICaveatEnforcer.beforeHook.selector,
323
+ d.caveats[j].terms,
324
+ d.caveats[j].args,
325
+ dHash,
326
+ d.delegator,
327
+ sender,
328
+ target,
329
+ value,
330
+ data
331
+ )
332
+ );
333
+ if (!passed) return (false, "caveat-failed");
334
+ }
335
+ }
336
+ return (true, "");
337
+ }
338
+
339
+ /// @dev View-only sibling of `_validateDelegation`. Same checks, no state
340
+ /// writes, no events. Used by `verifyAuthorization`.
341
+ function _validateDelegationView(
342
+ Delegation[] calldata delegations,
343
+ uint256 i,
344
+ address sender
345
+ ) internal view returns (bool ok, string memory reason) {
346
+ Delegation calldata d = delegations[i];
347
+ bytes32 dHash = hashDelegation(d);
348
+
349
+ if (_revoked[dHash]) return (false, "revoked");
350
+
351
+ // Delegate chain
352
+ if (i == 0) {
353
+ if (d.delegate != OPEN_DELEGATION && d.delegate != sender) {
354
+ return (false, "invalid-delegate");
355
+ }
356
+ } else {
357
+ if (d.delegate != OPEN_DELEGATION && d.delegate != delegations[i - 1].delegator) {
358
+ return (false, "invalid-delegate-chain");
359
+ }
360
+ }
361
+
362
+ // Authority chain
363
+ if (d.authority != ROOT_AUTHORITY) {
364
+ if (i + 1 >= delegations.length) return (false, "invalid-authority");
365
+ bytes32 parentHash = hashDelegation(delegations[i + 1]);
366
+ if (d.authority != parentHash) return (false, "invalid-authority-chain");
367
+ }
368
+
369
+ // Signature
370
+ if (!_isValidSignatureBool(d.delegator, dHash, d.signature)) {
371
+ return (false, "invalid-signature");
372
+ }
373
+
374
+ return (true, "");
375
+ }
376
+
377
+ /// @dev Bool-returning sibling of `_validateSignature`. Returns false
378
+ /// instead of reverting, so view-only callers can produce a typed
379
+ /// `(ok, reason)` instead of bubbling a revert.
380
+ function _isValidSignatureBool(
381
+ address signer,
382
+ bytes32 digest,
383
+ bytes calldata signature
384
+ ) internal view returns (bool) {
385
+ // ERC-1271 path for smart accounts
386
+ if (signer.code.length > 0) {
387
+ try IERC1271(signer).isValidSignature(digest, signature) returns (bytes4 result) {
388
+ return result == IERC1271.isValidSignature.selector;
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
393
+ // EOA — tryRecover the eth-signed message hash
394
+ bytes32 ethHash = digest.toEthSignedMessageHash();
395
+ (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(ethHash, signature);
396
+ if (err != ECDSA.RecoverError.NoError) return false;
397
+ return recovered == signer;
398
+ }
399
+
245
400
  // ─── Internal: Validation ──────────────────────────────────────────
246
401
 
247
402
  function _validateDelegation(
@@ -66,4 +66,39 @@ interface IDelegationManager {
66
66
 
67
67
  /// @notice Check if a delegation has been revoked.
68
68
  function isRevoked(bytes32 delegationHash) external view returns (bool);
69
+
70
+ /// @notice View-only verification of a delegation chain. Returns whether
71
+ /// the chain authorizes `sender` to redeem WITHOUT executing it.
72
+ /// Used by trust-substrate contracts (e.g. AttestationRegistry's
73
+ /// bilateral-consent path per spec 242 PD-9) to validate a packed
74
+ /// delegation as a signed authorization predicate.
75
+ /// @param delegations Delegation chain, leaf first
76
+ /// @param sender The address that would be the redeemer
77
+ /// @return ok True if the chain verifies
78
+ /// @return reason Empty if ok; otherwise short rejection reason
79
+ function verifyAuthorization(
80
+ Delegation[] calldata delegations,
81
+ address sender
82
+ ) external view returns (bool ok, string memory reason);
83
+
84
+ /// @notice View-only authorization of a SPECIFIC call (ADR-0027 / spec 249 RW1-4):
85
+ /// validates the chain AND evaluates every caveat's `beforeHook` against the exact
86
+ /// `(target, value, data)` read-only, fail-closed. `true` ONLY if the chain validates
87
+ /// and every caveat passes under `staticcall`; a denied OR non-view-evaluable caveat
88
+ /// yields `false` ("use live redemption"). Lets a registry bind consent to the exact
89
+ /// call inside its own transaction. Caveat evaluation mirrors `redeemDelegation`.
90
+ /// @param delegations Delegation chain, leaf first
91
+ /// @param sender The address that would be the redeemer
92
+ /// @param target The exact target of the delegated call
93
+ /// @param value The exact ETH value
94
+ /// @param data The exact calldata
95
+ /// @return ok True if the chain + every caveat authorize this exact call
96
+ /// @return reason Empty if ok; otherwise short rejection reason
97
+ function verifyAuthorizationForCall(
98
+ Delegation[] calldata delegations,
99
+ address sender,
100
+ address target,
101
+ uint256 value,
102
+ bytes calldata data
103
+ ) external view returns (bool ok, string memory reason);
69
104
  }