tempo_contracts/precompiles/account_keychain.rs
1#![allow(clippy::too_many_arguments)]
2
3pub use IAccountKeychain::{
4 IAccountKeychainErrors as AccountKeychainError, IAccountKeychainEvents as AccountKeychainEvent,
5 authorizeKey_0Call as legacyAuthorizeKeyCall, authorizeKey_1Call as authorizeKeyCall,
6 getAllowedCallsReturn, getRemainingLimitWithPeriodCall,
7 getRemainingLimitWithPeriodReturn as getRemainingLimitReturn,
8};
9
10crate::sol! {
11 /// Account Keychain interface for managing authorized keys
12 ///
13 /// This precompile allows accounts to authorize secondary keys with:
14 /// - Different signature types (secp256k1, P256, WebAuthn)
15 /// - Expiry times for key rotation
16 /// - Per-token spending limits for security
17 ///
18 /// Only the main account key can authorize/revoke keys, while secondary keys
19 /// can be used for regular transactions within their spending limits.
20 #[derive(Debug, PartialEq, Eq)]
21 #[sol(abi)]
22 interface IAccountKeychain {
23 enum SignatureType {
24 Secp256k1,
25 P256,
26 WebAuthn,
27 }
28
29 /// Legacy token spending limit structure used before T3.
30 struct LegacyTokenLimit {
31 address token;
32 uint256 amount;
33 }
34
35 /// Token spending limit structure
36 struct TokenLimit {
37 address token;
38 uint256 amount;
39 uint64 period;
40 }
41
42 /// Selector-level recipient rule.
43 struct SelectorRule {
44 bytes4 selector;
45 /// Empty means no recipient restriction for this selector.
46 /// To block the selector entirely, remove the selector rule instead of passing `[]`.
47 address[] recipients;
48 }
49
50 /// Per-target call scope.
51 struct CallScope {
52 address target;
53 /// Empty means no selector restriction for this target.
54 /// To block the target entirely, omit this scope from `allowedCalls` or call
55 /// `removeAllowedCalls` for incremental updates.
56 SelectorRule[] selectorRules;
57 }
58
59 /// Optional access-key restrictions configured at authorization time.
60 struct KeyRestrictions {
61 uint64 expiry;
62 bool enforceLimits;
63 TokenLimit[] limits;
64 /// `true` means the key is unrestricted and `allowedCalls` must be empty.
65 /// `false` means `allowedCalls` defines the full call scope (including deny-all with `[]`).
66 bool allowAnyCalls;
67 CallScope[] allowedCalls;
68 }
69
70 /// Key information structure
71 struct KeyInfo {
72 SignatureType signatureType;
73 address keyId;
74 uint64 expiry;
75 bool enforceLimits;
76 bool isRevoked;
77 }
78 /// Emitted when a new key is authorized
79 event KeyAuthorized(address indexed account, address indexed publicKey, uint8 signatureType, uint64 expiry);
80
81 /// Emitted when a key is revoked
82 event KeyRevoked(address indexed account, address indexed publicKey);
83
84 /// Emitted when a spending limit is updated
85 event SpendingLimitUpdated(address indexed account, address indexed publicKey, address indexed token, uint256 newLimit);
86
87 event AccessKeySpend(
88 address indexed account,
89 address indexed publicKey,
90 address indexed token,
91 uint256 amount,
92 uint256 remainingLimit
93 );
94
95 /// Legacy authorize-key entrypoint used before T3.
96 function authorizeKey(
97 address keyId,
98 SignatureType signatureType,
99 uint64 expiry,
100 bool enforceLimits,
101 LegacyTokenLimit[] calldata limits
102 ) external;
103
104 /// Authorize a new key for the caller's account with T3 extensions.
105 /// @param keyId The key identifier (address derived from public key)
106 /// @param signatureType 0: secp256k1, 1: P256, 2: WebAuthn
107 /// @param config Access-key expiry and optional limits / call restrictions
108 function authorizeKey(
109 address keyId,
110 SignatureType signatureType,
111 KeyRestrictions calldata config
112 ) external;
113
114 /// Revoke an authorized key
115 /// @param publicKey The public key to revoke
116 function revokeKey(address keyId) external;
117
118 /// Update spending limit for a key-token pair
119 /// @param publicKey The public key
120 /// @param token The token address
121 /// @param newLimit The new spending limit
122 function updateSpendingLimit(
123 address keyId,
124 address token,
125 uint256 newLimit
126 ) external;
127
128 /// Set or replace allowed calls for one or more key+target pairs.
129 /// @dev Reverts if `scopes` is empty; use `removeAllowedCalls` to delete target scopes.
130 /// @dev `scope.selectorRules = []` does NOT block the target; it allows any selector on that target.
131 /// @dev To block the target entirely, call `removeAllowedCalls`. To block one selector,
132 /// omit that selector rule from `scope.selectorRules`.
133 function setAllowedCalls(
134 address keyId,
135 CallScope[] calldata scopes
136 ) external;
137
138 /// Remove any configured call scope for a key+target pair.
139 function removeAllowedCalls(address keyId, address target) external;
140
141 /// Get key information
142 /// @param account The account address
143 /// @param publicKey The public key
144 /// @return Key information
145 function getKey(address account, address keyId) external view returns (KeyInfo memory);
146
147 /// Get remaining spending limit using the legacy pre-T3 return shape.
148 /// @param account The account address
149 /// @param publicKey The public key
150 /// @param token The token address
151 function getRemainingLimit(
152 address account,
153 address keyId,
154 address token
155 ) external view returns (uint256 remaining);
156
157 /// Get remaining spending limit together with the active period end.
158 /// @param account The account address
159 /// @param publicKey The public key
160 /// @param token The token address
161 /// @return remaining Remaining spending amount
162 /// @return periodEnd Period end timestamp for periodic limits (0 for one-time)
163 function getRemainingLimitWithPeriod(
164 address account,
165 address keyId,
166 address token
167 ) external view returns (uint256 remaining, uint64 periodEnd);
168
169 /// Returns whether an account key is call-scoped and, if so, the configured call scopes.
170 /// @dev `isScoped = false` means unrestricted. `isScoped = true && scopes.length == 0`
171 /// means scoped deny-all.
172 /// @dev Missing, revoked, or expired access keys also return scoped deny-all so callers do
173 /// not observe stale persisted scope state.
174 function getAllowedCalls(
175 address account,
176 address keyId
177 ) external view returns (bool isScoped, CallScope[] memory scopes);
178
179 /// Get the key used in the current transaction
180 /// @return The keyId used in the current transaction
181 function getTransactionKey() external view returns (address);
182
183 // Errors
184 error UnauthorizedCaller();
185 error KeyAlreadyExists();
186 error KeyNotFound();
187 error KeyExpired();
188 error SpendingLimitExceeded();
189 error InvalidSpendingLimit();
190 error InvalidSignatureType();
191 error ZeroPublicKey();
192 error ExpiryInPast();
193 error KeyAlreadyRevoked();
194 error SignatureTypeMismatch(uint8 expected, uint8 actual);
195 error CallNotAllowed();
196 error InvalidCallScope();
197 error LegacyAuthorizeKeySelectorChanged(bytes4 newSelector);
198 }
199}
200
201impl AccountKeychainError {
202 /// Creates an error for signature type mismatch.
203 pub const fn signature_type_mismatch(expected: u8, actual: u8) -> Self {
204 Self::SignatureTypeMismatch(IAccountKeychain::SignatureTypeMismatch { expected, actual })
205 }
206
207 /// Creates an error for unauthorized caller.
208 pub const fn unauthorized_caller() -> Self {
209 Self::UnauthorizedCaller(IAccountKeychain::UnauthorizedCaller {})
210 }
211
212 /// Creates an error for key already exists.
213 pub const fn key_already_exists() -> Self {
214 Self::KeyAlreadyExists(IAccountKeychain::KeyAlreadyExists {})
215 }
216
217 /// Creates an error for key not found.
218 pub const fn key_not_found() -> Self {
219 Self::KeyNotFound(IAccountKeychain::KeyNotFound {})
220 }
221
222 /// Creates an error for key expired.
223 pub const fn key_expired() -> Self {
224 Self::KeyExpired(IAccountKeychain::KeyExpired {})
225 }
226
227 /// Creates an error for spending limit exceeded.
228 pub const fn spending_limit_exceeded() -> Self {
229 Self::SpendingLimitExceeded(IAccountKeychain::SpendingLimitExceeded {})
230 }
231
232 /// Creates an error for spending limits that exceed the TIP-20 u128 supply cap.
233 pub const fn invalid_spending_limit() -> Self {
234 Self::InvalidSpendingLimit(IAccountKeychain::InvalidSpendingLimit {})
235 }
236
237 /// Creates an error for invalid signature type.
238 pub const fn invalid_signature_type() -> Self {
239 Self::InvalidSignatureType(IAccountKeychain::InvalidSignatureType {})
240 }
241
242 /// Creates an error for zero public key.
243 pub const fn zero_public_key() -> Self {
244 Self::ZeroPublicKey(IAccountKeychain::ZeroPublicKey {})
245 }
246
247 /// Creates an error for expiry timestamp in the past.
248 pub const fn expiry_in_past() -> Self {
249 Self::ExpiryInPast(IAccountKeychain::ExpiryInPast {})
250 }
251
252 /// Creates an error for when a key_id has already been revoked.
253 /// Once revoked, a key_id can never be re-authorized for the same account.
254 /// This prevents replay attacks where a revoked key's authorization is reused.
255 pub const fn key_already_revoked() -> Self {
256 Self::KeyAlreadyRevoked(IAccountKeychain::KeyAlreadyRevoked {})
257 }
258
259 /// Creates an error for disallowed call attempts by scoped access keys.
260 pub const fn call_not_allowed() -> Self {
261 Self::CallNotAllowed(IAccountKeychain::CallNotAllowed {})
262 }
263
264 /// Creates an error for invalid scope configuration.
265 pub const fn invalid_call_scope() -> Self {
266 Self::InvalidCallScope(IAccountKeychain::InvalidCallScope {})
267 }
268
269 /// Creates an error for the legacy authorize-key selector being unavailable on T3+.
270 pub fn legacy_authorize_key_selector_changed(new_selector: [u8; 4]) -> Self {
271 Self::LegacyAuthorizeKeySelectorChanged(
272 IAccountKeychain::LegacyAuthorizeKeySelectorChanged {
273 newSelector: new_selector.into(),
274 },
275 )
276 }
277}