Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ forge install tempoxyz/tempo-std
<pre>
src
├── interfaces
│ ├── <a href="./src/interfaces/IAccountKeychain.sol">IAccountKeychain.sol</a>: Account Keychain | <a href="https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#keychain-precompile">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/account_keychain/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IAddressRegistry.sol">IAddressRegistry.sol</a>: TIP-1022 Virtual Address Registry | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/address_registry/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IAccountKeychain.sol">IAccountKeychain.sol</a>: Account Keychain (T3: call scopes, periodic limits) | <a href="https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#keychain-precompile">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/account_keychain/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IAddressRegistry.sol">IAddressRegistry.sol</a>: TIP-1022 Virtual Address Registry (T3+) | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/address_registry/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IFeeAMM.sol">IFeeAMM.sol</a>: Fee AMM | <a href="https://docs.tempo.xyz/protocol/fees/fee-amm">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip_fee_manager/amm.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IFeeManager.sol">IFeeManager.sol</a>: Fee AMM Management | <a href="https://docs.tempo.xyz/protocol/fees/spec-fee-amm#2-feemanager-contract">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip_fee_manager/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/INonce.sol">INonce.sol</a>: 2D Nonce Management for Tempo Transactions | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/nonce/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/ISignatureVerifier.sol">ISignatureVerifier.sol</a>: TIP-1020 Signature Verification Precompile | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/signature_verifier/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/ISignatureVerifier.sol">ISignatureVerifier.sol</a>: TIP-1020 Signature Verification Precompile (T3+) | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/signature_verifier/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/IStablecoinDEX.sol">IStablecoinDEX.sol</a>: Stablecoin DEX | <a href="https://docs.tempo.xyz/protocol/exchange/spec#stablecoin-dex">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/stablecoin_dex/mod.rs">Implementation</a>
│ ├── <a href="./src/interfaces/ITempoStreamChannel.sol">ITempoStreamChannel.sol</a>: Streaming payment channel escrow (concept) | <a href="https://github.com/tempoxyz/tempo/blob/main/tips/ref-impls/src/TempoStreamChannel.sol">Implementation</a>
│ ├── <a href="./src/interfaces/ITIP20Factory.sol">ITIP20Factory.sol</a>: TIP-20: Factory Contract | <a href="https://docs.tempo.xyz/protocol/tip20/spec#tip20factory">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip20_factory/mod.rs">Implementation</a>
Expand Down
9 changes: 9 additions & 0 deletions src/StdPrecompiles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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);
}
121 changes: 108 additions & 13 deletions src/interfaces/IAccountKeychain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -94,27 +136,52 @@ 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;

/**
* @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
//////////////////////////////////////////////////////////////*/
Expand All @@ -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
Expand Down