starknet_accounts/account/
mod.rs

1use async_trait::async_trait;
2use auto_impl::auto_impl;
3use starknet_core::types::{
4    contract::ComputeClassHashError, BlockId, BlockTag, Call, Felt, FlattenedSierraClass,
5};
6use starknet_providers::{Provider, ProviderError};
7use starknet_signers::SignerInteractivityContext;
8use std::{error::Error, sync::Arc};
9
10mod declaration;
11mod execution;
12
13/// The standard Starknet account contract interface. It makes no assumption about the underlying
14/// signer or provider. Account implementations that come with an active connection to the network
15/// should also implement [`ConnectedAccount`] for useful functionalities like estimating fees and
16/// sending transactions.
17#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
18#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
19pub trait Account: ExecutionEncoder + Sized {
20    /// Possible errors for signing transactions.
21    type SignError: Error + Send + Sync;
22
23    /// Gets the account contract's address.
24    fn address(&self) -> Felt;
25
26    /// Gets the chain ID of the network where the account contract was deployed.
27    fn chain_id(&self) -> Felt;
28
29    /// Signs an execution request to authorize an `INVOKE` v3 transaction that pays transaction
30    /// fees in `STRK`.
31    ///
32    /// If `query_only` is `true`, the commitment must be constructed in a way that a real state-
33    /// changing transaction cannot be authenticated. This is to prevent replay attacks.
34    async fn sign_execution_v3(
35        &self,
36        execution: &RawExecutionV3,
37        query_only: bool,
38    ) -> Result<Vec<Felt>, Self::SignError>;
39
40    /// Signs an execution request to authorize an `DECLARE` v3 transaction that pays transaction
41    /// fees in `STRK` for declaring Cairo 1 classes.
42    ///
43    /// If `query_only` is `true`, the commitment must be constructed in a way that a real state-
44    /// changing transaction cannot be authenticated. This is to prevent replay attacks.
45    async fn sign_declaration_v3(
46        &self,
47        declaration: &RawDeclarationV3,
48        query_only: bool,
49    ) -> Result<Vec<Felt>, Self::SignError>;
50
51    /// Whether the underlying signer implementation is interactive, such as a hardware wallet.
52    /// Implementations should return `true` if the signing operation is very expensive, even if not
53    /// strictly "interactive" as in requiring human input.
54    ///
55    /// This affects how an account makes decision on whether to request a real signature for
56    /// estimation/simulation purposes.
57    fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool;
58
59    /// Generates an instance of [`ExecutionV3`] for sending `INVOKE` v3 transactions. Pays
60    /// transaction fees in `STRK`.
61    fn execute_v3(&self, calls: Vec<Call>) -> ExecutionV3<'_, Self> {
62        ExecutionV3::new(calls, self)
63    }
64
65    /// Generates an instance of [`ExecutionV3`] for sending `INVOKE` v3 transactions. Pays
66    /// transaction fees in `STRK`.
67    #[deprecated = "transaction version used might change unexpectedly; use `execute_v3` instead"]
68    fn execute(&self, calls: Vec<Call>) -> ExecutionV3<'_, Self> {
69        self.execute_v3(calls)
70    }
71
72    /// Generates an instance of [`DeclarationV3`] for sending `DECLARE` v3 transactions. Pays
73    /// transaction fees in `STRK`.
74    ///
75    /// To declare a Sierra (Cairo 1) class, a `compiled_class_hash` must be provided. This can be
76    /// obtained by compiling the Sierra class to obtain a CASM class, and then hashing it.
77    ///
78    /// The compilation of Sierra to CASM can either be done interactively via the
79    /// `starknet-sierra-compile` command from the Cairo toolchain, or programmatically through the
80    /// Cairo crates.
81    ///
82    /// Hashing the resulting CASM class is supported in the `starknet-core` crate. It can also be
83    /// done interactively via Starkli with its `starkli class-hash` command.
84    ///
85    /// This method is only used for declaring Sierra (Cairo 1) classes. Declaring legacy (Cairo 0)
86    /// classes is no longer supported.
87    fn declare_v3(
88        &self,
89        contract_class: Arc<FlattenedSierraClass>,
90        compiled_class_hash: Felt,
91    ) -> DeclarationV3<'_, Self> {
92        DeclarationV3::new(contract_class, compiled_class_hash, self)
93    }
94
95    /// Generates an instance of [`DeclarationV3`] for sending `DECLARE` v3 transactions. Pays
96    /// transaction fees in `STRK`.
97    ///
98    /// To declare a Sierra (Cairo 1) class, a `compiled_class_hash` must be provided. This can be
99    /// obtained by compiling the Sierra class to obtain a CASM class, and then hashing it.
100    ///
101    /// The compilation of Sierra to CASM can either be done interactively via the
102    /// `starknet-sierra-compile` command from the Cairo toolchain, or programmatically through the
103    /// Cairo crates.
104    ///
105    /// Hashing the resulting CASM class is supported in the `starknet-core` crate. It can also be
106    /// done interactively via Starkli with its `starkli class-hash` command.
107    ///
108    /// This method is only used for declaring Sierra (Cairo 1) classes. Declaring legacy (Cairo 0)
109    /// classes is no longer supported.
110    #[deprecated = "transaction version used might change unexpectedly; use `declare_v3` instead"]
111    fn declare(
112        &self,
113        contract_class: Arc<FlattenedSierraClass>,
114        compiled_class_hash: Felt,
115    ) -> DeclarationV3<'_, Self> {
116        self.declare_v3(contract_class, compiled_class_hash)
117    }
118}
119
120/// An abstraction over different ways to encode [`Vec<Call>`] into [`Vec<Felt>`].
121///
122/// Standard Cairo 0 and Cairo 1 account contracts encodes calls differently. Custom account
123/// contract implementations might also impose arbitrary encoding rules.
124#[auto_impl(&, Box, Arc)]
125pub trait ExecutionEncoder {
126    /// Encodes the list of [`Call`] into a list of [`Felt`] to be used as calldata to the account's
127    /// `__execute__` entrypoint.
128    fn encode_calls(&self, calls: &[Call]) -> Vec<Felt>;
129}
130
131/// An [`Account`] implementation that also comes with a [`Provider`]. Functionalities that require
132/// a connection to the sequencer or node are offloaded to this trait to keep the base [`Account`]
133/// clean and flexible.
134#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
135#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
136pub trait ConnectedAccount: Account {
137    /// The [`Provider`] type attached to this account.
138    type Provider: Provider + Sync;
139
140    /// Gets a reference to the attached [`Provider`] instance.
141    fn provider(&self) -> &Self::Provider;
142
143    /// Gets block ID to use when checking nonce and estimating fees.
144    fn block_id(&self) -> BlockId {
145        BlockId::Tag(BlockTag::Latest)
146    }
147
148    /// Gets the next available nonce to be used.
149    async fn get_nonce(&self) -> Result<Felt, ProviderError> {
150        self.provider()
151            .get_nonce(self.block_id(), self.address())
152            .await
153    }
154}
155
156/// Abstraction over `INVOKE` transactions from accounts for invoking contracts. This struct uses
157/// v3 `INVOKE` transactions under the hood, and hence pays transaction fees in STRK.
158///
159/// This is an intermediate type allowing users to optionally specify `nonce` and transaction fee
160/// options.
161#[must_use]
162#[derive(Debug)]
163pub struct ExecutionV3<'a, A> {
164    account: &'a A,
165    calls: Vec<Call>,
166    nonce: Option<Felt>,
167    l1_gas: Option<u64>,
168    l1_gas_price: Option<u128>,
169    l2_gas: Option<u64>,
170    l2_gas_price: Option<u128>,
171    l1_data_gas: Option<u64>,
172    l1_data_gas_price: Option<u128>,
173    gas_estimate_multiplier: f64,
174    gas_price_estimate_multiplier: f64,
175    tip: Option<u64>,
176}
177
178/// Abstraction over `DECLARE` transactions from accounts for invoking contracts. This struct uses
179/// v3 `DECLARE` transactions under the hood, and hence pays transaction fees in STRK.
180///
181/// This is an intermediate type allowing users to optionally specify `nonce` and transaction fee
182/// options.
183#[must_use]
184#[derive(Debug)]
185pub struct DeclarationV3<'a, A> {
186    account: &'a A,
187    contract_class: Arc<FlattenedSierraClass>,
188    compiled_class_hash: Felt,
189    nonce: Option<Felt>,
190    l1_gas: Option<u64>,
191    l1_gas_price: Option<u128>,
192    l2_gas: Option<u64>,
193    l2_gas_price: Option<u128>,
194    l1_data_gas: Option<u64>,
195    l1_data_gas_price: Option<u128>,
196    gas_estimate_multiplier: f64,
197    gas_price_estimate_multiplier: f64,
198    tip: Option<u64>,
199}
200
201/// [`ExecutionV3`] but with `nonce` and other transaction fee options already determined.
202#[derive(Debug)]
203pub struct RawExecutionV3 {
204    calls: Vec<Call>,
205    nonce: Felt,
206    l1_gas: u64,
207    l1_gas_price: u128,
208    l2_gas: u64,
209    l2_gas_price: u128,
210    l1_data_gas: u64,
211    l1_data_gas_price: u128,
212    tip: u64,
213}
214
215/// [`DeclarationV3`] but with `nonce` and other transaction fee options already determined.
216#[derive(Debug)]
217pub struct RawDeclarationV3 {
218    contract_class: Arc<FlattenedSierraClass>,
219    compiled_class_hash: Felt,
220    nonce: Felt,
221    l1_gas: u64,
222    l1_gas_price: u128,
223    l2_gas: u64,
224    l2_gas_price: u128,
225    l1_data_gas: u64,
226    l1_data_gas_price: u128,
227    tip: u64,
228}
229
230/// [`RawExecutionV3`] but with an account associated.
231#[derive(Debug)]
232pub struct PreparedExecutionV3<'a, A> {
233    account: &'a A,
234    inner: RawExecutionV3,
235}
236
237/// [`RawDeclarationV3`] but with an account associated.
238#[derive(Debug)]
239pub struct PreparedDeclarationV3<'a, A> {
240    account: &'a A,
241    inner: RawDeclarationV3,
242}
243
244/// Errors using Starknet accounts.
245#[derive(Debug, thiserror::Error)]
246pub enum AccountError<S> {
247    /// An error is encountered when signing a request.
248    #[error(transparent)]
249    Signing(S),
250    /// An error is encountered with communicating with the network.
251    #[error(transparent)]
252    Provider(ProviderError),
253    /// Unable to calculate the class hash for declaration.
254    #[error(transparent)]
255    ClassHashCalculation(ComputeClassHashError),
256    /// Transaction fee calculation overflow.
257    #[error("fee calculation overflow")]
258    FeeOutOfRange,
259}
260
261#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
262#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
263impl<A> Account for &A
264where
265    A: Account + Sync,
266{
267    type SignError = A::SignError;
268
269    fn address(&self) -> Felt {
270        (*self).address()
271    }
272
273    fn chain_id(&self) -> Felt {
274        (*self).chain_id()
275    }
276
277    async fn sign_execution_v3(
278        &self,
279        execution: &RawExecutionV3,
280        query_only: bool,
281    ) -> Result<Vec<Felt>, Self::SignError> {
282        (*self).sign_execution_v3(execution, query_only).await
283    }
284
285    async fn sign_declaration_v3(
286        &self,
287        declaration: &RawDeclarationV3,
288        query_only: bool,
289    ) -> Result<Vec<Felt>, Self::SignError> {
290        (*self).sign_declaration_v3(declaration, query_only).await
291    }
292
293    fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool {
294        (*self).is_signer_interactive(context)
295    }
296}
297
298#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
299#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
300impl<A> Account for Box<A>
301where
302    A: Account + Sync + Send,
303{
304    type SignError = A::SignError;
305
306    fn address(&self) -> Felt {
307        self.as_ref().address()
308    }
309
310    fn chain_id(&self) -> Felt {
311        self.as_ref().chain_id()
312    }
313
314    async fn sign_execution_v3(
315        &self,
316        execution: &RawExecutionV3,
317        query_only: bool,
318    ) -> Result<Vec<Felt>, Self::SignError> {
319        self.as_ref().sign_execution_v3(execution, query_only).await
320    }
321
322    async fn sign_declaration_v3(
323        &self,
324        declaration: &RawDeclarationV3,
325        query_only: bool,
326    ) -> Result<Vec<Felt>, Self::SignError> {
327        self.as_ref()
328            .sign_declaration_v3(declaration, query_only)
329            .await
330    }
331
332    fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool {
333        self.as_ref().is_signer_interactive(context)
334    }
335}
336
337#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
338#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
339impl<A> Account for Arc<A>
340where
341    A: Account + Sync + Send,
342{
343    type SignError = A::SignError;
344
345    fn address(&self) -> Felt {
346        self.as_ref().address()
347    }
348
349    fn chain_id(&self) -> Felt {
350        self.as_ref().chain_id()
351    }
352
353    async fn sign_execution_v3(
354        &self,
355        execution: &RawExecutionV3,
356        query_only: bool,
357    ) -> Result<Vec<Felt>, Self::SignError> {
358        self.as_ref().sign_execution_v3(execution, query_only).await
359    }
360
361    async fn sign_declaration_v3(
362        &self,
363        declaration: &RawDeclarationV3,
364        query_only: bool,
365    ) -> Result<Vec<Felt>, Self::SignError> {
366        self.as_ref()
367            .sign_declaration_v3(declaration, query_only)
368            .await
369    }
370
371    fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool {
372        self.as_ref().is_signer_interactive(context)
373    }
374}
375
376#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
377#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
378impl<A> ConnectedAccount for &A
379where
380    A: ConnectedAccount + Sync,
381{
382    type Provider = A::Provider;
383
384    fn provider(&self) -> &Self::Provider {
385        (*self).provider()
386    }
387
388    fn block_id(&self) -> BlockId {
389        (*self).block_id()
390    }
391
392    async fn get_nonce(&self) -> Result<Felt, ProviderError> {
393        (*self).get_nonce().await
394    }
395}
396
397#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
398#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
399impl<A> ConnectedAccount for Box<A>
400where
401    A: ConnectedAccount + Sync + Send,
402{
403    type Provider = A::Provider;
404
405    fn provider(&self) -> &Self::Provider {
406        self.as_ref().provider()
407    }
408
409    fn block_id(&self) -> BlockId {
410        self.as_ref().block_id()
411    }
412
413    async fn get_nonce(&self) -> Result<Felt, ProviderError> {
414        self.as_ref().get_nonce().await
415    }
416}
417
418#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
419#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
420impl<A> ConnectedAccount for Arc<A>
421where
422    A: ConnectedAccount + Sync + Send,
423{
424    type Provider = A::Provider;
425
426    fn provider(&self) -> &Self::Provider {
427        self.as_ref().provider()
428    }
429
430    fn block_id(&self) -> BlockId {
431        self.as_ref().block_id()
432    }
433
434    async fn get_nonce(&self) -> Result<Felt, ProviderError> {
435        self.as_ref().get_nonce().await
436    }
437}