Skip to main content

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}