Skip to main content

foundry_common/tempo/
keystore.rs

1//! Tempo wallet keystore types and discovery helpers.
2//!
3//! Shared types for reading keys from the Tempo CLI wallet keystore
4//! (`$TEMPO_HOME/wallet/keys.toml`, defaulting to `~/.tempo/wallet/keys.toml`).
5
6use alloy_primitives::{Address, hex};
7use alloy_rlp::Decodable;
8use serde::Deserialize;
9use std::path::PathBuf;
10
11/// Environment variable for an ephemeral Tempo private key.
12pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY";
13
14/// Environment variable to override the Tempo home directory.
15pub const TEMPO_HOME_ENV: &str = "TEMPO_HOME";
16
17/// Default Tempo home directory relative to the user's home.
18pub const DEFAULT_TEMPO_HOME: &str = ".tempo";
19
20/// Relative path from Tempo home to the wallet keys file.
21pub const WALLET_KEYS_PATH: &str = "wallet/keys.toml";
22
23/// Wallet type matching `tempo-common`'s `WalletType` enum.
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum WalletType {
27    #[default]
28    Local,
29    Passkey,
30}
31
32/// Cryptographic key type.
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum KeyType {
36    #[default]
37    Secp256k1,
38    P256,
39    WebAuthn,
40}
41
42/// Per-token spending limit stored in `keys.toml`.
43#[derive(Debug, Default, Deserialize)]
44pub struct StoredTokenLimit {
45    pub currency: Address,
46    pub limit: String,
47}
48
49/// A single key entry in `keys.toml`.
50///
51/// Mirrors the fields from `tempo-common::keys::model::KeyEntry`.
52/// Unknown fields are ignored by serde.
53#[derive(Debug, Default, Deserialize)]
54pub struct KeyEntry {
55    /// Wallet type: "local" or "passkey".
56    #[serde(default)]
57    pub wallet_type: WalletType,
58    /// Smart wallet address (the on-chain account).
59    #[serde(default)]
60    pub wallet_address: Address,
61    /// Chain ID.
62    #[serde(default)]
63    pub chain_id: u64,
64    /// Cryptographic key type.
65    #[serde(default)]
66    pub key_type: KeyType,
67    /// Key address (the EOA derived from the private key).
68    #[serde(default)]
69    pub key_address: Option<Address>,
70    /// Key private key, stored inline in keys.toml.
71    #[serde(default)]
72    pub key: Option<String>,
73    /// RLP-encoded signed key authorization (hex string).
74    /// Used in keychain mode to atomically provision the access key on-chain.
75    #[serde(default)]
76    pub key_authorization: Option<String>,
77    /// Expiry timestamp.
78    #[serde(default)]
79    pub expiry: Option<u64>,
80    /// Per-token spending limits.
81    #[serde(default)]
82    pub limits: Vec<StoredTokenLimit>,
83}
84
85impl KeyEntry {
86    /// Whether this entry has a non-empty inline private key.
87    pub fn has_inline_key(&self) -> bool {
88        self.key.as_ref().is_some_and(|k| !k.trim().is_empty())
89    }
90}
91
92/// The top-level structure of `keys.toml`.
93#[derive(Debug, Default, Deserialize)]
94pub struct KeysFile {
95    #[serde(default)]
96    pub keys: Vec<KeyEntry>,
97}
98
99/// Resolve the Tempo home directory.
100///
101/// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`.
102pub fn tempo_home() -> Option<PathBuf> {
103    if let Ok(home) = std::env::var(TEMPO_HOME_ENV) {
104        return Some(PathBuf::from(home));
105    }
106    dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME))
107}
108
109/// Returns the path to the Tempo wallet keys file.
110pub fn tempo_keys_path() -> Option<PathBuf> {
111    tempo_home().map(|home| home.join(WALLET_KEYS_PATH))
112}
113
114/// Read and parse the Tempo wallet keys file.
115///
116/// Returns `None` if the file doesn't exist or can't be read/parsed.
117/// Errors are logged as warnings.
118pub fn read_tempo_keys_file() -> Option<KeysFile> {
119    let keys_path = tempo_keys_path()?;
120    if !keys_path.exists() {
121        tracing::trace!(?keys_path, "tempo keys file not found");
122        return None;
123    }
124
125    let contents = match std::fs::read_to_string(&keys_path) {
126        Ok(c) => c,
127        Err(e) => {
128            tracing::warn!(?keys_path, %e, "failed to read tempo keys file");
129            return None;
130        }
131    };
132
133    match toml::from_str(&contents) {
134        Ok(f) => Some(f),
135        Err(e) => {
136            tracing::warn!(?keys_path, %e, "failed to parse tempo keys file");
137            None
138        }
139    }
140}
141
142/// Decodes a hex-encoded, RLP-encoded key authorization.
143///
144/// The input should be a hex string (with or without 0x prefix) containing
145/// RLP-encoded `SignedKeyAuthorization` data.
146pub fn decode_key_authorization<T: Decodable>(hex_str: &str) -> eyre::Result<T> {
147    let bytes = hex::decode(hex_str)?;
148    let auth = T::decode(&mut bytes.as_slice())?;
149    Ok(auth)
150}