spark_rust/wallet/handlers/
swap.rs1use crate::constants::spark::DEFAULT_TRANSFER_EXPIRY;
2use crate::error::{IoError, NetworkError, SparkSdkError};
3use crate::signer::traits::derivation_path::SparkKeyType;
4use crate::signer::traits::SparkSigner;
5use crate::wallet::internal_handlers::traits::ssp::SspInternalHandlers;
6use crate::wallet::internal_handlers::traits::ssp::SwapLeaf;
7use crate::wallet::internal_handlers::traits::transfer::LeafKeyTweak;
8use crate::wallet::internal_handlers::traits::transfer::TransferInternalHandlers;
9use crate::wallet::leaf_manager::SparkNodeStatus;
10use crate::wallet::utils::bitcoin::{bitcoin_tx_from_bytes, parse_public_key, sighash_from_tx};
11use crate::wallet::utils::bitcoin::{
12 compute_taproot_key_no_script_from_internal_key, parse_secret_key,
13};
14use crate::with_handler_lock;
15use crate::SparkSdk;
16use bitcoin::key::Secp256k1;
17use spark_cryptography::adaptor_signature::apply_adaptor_to_signature;
18use spark_cryptography::adaptor_signature::generate_adaptor_from_signature;
19use spark_cryptography::adaptor_signature::generate_signature_from_existing_adaptor;
20use spark_protos::spark::query_nodes_request::Source;
21use spark_protos::spark::QueryNodesRequest;
22use spark_protos::spark::TreeNodeIds;
23
24impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
25 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
85 pub async fn request_leaves_swap(&self, target_amount: u64) -> Result<String, SparkSdkError> {
86 with_handler_lock!(self, async {
87 self.request_leaves_swap_internal(target_amount).await
88 })
89 .await
90 }
91
92 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
93 pub(crate) async fn request_leaves_swap_internal(
94 &self,
95 target_amount: u64,
96 ) -> Result<String, SparkSdkError> {
97 let available_leaves = self
100 .leaf_manager
101 .lock_available_bitcoin_leaves(SparkNodeStatus::Swap);
102
103 let mut leaf_key_tweaks = Vec::with_capacity(available_leaves.leaves.len());
105
106 for leaf in available_leaves.leaves.iter() {
107 let tree_leaf = leaf.get_tree_node()?;
108
109 let old_signing_private_key = self.signer.expose_leaf_secret_key_for_transfer(
110 leaf.get_id().clone(),
111 SparkKeyType::BaseSigning,
112 0,
113 self.config.spark_config.network.to_bitcoin_network(),
114 )?;
115
116 let new_signing_public_key = self.signer.new_ephemeral_keypair()?;
118
119 leaf_key_tweaks.push(LeafKeyTweak {
120 leaf: tree_leaf,
121 old_signing_private_key,
122 new_signing_public_key,
123 });
124 }
125
126 let expiry_time = chrono::Utc::now().timestamp() as u64 + DEFAULT_TRANSFER_EXPIRY;
127
128 let (transfer, refund_signature_map) = self
129 .send_transfer_sign_refunds(
130 &leaf_key_tweaks,
131 &self.config.spark_config.ssp_identity_public_key,
132 expiry_time,
133 )
134 .await?;
135
136 let leaf = transfer.leaves[0].leaf.as_ref().unwrap();
137 let leaf_refund_signature = refund_signature_map[&leaf.id].clone();
138
139 let adaptor_signature =
140 generate_adaptor_from_signature(leaf_refund_signature.as_slice().try_into().unwrap())
141 .unwrap();
142
143 let mut user_leaves = Vec::new();
144 user_leaves.push(SwapLeaf {
145 leaf_id: transfer.leaves[0].leaf.as_ref().unwrap().id.clone(),
146 raw_unsigned_refund_transaction: hex::encode(
147 &transfer.leaves[0].intermediate_refund_tx,
148 ),
149 adaptor_added_signature: hex::encode(adaptor_signature.signature),
150 });
151
152 for leaf in transfer.leaves.iter().skip(1) {
153 let leaf_id = leaf.leaf.as_ref().unwrap().id.clone();
154 let leaf_refund_signature = refund_signature_map[&leaf_id].clone();
155
156 let signature = generate_signature_from_existing_adaptor(
157 &leaf_refund_signature,
158 adaptor_signature.adaptor_private_key.as_slice(),
159 )
160 .unwrap();
161
162 user_leaves.push(SwapLeaf {
163 leaf_id,
164 raw_unsigned_refund_transaction: hex::encode(&leaf.intermediate_refund_tx),
165 adaptor_added_signature: hex::encode(signature),
166 });
167 }
168
169 let total_amount = leaf_key_tweaks
171 .iter()
172 .map(|leaf| leaf.leaf.value)
173 .sum::<u64>();
174
175 let secp = Secp256k1::new();
176 let adaptor_private_key =
177 parse_secret_key(&adaptor_signature.adaptor_private_key.to_vec())?;
178 let adaptor_public_key = adaptor_private_key.public_key(&secp);
179
180 let (request_id, leaves) = self
181 .request_swap_leaves_with_ssp(
182 hex::encode(adaptor_public_key.serialize()),
183 total_amount,
184 target_amount,
185 0,
186 user_leaves,
187 )
188 .await?;
189
190 let network = self.config.spark_config.network;
191
192 for leaf in &leaves {
193 let leaf_id = leaf.leaf_id.clone();
194 let request_data = QueryNodesRequest {
195 source: Some(Source::NodeIds(TreeNodeIds {
196 node_ids: vec![leaf_id.clone()],
197 })),
198 include_parents: Default::default(),
199 network: network.marshal_proto(),
200 };
201
202 let response = self
203 .config
204 .spark_config
205 .call_with_retry(
206 request_data,
207 |mut client, req| Box::pin(async move { client.query_nodes(req).await }),
208 None,
209 )
210 .await?;
211
212 let node = response
213 .nodes
214 .get(&leaf_id)
215 .ok_or(SparkSdkError::from(NetworkError::InvalidResponse))?;
216
217 #[cfg(feature = "telemetry")]
218 tracing::trace!(
219 leaf_id = leaf_id.clone(),
220 "Leaf balance returned for SSP swap: {:?}",
221 node.value
222 );
223
224 let node_tx = bitcoin_tx_from_bytes(&node.node_tx)?;
225 let refund_tx_bytes = hex::decode(&leaf.raw_unsigned_refund_transaction)
226 .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
227 let refund_tx = bitcoin_tx_from_bytes(&refund_tx_bytes)?;
228
229 let sighash = sighash_from_tx(&refund_tx, 0, &node_tx.output[0])?;
230
231 let verifying_public_key = parse_public_key(&node.verifying_public_key)?;
233
234 let taproot_key = compute_taproot_key_no_script_from_internal_key(
235 &verifying_public_key.x_only_public_key().0.serialize(),
236 )?;
237
238 let adaptor_signature_bytes = hex::decode(&leaf.adaptor_added_signature)
239 .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
240
241 let _ = apply_adaptor_to_signature(
242 &taproot_key,
243 &sighash,
244 &adaptor_signature_bytes,
245 &adaptor_private_key.secret_bytes(),
246 )
247 .unwrap();
248 }
249
250 let transfer_id = transfer.id.clone();
251 self.send_transfer_tweak_key(transfer.clone(), &leaf_key_tweaks, &refund_signature_map)
252 .await?;
253
254 let _completion = self
256 .complete_leaves_swap_with_ssp(
257 hex::encode(adaptor_private_key.secret_bytes()),
258 transfer_id,
259 request_id.clone(),
260 )
261 .await?;
262
263 self.claim_transfers_internal().await?;
264
265 let leaf_ids = available_leaves
267 .leaves
268 .iter()
269 .map(|leaf| leaf.get_id().clone())
270 .collect();
271 self.leaf_manager
272 .unlock_leaves(available_leaves.unlocking_id.unwrap(), &leaf_ids, true)?;
273
274 let leaf_id = leaves[0].leaf_id.clone();
276
277 Ok(leaf_id)
278 }
279}