@agenticprimitives/contracts 0.1.0-alpha.2

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 (84) hide show
  1. package/AUDIT.md +67 -0
  2. package/CLAUDE.md +40 -0
  3. package/LICENSE +21 -0
  4. package/README.md +45 -0
  5. package/deployments-anvil.json +1 -0
  6. package/deployments-base-sepolia.json +1 -0
  7. package/dist/abi/AgentNameAttributeResolver.json +798 -0
  8. package/dist/abi/AgentNamePredicates.json +1 -0
  9. package/dist/abi/AgentNameRegistry.json +826 -0
  10. package/dist/abi/AgentNameUniversalResolver.json +222 -0
  11. package/dist/abi/AgentProfilePredicates.json +1 -0
  12. package/dist/abi/AgentProfileResolver.json +1044 -0
  13. package/dist/abi/AgentRelationship.json +583 -0
  14. package/dist/abi/AgentRelationshipPredicates.json +1 -0
  15. package/dist/abi/AgenticGovernance.json +259 -0
  16. package/dist/abi/AllowedMethodsEnforcer.json +108 -0
  17. package/dist/abi/AllowedTargetsEnforcer.json +103 -0
  18. package/dist/abi/ApprovedHashRegistry.json +114 -0
  19. package/dist/abi/AttributeStorage.json +557 -0
  20. package/dist/abi/CaveatEnforcerBase.json +130 -0
  21. package/dist/abi/GovernanceManaged.json +43 -0
  22. package/dist/abi/IAttributeReader.json +98 -0
  23. package/dist/abi/ICaveatEnforcer.json +98 -0
  24. package/dist/abi/IDelegationManager.json +211 -0
  25. package/dist/abi/IERC7579Module.json +34 -0
  26. package/dist/abi/IERC7579ModuleLifecycle.json +60 -0
  27. package/dist/abi/IGovernanceView.json +34 -0
  28. package/dist/abi/MultiSendCallOnly.json +29 -0
  29. package/dist/abi/MultiSendCallOnlyHarness.json +42 -0
  30. package/dist/abi/OntologyTermRegistry.json +397 -0
  31. package/dist/abi/P256Verifier.json +1 -0
  32. package/dist/abi/PermissionlessSubregistry.json +207 -0
  33. package/dist/abi/RelationshipTypeRegistry.json +455 -0
  34. package/dist/abi/ShapeRegistry.json +627 -0
  35. package/dist/abi/SmartAgentModuleTypes.json +1 -0
  36. package/dist/abi/TimestampEnforcer.json +108 -0
  37. package/dist/abi/ValueEnforcer.json +103 -0
  38. package/dist/abi/WebAuthnLib.json +1 -0
  39. package/dist/abi/index.d.ts +35 -0
  40. package/dist/abi/index.js +35 -0
  41. package/package.json +48 -0
  42. package/spec.md +52 -0
  43. package/src/AgentAccount.sol +1374 -0
  44. package/src/AgentAccountFactory.sol +274 -0
  45. package/src/ApprovedHashRegistry.sol +57 -0
  46. package/src/IAgentAccount.sol +138 -0
  47. package/src/SmartAgentPaymaster.sol +281 -0
  48. package/src/UniversalSignatureValidator.sol +136 -0
  49. package/src/agency/DelegationManager.sol +374 -0
  50. package/src/agency/ICaveatEnforcer.sol +62 -0
  51. package/src/agency/IDelegationManager.sol +69 -0
  52. package/src/custody/CustodyPolicy.sol +892 -0
  53. package/src/custody/IERC7579Module.sol +60 -0
  54. package/src/enforcers/AllowedMethodsEnforcer.AUDIT.md +51 -0
  55. package/src/enforcers/AllowedMethodsEnforcer.sol +48 -0
  56. package/src/enforcers/AllowedTargetsEnforcer.AUDIT.md +49 -0
  57. package/src/enforcers/AllowedTargetsEnforcer.sol +44 -0
  58. package/src/enforcers/CaveatEnforcerBase.sol +19 -0
  59. package/src/enforcers/QuorumEnforcer.AUDIT.md +71 -0
  60. package/src/enforcers/QuorumEnforcer.sol +191 -0
  61. package/src/enforcers/TimestampEnforcer.AUDIT.md +50 -0
  62. package/src/enforcers/TimestampEnforcer.sol +43 -0
  63. package/src/enforcers/ValueEnforcer.AUDIT.md +51 -0
  64. package/src/enforcers/ValueEnforcer.sol +41 -0
  65. package/src/governance/AgenticGovernance.sol +140 -0
  66. package/src/governance/GovernanceManaged.sol +75 -0
  67. package/src/governance/IGovernance.sol +15 -0
  68. package/src/identity/AgentProfilePredicates.sol +40 -0
  69. package/src/identity/AgentProfileResolver.sol +194 -0
  70. package/src/libraries/MultiSendCallOnly.sol +95 -0
  71. package/src/libraries/P256Verifier.sol +47 -0
  72. package/src/libraries/SignatureSlotRecovery.sol +196 -0
  73. package/src/libraries/WebAuthnLib.sol +164 -0
  74. package/src/naming/AgentNameAttributeResolver.sol +95 -0
  75. package/src/naming/AgentNamePredicates.sol +74 -0
  76. package/src/naming/AgentNameRegistry.sol +362 -0
  77. package/src/naming/AgentNameUniversalResolver.sol +210 -0
  78. package/src/naming/PermissionlessSubregistry.sol +98 -0
  79. package/src/ontology/AttributeStorage.sol +289 -0
  80. package/src/ontology/OntologyTermRegistry.sol +146 -0
  81. package/src/ontology/ShapeRegistry.sol +240 -0
  82. package/src/relationships/AgentRelationship.sol +289 -0
  83. package/src/relationships/AgentRelationshipPredicates.sol +44 -0
  84. package/src/relationships/RelationshipTypeRegistry.sol +143 -0
@@ -0,0 +1,274 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
5
+ import "./AgentAccount.sol";
6
+ import {AgentAccountInitParams} from "./IAgentAccount.sol";
7
+ import {CustodyPolicy} from "./custody/CustodyPolicy.sol";
8
+ import "./governance/GovernanceManaged.sol";
9
+
10
+ /**
11
+ * @title AgentAccountFactory
12
+ * @notice Single-entry factory for AgentAccount proxies with
13
+ * deterministic CREATE2 addresses. Phase 6f.5 — collapsed
14
+ * createPersonAgent + createMultiSigSmartAgent into one
15
+ * entry point so every account on chain has the same shape;
16
+ * the only axis is `mode` on the init params.
17
+ *
18
+ * - mode == 0 → no CustodyPolicy installed. Single-signer
19
+ * "simple" shape (admin changes go through the account's
20
+ * onlySelf surface only). Trustees ignored.
21
+ * - mode > 0 → CustodyPolicy module installed at birth;
22
+ * trustees are REQUIRED (≥1 for hybrid, ≥2 for threshold,
23
+ * ≥3 for org per spec § 8). Cannot ship an un-recoverable
24
+ * multi-sig account through this path.
25
+ *
26
+ * All accounts accept any combination of external custodians
27
+ * (EOAs / SIWE / third-party smart wallets) plus an optional
28
+ * initial passkey. At least one signer must be supplied. The
29
+ * AgentAccount initializer enforces the spec 211 § 3 /
30
+ * spec 212 § 2.2 invariant: no agenticprimitives AgentAccount
31
+ * may appear in another account's custodian set (ERC-165
32
+ * marker check).
33
+ *
34
+ * The canonical CustodyPolicy address is wired at construction
35
+ * (factory-immutable) — there is no per-call validator
36
+ * override. Swapping CustodyPolicy versions means redeploying
37
+ * the factory, which is the same blast radius as a CREATE2
38
+ * salt bump.
39
+ *
40
+ * Phase A (spec 007) capability roles:
41
+ * `bundlerSigner` and `sessionIssuer` are factory-level capability
42
+ * addresses. They are NEVER custodians of any deployed account; each
43
+ * account reads them off this factory on demand. Phase A.5 made both
44
+ * mutable under governance — rotation flows through a governance
45
+ * proposal + 48h timelock and propagates automatically to every
46
+ * existing AgentAccount.
47
+ */
48
+ contract AgentAccountFactory is GovernanceManaged {
49
+ AgentAccount public immutable accountImplementation;
50
+ address public immutable delegationManager;
51
+ address public immutable custodyPolicy;
52
+
53
+ address public bundlerSigner;
54
+ address public sessionIssuer;
55
+
56
+ event AgentAccountCreated(
57
+ address indexed account,
58
+ uint8 mode,
59
+ uint256 nExternalCustodians,
60
+ bool withPasskey,
61
+ uint256 salt
62
+ );
63
+
64
+ event BundlerSignerChanged(address indexed oldVal, address indexed newVal);
65
+ event SessionIssuerChanged(address indexed oldVal, address indexed newVal);
66
+
67
+ uint256[50] private __gap;
68
+
69
+ // ─── Errors ─────────────────────────────────────────────────────────
70
+
71
+ error InvalidMode(uint8 mode);
72
+ error NoInitialSigner();
73
+ error InsufficientTrusteesForMode(uint8 mode, uint256 actual, uint256 required);
74
+ error TrusteesRequiredForRecoverableMode(uint8 mode);
75
+ error ZeroAddress();
76
+
77
+ constructor(
78
+ IEntryPoint entryPoint_,
79
+ address delegationManager_,
80
+ address custodyPolicy_,
81
+ address bundlerSigner_,
82
+ address sessionIssuer_,
83
+ address governance_
84
+ ) GovernanceManaged(governance_) {
85
+ if (custodyPolicy_ == address(0)) revert ZeroAddress();
86
+ accountImplementation = new AgentAccount(entryPoint_);
87
+ delegationManager = delegationManager_;
88
+ custodyPolicy = custodyPolicy_;
89
+ bundlerSigner = bundlerSigner_;
90
+ sessionIssuer = sessionIssuer_;
91
+ emit BundlerSignerChanged(address(0), bundlerSigner_);
92
+ emit SessionIssuerChanged(address(0), sessionIssuer_);
93
+ }
94
+
95
+ // ─── Governance-only setters ─────────────────────────────────────────
96
+
97
+ function setBundlerSigner(address newBundler) external onlyGovernance {
98
+ address old = bundlerSigner;
99
+ bundlerSigner = newBundler;
100
+ emit BundlerSignerChanged(old, newBundler);
101
+ }
102
+
103
+ function setSessionIssuer(address newIssuer) external onlyGovernance {
104
+ address old = sessionIssuer;
105
+ sessionIssuer = newIssuer;
106
+ emit SessionIssuerChanged(old, newIssuer);
107
+ }
108
+
109
+ // ─── Factory entry ──────────────────────────────────────────────────
110
+
111
+ /**
112
+ * @notice Deploy an AgentAccount. Mode picks the shape:
113
+ * - 0 → simple (no CustodyPolicy)
114
+ * - 1 → hybrid (CustodyPolicy, ≥1 trustee required)
115
+ * - 2 → threshold (CustodyPolicy, ≥2 trustees required)
116
+ * - 3 → org (CustodyPolicy, ≥3 trustees required)
117
+ *
118
+ * @param params Init params bundle.
119
+ * @param timelockOverrides Per-tier timelock override (index 0 unused;
120
+ * tier t uses index t for t in 1..6). Where the override is 0,
121
+ * the factory default applies: T4=1h, T5=24h, T6=48h. Demo
122
+ * deploys can set short overrides; production uses 0s
123
+ * everywhere to inherit the spec defaults. Ignored for mode 0.
124
+ * @param salt CREATE2 deployment salt.
125
+ */
126
+ function createAgentAccount(
127
+ AgentAccountInitParams calldata params,
128
+ uint32[7] calldata timelockOverrides,
129
+ uint256 salt
130
+ ) external whenNotPaused returns (AgentAccount account) {
131
+ // H7-C.10 / EXT3-010: gated behind the system-wide governance pause.
132
+ // An incident-mode guardian can freeze new account creation while
133
+ // an exploit is being investigated; unpause requires the 24h
134
+ // timelock (see AgenticGovernance).
135
+ _validateInitParams(params);
136
+
137
+ address addr = _getAddressForAgentAccount(params, salt);
138
+ if (addr.code.length > 0) return AgentAccount(payable(addr));
139
+
140
+ ERC1967Proxy proxy = new ERC1967Proxy{salt: bytes32(salt)}(
141
+ address(accountImplementation),
142
+ _initData(params)
143
+ );
144
+ account = AgentAccount(payable(address(proxy)));
145
+
146
+ if (params.mode > 0) {
147
+ bytes memory validatorInit = _buildValidatorInitData(params, timelockOverrides);
148
+ account.installModule(2 /* MODULE_TYPE_EXECUTOR */, custodyPolicy, validatorInit);
149
+ }
150
+
151
+ bool hasPasskey = params.initialPasskeyX != 0 && params.initialPasskeyY != 0;
152
+ emit AgentAccountCreated(
153
+ address(account),
154
+ params.mode,
155
+ params.custodians.length,
156
+ hasPasskey,
157
+ salt
158
+ );
159
+ }
160
+
161
+ /// @notice Counterfactual address for `createAgentAccount`.
162
+ function getAddressForAgentAccount(
163
+ AgentAccountInitParams calldata params,
164
+ uint256 salt
165
+ ) external view returns (address) {
166
+ _validateInitParams(params);
167
+ return _getAddressForAgentAccount(params, salt);
168
+ }
169
+
170
+ // ─── Internals ──────────────────────────────────────────────────────
171
+
172
+ /// @dev Build the unified-initializer calldata. Used by both factory
173
+ /// entries + counterfactual derivation so the CREATE2 address
174
+ /// computation matches the actual deploy bytecode exactly.
175
+ function _initData(AgentAccountInitParams calldata params) internal view returns (bytes memory) {
176
+ return abi.encodeCall(
177
+ AgentAccount.initialize,
178
+ (
179
+ params.custodians,
180
+ params.initialPasskeyCredentialIdDigest,
181
+ params.initialPasskeyX,
182
+ params.initialPasskeyY,
183
+ params.initialPasskeyRpIdHash,
184
+ delegationManager,
185
+ address(this)
186
+ )
187
+ );
188
+ }
189
+
190
+ function _getAddressForAgentAccount(
191
+ AgentAccountInitParams calldata params,
192
+ uint256 salt
193
+ ) internal view returns (address) {
194
+ bytes memory initData = _initData(params);
195
+ return _create2Address(initData, salt);
196
+ }
197
+
198
+ function _create2Address(bytes memory initData, uint256 salt) internal view returns (address) {
199
+ bytes memory proxyBytecode = abi.encodePacked(
200
+ type(ERC1967Proxy).creationCode,
201
+ abi.encode(address(accountImplementation), initData)
202
+ );
203
+ bytes32 bytecodeHash = keccak256(proxyBytecode);
204
+ return address(uint160(uint256(keccak256(
205
+ abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), bytecodeHash)
206
+ ))));
207
+ }
208
+
209
+ /// @dev Per-mode validation:
210
+ /// - mode ≤ 3
211
+ /// - at least one signer (custodian or passkey)
212
+ /// - modes 1-3 require trustees ≥ {1, 2, 3}
213
+ ///
214
+ /// The AgentAccount initializer enforces the actual "≥1 signer"
215
+ /// check; we duplicate it here so counterfactual `getAddress`
216
+ /// consistently reverts on the same invariant.
217
+ function _validateInitParams(AgentAccountInitParams calldata params) internal pure {
218
+ if (params.mode > 3) revert InvalidMode(params.mode);
219
+
220
+ bool hasPasskey = params.initialPasskeyX != 0 && params.initialPasskeyY != 0;
221
+ if (params.custodians.length == 0 && !hasPasskey) revert NoInitialSigner();
222
+
223
+ if (params.mode == 0) return;
224
+
225
+ uint256 nTrustees = params.trustees.length;
226
+ if (nTrustees == 0) revert TrusteesRequiredForRecoverableMode(params.mode);
227
+ if (params.mode == 2 && nTrustees < 2) {
228
+ revert InsufficientTrusteesForMode(params.mode, nTrustees, 2);
229
+ }
230
+ if (params.mode == 3 && nTrustees < 3) {
231
+ revert InsufficientTrusteesForMode(params.mode, nTrustees, 3);
232
+ }
233
+ }
234
+
235
+ /// @dev Composes the ABI-encoded init blob the validator's `onInstall`
236
+ /// expects. Pulls per-tier thresholds from the spec § 5.1
237
+ /// matrix; uses caller-supplied timelock overrides where
238
+ /// non-zero, otherwise falls back to spec defaults
239
+ /// (T4=1h / T5=24h / T6=48h); default T3 ceiling 0.01 ETH;
240
+ /// recovery threshold floor(N/2)+1 (trustees are required for
241
+ /// modes 1-3 so this is always ≥ 1).
242
+ ///
243
+ /// `N` for the threshold-matrix is the total signer count =
244
+ /// external custodians + (1 if initial passkey, else 0).
245
+ function _buildValidatorInitData(
246
+ AgentAccountInitParams calldata params,
247
+ uint32[7] calldata timelockOverrides
248
+ ) internal view returns (bytes memory) {
249
+ uint256 nSigners = params.custodians.length;
250
+ if (params.initialPasskeyX != 0 && params.initialPasskeyY != 0) nSigners++;
251
+
252
+ uint8[7] memory thresholds;
253
+ for (uint8 t = 1; t <= 5; t++) {
254
+ thresholds[t] = CustodyPolicy(custodyPolicy).defaultApprovals(uint8(nSigners), t);
255
+ }
256
+
257
+ uint32[7] memory timelocks;
258
+ timelocks[4] = timelockOverrides[4] == 0 ? uint32(1 hours) : timelockOverrides[4];
259
+ timelocks[5] = timelockOverrides[5] == 0 ? uint32(24 hours) : timelockOverrides[5];
260
+ timelocks[6] = timelockOverrides[6] == 0 ? uint32(48 hours) : timelockOverrides[6];
261
+
262
+ uint8 recThr = uint8(params.trustees.length / 2 + 1);
263
+
264
+ return abi.encode(
265
+ params.mode,
266
+ recThr,
267
+ params.trustees,
268
+ thresholds,
269
+ timelocks,
270
+ uint256(0.01 ether),
271
+ address(0)
272
+ );
273
+ }
274
+ }
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ /**
5
+ * @title ApprovedHashRegistry
6
+ * @notice On-chain pre-approved hashes — the `v == 1` path of Safe's
7
+ * `checkSignatures` packing format, ported into the
8
+ * agenticprimitives multi-sig surface so signers that can't
9
+ * easily produce off-chain ECDSA over arbitrary EIP-712 payloads
10
+ * (passkey-only smart accounts, hardware wallets without typed
11
+ * data support, etc.) can pre-approve a hash by transaction
12
+ * instead of off-chain signature.
13
+ *
14
+ * @dev Mirrors Safe's `Safe.approveHash` semantics exactly:
15
+ * `QuorumEnforcer.beforeHook` consults `isApproved(signer, hash)`
16
+ * when it parses a 65-byte signature slot with `v = 1`. The signer
17
+ * address rides in the slot's `r` field (left-padded); the
18
+ * pre-approval gates whether that signer's "signature" counts
19
+ * toward the threshold.
20
+ *
21
+ * Anyone may approve their own hashes — the verifier
22
+ * (`QuorumEnforcer`) only counts approvals from addresses present
23
+ * in the delegation's bound signer set, so spam approvals from
24
+ * non-signers are no-ops.
25
+ *
26
+ * Pairs with `MultiSendCallOnly` (atomic batch) so a passkey
27
+ * smart account can `approveHash` + perform the delegated action
28
+ * in one userOp.
29
+ */
30
+ contract ApprovedHashRegistry {
31
+ /// signer => hash => approved
32
+ mapping(address => mapping(bytes32 => bool)) public approved;
33
+
34
+ event HashApproved(address indexed signer, bytes32 indexed hash);
35
+ event HashRevoked(address indexed signer, bytes32 indexed hash);
36
+
37
+ /**
38
+ * @notice Pre-approve a hash. `msg.sender` is the signer; only
39
+ * this address can later be counted by `QuorumEnforcer`
40
+ * for this hash.
41
+ */
42
+ function approveHash(bytes32 hash) external {
43
+ approved[msg.sender][hash] = true;
44
+ emit HashApproved(msg.sender, hash);
45
+ }
46
+
47
+ /// @notice Revoke a previously-approved hash before redemption.
48
+ function revokeHash(bytes32 hash) external {
49
+ approved[msg.sender][hash] = false;
50
+ emit HashRevoked(msg.sender, hash);
51
+ }
52
+
53
+ /// @notice External view used by `QuorumEnforcer`'s `v == 1` path.
54
+ function isApproved(address signer, bytes32 hash) external view returns (bool) {
55
+ return approved[signer][hash];
56
+ }
57
+ }
@@ -0,0 +1,138 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import "account-abstraction/interfaces/IAccount.sol";
5
+
6
+ /**
7
+ * @title IAgenticPrimitivesAgentAccount
8
+ * @notice ERC-165 marker interface for AgentAccounts deployed by
9
+ * agenticprimitives. `AgentAccount.addCustodian` queries this
10
+ * (via ERC-165 `supportsInterface`) to enforce the architectural
11
+ * invariant from spec 211 § 3 + spec 212 § 2.2: an
12
+ * agenticprimitives AgentAccount MUST NEVER appear in another
13
+ * AgentAccount's custodian set. Smart-agent ↔ smart-agent
14
+ * relationships are stewardship/delegation, not custody —
15
+ * custody bottoms out at external signer authority (EOA / SIWE /
16
+ * passkey / third-party smart wallet).
17
+ *
18
+ * Third-party smart wallets (Safe, Argent, Privy, …) are
19
+ * intentionally permitted as custodians because they wrap
20
+ * external human signers and validate via ERC-1271 without
21
+ * recursing into our own custody system.
22
+ */
23
+ interface IAgenticPrimitivesAgentAccount {
24
+ /// @dev Marker — implementations MUST return true. The selector is
25
+ /// the ERC-165 interfaceId via `type(IAgenticPrimitivesAgentAccount).interfaceId`.
26
+ function isAgenticPrimitivesAgentAccount() external pure returns (bool);
27
+ }
28
+
29
+ /**
30
+ * @notice Parameters bundled into one struct so the factory entry point
31
+ * + the matching `initializeWithThresholdPolicy` initializer
32
+ * share an exact shape. Captures everything the spec 207
33
+ * threshold-policy surface needs at account birth — mode,
34
+ * owners, guardians, optional initial passkey. The factory
35
+ * installs the spec § 5.1 default threshold matrix + default
36
+ * T4/T5/T6 timelocks automatically; callers tune them
37
+ * post-deploy via T4/T5 admin flows.
38
+ *
39
+ * `mode`: 0=single, 1=hybrid, 2=threshold, 3=org. Factory enforces
40
+ * per-mode guardian-count minima per spec § 8 (single: 0,
41
+ * hybrid: 0+, threshold: ≥ 2, org: ≥ 3).
42
+ */
43
+ struct AgentAccountInitParams {
44
+ uint8 mode;
45
+ address[] custodians;
46
+ address[] trustees;
47
+ bytes32 initialPasskeyCredentialIdDigest; // 0x0 to skip the passkey
48
+ uint256 initialPasskeyX;
49
+ uint256 initialPasskeyY;
50
+ // H7-C.1 / CON-WEBAUTHN-001: rpIdHash the initial passkey was registered
51
+ // against. Required (non-zero) when the passkey is present.
52
+ bytes32 initialPasskeyRpIdHash;
53
+ }
54
+
55
+ /**
56
+ * @notice One passkey to add as part of a T6 recovery. Coupled to
57
+ * `AgentAccountRecoveryArgs.addPasskeys` so callers can name
58
+ * each entry instead of juggling parallel arrays.
59
+ *
60
+ * H7-C.1 added `rpIdHash` — see `IAgentAccount.addPasskey`.
61
+ */
62
+ struct AgentAccountRecoveryPasskeyAdd {
63
+ bytes32 credentialIdDigest;
64
+ uint256 x;
65
+ uint256 y;
66
+ bytes32 rpIdHash;
67
+ }
68
+
69
+ /**
70
+ * @notice Payload for `AdminAction.RecoverAccount` (T6). Atomically
71
+ * adds + removes owners and passkeys in a single executed
72
+ * action so the recovery flow doesn't pass through fragmented
73
+ * intermediate states (half-rotated signer set).
74
+ *
75
+ * Spec 207 § 8 describes the flow:
76
+ * 1. Guardian quorum proposes recovery.
77
+ * 2. 48h timelock; first 24h is the primary-owner
78
+ * cancel window.
79
+ * 3. Guardian quorum executes; signer set rotates atomically.
80
+ *
81
+ * Removal lists may name signers that don't exist (no-op);
82
+ * add lists may include signers already present (no-op). This
83
+ * lets callers idempotently re-propose if a recovery race
84
+ * partially succeeded.
85
+ */
86
+ struct AgentAccountRecoveryArgs {
87
+ address[] addOwners;
88
+ address[] removeOwners;
89
+ AgentAccountRecoveryPasskeyAdd[] addPasskeys;
90
+ bytes32[] removePasskeyCredentialIdDigests;
91
+ }
92
+
93
+ /**
94
+ * @title IAgentAccount
95
+ * @notice Interface for an agent-native ERC-4337 smart account.
96
+ */
97
+ interface IAgentAccount is IAccount {
98
+ /// @notice Emitted when an owner is added.
99
+ event CustodianAdded(address indexed owner);
100
+
101
+ /// @notice Emitted when an owner is removed.
102
+ event CustodianRemoved(address indexed owner);
103
+
104
+ /// @notice Returns true if the address is an owner.
105
+ function isCustodian(address account) external view returns (bool);
106
+
107
+ /// @notice Returns the number of owners.
108
+ function custodianCount() external view returns (uint256);
109
+
110
+ /// @notice Add a new owner. Callable only by the account itself (via UserOp).
111
+ function addCustodian(address owner) external;
112
+
113
+ /// @notice Remove an owner. Callable only by the account itself (via UserOp).
114
+ function removeCustodian(address owner) external;
115
+
116
+ /// @notice ERC-1271: validate a signature against account owners.
117
+ function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4);
118
+
119
+ // ─── Spec 007 Phase A — capability roles ────────────────────────
120
+
121
+ /// @notice Bundler signer (resolved through the factory).
122
+ function bundlerSigner() external view returns (address);
123
+
124
+ /// @notice Session-issuer (resolved through the factory).
125
+ function sessionIssuer() external view returns (address);
126
+
127
+ /// @notice True iff the owner has pre-authorized this session
128
+ /// delegation hash on chain (Variant B).
129
+ function hasAcceptedSessionDelegation(bytes32 sessionDelegationHash) external view returns (bool);
130
+
131
+ /// @notice Pre-authorize an on-chain session delegation (Variant B).
132
+ /// `onlySelf` — must be reached via a userOp the owner signed.
133
+ function acceptSessionDelegation(bytes32 sessionDelegationHash) external;
134
+
135
+ /// @notice Owner-signed UUPS upgrade. Any caller can submit the tx;
136
+ /// what matters is whose signature `ownerSig` recovers to.
137
+ function upgradeToWithAuthorization(address newImpl, bytes calldata ownerSig) external;
138
+ }