spark_rust/wallet/handlers/
deposit.rs1use std::collections::HashMap;
2
3use bitcoin::secp256k1::PublicKey;
4use spark_protos::spark::{
5 DepositAddressQueryResult, GenerateDepositAddressRequest, QueryUnusedDepositAddressesRequest,
6 TreeNode,
7};
8use tonic::Request;
9
10use crate::{
11 error::{NetworkError, SparkSdkError, ValidationError},
12 signer::traits::SparkSigner,
13 wallet::{
14 internal_handlers::{
15 implementations::deposit::Address,
16 traits::{deposit::DepositInternalHandlers, mempool::MempoolInternalHandlers},
17 },
18 utils::bitcoin::serialize_bitcoin_transaction,
19 },
20 SparkSdk,
21};
22
23pub struct GenerateDepositAddressSdkResponse {
25 pub deposit_address: bitcoin::Address,
27
28 pub signing_public_key: PublicKey,
30
31 pub verifying_public_key: PublicKey,
33}
34
35impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
36 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
66 pub async fn generate_deposit_address(
67 &self,
68 ) -> Result<GenerateDepositAddressSdkResponse, SparkSdkError> {
69 let identity_public_key = self.get_spark_address()?;
70
71 let signing_public_key = self
72 .signer
73 .get_deposit_signing_key(self.config.spark_config.network.to_bitcoin_network())?;
74
75 let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
76 let mut request = tonic::Request::new(GenerateDepositAddressRequest {
77 signing_public_key: signing_public_key.serialize().to_vec(),
78 identity_public_key: identity_public_key.serialize().to_vec(),
79 network: self.config.spark_config.network.marshal_proto(),
80 });
81 self.add_authorization_header_to_request(&mut request, None);
82
83 let response = spark_client
84 .generate_deposit_address(request)
85 .await
86 .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
87
88 let response = response.into_inner();
89 let address = Address::try_from_address(
90 response
91 .deposit_address
92 .ok_or(SparkSdkError::from(NetworkError::InvalidResponse))?,
93 self.config.spark_config.network,
94 )?;
95
96 self.validate_deposit_address(&address, &signing_public_key)
97 .await?;
98
99 Ok(GenerateDepositAddressSdkResponse {
100 deposit_address: address.address,
101 signing_public_key,
102 verifying_public_key: address.verifying_key,
103 })
104 }
105
106 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
134 pub async fn claim_deposit(&self, txid: String) -> Result<Vec<TreeNode>, SparkSdkError> {
135 let deposit_tx = self.query_mempool_transaction_by_txid(txid).await?;
137
138 let time_start = std::time::Instant::now();
140 let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
141 let identity_public_key = self.get_spark_address();
142 let mut request = Request::new(QueryUnusedDepositAddressesRequest {
143 identity_public_key: identity_public_key?.serialize().to_vec(),
144 });
145 self.add_authorization_header_to_request(&mut request, None);
146 let response_outer = spark_client
147 .query_unused_deposit_addresses(request)
148 .await
149 .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
150 let deposit_addresses = response_outer.into_inner().deposit_addresses;
151
152 let duration = time_start.elapsed();
153 #[cfg(feature = "telemetry")]
154 tracing::debug!(duration = ?duration, "query_unused_deposit_addresses");
155
156 let unused_deposit_addresses: HashMap<String, DepositAddressQueryResult> =
158 deposit_addresses
159 .iter()
160 .map(|deposit_address| {
161 (
162 deposit_address.deposit_address.clone(),
163 deposit_address.clone(),
164 )
165 })
166 .collect();
167
168 let mut vout: u32 = 0;
169 let mut found_deposit_data: Option<DepositAddressQueryResult> = None;
170
171 for (i, tx_output) in deposit_tx.output.iter().enumerate() {
172 let parsed_script = tx_output.script_pubkey.clone();
173 let script_address = bitcoin::Address::from_script(
174 &parsed_script,
175 self.config.spark_config.network.to_bitcoin_network(),
176 )
177 .ok()
178 .map(|addr| addr.to_string())
179 .ok_or_else(|| {
180 SparkSdkError::from(ValidationError::InvalidAddress {
181 address: "Invalid address".to_string(),
182 })
183 })?;
184
185 if unused_deposit_addresses.contains_key(&script_address) {
186 vout = i as u32;
188 found_deposit_data = Some(
189 unused_deposit_addresses
190 .get(&script_address)
191 .unwrap()
192 .clone(),
193 );
194
195 #[cfg(feature = "telemetry")]
196 tracing::debug!(address = script_address, "Found matching deposit address");
197
198 break;
199 }
200 }
201
202 let deposit_data = found_deposit_data.ok_or_else(|| {
204 SparkSdkError::from(ValidationError::InvalidAddress {
205 address: "Deposit address not found".to_string(),
206 })
207 })?;
208
209 let nodes = self
210 .finalize_deposit(
211 deposit_data.user_signing_public_key.clone(),
212 deposit_data.verifying_public_key.clone(),
213 deposit_tx.clone(),
214 vout,
215 )
216 .await?;
217
218 self.claim_transfers().await?;
219
220 Ok(nodes)
221 }
222
223 pub async fn finalize_deposit(
241 &self,
242 signing_pubkey: Vec<u8>,
243 verifying_pubkey: Vec<u8>,
244 deposit_tx: bitcoin::Transaction,
245 vout: u32,
246 ) -> Result<Vec<TreeNode>, SparkSdkError> {
247 let time_start = std::time::Instant::now();
248 let created_root = self
249 .create_tree_root(
250 signing_pubkey,
251 verifying_pubkey,
252 serialize_bitcoin_transaction(&deposit_tx)?,
253 vout,
254 )
255 .await?;
256 let duration = time_start.elapsed();
257
258 #[cfg(feature = "telemetry")]
259 tracing::debug!(duration = ?duration, "finalize_deposit");
260
261 let time_start = std::time::Instant::now();
263 self.transfer_deposits_to_self(
264 created_root
265 .nodes
266 .iter()
267 .map(|node| node.id.clone())
268 .collect(),
269 )
270 .await?;
271
272 let duration = time_start.elapsed();
273 #[cfg(feature = "telemetry")]
274 tracing::debug!(duration = ?duration, "transfer_deposits_to_self");
275
276 Ok(created_root.nodes)
277 }
278
279 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
280 pub async fn query_unused_deposit_addresses(
281 &self,
282 ) -> Result<Vec<DepositAddressQueryResult>, SparkSdkError> {
283 let identity_public_key = self.get_spark_address()?;
284 let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
285 let mut request = Request::new(QueryUnusedDepositAddressesRequest {
286 identity_public_key: identity_public_key.serialize().to_vec(),
287 });
288 self.add_authorization_header_to_request(&mut request, None);
289 let response = spark_client
290 .query_unused_deposit_addresses(request)
291 .await
292 .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
293
294 let response = response.into_inner();
295 Ok(response.deposit_addresses)
296 }
297
298 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
299 async fn transfer_deposits_to_self(&self, leaf_ids: Vec<String>) -> Result<(), SparkSdkError> {
300 self.transfer_leaf_ids(leaf_ids, &self.get_spark_address()?)
301 .await?;
302
303 Ok(())
304 }
305}