1use alloy_primitives::{Address, hex};
2use alloy_rlp::Decodable;
3use eyre::Result;
4use std::path::PathBuf;
5use tempo_primitives::transaction::SignedKeyAuthorization;
6
7use crate::{WalletSigner, utils};
8
9#[derive(Clone, Copy, Default, serde::Deserialize)]
11#[serde(rename_all = "lowercase")]
12enum WalletType {
13 #[default]
14 Local,
15 Passkey,
16}
17
18#[derive(Clone, Copy, Default, serde::Deserialize)]
20#[serde(rename_all = "lowercase")]
21enum KeyType {
22 #[default]
23 Secp256k1,
24 P256,
25 WebAuthn,
26}
27
28#[derive(serde::Deserialize)]
30#[allow(dead_code)]
31struct KeyEntry {
32 #[serde(default)]
33 wallet_type: WalletType,
34 #[serde(default)]
35 wallet_address: Address,
36 #[serde(default)]
37 chain_id: u64,
38 #[serde(default)]
39 key_type: KeyType,
40 #[serde(default)]
41 key_address: Option<Address>,
42 #[serde(default)]
43 key: Option<String>,
44 #[serde(default)]
45 key_authorization: Option<String>,
46 #[serde(default)]
47 expiry: Option<u64>,
48 #[serde(default)]
49 limits: Vec<StoredTokenLimit>,
50}
51
52#[derive(serde::Deserialize)]
54struct StoredTokenLimit {
55 #[allow(dead_code)]
56 currency: Address,
57 #[allow(dead_code)]
58 limit: String,
59}
60
61#[derive(serde::Deserialize)]
63struct KeysFile {
64 #[serde(default)]
65 keys: Vec<KeyEntry>,
66}
67
68#[derive(Debug, Clone)]
74pub struct TempoAccessKeyConfig {
75 pub wallet_address: Address,
77 pub key_address: Address,
79 pub key_authorization: Option<SignedKeyAuthorization>,
84}
85
86pub enum TempoLookup {
88 Direct(WalletSigner),
90 Keychain(WalletSigner, Box<TempoAccessKeyConfig>),
92 NotFound,
94}
95
96fn keys_path() -> Option<PathBuf> {
100 let base = std::env::var_os("TEMPO_HOME")
101 .map(PathBuf::from)
102 .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?;
103 Some(base.join("wallet").join("keys.toml"))
104}
105
106fn decode_key_authorization(hex_str: &str) -> Result<SignedKeyAuthorization> {
108 let bytes = hex::decode(hex_str)?;
109 let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?;
110 Ok(auth)
111}
112
113pub fn lookup_signer(from: Address) -> Result<TempoLookup> {
119 let path = match keys_path() {
120 Some(p) if p.is_file() => p,
121 _ => return Ok(TempoLookup::NotFound),
122 };
123
124 let contents = std::fs::read_to_string(&path)?;
125 let file: KeysFile = toml::from_str(&contents)?;
126
127 for entry in &file.keys {
128 if entry.wallet_address != from {
129 continue;
130 }
131
132 let Some(key) = &entry.key else {
133 continue;
134 };
135
136 let is_direct =
138 entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address);
139
140 let signer = utils::create_private_key_signer(key)?;
141
142 if is_direct {
143 return Ok(TempoLookup::Direct(signer));
144 }
145
146 let key_authorization =
148 entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?;
149
150 let config = TempoAccessKeyConfig {
151 wallet_address: entry.wallet_address,
152 key_address: entry.key_address.unwrap(),
154 key_authorization,
155 };
156 return Ok(TempoLookup::Keychain(signer, Box::new(config)));
157 }
158
159 Ok(TempoLookup::NotFound)
160}