starknet_accounts/
single_owner.rs

1use crate::{Account, ConnectedAccount, ExecutionEncoder, RawDeclarationV3, RawExecutionV3};
2
3use async_trait::async_trait;
4use starknet_core::types::{contract::ComputeClassHashError, BlockId, BlockTag, Call, Felt};
5use starknet_providers::Provider;
6use starknet_signers::{Signer, SignerInteractivityContext};
7
8/// A generic [`Account`] implementation for controlling account contracts that only have one signer
9/// using ECDSA the STARK curve.
10#[derive(Debug, Clone)]
11pub struct SingleOwnerAccount<P, S>
12where
13    P: Provider + Send,
14    S: Signer + Send,
15{
16    provider: P,
17    signer: S,
18    address: Felt,
19    chain_id: Felt,
20    block_id: BlockId,
21    encoding: ExecutionEncoding,
22}
23
24/// Errors signing an execution/declaration request.
25#[derive(Debug, thiserror::Error)]
26pub enum SignError<S> {
27    /// An error encountered by the signer implementation.
28    #[error(transparent)]
29    Signer(S),
30    /// Failure to compute the class hash of the class being declared.
31    #[error(transparent)]
32    ClassHash(ComputeClassHashError),
33}
34
35/// How calldata for the `__execute__` entrypoint is encoded.
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub enum ExecutionEncoding {
38    /// Encode `__execute__` calldata in Cairo 0 style, where calldata from all calls are concated
39    /// and appended at the end.
40    Legacy,
41    /// Encode `__execute__` calldata in Cairo (1) style, where each call is self-contained.
42    New,
43}
44
45impl<P, S> SingleOwnerAccount<P, S>
46where
47    P: Provider + Sync + Send,
48    S: Signer + Sync + Send,
49{
50    /// Create a new account controlled by a single signer.
51    ///
52    /// ### Parameters
53    ///
54    /// - `provider`: A `Provider` implementation that provides access to the Starknet network.
55    /// - `signer`: A `Signer` implementation that can generate valid signatures for this account.
56    /// - `address`: Account contract address.
57    /// - `chain_id`: Network chain ID.
58    /// - `encoding`: How `__execute__` calldata should be encoded.
59    pub const fn new(
60        provider: P,
61        signer: S,
62        address: Felt,
63        chain_id: Felt,
64        encoding: ExecutionEncoding,
65    ) -> Self {
66        Self {
67            provider,
68            signer,
69            address,
70            chain_id,
71            block_id: BlockId::Tag(BlockTag::PreConfirmed),
72            encoding,
73        }
74    }
75
76    /// Sets a new block ID to run queries against.
77    pub fn set_block_id(&mut self, block_id: BlockId) -> &Self {
78        self.block_id = block_id;
79        self
80    }
81}
82
83#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
84#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
85impl<P, S> Account for SingleOwnerAccount<P, S>
86where
87    P: Provider + Sync + Send,
88    S: Signer + Sync + Send,
89{
90    type SignError = SignError<S::SignError>;
91
92    fn address(&self) -> Felt {
93        self.address
94    }
95
96    fn chain_id(&self) -> Felt {
97        self.chain_id
98    }
99
100    async fn sign_execution_v3(
101        &self,
102        execution: &RawExecutionV3,
103        query_only: bool,
104    ) -> Result<Vec<Felt>, Self::SignError> {
105        let tx_hash = execution.transaction_hash(self.chain_id, self.address, query_only, self);
106        let signature = self
107            .signer
108            .sign_hash(&tx_hash)
109            .await
110            .map_err(SignError::Signer)?;
111
112        Ok(vec![signature.r, signature.s])
113    }
114
115    async fn sign_declaration_v3(
116        &self,
117        declaration: &RawDeclarationV3,
118        query_only: bool,
119    ) -> Result<Vec<Felt>, Self::SignError> {
120        let tx_hash = declaration.transaction_hash(self.chain_id, self.address, query_only);
121        let signature = self
122            .signer
123            .sign_hash(&tx_hash)
124            .await
125            .map_err(SignError::Signer)?;
126
127        Ok(vec![signature.r, signature.s])
128    }
129
130    fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool {
131        self.signer.is_interactive(context)
132    }
133}
134
135impl<P, S> ExecutionEncoder for SingleOwnerAccount<P, S>
136where
137    P: Provider + Send,
138    S: Signer + Send,
139{
140    fn encode_calls(&self, calls: &[Call]) -> Vec<Felt> {
141        let mut execute_calldata: Vec<Felt> = vec![calls.len().into()];
142
143        match self.encoding {
144            ExecutionEncoding::Legacy => {
145                let mut concated_calldata: Vec<Felt> = vec![];
146                for call in calls {
147                    execute_calldata.push(call.to); // to
148                    execute_calldata.push(call.selector); // selector
149                    execute_calldata.push(concated_calldata.len().into()); // data_offset
150                    execute_calldata.push(call.calldata.len().into()); // data_len
151
152                    for item in &call.calldata {
153                        concated_calldata.push(*item);
154                    }
155                }
156
157                execute_calldata.push(concated_calldata.len().into()); // calldata_len
158                execute_calldata.extend_from_slice(&concated_calldata);
159            }
160            ExecutionEncoding::New => {
161                for call in calls {
162                    execute_calldata.push(call.to); // to
163                    execute_calldata.push(call.selector); // selector
164
165                    execute_calldata.push(call.calldata.len().into()); // calldata.len()
166                    execute_calldata.extend_from_slice(&call.calldata);
167                }
168            }
169        }
170
171        execute_calldata
172    }
173}
174
175impl<P, S> ConnectedAccount for SingleOwnerAccount<P, S>
176where
177    P: Provider + Sync + Send,
178    S: Signer + Sync + Send,
179{
180    type Provider = P;
181
182    fn provider(&self) -> &Self::Provider {
183        &self.provider
184    }
185
186    fn block_id(&self) -> BlockId {
187        self.block_id
188    }
189}