@agenticprimitives/contracts 1.0.0-alpha.26 → 1.0.0-alpha.27

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.
@@ -0,0 +1,369 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "name": "budget",
5
+ "inputs": [
6
+ {
7
+ "name": "account",
8
+ "type": "address",
9
+ "internalType": "address"
10
+ }
11
+ ],
12
+ "outputs": [
13
+ {
14
+ "name": "asset",
15
+ "type": "address",
16
+ "internalType": "address"
17
+ },
18
+ {
19
+ "name": "ceiling",
20
+ "type": "uint256",
21
+ "internalType": "uint256"
22
+ },
23
+ {
24
+ "name": "windowSeconds",
25
+ "type": "uint64",
26
+ "internalType": "uint64"
27
+ },
28
+ {
29
+ "name": "windowStart",
30
+ "type": "uint64",
31
+ "internalType": "uint64"
32
+ },
33
+ {
34
+ "name": "windowIndex",
35
+ "type": "uint64",
36
+ "internalType": "uint64"
37
+ },
38
+ {
39
+ "name": "spent",
40
+ "type": "uint256",
41
+ "internalType": "uint256"
42
+ },
43
+ {
44
+ "name": "installed",
45
+ "type": "bool",
46
+ "internalType": "bool"
47
+ }
48
+ ],
49
+ "stateMutability": "view"
50
+ },
51
+ {
52
+ "type": "function",
53
+ "name": "decodeBatch",
54
+ "inputs": [
55
+ {
56
+ "name": "msgData",
57
+ "type": "bytes",
58
+ "internalType": "bytes"
59
+ }
60
+ ],
61
+ "outputs": [
62
+ {
63
+ "name": "",
64
+ "type": "tuple[]",
65
+ "internalType": "struct TreasurySpendPolicy.Call[]",
66
+ "components": [
67
+ {
68
+ "name": "target",
69
+ "type": "address",
70
+ "internalType": "address"
71
+ },
72
+ {
73
+ "name": "value",
74
+ "type": "uint256",
75
+ "internalType": "uint256"
76
+ },
77
+ {
78
+ "name": "data",
79
+ "type": "bytes",
80
+ "internalType": "bytes"
81
+ }
82
+ ]
83
+ }
84
+ ],
85
+ "stateMutability": "pure"
86
+ },
87
+ {
88
+ "type": "function",
89
+ "name": "decodeSingle",
90
+ "inputs": [
91
+ {
92
+ "name": "msgData",
93
+ "type": "bytes",
94
+ "internalType": "bytes"
95
+ }
96
+ ],
97
+ "outputs": [
98
+ {
99
+ "name": "",
100
+ "type": "address",
101
+ "internalType": "address"
102
+ },
103
+ {
104
+ "name": "",
105
+ "type": "uint256",
106
+ "internalType": "uint256"
107
+ },
108
+ {
109
+ "name": "",
110
+ "type": "bytes",
111
+ "internalType": "bytes"
112
+ }
113
+ ],
114
+ "stateMutability": "pure"
115
+ },
116
+ {
117
+ "type": "function",
118
+ "name": "isModuleType",
119
+ "inputs": [
120
+ {
121
+ "name": "moduleTypeId",
122
+ "type": "uint256",
123
+ "internalType": "uint256"
124
+ }
125
+ ],
126
+ "outputs": [
127
+ {
128
+ "name": "",
129
+ "type": "bool",
130
+ "internalType": "bool"
131
+ }
132
+ ],
133
+ "stateMutability": "pure"
134
+ },
135
+ {
136
+ "type": "function",
137
+ "name": "moduleId",
138
+ "inputs": [],
139
+ "outputs": [
140
+ {
141
+ "name": "",
142
+ "type": "string",
143
+ "internalType": "string"
144
+ }
145
+ ],
146
+ "stateMutability": "pure"
147
+ },
148
+ {
149
+ "type": "function",
150
+ "name": "onInstall",
151
+ "inputs": [
152
+ {
153
+ "name": "initData",
154
+ "type": "bytes",
155
+ "internalType": "bytes"
156
+ }
157
+ ],
158
+ "outputs": [],
159
+ "stateMutability": "nonpayable"
160
+ },
161
+ {
162
+ "type": "function",
163
+ "name": "onUninstall",
164
+ "inputs": [
165
+ {
166
+ "name": "",
167
+ "type": "bytes",
168
+ "internalType": "bytes"
169
+ }
170
+ ],
171
+ "outputs": [],
172
+ "stateMutability": "nonpayable"
173
+ },
174
+ {
175
+ "type": "function",
176
+ "name": "postCheck",
177
+ "inputs": [
178
+ {
179
+ "name": "",
180
+ "type": "bytes",
181
+ "internalType": "bytes"
182
+ }
183
+ ],
184
+ "outputs": [],
185
+ "stateMutability": "nonpayable"
186
+ },
187
+ {
188
+ "type": "function",
189
+ "name": "preCheck",
190
+ "inputs": [
191
+ {
192
+ "name": "",
193
+ "type": "address",
194
+ "internalType": "address"
195
+ },
196
+ {
197
+ "name": "",
198
+ "type": "uint256",
199
+ "internalType": "uint256"
200
+ },
201
+ {
202
+ "name": "msgData",
203
+ "type": "bytes",
204
+ "internalType": "bytes"
205
+ }
206
+ ],
207
+ "outputs": [
208
+ {
209
+ "name": "",
210
+ "type": "bytes",
211
+ "internalType": "bytes"
212
+ }
213
+ ],
214
+ "stateMutability": "nonpayable"
215
+ },
216
+ {
217
+ "type": "function",
218
+ "name": "remaining",
219
+ "inputs": [
220
+ {
221
+ "name": "account",
222
+ "type": "address",
223
+ "internalType": "address"
224
+ }
225
+ ],
226
+ "outputs": [
227
+ {
228
+ "name": "left",
229
+ "type": "uint256",
230
+ "internalType": "uint256"
231
+ },
232
+ {
233
+ "name": "ceiling",
234
+ "type": "uint256",
235
+ "internalType": "uint256"
236
+ }
237
+ ],
238
+ "stateMutability": "view"
239
+ },
240
+ {
241
+ "type": "event",
242
+ "name": "BudgetInstalled",
243
+ "inputs": [
244
+ {
245
+ "name": "account",
246
+ "type": "address",
247
+ "indexed": true,
248
+ "internalType": "address"
249
+ },
250
+ {
251
+ "name": "asset",
252
+ "type": "address",
253
+ "indexed": true,
254
+ "internalType": "address"
255
+ },
256
+ {
257
+ "name": "ceiling",
258
+ "type": "uint256",
259
+ "indexed": false,
260
+ "internalType": "uint256"
261
+ },
262
+ {
263
+ "name": "windowSeconds",
264
+ "type": "uint64",
265
+ "indexed": false,
266
+ "internalType": "uint64"
267
+ },
268
+ {
269
+ "name": "windowStart",
270
+ "type": "uint64",
271
+ "indexed": false,
272
+ "internalType": "uint64"
273
+ }
274
+ ],
275
+ "anonymous": false
276
+ },
277
+ {
278
+ "type": "event",
279
+ "name": "BudgetUninstalled",
280
+ "inputs": [
281
+ {
282
+ "name": "account",
283
+ "type": "address",
284
+ "indexed": true,
285
+ "internalType": "address"
286
+ }
287
+ ],
288
+ "anonymous": false
289
+ },
290
+ {
291
+ "type": "event",
292
+ "name": "Spent",
293
+ "inputs": [
294
+ {
295
+ "name": "account",
296
+ "type": "address",
297
+ "indexed": true,
298
+ "internalType": "address"
299
+ },
300
+ {
301
+ "name": "windowIndex",
302
+ "type": "uint64",
303
+ "indexed": true,
304
+ "internalType": "uint64"
305
+ },
306
+ {
307
+ "name": "amount",
308
+ "type": "uint256",
309
+ "indexed": false,
310
+ "internalType": "uint256"
311
+ },
312
+ {
313
+ "name": "spentInWindow",
314
+ "type": "uint256",
315
+ "indexed": false,
316
+ "internalType": "uint256"
317
+ }
318
+ ],
319
+ "anonymous": false
320
+ },
321
+ {
322
+ "type": "error",
323
+ "name": "AlreadyInstalledOn",
324
+ "inputs": [
325
+ {
326
+ "name": "account",
327
+ "type": "address",
328
+ "internalType": "address"
329
+ }
330
+ ]
331
+ },
332
+ {
333
+ "type": "error",
334
+ "name": "BadBudget",
335
+ "inputs": []
336
+ },
337
+ {
338
+ "type": "error",
339
+ "name": "BudgetExceeded",
340
+ "inputs": [
341
+ {
342
+ "name": "ceiling",
343
+ "type": "uint256",
344
+ "internalType": "uint256"
345
+ },
346
+ {
347
+ "name": "spentInWindow",
348
+ "type": "uint256",
349
+ "internalType": "uint256"
350
+ },
351
+ {
352
+ "name": "amount",
353
+ "type": "uint256",
354
+ "internalType": "uint256"
355
+ }
356
+ ]
357
+ },
358
+ {
359
+ "type": "error",
360
+ "name": "NotInstalledOn",
361
+ "inputs": [
362
+ {
363
+ "name": "account",
364
+ "type": "address",
365
+ "internalType": "address"
366
+ }
367
+ ]
368
+ }
369
+ ]
@@ -24,6 +24,7 @@ export declare const CaveatEnforcerBase: Abi;
24
24
  export declare const CustodyPolicy: Abi;
25
25
  export declare const DelegationManager: Abi;
26
26
  export declare const DigestBindingEnforcer: Abi;
27
+ export declare const EstateProjectionRegistry: Abi;
27
28
  export declare const GeoFeatureRegistry: Abi;
28
29
  export declare const GovernanceManaged: Abi;
29
30
  export declare const IAgentAccount: Abi;
@@ -32,6 +33,7 @@ export declare const IAgentAccountFactoryView: Abi;
32
33
  export declare const IAgentAccountPauseView: Abi;
33
34
  export declare const IAgenticPrimitivesAgentAccount: Abi;
34
35
  export declare const IApprovedHashRegistryView: Abi;
36
+ export declare const IApprovedHashRegistryWrite: Abi;
35
37
  export declare const IAttributeReader: Abi;
36
38
  export declare const ICaveatEnforcer: Abi;
37
39
  export declare const ICustodyPolicyAccountModuleView: Abi;
@@ -50,6 +52,7 @@ export declare const MultiSendCallOnly: Abi;
50
52
  export declare const MultiSendCallOnlyHarness: Abi;
51
53
  export declare const OntologyTermRegistry: Abi;
52
54
  export declare const P256Verifier: Abi;
55
+ export declare const PayloadClassesEnforcer: Abi;
53
56
  export declare const PaymentEnforcer: Abi;
54
57
  export declare const PaymentEscrow: Abi;
55
58
  export declare const PaymentReceiptRegistry: Abi;
@@ -63,6 +66,7 @@ export declare const SkillDefinitionRegistry: Abi;
63
66
  export declare const SmartAgentModuleTypes: Abi;
64
67
  export declare const SmartAgentPaymaster: Abi;
65
68
  export declare const TimestampEnforcer: Abi;
69
+ export declare const TreasurySpendPolicy: Abi;
66
70
  export declare const UniversalSignatureValidator: Abi;
67
71
  export declare const ValueEnforcer: Abi;
68
72
  export declare const WebAuthnLib: Abi;
package/dist/abi/index.js CHANGED
@@ -24,6 +24,7 @@ export { default as CaveatEnforcerBase } from './CaveatEnforcerBase.json' with {
24
24
  export { default as CustodyPolicy } from './CustodyPolicy.json' with { type: 'json' };
25
25
  export { default as DelegationManager } from './DelegationManager.json' with { type: 'json' };
26
26
  export { default as DigestBindingEnforcer } from './DigestBindingEnforcer.json' with { type: 'json' };
27
+ export { default as EstateProjectionRegistry } from './EstateProjectionRegistry.json' with { type: 'json' };
27
28
  export { default as GeoFeatureRegistry } from './GeoFeatureRegistry.json' with { type: 'json' };
28
29
  export { default as GovernanceManaged } from './GovernanceManaged.json' with { type: 'json' };
29
30
  export { default as IAgentAccount } from './IAgentAccount.json' with { type: 'json' };
@@ -32,6 +33,7 @@ export { default as IAgentAccountFactoryView } from './IAgentAccountFactoryView.
32
33
  export { default as IAgentAccountPauseView } from './IAgentAccountPauseView.json' with { type: 'json' };
33
34
  export { default as IAgenticPrimitivesAgentAccount } from './IAgenticPrimitivesAgentAccount.json' with { type: 'json' };
34
35
  export { default as IApprovedHashRegistryView } from './IApprovedHashRegistryView.json' with { type: 'json' };
36
+ export { default as IApprovedHashRegistryWrite } from './IApprovedHashRegistryWrite.json' with { type: 'json' };
35
37
  export { default as IAttributeReader } from './IAttributeReader.json' with { type: 'json' };
36
38
  export { default as ICaveatEnforcer } from './ICaveatEnforcer.json' with { type: 'json' };
37
39
  export { default as ICustodyPolicyAccountModuleView } from './ICustodyPolicyAccountModuleView.json' with { type: 'json' };
@@ -50,6 +52,7 @@ export { default as MultiSendCallOnly } from './MultiSendCallOnly.json' with { t
50
52
  export { default as MultiSendCallOnlyHarness } from './MultiSendCallOnlyHarness.json' with { type: 'json' };
51
53
  export { default as OntologyTermRegistry } from './OntologyTermRegistry.json' with { type: 'json' };
52
54
  export { default as P256Verifier } from './P256Verifier.json' with { type: 'json' };
55
+ export { default as PayloadClassesEnforcer } from './PayloadClassesEnforcer.json' with { type: 'json' };
53
56
  export { default as PaymentEnforcer } from './PaymentEnforcer.json' with { type: 'json' };
54
57
  export { default as PaymentEscrow } from './PaymentEscrow.json' with { type: 'json' };
55
58
  export { default as PaymentReceiptRegistry } from './PaymentReceiptRegistry.json' with { type: 'json' };
@@ -63,6 +66,7 @@ export { default as SkillDefinitionRegistry } from './SkillDefinitionRegistry.js
63
66
  export { default as SmartAgentModuleTypes } from './SmartAgentModuleTypes.json' with { type: 'json' };
64
67
  export { default as SmartAgentPaymaster } from './SmartAgentPaymaster.json' with { type: 'json' };
65
68
  export { default as TimestampEnforcer } from './TimestampEnforcer.json' with { type: 'json' };
69
+ export { default as TreasurySpendPolicy } from './TreasurySpendPolicy.json' with { type: 'json' };
66
70
  export { default as UniversalSignatureValidator } from './UniversalSignatureValidator.json' with { type: 'json' };
67
71
  export { default as ValueEnforcer } from './ValueEnforcer.json' with { type: 'json' };
68
72
  export { default as WebAuthnLib } from './WebAuthnLib.json' with { type: 'json' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticprimitives/contracts",
3
- "version": "1.0.0-alpha.26",
3
+ "version": "1.0.0-alpha.27",
4
4
  "type": "module",
5
5
  "description": "Solidity contracts + ABIs + flattened sources + per-network deployment addresses for the agenticprimitives stack.",
6
6
  "homepage": "https://agenticprimitives.dev",
@@ -56,6 +56,16 @@ interface IApprovedHashRegistryView {
56
56
  function isApproved(address signer, bytes32 hash) external view returns (bool);
57
57
  }
58
58
 
59
+ /// @dev Spec 410 §1 (THESIS-1a) — the ONLY write path from this account to its registry. The account
60
+ /// computes the epoch-bound key itself (`approveDigest`); `execute` / `executeBatch` /
61
+ /// `executeFromModule` refuse the registry as a target unconditionally, so no caller — custodian,
62
+ /// module or delegate — can approve a key under an epoch the account is not in.
63
+ interface IApprovedHashRegistryWrite {
64
+ function approveHash(bytes32 hash) external;
65
+ function revokeHash(bytes32 hash) external;
66
+ function revokeAll() external;
67
+ }
68
+
59
69
  /**
60
70
  * @title AgentAccount
61
71
  * @notice ERC-4337 + UUPS-upgradeable smart account — agent identity anchor.
@@ -268,6 +278,9 @@ contract AgentAccount is
268
278
  /// @notice A delegation-originated execution stack tried to self-call an
269
279
  /// account-governance selector. Delegations may never reach governance.
270
280
  error DelegatedGovernanceForbidden();
281
+ /// @dev Spec 410 §1 (THESIS-1a): the approved-hash registry is reachable only through
282
+ /// `approveDigest` / `revokeDigest` / `revokeAllApprovedDigests`, never as an execute target.
283
+ error ApprovedHashRegistryOnlyViaAccount();
271
284
 
272
285
  /// @notice Emitted when `setDelegationManager` rotates the DM via a
273
286
  /// self-call (the only authorized path post-Wave-2A).
@@ -1057,6 +1070,7 @@ contract AgentAccount is
1057
1070
  revert NotInstalledExecutor(msg.sender);
1058
1071
  }
1059
1072
  emit ModuleExecuted(msg.sender, target, value);
1073
+ _rejectDirectRegistryCall(target);
1060
1074
  // CA-1 + Wave-1/ACCT-C1 (spec 300): a module action from the CANONICAL CustodyPolicy is a
1061
1075
  // custody-governed context (gated upstream by its own T5 quorum + timelock). `_custodyGovCtx`
1062
1076
  // authorizes every governance mutator this call dispatches (the ONLY way past
@@ -1146,6 +1160,7 @@ contract AgentAccount is
1146
1160
  // executeBatch(...)) nest) and forbid it from self-calling governance.
1147
1161
  bool setDelCtx = (msg.sender == _delegationManager && !_delegationExecCtx);
1148
1162
  if (setDelCtx) _delegationExecCtx = true;
1163
+ _rejectDirectRegistryCall(target);
1149
1164
  _rejectDelegatedGovernance(target, data);
1150
1165
 
1151
1166
  ModulesStorage storage $ = _modulesStorage();
@@ -1232,6 +1247,7 @@ contract AgentAccount is
1232
1247
  }
1233
1248
 
1234
1249
  for (uint256 i = 0; i < calls.length; i++) {
1250
+ _rejectDirectRegistryCall(calls[i].target);
1235
1251
  _rejectDelegatedGovernance(calls[i].target, calls[i].data);
1236
1252
  (bool ok, bytes memory ret) = calls[i].target.call{ value: calls[i].value }(calls[i].data);
1237
1253
  if (!ok) {
@@ -1328,6 +1344,16 @@ contract AgentAccount is
1328
1344
  if (_isAccountTrustedTarget(target)) revert DelegatedGovernanceForbidden();
1329
1345
  }
1330
1346
 
1347
+ /// @dev Spec 410 §1 (THESIS-1a). `ApprovedHashRegistry.approveHash` stores whatever 32 bytes the account
1348
+ /// sends, and the custody epoch is public — so a custodian who could reach the registry through
1349
+ /// `execute` could approve `keccak256(abi.encode(h, epoch + 1))`: inert now, live the moment a
1350
+ /// recovery bumps the epoch to expel that very custodian. Closing it: the registry is never an
1351
+ /// execute target; the account derives the key on chain in `approveDigest`, under the epoch it is
1352
+ /// in, and nowhere else. Unconditional — not only under the delegation context (R917-C-1/C-2).
1353
+ function _rejectDirectRegistryCall(address target) internal view {
1354
+ if (target == _approvedHashRegistry && target != address(0)) revert ApprovedHashRegistryOnlyViaAccount();
1355
+ }
1356
+
1331
1357
  /// @dev The contracts that take THIS ACCOUNT as principal when it is `msg.sender` — reachable only from
1332
1358
  /// the account's own governance paths (`uninstallModule`, a custodian-signed userOp), never from a
1333
1359
  /// delegation. The custody policy is read from the factory (the canonical one; a legacy direct
@@ -1357,7 +1383,10 @@ contract AgentAccount is
1357
1383
  s == IAgentAccount.removeCustodian.selector ||
1358
1384
  s == this.addPasskey.selector ||
1359
1385
  s == this.removePasskey.selector ||
1360
- s == this.acceptSessionDelegation.selector;
1386
+ s == this.acceptSessionDelegation.selector ||
1387
+ s == this.approveDigest.selector ||
1388
+ s == this.revokeDigest.selector ||
1389
+ s == this.revokeAllApprovedDigests.selector;
1361
1390
  }
1362
1391
 
1363
1392
  // ─── ERC-1271 ───────────────────────────────────────────────────
@@ -1697,6 +1726,11 @@ contract AgentAccount is
1697
1726
  event PasskeyRemoved(bytes32 indexed credentialIdDigest);
1698
1727
  /// @dev R917-C-2 — a credential was retired; every approved hash keyed to the previous epoch is void.
1699
1728
  event CustodyEpochBumped(uint64 epoch);
1729
+ /// @dev Spec 410 §1 — `digest` approved under `epoch` through the account's own key derivation.
1730
+ event DigestApproved(bytes32 indexed digest, uint64 epoch);
1731
+ event DigestRevoked(bytes32 indexed digest, uint64 epoch);
1732
+ /// @dev Spec 410 §1 — every approval this account ever made is void (registry-side epoch bump).
1733
+ event AllDigestsRevoked();
1700
1734
 
1701
1735
  /// @notice Register a new WebAuthn credential bound to a specific RP.
1702
1736
  /// `onlySelf` — callable via a UserOp signed by any existing signer
@@ -1802,6 +1836,41 @@ contract AgentAccount is
1802
1836
  emit CustodyEpochBumped(next);
1803
1837
  }
1804
1838
 
1839
+ // ─── Approved digests (spec 410 §1, THESIS-1a) ───────────────────
1840
+
1841
+ /// @notice Approve `digest` for this account's `0x03` sentinel under the epoch the account is IN. The
1842
+ /// only path to the registry (`execute` refuses it as a target), so the key is never computed
1843
+ /// by a caller and never for an epoch other than the current one. `onlySelf`, not
1844
+ /// custody-gated: spec 253's one-prompt org-create batches it into the deploy userOp under the
1845
+ /// creating custodian, and the epoch (R917-C-2) plus `revokeAllApprovedDigests` on every
1846
+ /// recovery path (spec 410 §1.2) close the persistence risk instead. A delegation stack cannot
1847
+ /// reach it (`_isGovernanceSelector`).
1848
+ function approveDigest(bytes32 digest) external onlySelf {
1849
+ if (_approvedHashRegistry == address(0)) revert ApprovedHashRegistryOnlyViaAccount();
1850
+ IApprovedHashRegistryWrite(_approvedHashRegistry).approveHash(approvedHashKey(digest));
1851
+ emit DigestApproved(digest, _custodyEpoch);
1852
+ }
1853
+
1854
+ /// @notice Revoke one approval made under the current epoch.
1855
+ function revokeDigest(bytes32 digest) external onlySelf {
1856
+ if (_approvedHashRegistry == address(0)) revert ApprovedHashRegistryOnlyViaAccount();
1857
+ IApprovedHashRegistryWrite(_approvedHashRegistry).revokeHash(approvedHashKey(digest));
1858
+ emit DigestRevoked(digest, _custodyEpoch);
1859
+ }
1860
+
1861
+ /// @notice Void EVERY approval this account ever made, under any epoch — including one planted for a
1862
+ /// future epoch before this contract generation refused direct registry calls. `CustodyPolicy`
1863
+ /// calls it as the last step of every recovery path; a custody-governed account reaches it
1864
+ /// only through the module (`_requireCustodyGoverned`), a mode-0 person through her own userOp.
1865
+ /// Recovery re-approves nothing: the person re-issues from a reviewed list (spec 410 §1.2).
1866
+ function revokeAllApprovedDigests() external onlySelf {
1867
+ _requireCustodyGoverned();
1868
+ // No registry wired: nothing was ever approvable (the sentinel fails closed, l. ~1388), so there is
1869
+ // nothing to void — a no-op, never a revert, so a recovery on such an account still completes.
1870
+ if (_approvedHashRegistry != address(0)) IApprovedHashRegistryWrite(_approvedHashRegistry).revokeAll();
1871
+ emit AllDigestsRevoked();
1872
+ }
1873
+
1805
1874
  // ─── Custody policy + scheduled changes ───────────────────────────
1806
1875
  //
1807
1876
  // The schedule / apply / cancel custody-change surface, CustodyAction
@@ -90,6 +90,29 @@ struct AgentAccountRecoveryArgs {
90
90
  bytes32[] removePasskeyCredentialIdDigests;
91
91
  }
92
92
 
93
+ /**
94
+ * @notice Payload for `CustodyAction.RotateCredential` (T4) — spec 410 §1.2. A PLANNED rotation: the
95
+ * old credential is still controlled, the person (or an org's quorum) registers the new one,
96
+ * retires the old one, and re-approves every standing approved-digest wire under the new epoch
97
+ * — in ONE applied change, so no wire is dead between the retirement and the re-approval.
98
+ *
99
+ * Exactly one credential is added (`addOwner` XOR `addPasskey.x != 0`) and exactly one retired
100
+ * (`removeOwner` XOR `removePasskeyCredentialIdDigest != 0`). `reapprove` lists the delegation
101
+ * digests to approve after the retirement; the module calls `AgentAccount.approveDigest` for
102
+ * each, so the key is derived on chain under the post-rotation epoch.
103
+ *
104
+ * This is the ONLY custody action that carries a re-approval list. `AgentAccountRecoveryArgs`
105
+ * has no such field by construction: a recovery is the moment a planted grant would otherwise
106
+ * ride through, so recovery re-approves nothing and ends with `revokeAllApprovedDigests`.
107
+ */
108
+ struct AgentAccountRotateCredentialArgs {
109
+ address addOwner;
110
+ AgentAccountRecoveryPasskeyAdd addPasskey;
111
+ address removeOwner;
112
+ bytes32 removePasskeyCredentialIdDigest;
113
+ bytes32[] reapprove;
114
+ }
115
+
93
116
  /**
94
117
  * @title IAgentAccount
95
118
  * @notice Interface for an agent-native ERC-4337 smart account.