1use alloy_contract::Result as ContractResult;
2use alloy_primitives::{Address, U256};
3use alloy_provider::{
4 Identity, Provider, ProviderBuilder,
5 fillers::{JoinFill, RecommendedFillers},
6};
7use tempo_contracts::precompiles::{
8 ACCOUNT_KEYCHAIN_ADDRESS,
9 IAccountKeychain::{IAccountKeychainInstance, KeyInfo},
10};
11
12use crate::{
13 TempoFillers, TempoNetwork,
14 fillers::{ExpiringNonceFiller, NonceKeyFiller, Random2DNonceFiller},
15};
16
17#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
19#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
20pub trait TempoProviderExt: Provider<TempoNetwork> {
21 fn account_keychain(&self) -> IAccountKeychainInstance<&Self, TempoNetwork>
23 where
24 Self: Sized,
25 {
26 IAccountKeychainInstance::new(ACCOUNT_KEYCHAIN_ADDRESS, self)
27 }
28
29 async fn get_keychain_key(&self, account: Address, key_id: Address) -> ContractResult<KeyInfo>
31 where
32 Self: Sized,
33 {
34 self.account_keychain().getKey(account, key_id).call().await
35 }
36
37 async fn get_keychain_remaining_limit(
39 &self,
40 account: Address,
41 key_id: Address,
42 token: Address,
43 ) -> ContractResult<U256>
44 where
45 Self: Sized,
46 {
47 self.account_keychain()
48 .getRemainingLimit(account, key_id, token)
49 .call()
50 .await
51 }
52
53 async fn get_keychain_transaction_key(&self) -> ContractResult<Address>
55 where
56 Self: Sized,
57 {
58 self.account_keychain().getTransactionKey().call().await
59 }
60}
61
62#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
63#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
64impl<P> TempoProviderExt for P where P: Provider<TempoNetwork> {}
65
66pub trait TempoProviderBuilderExt {
68 fn with_random_2d_nonces(
72 self,
73 ) -> ProviderBuilder<
74 Identity,
75 JoinFill<Identity, TempoFillers<Random2DNonceFiller>>,
76 TempoNetwork,
77 >;
78
79 fn with_expiring_nonces(
85 self,
86 ) -> ProviderBuilder<
87 Identity,
88 JoinFill<Identity, TempoFillers<ExpiringNonceFiller>>,
89 TempoNetwork,
90 >;
91
92 fn with_nonce_key_filler(
99 self,
100 ) -> ProviderBuilder<Identity, JoinFill<Identity, TempoFillers<NonceKeyFiller>>, TempoNetwork>;
101}
102
103impl TempoProviderBuilderExt
104 for ProviderBuilder<
105 Identity,
106 JoinFill<Identity, <TempoNetwork as RecommendedFillers>::RecommendedFillers>,
107 TempoNetwork,
108 >
109{
110 fn with_random_2d_nonces(
111 self,
112 ) -> ProviderBuilder<
113 Identity,
114 JoinFill<Identity, TempoFillers<Random2DNonceFiller>>,
115 TempoNetwork,
116 > {
117 ProviderBuilder::default().filler(TempoFillers::default())
118 }
119
120 fn with_expiring_nonces(
121 self,
122 ) -> ProviderBuilder<
123 Identity,
124 JoinFill<Identity, TempoFillers<ExpiringNonceFiller>>,
125 TempoNetwork,
126 > {
127 ProviderBuilder::default().filler(TempoFillers::default())
128 }
129
130 fn with_nonce_key_filler(
131 self,
132 ) -> ProviderBuilder<Identity, JoinFill<Identity, TempoFillers<NonceKeyFiller>>, TempoNetwork>
133 {
134 ProviderBuilder::default().filler(TempoFillers::default())
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use alloy::sol_types::SolCall;
141 use alloy_primitives::{Address, Bytes, U256};
142 use alloy_provider::{Identity, ProviderBuilder, fillers::JoinFill, mock::Asserter};
143 use tempo_contracts::precompiles::IAccountKeychain::{
144 KeyInfo, SignatureType, getKeyCall, getRemainingLimitCall, getTransactionKeyCall,
145 };
146
147 use crate::{
148 TempoFillers, TempoNetwork,
149 fillers::{ExpiringNonceFiller, NonceKeyFiller, Random2DNonceFiller},
150 provider::ext::{TempoProviderBuilderExt, TempoProviderExt},
151 };
152
153 fn mock_provider(asserter: Asserter) -> impl alloy_provider::Provider<TempoNetwork> {
154 ProviderBuilder::<_, _, TempoNetwork>::default().connect_mocked_client(asserter)
155 }
156
157 #[test]
158 fn test_with_random_nonces() {
159 let _: ProviderBuilder<_, JoinFill<Identity, TempoFillers<Random2DNonceFiller>>, _> =
160 ProviderBuilder::new_with_network::<TempoNetwork>().with_random_2d_nonces();
161 }
162
163 #[test]
164 fn test_with_expiring_nonces() {
165 let _: ProviderBuilder<_, JoinFill<Identity, TempoFillers<ExpiringNonceFiller>>, _> =
166 ProviderBuilder::new_with_network::<TempoNetwork>().with_expiring_nonces();
167 }
168
169 #[test]
170 fn test_with_nonce_key_filler() {
171 let _: ProviderBuilder<_, JoinFill<Identity, TempoFillers<NonceKeyFiller>>, _> =
172 ProviderBuilder::new_with_network::<TempoNetwork>().with_nonce_key_filler();
173 }
174
175 #[tokio::test]
176 async fn test_get_keychain_key() {
177 let asserter = Asserter::new();
178 let provider = mock_provider(asserter.clone());
179 let account = Address::repeat_byte(0x11);
180 let key_id = Address::repeat_byte(0x22);
181 let expected = KeyInfo {
182 signatureType: SignatureType::P256,
183 keyId: key_id,
184 expiry: 1_234_567_890,
185 enforceLimits: true,
186 isRevoked: false,
187 };
188
189 asserter.push_success(&Bytes::from(getKeyCall::abi_encode_returns(&expected)));
190
191 let actual = provider
192 .get_keychain_key(account, key_id)
193 .await
194 .expect("key info call succeeds");
195
196 assert_eq!(actual, expected);
197 }
198
199 #[tokio::test]
200 async fn test_get_keychain_remaining_limit() {
201 let asserter = Asserter::new();
202 let provider = mock_provider(asserter.clone());
203 let account = Address::repeat_byte(0x11);
204 let key_id = Address::repeat_byte(0x22);
205 let token = Address::repeat_byte(0x33);
206 let expected = U256::from(42_u64);
207
208 asserter.push_success(&Bytes::from(getRemainingLimitCall::abi_encode_returns(
209 &expected,
210 )));
211
212 let actual = provider
213 .get_keychain_remaining_limit(account, key_id, token)
214 .await
215 .expect("remaining limit call succeeds");
216
217 assert_eq!(actual, expected);
218 }
219
220 #[tokio::test]
221 async fn test_get_keychain_transaction_key() {
222 let asserter = Asserter::new();
223 let provider = mock_provider(asserter.clone());
224 let expected = Address::repeat_byte(0x44);
225
226 asserter.push_success(&Bytes::from(getTransactionKeyCall::abi_encode_returns(
227 &expected,
228 )));
229
230 let actual = provider
231 .get_keychain_transaction_key()
232 .await
233 .expect("transaction key call succeeds");
234
235 assert_eq!(actual, expected);
236 }
237
238 #[tokio::test]
239 async fn test_account_keychain_accessor() {
240 let asserter = Asserter::new();
241 let provider = mock_provider(asserter.clone());
242 let account = Address::repeat_byte(0x11);
243 let key_id = Address::repeat_byte(0x22);
244 let expected = KeyInfo {
245 signatureType: SignatureType::Secp256k1,
246 keyId: key_id,
247 expiry: u64::MAX,
248 enforceLimits: false,
249 isRevoked: true,
250 };
251
252 asserter.push_success(&Bytes::from(getKeyCall::abi_encode_returns(&expected)));
253
254 let actual = provider
255 .account_keychain()
256 .getKey(account, key_id)
257 .call()
258 .await
259 .expect("typed instance call succeeds");
260
261 assert_eq!(actual, expected);
262 }
263
264 #[tokio::test]
265 async fn test_get_keychain_key_propagates_errors() {
266 let asserter = Asserter::new();
267 let provider = mock_provider(asserter.clone());
268
269 asserter.push_failure_msg("boom");
270
271 let err = provider
272 .get_keychain_key(Address::repeat_byte(0x11), Address::repeat_byte(0x22))
273 .await
274 .expect_err("errors should propagate");
275
276 assert!(matches!(err, alloy_contract::Error::TransportError(_)));
277 assert!(err.to_string().contains("boom"));
278 }
279}