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::{
11 bitcoin_tx_from_bytes, parse_public_key, serialize_bitcoin_transaction, sighash_from_tx,
12};
13use crate::wallet::utils::bitcoin::{
14 compute_taproot_key_no_script_from_internal_key, parse_secret_key,
15};
16use crate::with_handler_lock;
17use crate::SparkSdk;
18use bitcoin::key::Secp256k1;
19use spark_cryptography::adaptor_signature::apply_adaptor_to_signature;
20use spark_cryptography::adaptor_signature::generate_adaptor_from_signature;
21use spark_cryptography::adaptor_signature::generate_signature_from_existing_adaptor;
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 old_signing_private_key = self.signer.expose_leaf_secret_key_for_transfer(
112 leaf.get_id().clone(),
113 SparkKeyType::BaseSigning,
114 0,
115 self.config.spark_config.network.to_bitcoin_network(),
116 )?;
117
118 let new_signing_public_key = self.signer.new_ephemeral_keypair()?;
120
121 leaf_key_tweaks.push(LeafKeyTweak {
122 leaf: tree_leaf,
123 old_signing_private_key,
124 new_signing_public_key,
125 });
126 }
127
128 let expiry_time = chrono::Utc::now().timestamp() as u64 + DEFAULT_TRANSFER_EXPIRY;
129
130 let (transfer, refund_signature_map) = self
131 .send_transfer_sign_refunds(
132 &leaf_key_tweaks,
133 &self.config.spark_config.ssp_identity_public_key,
134 expiry_time,
135 )
136 .await?;
137
138 let leaf = transfer.leaves[0].leaf.as_ref().unwrap();
139 let leaf_refund_signature = refund_signature_map[&leaf.id.to_string()].clone();
140
141 let adaptor_signature =
142 generate_adaptor_from_signature(leaf_refund_signature.as_slice().try_into().unwrap())
143 .unwrap();
144
145 let mut user_leaves = Vec::new();
146 user_leaves.push(SwapLeaf {
147 leaf_id: transfer.leaves[0].leaf.as_ref().unwrap().id.to_string(),
148 raw_unsigned_refund_transaction: hex::encode(serialize_bitcoin_transaction(
149 &transfer.leaves[0].intermediate_refund_tx,
150 )?),
151 adaptor_added_signature: hex::encode(adaptor_signature.signature),
152 });
153
154 for leaf in transfer.leaves.iter().skip(1) {
155 let leaf_id = leaf.leaf.as_ref().unwrap().id;
156 let leaf_refund_signature = refund_signature_map[&leaf_id.to_string()].clone();
157
158 let signature = generate_signature_from_existing_adaptor(
159 &leaf_refund_signature,
160 adaptor_signature.adaptor_private_key.as_slice(),
161 )
162 .unwrap();
163
164 user_leaves.push(SwapLeaf {
165 leaf_id: leaf_id.to_string(),
166 raw_unsigned_refund_transaction: hex::encode(serialize_bitcoin_transaction(
167 &leaf.intermediate_refund_tx,
168 )?),
169 adaptor_added_signature: hex::encode(signature),
170 });
171 }
172
173 let total_amount = leaf_key_tweaks
175 .iter()
176 .map(|leaf| leaf.leaf.value)
177 .sum::<u64>();
178
179 let secp = Secp256k1::new();
180 let adaptor_private_key =
181 parse_secret_key(&adaptor_signature.adaptor_private_key.to_vec())?;
182 let adaptor_public_key = adaptor_private_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 network: network.marshal_proto(),
204 };
205
206 let response = self
207 .config
208 .spark_config
209 .call_with_retry(
210 request_data,
211 |mut client, req| Box::pin(async move { client.query_nodes(req).await }),
212 None,
213 )
214 .await?;
215
216 let node = response
217 .nodes
218 .get(&leaf_id)
219 .ok_or(SparkSdkError::from(NetworkError::InvalidResponse))?;
220
221 #[cfg(feature = "telemetry")]
222 tracing::trace!(
223 leaf_id = leaf_id.clone(),
224 "Leaf balance returned for SSP swap: {:?}",
225 node.value
226 );
227
228 let node_tx = bitcoin_tx_from_bytes(&node.node_tx)?;
229 let refund_tx_bytes = hex::decode(&leaf.raw_unsigned_refund_transaction)
230 .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
231 let refund_tx = bitcoin_tx_from_bytes(&refund_tx_bytes)?;
232
233 let sighash = sighash_from_tx(&refund_tx, 0, &node_tx.output[0])?;
234
235 let verifying_public_key = parse_public_key(&node.verifying_public_key)?;
237
238 let taproot_key = compute_taproot_key_no_script_from_internal_key(
239 &verifying_public_key.x_only_public_key().0.serialize(),
240 )?;
241
242 let adaptor_signature_bytes = hex::decode(&leaf.adaptor_added_signature)
243 .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
244
245 let _ = apply_adaptor_to_signature(
246 &taproot_key,
247 &sighash,
248 &adaptor_signature_bytes,
249 &adaptor_private_key.secret_bytes(),
250 )
251 .unwrap();
252 }
253
254 let transfer_id = transfer.id;
255 self.send_transfer_tweak_key(&transfer, &leaf_key_tweaks, &refund_signature_map)
256 .await?;
257
258 let _completion = self
260 .complete_leaves_swap_with_ssp(
261 hex::encode(adaptor_private_key.secret_bytes()),
262 transfer_id.to_string(),
263 request_id.clone(),
264 )
265 .await?;
266
267 self.claim_transfers_internal().await?;
268
269 let leaf_ids = available_leaves
271 .leaves
272 .iter()
273 .map(|leaf| leaf.get_id().clone())
274 .collect();
275 self.leaf_manager
276 .unlock_leaves(available_leaves.unlocking_id.unwrap(), &leaf_ids, true)?;
277
278 let leaf_id = leaves[0].leaf_id.clone();
280
281 Ok(leaf_id)
282 }
283}