@@ -9,9 +9,11 @@ pragma solidity >=0.8.13 <0.9.0;
99 * The Account Keychain allows accounts to authorize secondary keys (Access Keys) that can sign
1010 * transactions on behalf of the account. Access Keys can be scoped by:
1111 * - Expiry timestamp (when the key becomes invalid)
12- * - Per-TIP20 token spending limits that deplete as the key spends
12+ * - Per-TIP20 token spending limits (one-time or periodic) that deplete as the key spends
13+ * - Call scopes restricting which contracts/selectors the key may call (T3+)
1314 *
14- * Only the Root Key can call authorizeKey, revokeKey, and updateSpendingLimit.
15+ * Only the Root Key can call authorizeKey, revokeKey, updateSpendingLimit, setAllowedCalls,
16+ * and removeAllowedCalls.
1517 * This restriction is enforced by the protocol at transaction validation time.
1618 * Access Keys attempting to call these functions will fail with UnauthorizedCaller.
1719 *
@@ -30,10 +32,38 @@ interface IAccountKeychain {
3032 WebAuthn
3133 }
3234
35+ /// @notice Legacy token spending limit structure used before T3
36+ struct LegacyTokenLimit {
37+ address token; // TIP20 token address
38+ uint256 amount; // Spending limit amount
39+ }
40+
3341 /// @notice Token spending limit structure
3442 struct TokenLimit {
3543 address token; // TIP20 token address
3644 uint256 amount; // Spending limit amount
45+ uint64 period; // Period duration in seconds (0 = one-time limit, >0 = periodic reset)
46+ }
47+
48+ /// @notice Selector-level recipient rule
49+ struct SelectorRule {
50+ bytes4 selector; // 4-byte function selector
51+ address [] recipients; // Empty means no recipient restriction for this selector
52+ }
53+
54+ /// @notice Per-target call scope
55+ struct CallScope {
56+ address target; // Target contract address
57+ SelectorRule[] selectorRules; // Empty means any selector is allowed for this target
58+ }
59+
60+ /// @notice Optional access-key restrictions configured at authorization time
61+ struct KeyRestrictions {
62+ uint64 expiry; // Unix timestamp when key expires (use type(uint64).max for never)
63+ bool enforceLimits; // Whether spending limits are enforced for this key
64+ TokenLimit[] limits; // Token spending limits
65+ bool allowAnyCalls; // true = unrestricted calls (allowedCalls must be empty), false = allowedCalls defines scope
66+ CallScope[] allowedCalls; // Call scopes when allowAnyCalls is false
3767 }
3868
3969 /// @notice Key information structure
@@ -60,29 +90,41 @@ interface IAccountKeychain {
6090 address indexed account , address indexed publicKey , address indexed token , uint256 newLimit
6191 );
6292
93+ /// @notice Emitted when an access key spends tokens
94+ event AccessKeySpend (
95+ address indexed account ,
96+ address indexed publicKey ,
97+ address indexed token ,
98+ uint256 amount ,
99+ uint256 remainingLimit
100+ );
101+
63102 /*//////////////////////////////////////////////////////////////
64103 ERRORS
65104 //////////////////////////////////////////////////////////////*/
66105
106+ error UnauthorizedCaller ();
67107 error KeyAlreadyExists ();
68108 error KeyNotFound ();
69- error KeyInactive ();
70109 error KeyExpired ();
71- error KeyAlreadyRevoked ();
72110 error SpendingLimitExceeded ();
111+ error InvalidSpendingLimit ();
73112 error InvalidSignatureType ();
74113 error ZeroPublicKey ();
75114 error ExpiryInPast ();
76- error UnauthorizedCaller ();
115+ error KeyAlreadyRevoked ();
116+ error SignatureTypeMismatch (uint8 expected , uint8 actual );
117+ error CallNotAllowed ();
118+ error InvalidCallScope ();
119+ error LegacyAuthorizeKeySelectorChanged (bytes4 newSelector );
77120
78121 /*//////////////////////////////////////////////////////////////
79122 MANAGEMENT FUNCTIONS
80123 //////////////////////////////////////////////////////////////*/
81124
82125 /**
83- * @notice Authorize a new key for the caller's account
126+ * @notice Legacy authorize- key entrypoint used before T3
84127 * @dev MUST only be called in transactions signed by the Root Key
85- * The protocol enforces this restriction by checking transactionKey[msg.sender]
86128 * @param keyId The key identifier (address) to authorize
87129 * @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
88130 * @param expiry Unix timestamp when key expires (use type(uint64).max for never expires)
@@ -94,27 +136,52 @@ interface IAccountKeychain {
94136 SignatureType signatureType ,
95137 uint64 expiry ,
96138 bool enforceLimits ,
97- TokenLimit [] calldata limits
139+ LegacyTokenLimit [] calldata limits
98140 ) external ;
99141
142+ /**
143+ * @notice Authorize a new key for the caller's account with T3 extensions
144+ * @dev MUST only be called in transactions signed by the Root Key
145+ * @param keyId The key identifier (address derived from public key)
146+ * @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
147+ * @param config Access-key expiry and optional limits / call restrictions
148+ */
149+ function authorizeKey (address keyId , SignatureType signatureType , KeyRestrictions calldata config ) external ;
150+
100151 /**
101152 * @notice Revoke an authorized key
102153 * @dev MUST only be called in transactions signed by the Root Key
103- * The protocol enforces this restriction by checking transactionKey[msg.sender]
104154 * @param keyId The key ID to revoke
105155 */
106156 function revokeKey (address keyId ) external ;
107157
108158 /**
109159 * @notice Update spending limit for a specific token on an authorized key
110160 * @dev MUST only be called in transactions signed by the Root Key
111- * The protocol enforces this restriction by checking transactionKey[msg.sender]
112161 * @param keyId The key ID to update
113162 * @param token The token address
114163 * @param newLimit The new spending limit
115164 */
116165 function updateSpendingLimit (address keyId , address token , uint256 newLimit ) external ;
117166
167+ /**
168+ * @notice Set or replace allowed calls for one or more key+target pairs
169+ * @dev MUST only be called in transactions signed by the Root Key.
170+ * Reverts if `scopes` is empty; use `removeAllowedCalls` to delete target scopes.
171+ * `scope.selectorRules = []` allows any selector on that target.
172+ * @param keyId The key ID to configure
173+ * @param scopes The call scopes to set
174+ */
175+ function setAllowedCalls (address keyId , CallScope[] calldata scopes ) external ;
176+
177+ /**
178+ * @notice Remove any configured call scope for a key+target pair
179+ * @dev MUST only be called in transactions signed by the Root Key
180+ * @param keyId The key ID to update
181+ * @param target The target contract to remove from allowed calls
182+ */
183+ function removeAllowedCalls (address keyId , address target ) external ;
184+
118185 /*//////////////////////////////////////////////////////////////
119186 VIEW FUNCTIONS
120187 //////////////////////////////////////////////////////////////*/
@@ -128,13 +195,41 @@ interface IAccountKeychain {
128195 function getKey (address account , address keyId ) external view returns (KeyInfo memory );
129196
130197 /**
131- * @notice Get remaining spending limit for a key-token pair
198+ * @notice Get remaining spending limit for a key-token pair (legacy)
132199 * @param account The account address
133200 * @param keyId The key ID
134201 * @param token The token address
135- * @return Remaining spending amount
202+ * @return remaining Remaining spending amount
203+ */
204+ function getRemainingLimit (address account , address keyId , address token ) external view returns (uint256 remaining );
205+
206+ /**
207+ * @notice Get remaining spending limit together with the active period end
208+ * @param account The account address
209+ * @param keyId The key ID
210+ * @param token The token address
211+ * @return remaining Remaining spending amount
212+ * @return periodEnd Period end timestamp for periodic limits (0 for one-time)
213+ */
214+ function getRemainingLimitWithPeriod (address account , address keyId , address token )
215+ external
216+ view
217+ returns (uint256 remaining , uint64 periodEnd );
218+
219+ /**
220+ * @notice Returns whether an account key is call-scoped and, if so, the configured call scopes
221+ * @dev `isScoped = false` means unrestricted. `isScoped = true && scopes.length == 0`
222+ * means scoped deny-all. Missing, revoked, or expired access keys also return scoped
223+ * deny-all so callers do not observe stale persisted scope state.
224+ * @param account The account address
225+ * @param keyId The key ID
226+ * @return isScoped Whether the key is call-scoped
227+ * @return scopes The configured call scopes
136228 */
137- function getRemainingLimit (address account , address keyId , address token ) external view returns (uint256 );
229+ function getAllowedCalls (address account , address keyId )
230+ external
231+ view
232+ returns (bool isScoped , CallScope[] memory scopes );
138233
139234 /**
140235 * @notice Get the transaction key used in the current transaction
0 commit comments