foundry_common/tempo/
keystore.rs1use alloy_primitives::{Address, hex};
4use alloy_rlp::Decodable;
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7use tempo_alloy::accounts::{TempoAccountsStore, default_accounts_store_path};
8use tempo_primitives::{
9 SignatureType,
10 transaction::{SignedKeyAuthorization, TokenLimit},
11};
12
13pub const TEMPO_HOME_ENV: &str = "TEMPO_HOME";
15
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
18#[serde(rename_all = "lowercase")]
19pub enum KeyType {
20 #[default]
21 Secp256k1,
22 P256,
23 WebAuthn,
24}
25
26impl From<SignatureType> for KeyType {
27 fn from(value: SignatureType) -> Self {
28 match value {
29 SignatureType::Secp256k1 => Self::Secp256k1,
30 SignatureType::P256 => Self::P256,
31 SignatureType::WebAuthn => Self::WebAuthn,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct StoredTokenLimit {
39 pub currency: Address,
40 pub limit: String,
41 pub period: u64,
42}
43
44impl From<&TokenLimit> for StoredTokenLimit {
45 fn from(value: &TokenLimit) -> Self {
46 Self { currency: value.token, limit: value.limit.to_string(), period: value.period }
47 }
48}
49
50#[derive(Clone, Default)]
52pub struct KeyEntry {
53 pub wallet_address: Address,
54 pub chain_id: u64,
55 pub key_type: KeyType,
56 pub key_address: Address,
57 pub key_authorization: Option<SignedKeyAuthorization>,
58 pub expiry: Option<u64>,
59 pub limits: Vec<StoredTokenLimit>,
60 locally_signable: bool,
61}
62
63impl std::fmt::Debug for KeyEntry {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.debug_struct("KeyEntry")
66 .field("wallet_address", &self.wallet_address)
67 .field("chain_id", &self.chain_id)
68 .field("key_type", &self.key_type)
69 .field("key_address", &self.key_address)
70 .field("has_key", &self.locally_signable)
71 .field("has_key_authorization", &self.key_authorization.is_some())
72 .field("expiry", &self.expiry)
73 .field("limits", &self.limits)
74 .finish()
75 }
76}
77
78impl KeyEntry {
79 pub const fn new(
81 wallet_address: Address,
82 chain_id: u64,
83 key_type: KeyType,
84 key_address: Address,
85 ) -> Self {
86 Self {
87 wallet_address,
88 chain_id,
89 key_type,
90 key_address,
91 key_authorization: None,
92 expiry: None,
93 limits: Vec::new(),
94 locally_signable: false,
95 }
96 }
97
98 #[doc(hidden)]
100 pub fn with_key_authorization(mut self, authorization: SignedKeyAuthorization) -> Self {
101 self.key_authorization = Some(authorization);
102 self
103 }
104
105 pub const fn has_inline_key(&self) -> bool {
107 self.locally_signable
108 }
109
110 #[doc(hidden)]
112 pub const fn with_locally_signable(mut self, locally_signable: bool) -> Self {
113 self.locally_signable = locally_signable;
114 self
115 }
116}
117
118#[derive(Debug, Default)]
120pub struct AccountsStoreView {
121 pub keys: Vec<KeyEntry>,
122}
123
124pub fn tempo_accounts_store_path() -> Option<PathBuf> {
126 default_accounts_store_path().ok()
127}
128
129pub fn read_tempo_accounts_store() -> Option<AccountsStoreView> {
131 let store = match TempoAccountsStore::try_open_default() {
132 Ok(Some(store)) => store,
133 Ok(None) => return None,
134 Err(error) => {
135 tracing::warn!(%error, "failed to open Tempo Accounts store");
136 return None;
137 }
138 };
139 let keys = match store.access_keys() {
140 Ok(keys) => keys,
141 Err(error) => {
142 tracing::warn!(%error, path = %store.path().display(), "failed to read Tempo Accounts access keys");
143 return None;
144 }
145 };
146 Some(AccountsStoreView {
147 keys: keys
148 .into_iter()
149 .map(|key| KeyEntry {
150 wallet_address: key.account(),
151 chain_id: key.chain_id(),
152 key_type: key.key_type().into(),
153 key_address: key.address(),
154 key_authorization: key.key_authorization().cloned(),
155 expiry: key.expiry(),
156 limits: key.limits().iter().map(Into::into).collect(),
157 locally_signable: key.is_locally_signable(),
158 })
159 .collect(),
160 })
161}
162
163pub fn decode_key_authorization<T: Decodable>(encoded: &str) -> eyre::Result<T> {
165 let bytes = hex::decode(encoded)?;
166 let mut bytes = bytes.as_slice();
167 let authorization = T::decode(&mut bytes)?;
168 if !bytes.is_empty() {
169 eyre::bail!("key authorization has trailing bytes");
170 }
171 Ok(authorization)
172}