diff --git a/README.md b/README.md index 10945bf..4123e30 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ forge install tempoxyz/tempo-std
 src
 ├── interfaces
-│   ├── IAccountKeychain.sol: Account Keychain | Docs | Implementation
-│   ├── IAddressRegistry.sol: TIP-1022 Virtual Address Registry | Implementation
+│   ├── IAccountKeychain.sol: Account Keychain (T3: call scopes, periodic limits) | Docs | Implementation
+│   ├── IAddressRegistry.sol: TIP-1022 Virtual Address Registry (T3+) | Implementation
 │   ├── IFeeAMM.sol: Fee AMM | Docs | Implementation
 │   ├── IFeeManager.sol: Fee AMM Management | Docs | Implementation
 │   ├── INonce.sol: 2D Nonce Management for Tempo Transactions | Implementation
-│   ├── ISignatureVerifier.sol: TIP-1020 Signature Verification Precompile | Implementation
+│   ├── ISignatureVerifier.sol: TIP-1020 Signature Verification Precompile (T3+) | Implementation
 │   ├── IStablecoinDEX.sol: Stablecoin DEX | Docs | Implementation
 │   ├── ITempoStreamChannel.sol: Streaming payment channel escrow (concept) | Implementation
 │   ├── ITIP20Factory.sol: TIP-20: Factory Contract | Docs | Implementation
diff --git a/src/StdPrecompiles.sol b/src/StdPrecompiles.sol
index e0e90d3..513b6c5 100644
--- a/src/StdPrecompiles.sol
+++ b/src/StdPrecompiles.sol
@@ -2,12 +2,15 @@
 pragma solidity >=0.8.13 <0.9.0;
 
 import {IAccountKeychain} from "./interfaces/IAccountKeychain.sol";
+import {IAddressRegistry} from "./interfaces/IAddressRegistry.sol";
 import {IFeeManager} from "./interfaces/IFeeManager.sol";
+import {ISignatureVerifier} from "./interfaces/ISignatureVerifier.sol";
 import {ITIP403Registry} from "./interfaces/ITIP403Registry.sol";
 import {ITIP20Factory} from "./interfaces/ITIP20Factory.sol";
 import {ITIP20RewardsRegistry} from "./interfaces/ITIP20RewardsRegistry.sol";
 import {IStablecoinDEX} from "./interfaces/IStablecoinDEX.sol";
 import {IValidatorConfig} from "./interfaces/IValidatorConfig.sol";
+import {IValidatorConfigV2} from "./interfaces/IValidatorConfigV2.sol";
 import {INonce} from "./interfaces/INonce.sol";
 
 /// @title Standard Precompiles Library for Tempo
@@ -22,6 +25,9 @@ library StdPrecompiles {
     address internal constant NONCE_ADDRESS = 0x4e4F4E4345000000000000000000000000000000;
     address internal constant VALIDATOR_CONFIG_ADDRESS = 0xCccCcCCC00000000000000000000000000000000;
     address internal constant ACCOUNT_KEYCHAIN_ADDRESS = 0xaAAAaaAA00000000000000000000000000000000;
+    address internal constant VALIDATOR_CONFIG_V2_ADDRESS = 0xCcCCCCcC00000000000000000000000000000001;
+    address internal constant ADDRESS_REGISTRY_ADDRESS = 0xfDC0000000000000000000000000000000000000;
+    address internal constant SIGNATURE_VERIFIER_ADDRESS = 0x5165300000000000000000000000000000000000;
 
     IFeeManager internal constant TIP_FEE_MANAGER = IFeeManager(TIP_FEE_MANAGER_ADDRESS);
     ITIP403Registry internal constant TIP403_REGISTRY = ITIP403Registry(TIP403_REGISTRY_ADDRESS);
@@ -32,4 +38,7 @@ library StdPrecompiles {
     INonce internal constant NONCE_PRECOMPILE = INonce(NONCE_ADDRESS);
     IValidatorConfig internal constant VALIDATOR_CONFIG = IValidatorConfig(VALIDATOR_CONFIG_ADDRESS);
     IAccountKeychain internal constant ACCOUNT_KEYCHAIN = IAccountKeychain(ACCOUNT_KEYCHAIN_ADDRESS);
+    IValidatorConfigV2 internal constant VALIDATOR_CONFIG_V2 = IValidatorConfigV2(VALIDATOR_CONFIG_V2_ADDRESS);
+    IAddressRegistry internal constant ADDRESS_REGISTRY = IAddressRegistry(ADDRESS_REGISTRY_ADDRESS);
+    ISignatureVerifier internal constant SIGNATURE_VERIFIER = ISignatureVerifier(SIGNATURE_VERIFIER_ADDRESS);
 }
diff --git a/src/interfaces/IAccountKeychain.sol b/src/interfaces/IAccountKeychain.sol
index 0d57737..e7297b9 100644
--- a/src/interfaces/IAccountKeychain.sol
+++ b/src/interfaces/IAccountKeychain.sol
@@ -9,9 +9,11 @@ pragma solidity >=0.8.13 <0.9.0;
  * The Account Keychain allows accounts to authorize secondary keys (Access Keys) that can sign
  * transactions on behalf of the account. Access Keys can be scoped by:
  * - Expiry timestamp (when the key becomes invalid)
- * - Per-TIP20 token spending limits that deplete as the key spends
+ * - Per-TIP20 token spending limits (one-time or periodic) that deplete as the key spends
+ * - Call scopes restricting which contracts/selectors the key may call (T3+)
  *
- * Only the Root Key can call authorizeKey, revokeKey, and updateSpendingLimit.
+ * Only the Root Key can call authorizeKey, revokeKey, updateSpendingLimit, setAllowedCalls,
+ * and removeAllowedCalls.
  * This restriction is enforced by the protocol at transaction validation time.
  * Access Keys attempting to call these functions will fail with UnauthorizedCaller.
  *
@@ -30,10 +32,38 @@ interface IAccountKeychain {
         WebAuthn
     }
 
+    /// @notice Legacy token spending limit structure used before T3
+    struct LegacyTokenLimit {
+        address token; // TIP20 token address
+        uint256 amount; // Spending limit amount
+    }
+
     /// @notice Token spending limit structure
     struct TokenLimit {
         address token; // TIP20 token address
         uint256 amount; // Spending limit amount
+        uint64 period; // Period duration in seconds (0 = one-time limit, >0 = periodic reset)
+    }
+
+    /// @notice Selector-level recipient rule
+    struct SelectorRule {
+        bytes4 selector; // 4-byte function selector
+        address[] recipients; // Empty means no recipient restriction for this selector
+    }
+
+    /// @notice Per-target call scope
+    struct CallScope {
+        address target; // Target contract address
+        SelectorRule[] selectorRules; // Empty means any selector is allowed for this target
+    }
+
+    /// @notice Optional access-key restrictions configured at authorization time
+    struct KeyRestrictions {
+        uint64 expiry; // Unix timestamp when key expires (use type(uint64).max for never)
+        bool enforceLimits; // Whether spending limits are enforced for this key
+        TokenLimit[] limits; // Token spending limits
+        bool allowAnyCalls; // true = unrestricted calls (allowedCalls must be empty), false = allowedCalls defines scope
+        CallScope[] allowedCalls; // Call scopes when allowAnyCalls is false
     }
 
     /// @notice Key information structure
@@ -60,29 +90,41 @@ interface IAccountKeychain {
         address indexed account, address indexed publicKey, address indexed token, uint256 newLimit
     );
 
+    /// @notice Emitted when an access key spends tokens
+    event AccessKeySpend(
+        address indexed account,
+        address indexed publicKey,
+        address indexed token,
+        uint256 amount,
+        uint256 remainingLimit
+    );
+
     /*//////////////////////////////////////////////////////////////
                                 ERRORS
     //////////////////////////////////////////////////////////////*/
 
+    error UnauthorizedCaller();
     error KeyAlreadyExists();
     error KeyNotFound();
-    error KeyInactive();
     error KeyExpired();
-    error KeyAlreadyRevoked();
     error SpendingLimitExceeded();
+    error InvalidSpendingLimit();
     error InvalidSignatureType();
     error ZeroPublicKey();
     error ExpiryInPast();
-    error UnauthorizedCaller();
+    error KeyAlreadyRevoked();
+    error SignatureTypeMismatch(uint8 expected, uint8 actual);
+    error CallNotAllowed();
+    error InvalidCallScope();
+    error LegacyAuthorizeKeySelectorChanged(bytes4 newSelector);
 
     /*//////////////////////////////////////////////////////////////
                         MANAGEMENT FUNCTIONS
     //////////////////////////////////////////////////////////////*/
 
     /**
-     * @notice Authorize a new key for the caller's account
+     * @notice Legacy authorize-key entrypoint used before T3
      * @dev MUST only be called in transactions signed by the Root Key
-     *      The protocol enforces this restriction by checking transactionKey[msg.sender]
      * @param keyId The key identifier (address) to authorize
      * @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
      * @param expiry Unix timestamp when key expires (use type(uint64).max for never expires)
@@ -94,13 +136,21 @@ interface IAccountKeychain {
         SignatureType signatureType,
         uint64 expiry,
         bool enforceLimits,
-        TokenLimit[] calldata limits
+        LegacyTokenLimit[] calldata limits
     ) external;
 
+    /**
+     * @notice Authorize a new key for the caller's account with T3 extensions
+     * @dev MUST only be called in transactions signed by the Root Key
+     * @param keyId The key identifier (address derived from public key)
+     * @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
+     * @param config Access-key expiry and optional limits / call restrictions
+     */
+    function authorizeKey(address keyId, SignatureType signatureType, KeyRestrictions calldata config) external;
+
     /**
      * @notice Revoke an authorized key
      * @dev MUST only be called in transactions signed by the Root Key
-     *      The protocol enforces this restriction by checking transactionKey[msg.sender]
      * @param keyId The key ID to revoke
      */
     function revokeKey(address keyId) external;
@@ -108,13 +158,30 @@ interface IAccountKeychain {
     /**
      * @notice Update spending limit for a specific token on an authorized key
      * @dev MUST only be called in transactions signed by the Root Key
-     *      The protocol enforces this restriction by checking transactionKey[msg.sender]
      * @param keyId The key ID to update
      * @param token The token address
      * @param newLimit The new spending limit
      */
     function updateSpendingLimit(address keyId, address token, uint256 newLimit) external;
 
+    /**
+     * @notice Set or replace allowed calls for one or more key+target pairs
+     * @dev MUST only be called in transactions signed by the Root Key.
+     *      Reverts if `scopes` is empty; use `removeAllowedCalls` to delete target scopes.
+     *      `scope.selectorRules = []` allows any selector on that target.
+     * @param keyId The key ID to configure
+     * @param scopes The call scopes to set
+     */
+    function setAllowedCalls(address keyId, CallScope[] calldata scopes) external;
+
+    /**
+     * @notice Remove any configured call scope for a key+target pair
+     * @dev MUST only be called in transactions signed by the Root Key
+     * @param keyId The key ID to update
+     * @param target The target contract to remove from allowed calls
+     */
+    function removeAllowedCalls(address keyId, address target) external;
+
     /*//////////////////////////////////////////////////////////////
                         VIEW FUNCTIONS
     //////////////////////////////////////////////////////////////*/
@@ -128,13 +195,41 @@ interface IAccountKeychain {
     function getKey(address account, address keyId) external view returns (KeyInfo memory);
 
     /**
-     * @notice Get remaining spending limit for a key-token pair
+     * @notice Get remaining spending limit for a key-token pair (legacy)
      * @param account The account address
      * @param keyId The key ID
      * @param token The token address
-     * @return Remaining spending amount
+     * @return remaining Remaining spending amount
+     */
+    function getRemainingLimit(address account, address keyId, address token) external view returns (uint256 remaining);
+
+    /**
+     * @notice Get remaining spending limit together with the active period end
+     * @param account The account address
+     * @param keyId The key ID
+     * @param token The token address
+     * @return remaining Remaining spending amount
+     * @return periodEnd Period end timestamp for periodic limits (0 for one-time)
+     */
+    function getRemainingLimitWithPeriod(address account, address keyId, address token)
+        external
+        view
+        returns (uint256 remaining, uint64 periodEnd);
+
+    /**
+     * @notice Returns whether an account key is call-scoped and, if so, the configured call scopes
+     * @dev `isScoped = false` means unrestricted. `isScoped = true && scopes.length == 0`
+     *      means scoped deny-all. Missing, revoked, or expired access keys also return scoped
+     *      deny-all so callers do not observe stale persisted scope state.
+     * @param account The account address
+     * @param keyId The key ID
+     * @return isScoped Whether the key is call-scoped
+     * @return scopes The configured call scopes
      */
-    function getRemainingLimit(address account, address keyId, address token) external view returns (uint256);
+    function getAllowedCalls(address account, address keyId)
+        external
+        view
+        returns (bool isScoped, CallScope[] memory scopes);
 
     /**
      * @notice Get the transaction key used in the current transaction