spark_sdk/wallet/internal_handlers/implementations/
lightning.rs

1use crate::error::SparkSdkError;
2use crate::signer::traits::SparkSigner;
3use crate::wallet::internal_handlers::traits::lightning::LightningInternalHandlers;
4use crate::wallet::internal_handlers::traits::transfer::LeafKeyTweak;
5use crate::wallet::internal_handlers::utils::bitcoin_tx_from_bytes;
6use crate::wallet::internal_handlers::utils::parsers::parse_public_key;
7use crate::wallet::internal_handlers::utils::serialize_bitcoin_transaction;
8use crate::SparkSdk;
9use lightning_invoice::Bolt11Invoice;
10use spark_protos::common::SigningCommitment;
11use spark_protos::common::SigningResult;
12use spark_protos::spark::initiate_preimage_swap_request::Reason;
13use spark_protos::spark::GetSigningCommitmentsRequest;
14use spark_protos::spark::InitiatePreimageSwapRequest;
15use spark_protos::spark::InitiatePreimageSwapResponse;
16use spark_protos::spark::InvoiceAmountProof;
17use spark_protos::spark::RequestedSigningCommitments;
18use spark_protos::spark::SigningCommitments;
19use spark_protos::spark::UserSignedRefund;
20use std::collections::HashMap;
21use tonic::async_trait;
22use uuid::Uuid;
23
24#[async_trait]
25impl<S: SparkSigner + Send + Sync + Clone + 'static> LightningInternalHandlers<S> for SparkSdk<S> {
26    async fn swap_nodes_for_preimage(
27        &self,
28        leaves: Vec<LeafKeyTweak>,
29        receiver_identity_pubkey_bytes: Vec<u8>,
30        payment_hash: Vec<u8>,
31        invoice_string: Option<String>,
32        fee_sats: u64,
33        is_inbound_payment: bool,
34    ) -> Result<InitiatePreimageSwapResponse, SparkSdkError> {
35        let transfer_id = Uuid::now_v7();
36
37        // get the signing commitments from the operators
38        let mut node_ids = Vec::with_capacity(leaves.len());
39        for leaf in &leaves {
40            node_ids.push(leaf.leaf.id.clone());
41        }
42
43        let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
44        let mut request = tonic::Request::new(GetSigningCommitmentsRequest { node_ids });
45        self.add_authorization_header_to_request(&mut request, None);
46        let response = spark_client.get_signing_commitments(request).await?;
47        let spark_signing_commitments = response.into_inner().signing_commitments;
48
49        println!("Signing commitmente: {:?}", spark_signing_commitments);
50
51        // parse the response and sign the swap
52        let receiver_identity_pubkey = parse_public_key(&receiver_identity_pubkey_bytes)?;
53        let (signing_result, refund_txs, user_commitments) = self.signer.sign_for_lightning_swap(
54            &leaves,
55            &spark_signing_commitments,
56            receiver_identity_pubkey,
57        )?;
58
59        // prepare refunds
60        let user_signed_refunds = prepare_user_signed_refunds(
61            &leaves,
62            &refund_txs,
63            &signing_result.results,
64            &user_commitments,
65            &spark_signing_commitments,
66        )?;
67
68        let mut bolt11_string = "".to_string(); // TODO: ask
69        let amount_sats = if let Some(invoice_str) = &invoice_string {
70            bolt11_string = invoice_str.clone();
71            let invoice: Bolt11Invoice = bolt11_string.parse().unwrap();
72            invoice
73                .amount_milli_satoshis()
74                .ok_or(SparkSdkError::InvalidInput(
75                    "Invoice missing amount".to_string(),
76                ))?
77                / 1000
78        } else {
79            0 // Will be set later for non-invoice cases
80        };
81
82        let reason = if is_inbound_payment {
83            Reason::Receive
84        } else {
85            Reason::Send
86        };
87
88        println!("User signed refunds: {:?}", user_signed_refunds);
89        // internal validation
90        for refund in &user_signed_refunds {
91            // construct the refund tx
92            let refund_tx = bitcoin_tx_from_bytes(&refund.refund_tx)?;
93            println!(
94                "Refund tx is valid. It is: {:?}",
95                hex::encode(serialize_bitcoin_transaction(&refund_tx)?)
96            );
97        }
98
99        let mut request = tonic::Request::new(InitiatePreimageSwapRequest {
100            payment_hash: payment_hash.clone(),
101            user_signed_refunds,
102            reason: reason as i32,
103            invoice_amount: Some(spark_protos::spark::InvoiceAmount {
104                invoice_amount_proof: Some(InvoiceAmountProof {
105                    bolt11_invoice: bolt11_string,
106                }),
107                value_sats: amount_sats,
108            }),
109            transfer: Some(spark_protos::spark::StartSendTransferRequest {
110                transfer_id: transfer_id.to_string(),
111                owner_identity_public_key: self.get_identity_public_key().to_vec(),
112                receiver_identity_public_key: receiver_identity_pubkey_bytes.clone(),
113                leaves_to_send: Default::default(),
114                expiry_time: None,
115            }),
116            receiver_identity_public_key: receiver_identity_pubkey_bytes,
117            fee_sats,
118        });
119        self.add_authorization_header_to_request(&mut request, None);
120
121        let response = spark_client.initiate_preimage_swap(request).await?;
122
123        Ok(response.into_inner())
124    }
125}
126
127fn prepare_user_signed_refunds(
128    leaves: &Vec<LeafKeyTweak>,
129    refund_txs: &Vec<Vec<u8>>,
130    signing_results: &HashMap<String, SigningResult>,
131    user_commitments: &Vec<SigningCommitment>,
132    signing_commitments: &Vec<RequestedSigningCommitments>,
133) -> Result<Vec<UserSignedRefund>, SparkSdkError> {
134    let mut user_signed_refunds = Vec::new();
135
136    for (i, leaf) in leaves.iter().enumerate() {
137        // let user_commitment_proto = user_commitments[i].marshal_proto()?;
138        // let user_commitment_proto = marshal_frost_commitments(&user_commitments[i])?;
139
140        user_signed_refunds.push(UserSignedRefund {
141            node_id: leaf.leaf.id.clone(),
142            refund_tx: refund_txs[i].clone(),
143            user_signature: signing_results
144                .get(&leaf.leaf.id)
145                .ok_or(SparkSdkError::InvalidInput(
146                    "Missing signing result".to_string(),
147                ))?
148                .signature_share
149                .clone(),
150            signing_commitments: Some(SigningCommitments {
151                signing_commitments: signing_commitments[i].signing_nonce_commitments.clone(),
152            }),
153            user_signature_commitment: Some(user_commitments[i].clone()),
154        });
155    }
156
157    Ok(user_signed_refunds)
158}