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