diff --git a/src/pages/protocol/transactions/AccountKeychain.mdx b/src/pages/protocol/transactions/AccountKeychain.mdx index 1f8513cc..0329075e 100644 --- a/src/pages/protocol/transactions/AccountKeychain.mdx +++ b/src/pages/protocol/transactions/AccountKeychain.mdx @@ -1,34 +1,55 @@ --- -description: Technical specification for the Account Keychain precompile managing access keys with expiry timestamps and per-token spending limits. +description: Technical specification for the Account Keychain precompile managing access keys with expiry timestamps, spending limits, and post-T3 call-scope restrictions. --- # Account Keychain Precompile **Address:** `0xAAAAAAAA00000000000000000000000000000000` -:::info[T3 will change this spec] -The [T3 network upgrade](/protocol/upgrades/t3) will update this specification. The sections below describe the currently active behavior. See [Upcoming changes](#upcoming-changes) for the upcoming deltas. +:::info[T3 will change this precompile] +The [T3 network upgrade](/protocol/upgrades/t3) will update this specification. The sections below describe the current T2 behavior and include `T2 -> T3 changes` notes for each section. If you are migrating now, start with [Account keychain post-T3](#account-keychain-post-t3). ::: ## Overview The Account Keychain precompile manages authorized Access Keys for accounts, enabling Root Keys (e.g., passkeys) to provision scoped "secondary" Access Keys with expiry timestamps and per-TIP20 token spending limits. +At T3, `authorizeKey(...)` moves from top-level `expiry`, `enforceLimits`, and `limits` arguments to a `KeyRestrictions` tuple. `TokenLimit` gains `period`, so limits can be one-time or recurring, and access keys can optionally be restricted to specific targets, selectors, and recipients. Access-key-signed transactions also can no longer create contracts after T3 activation. + ## Motivation The Tempo Transaction type unlocks a number of new signature schemes, including WebAuthn (Passkeys). However, for an Account using a Passkey as its Root Key, the sender will subsequently be prompted with passkey prompts for every signature request. This can be a poor user experience for highly interactive or multi-step flows. Additionally, users would also see "Sign In" copy in prompts for signing transactions which is confusing. This proposal introduces the concept of the Root Key being able to provision a (scoped) Access Key that can be used for subsequent transactions, without the need for repetitive end-user prompting. -## Upcoming changes +### T2 -> T3 changes + +At T2, the main focus is expiry and spending limits. T3 extends the same model to cover recurring budgets and explicit call scoping, which makes access keys more powerful for subscriptions, connected apps, and session-key-style flows. + +## Account keychain post-T3 + +T3 changes the Account Keychain authorization shape on any network where the upgrade is active. Existing authorized keys continue to work. The breaking change is in how new key authorizations are encoded and what restrictions can now be enforced. -T3 updates the Account Keychain specification through [TIP-1011](/protocol/tips/tip-1011) in the following ways: +### Migration summary -- [TIP-1011](/protocol/tips/tip-1011) extends `TokenLimit` with an optional recurring `period`, so spending limits can be either one-time or periodic. -- `authorizeKey(...)` moves to the new ABI with `allowAnyCalls` and `allowedCalls`, enabling explicit call scoping during key authorization. -- New `SelectorRule` and `CallScope` structs define per-target and per-selector allowlists, including recipient-bound rules for supported TIP-20 selectors. -- New root-key-only functions `setAllowedCalls(...)` and `removeAllowedCalls(...)`, plus a new `getAllowedCalls(...)` view, are added for managing and inspecting call scopes. -- `getRemainingLimit(...)` changes to return both `remaining` and `periodEnd` so callers can observe periodic reset state. -- `updateSpendingLimit(...)` resets the remaining amount to `newLimit` but does not change the configured `period` or current `periodEnd`. -- Access-key-signed transactions can no longer create contracts in any configuration. Calling the `CREATE` opcode onchain still works. +- The legacy `authorizeKey(address,uint8,uint64,bool,(address,uint256)[])` entrypoint is no longer accepted after T3 activation. Calls to selector `0x54063a55` revert with `LegacyAuthorizeKeySelectorChanged(0x980a6025)`. +- `authorizeKey(...)` now takes a `KeyRestrictions` tuple that carries expiry, spending limits, and call scopes. +- `TokenLimit` now includes `period`, so limits can be one-time (`period = 0`) or recurring. +- Access keys can now be scoped to specific targets, selectors, and recipients. +- The precompile adds `setAllowedCalls(...)`, `removeAllowedCalls(...)`, `getAllowedCalls(...)`, and `getRemainingLimitWithPeriod(...)`. +- Access-key-signed transactions can no longer create contracts after T3 activation. Use a Root Key for deployment flows. + +### Before and after `authorizeKey(...)` + +```text +T2 +authorizeKey(address,uint8,uint64,bool,(address,uint256)[]) +selector: 0x54063a55 + +T3 +authorizeKey(address,uint8,(uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[])) +selector: 0x980a6025 +``` + +The T3 call must use the tuple-form signature above. A flattened seven-argument signature is not equivalent. In Foundry, that flattened form hashes to `0x203e2736`, which the precompile rejects as an unknown selector. ## Concepts @@ -44,6 +65,10 @@ Access Keys are secondary signing keys authorized by an account's Root Key. They - Native value transfers and `transferFrom()` are NOT limited - **Privilege Restrictions**: Cannot authorize new keys or modify their own limits +#### T2 -> T3 changes + +At T3, spending limits can recur through `TokenLimit.period`. A `period` of `0` keeps the limit one-time, while a non-zero value makes it recurring. Call scoping also becomes a first-class restriction type, and access-key-signed transactions can no longer create contracts after T3 activation. + ### Authorization Hierarchy The protocol enforces a strict hierarchy at validation time: @@ -57,6 +82,10 @@ The protocol enforces a strict hierarchy at validation time: - Subject to per-TIP20 token spending limits - Can have expiry timestamps +#### T2 -> T3 changes + +The hierarchy itself does not change. T3 adds new mutable call-scope management functions, and they remain Root-Key-only. Access Keys are also subject to call-scope checks during execution. + ## Storage The precompile uses a `keyId` (address) to uniquely identify each access key for an account. @@ -72,8 +101,14 @@ The precompile uses a `keyId` (address) to uniquely identify each access key for - byte 9: enforce_limits (bool) - byte 10: is_revoked (bool) +### T2 -> T3 changes + +At T3, `spendingLimits[...]` expands from a single remaining amount into `SpendingLimitState { remaining, max, period, periodEnd }`, and a new `keyScopes[keccak256(account || keyId)]` tree stores target, selector, and recipient allowlists. `transactionKey` remains the same. + ## Interface +### T2 interface + ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; @@ -100,7 +135,7 @@ interface IAccountKeychain { struct KeyInfo { SignatureType signatureType; // Signature type of the key address keyId; // The key identifier - uint64 expiry; // Unix timestamp when key expires (0 = never) + uint64 expiry; // Unix timestamp when key expires bool enforceLimits; // Whether spending limits are enforced for this key bool isRevoked; // Whether this key has been revoked } @@ -152,7 +187,7 @@ interface IAccountKeychain { * 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 (MUST be > current_timestamp, or 0 for never expires) + * @param expiry Unix timestamp when key expires (MUST be > current_timestamp) * @param enforceLimits Whether to enforce spending limits for this key * @param limits Initial spending limits for tokens (only used if enforceLimits is true) */ @@ -223,6 +258,113 @@ interface IAccountKeychain { } ``` +### T2 -> T3 changes + +At T3, `TokenLimit` gains `period`, and the interface adds `SelectorRule`, `CallScope`, and `KeyRestrictions`. `authorizeKey(...)` moves from top-level `expiry`, `enforceLimits`, and `limits` arguments to a `KeyRestrictions` tuple, while new Root-Key-only mutation methods (`setAllowedCalls(...)` and `removeAllowedCalls(...)`) and new views (`getRemainingLimitWithPeriod(...)` and `getAllowedCalls(...)`) are added around it. The legacy `getRemainingLimit(...)` selector is dropped at T3, and new T3-specific errors include `InvalidSpendingLimit()`, `ExpiryInPast()`, `SignatureTypeMismatch(uint8,uint8)`, `CallNotAllowed()`, `InvalidCallScope()`, and `LegacyAuthorizeKeySelectorChanged(bytes4)`. + +### T3 interface + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +interface IAccountKeychain { + enum SignatureType { + Secp256k1, + P256, + WebAuthn + } + + struct TokenLimit { + address token; + uint256 amount; + uint64 period; + } + + struct SelectorRule { + bytes4 selector; + address[] recipients; + } + + struct CallScope { + address target; + SelectorRule[] selectorRules; + } + + struct KeyRestrictions { + uint64 expiry; + bool enforceLimits; + TokenLimit[] limits; + bool allowAnyCalls; + CallScope[] allowedCalls; + } + + struct KeyInfo { + SignatureType signatureType; + address keyId; + uint64 expiry; + bool enforceLimits; + bool isRevoked; + } + + event KeyAuthorized(address indexed account, address indexed publicKey, uint8 signatureType, uint64 expiry); + event KeyRevoked(address indexed account, address indexed publicKey); + event SpendingLimitUpdated(address indexed account, address indexed publicKey, address indexed token, uint256 newLimit); + event AccessKeySpend(address indexed account, address indexed publicKey, address indexed token, uint256 amount, uint256 remainingLimit); + + error UnauthorizedCaller(); + error KeyAlreadyExists(); + error KeyNotFound(); + error KeyExpired(); + error SpendingLimitExceeded(); + error InvalidSpendingLimit(); + error InvalidSignatureType(); + error ZeroPublicKey(); + error ExpiryInPast(); + error KeyAlreadyRevoked(); + error SignatureTypeMismatch(uint8 expected, uint8 actual); + error CallNotAllowed(); + error InvalidCallScope(); + error LegacyAuthorizeKeySelectorChanged(bytes4 newSelector); + + function authorizeKey( + address keyId, + SignatureType signatureType, + KeyRestrictions calldata config + ) external; + + function revokeKey(address keyId) external; + + function updateSpendingLimit( + address keyId, + address token, + uint256 newLimit + ) external; + + function setAllowedCalls( + address keyId, + CallScope[] calldata scopes + ) external; + + function removeAllowedCalls(address keyId, address target) external; + + function getKey(address account, address keyId) external view returns (KeyInfo memory); + + function getRemainingLimitWithPeriod( + address account, + address keyId, + address token + ) external view returns (uint256 remaining, uint64 periodEnd); + + function getAllowedCalls( + address account, + address keyId + ) external view returns (bool isScoped, CallScope[] memory scopes); + + function getTransactionKey() external view returns (address); +} +``` + ## Behavior ### Key Authorization @@ -241,6 +383,10 @@ interface IAccountKeychain { - `enforceLimits` determines whether spending limits are enforced for this key - `limits` are only processed if `enforceLimits` is `true` +#### T2 -> T3 changes + +At T3, the legacy selector is no longer accepted and reverts with `LegacyAuthorizeKeySelectorChanged(newSelector: 0x980a6025)`. `authorizeKey(...)` now takes `KeyRestrictions` instead of top-level `expiry`, `enforceLimits`, and `limits` arguments, `config.expiry` must be greater than the current block timestamp, duplicate token entries are invalid, `allowAnyCalls = false` with `allowedCalls = []` means scoped deny-all, and recipient-constrained selector rules are validated before state is written. + ### Key Revocation - Marks the key as revoked by setting `isRevoked` to `true` and `expiry` to `0` @@ -252,6 +398,10 @@ interface IAccountKeychain { - MUST be called by Root Key only (verified by checking `transactionKey[msg.sender] == 0`) - `keyId` MUST exist (key with `expiry > 0`) (reverts with `KeyNotFound` if not found) +#### T2 -> T3 changes + +Revoked keys still behave as inactive for all legacy reads. T3 also treats any stored call-scope and periodic-limit state as inaccessible once the key is revoked, and `getAllowedCalls(...)` returns scoped deny-all for revoked keys. + ### Spending Limit Update - Updates the spending limit for a specific token on an authorized key @@ -265,6 +415,28 @@ interface IAccountKeychain { - `keyId` MUST exist and not be revoked (reverts with `KeyNotFound` or `KeyAlreadyRevoked`) - `keyId` MUST not be expired (reverts with `KeyExpired`) +#### T2 -> T3 changes + +At T3, `newLimit` resets both `remaining` and `max` while preserving the existing `period` and current `periodEnd`. `newLimit` must also fit within TIP20's `u128` supply range. + +### View Behavior + +- `getKey(...)` returns key metadata. +- `getRemainingLimit(...)` returns the remaining amount for a key-token pair. +- `getTransactionKey()` returns the key used in the current transaction. `address(0)` means the Root Key. + +#### T2 -> T3 changes + +At T3, callers must switch from `getRemainingLimit(...)` to `getRemainingLimitWithPeriod(...)`. The legacy `getRemainingLimit(...)` selector is dropped, while `getRemainingLimitWithPeriod(...)` returns both effective `remaining` and current `periodEnd`. Missing, revoked, or expired keys return zeroed limit values, and `getAllowedCalls(...)` distinguishes unrestricted keys from scoped deny-all keys by returning `isScoped = true, scopes = []` for missing, revoked, or expired access keys. + +### Allowed Call Updates + +This behavior does not exist at T2. + +#### T2 -> T3 changes + +At T3, `setAllowedCalls(...)` creates or replaces one or more target scopes, and `removeAllowedCalls(...)` removes one stored target scope. Empty `selectorRules` means any selector on that target is allowed, while `setAllowedCalls(...)` rejects an empty scope batch, zero targets, duplicate targets, duplicate selectors, duplicate recipients, and invalid recipient-constrained rules. + ## Security Considerations ### Access Key Storage @@ -275,6 +447,10 @@ Access Keys should be securely stored to prevent unauthorized access: - **Non-Extractable Keys**: Access Keys SHOULD be generated and stored in a non-extractable format to prevent theft. For example, use WebCrypto API with `extractable: false` when generating Keys in web browsers. - **Secure Storage**: Private Keys MUST never be stored in plaintext. Private Keys SHOULD be encrypted and stored in a secure manner. For web applications, use browser-native secure storage mechanisms like IndexedDB with non-extractable WebCrypto keys rather than storing raw key material. +#### T2 -> T3 changes + +- T3 call scopes make per-app and per-device key isolation more important, because a mis-scoped key may have a broader allowlist than intended. + ### Privilege Escalation Prevention Access Keys cannot escalate their own privileges because: @@ -283,6 +459,10 @@ Access Keys cannot escalate their own privileges because: 3. These management functions check that `transactionKey[msg.sender] == 0` (Root Key) before executing 4. Access Keys cannot bypass this check - transactions will revert with `UnauthorizedCaller` +#### T2 -> T3 changes + +The same Root-Key-only restriction applies to `setAllowedCalls(...)` and `removeAllowedCalls(...)`. On T2+ networks, mutable precompile calls also require `msg.sender == tx.origin`, which prevents contract-mediated confused-deputy patterns. + ### Spending Limit Enforcement - Spending limits are only enforced if `enforceLimits == true` for the key @@ -295,13 +475,29 @@ Access Keys cannot escalate their own privileges because: - Root keys (`keyId == address(0)`) have no spending limits - the function returns immediately - Failed limit checks revert the entire transaction with `SpendingLimitExceeded` +#### T2 -> T3 changes + +At T3, recurring limits roll over when `current_timestamp >= periodEnd`. Missing, revoked, or expired keys have an effective remaining limit of zero, and `getRemainingLimitWithPeriod(...)` lets callers observe rollover state directly. + +### Call Scope Enforcement + +This behavior does not exist at T2. + +#### T2 -> T3 changes + +At T3, call-scope checks run on top-level calls signed by an Access Key. If a key is scoped and a call does not match the stored target, selector, and recipient rules, execution reverts with `CallNotAllowed`, and access-key-signed transactions cannot create contracts after T3 activation. + ### Key Expiry - Keys with `expiry > 0` are checked against the current timestamp during validation - Expired keys cause transaction rejection with `KeyExpired` error (checked via `validate_keychain_authorization()`) -- `expiry == 0` means the key never expires +- New authorizations require a future expiry timestamp - Expiry is checked as: `current_timestamp >= expiry` (key is expired when current time reaches or exceeds expiry) +#### T2 -> T3 changes + +Expired keys return zeroed limit and call-scope reads at T3. + ## Usage Patterns ### First-Time Access Key Authorization @@ -312,14 +508,26 @@ Access Keys cannot escalate their own privileges because: 4. Protocol validates Passkey signature on `key_authorization`, sets `transactionKey[account] = 0`, calls `AccountKeychain.authorizeKey()`, then validates Access Key signature 5. Transaction executes with Access Key's spending limits enforced via internal `verify_and_update_spending()` +#### T2 -> T3 changes + +The same flow still applies, but the signed authorization now carries `KeyRestrictions` instead of top-level expiry and limit fields. That lets the same first-use flow provision recurring limits and call scopes. + ### Subsequent Access Key Usage 1. User's Access Key signs the transaction (no `key_authorization` needed) 2. Protocol validates the Access Key via `validate_keychain_authorization()`, sets `transactionKey[account] = keyId` 3. Transaction executes with spending limit enforcement via internal `verify_and_update_spending()` +#### T2 -> T3 changes + +The same flow still applies, but T3 also enforces call scopes during execution and disallows contract creation from access-key-signed transactions. + ### Root Key Revoking an Access Key 1. User signs Passkey prompt → signs transaction calling `revokeKey(keyId)` 2. Transaction executes, marking the Access Key as inactive 3. Future transactions signed by that Access Key will be rejected + +#### T2 -> T3 changes + +The Root Key can still call `revokeKey(...)`. It can additionally call `updateSpendingLimit(...)`, `setAllowedCalls(...)`, and `removeAllowedCalls(...)` to modify restrictions after authorization, and `updateSpendingLimit(...)` now preserves the token's configured `period` and current `periodEnd`. diff --git a/src/pages/protocol/upgrades/t3.mdx b/src/pages/protocol/upgrades/t3.mdx index 550709bc..a47d940b 100644 --- a/src/pages/protocol/upgrades/t3.mdx +++ b/src/pages/protocol/upgrades/t3.mdx @@ -5,9 +5,7 @@ description: Details and timeline for the T3 network upgrade, including enhanced # T3 Network Upgrade -T3 will change this spec -The [T3 network upgrade](https://docs.tempo.xyz/protocol/upgrades/t3) will update this specification. The sections below describe the currently active behavior. See [Upcoming changes](https://docs.tempo.xyz/protocol/tip20/spec#upcoming-changes) for the upcoming deltas. -::: +T3 is the next Tempo protocol upgrade. It introduces enhanced access keys, a standard signature verification precompile, and virtual addresses for TIP-20 deposit routing. ## Timeline @@ -18,7 +16,7 @@ The [T3 network upgrade](https://docs.tempo.xyz/protocol/upgrades/t3) will updat Partners should upgrade nodes to the T3-compatible release before the Moderato activation timestamp. -Overview +## Overview | TIP | What it does | Who should review | |-----|-------------|-------------------| @@ -28,14 +26,16 @@ Overview ## Breaking changes +These breaking changes only affect access-key integrations. You need to update your integration if you create new access keys, manually encode `key_authorization` or `authorizeKey(...)`, or rely on access-key-signed deployment flows. Existing authorized keys continue to work. + ### Access-key authorization ABI Integrations that directly call `AccountKeychain.authorizeKey(...)` or manually encode `key_authorization` must migrate to the TIP-1011 format after activation. Legacy calls fail with `LegacyAuthorizeKeySelectorChanged(newSelector: 0x980a6025)`. - **Before activation:** use the legacy ABI. The TIP-1011 format is not yet valid onchain. -- **After activation:** use the TIP-1011 format. The legacy ABI stops working. +- **After activation:** use the TIP-1011 tuple-form ABI. The legacy selector `0x54063a55` stops working. -If you support both pre-T3 and post-T3 networks, branch on network version or activation timestamp. +If you use an updated SDK, this is mostly a tooling upgrade. If you support both pre-T3 and post-T3 networks, branch on network version or activation timestamp. If you hand-encode calldata, use the exact tuple-form signature from the [Account Keychain precompile spec](/protocol/transactions/AccountKeychain). ### Access-key contract creation diff --git a/src/pages/sdk/foundry/index.mdx b/src/pages/sdk/foundry/index.mdx index becd544c..8edde459 100644 --- a/src/pages/sdk/foundry/index.mdx +++ b/src/pages/sdk/foundry/index.mdx @@ -290,8 +290,8 @@ cast send 'increment()' \ # Send with access key (delegated signing): # First authorize the key via Account Keychain precompile cast send 0xAAAAAAAA00000000000000000000000000000000 \ - 'authorizeKey(address,uint8,uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[])' \ - $ACCESS_KEY_ADDR 0 1893456000 false "[]" true "[]" \ + 'authorizeKey(address,uint8,(uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[]))' \ + $ACCESS_KEY_ADDR 0 '(1893456000,false,[],true,[])' \ --rpc-url $TEMPO_RPC_URL \ --private-key $ROOT_PRIVATE_KEY # Then send using the access key @@ -301,17 +301,44 @@ cast send 'increment()' \ --tempo.root-account $ROOT_ADDRESS ``` -Post-T3, direct `authorizeKey(...)` calls must use the enhanced TIP-1011 ABI shown above. If you need periodic limits or call scopes, fill those arrays instead of passing `[]`. +#### T2 -> T3 changes + +If you are migrating a pre-T3 integration, this is the key change: direct `authorizeKey(...)` calls must switch from the legacy ABI to the tuple-form TIP-1011 ABI shown above. If you need periodic limits or call scopes, fill those arrays instead of passing `[]`. The flattened seven-argument form is not equivalent. + +If the access key will be used with passkey or WebAuthn signatures, pass `2` for `SignatureType`. `1` is only for raw P256 signatures. Post-T3, access-key transactions also cannot create contracts, so use a root key for deployments or other flows that perform `CREATE`. +### Check your `authorizeKey(...)` migration with `cast` + +Most users can skip this if they use `cast keychain` below or the tuple-form `cast send` example above. This is mainly for hand-encoded calldata. + +```bash +# Legacy pre-T3 selector: +cast sig 'authorizeKey(address,uint8,uint64,bool,(address,uint256)[])' + +# Incorrect flattened post-T3 form. This is not accepted onchain: +cast sig 'authorizeKey(address,uint8,uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[])' + +# Correct post-T3 tuple-form selector: +cast sig 'authorizeKey(address,uint8,(uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[]))' + +# Build the exact post-T3 calldata locally: +cast calldata 'authorizeKey(address,uint8,(uint64,bool,(address,uint256,uint64)[],bool,(address,(bytes4,address[])[])[]))' \ + $ACCESS_KEY_ADDR 0 '(1893456000,false,[],true,[])' +``` + +The expected selectors are `0x54063a55`, `0x203e2736`, and `0x980a6025`. + +After T3 activation, the legacy selector reverts with `LegacyAuthorizeKeySelectorChanged(0x980a6025)`. Use the tuple-form `cast send` example above when you are ready to broadcast. + ### Local Development with Anvil Anvil supports Tempo mode for local testing and forking Tempo networks: ```bash -# Start anvil in Tempo mode -anvil --tempo --hardfork t1 +# Start anvil in Tempo mode with T3 rules +anvil --tempo --hardfork t3 # Fork a live Tempo network for local testing anvil --tempo --fork-url $TEMPO_RPC_URL @@ -367,36 +394,44 @@ Ledger and Trezor wallets are not yet compatible with any `--tempo.*` option. `cast keychain` provides a CLI interface to Tempo's [Account Keychain precompile](/protocol/transactions/AccountKeychain). +Prefer this over hand-encoding `authorizeKey(...)` calldata when you are working from the CLI. + :::info `cast keychain` only works on Tempo networks. ::: +#### T2 -> T3 changes + +Post-T3, `cast keychain` authorization flows should use a future expiry timestamp, `webauthn` for passkey-backed access keys, optional `TOKEN:AMOUNT:PERIOD_SECONDS` limits for recurring budgets, and `--scope` for target, selector, and recipient restrictions. + ```bash -# Authorize a new access key (signature types: secp256k1, p256, webauthn; expiry 0 = never): -cast keychain authorize secp256k1 0 \ +# Post-T3, access keys must be authorized with a future expiry timestamp. +EXPIRY=$(($(date +%s) + 86400)) + +# Authorize a new access key (signature types: secp256k1, p256, webauthn): +cast keychain authorize secp256k1 $EXPIRY \ --rpc-url $TEMPO_RPC_URL \ --private-key $PRIVATE_KEY # Authorize with a spending limit (TOKEN:AMOUNT or TOKEN:AMOUNT:PERIOD_SECONDS): -cast keychain authorize secp256k1 0 \ +cast keychain authorize secp256k1 $EXPIRY \ --limit :1000000 \ --rpc-url $TEMPO_RPC_URL \ --private-key $PRIVATE_KEY # Authorize with call scopes (restrict to specific contracts/functions): -cast keychain authorize secp256k1 0 \ +cast keychain authorize secp256k1 $EXPIRY \ --scope :transfer,approve \ --rpc-url $TEMPO_RPC_URL \ --private-key $PRIVATE_KEY # Authorize with call scope restricted to a specific recipient: -cast keychain authorize secp256k1 0 \ +cast keychain authorize secp256k1 $EXPIRY \ --scope :transfer@ \ --rpc-url $TEMPO_RPC_URL \ --private-key $PRIVATE_KEY # Full example: 24h expiry + spending limit + call scope: -EXPIRY=$(($(date +%s) + 86400)) cast keychain authorize secp256k1 $EXPIRY \ --limit :1000000 \ --scope :transfer \ @@ -425,11 +460,13 @@ cast keychain remove-scope \ --rpc-url $TEMPO_RPC_URL \ --private-key $PRIVATE_KEY -# Query key info (read-only): -cast keychain key-info \ +# Query key provisioning status (read-only): +cast keychain check \ --rpc-url $TEMPO_RPC_URL -# Query remaining spending limit: -cast keychain remaining-limit \ +# Query remaining spending limit via the precompile directly: +cast call 0xAAAAAAAA00000000000000000000000000000000 \ + 'getRemainingLimitWithPeriod(address,address,address)(uint256,uint64)' \ + \ --rpc-url $TEMPO_RPC_URL ```