clementine_tx_sender/
nonstandard.rs1use crate::{log_error_for_tx, TxSender, TxSenderDatabase, TxSenderSigner, TxSenderTxBuilder};
2use bitcoin::consensus::serialize;
3use bitcoin::Transaction;
4use clementine_errors::SendTxError;
5use std::collections::HashMap;
6
7impl<S, D, B> TxSender<S, D, B>
8where
9 S: TxSenderSigner,
10 D: TxSenderDatabase,
11 B: TxSenderTxBuilder,
12{
13 pub fn is_bridge_tx_nonstandard(&self, tx: &Transaction) -> bool {
26 tx.output.iter().any(|output| {
27 output.value.to_sat() == 0
28 && !self.is_p2a_anchor(output)
29 && !output.script_pubkey.is_op_return()
30 }) || tx.weight().to_wu() > 400_000
31 }
32
33 pub async fn send_testnet4_nonstandard_tx(
48 &self,
49 tx: &Transaction,
50 try_to_send_id: u32,
51 ) -> Result<(), SendTxError> {
52 let api_key = std::env::var("MEMPOOL_SPACE_API_KEY").map_err(|_| {
54 SendTxError::Other(eyre::eyre!(
55 "MEMPOOL_SPACE_API_KEY environment variable not set, cannot send nonstandard transactions to testnet4"
56 ))
57 })?;
58
59 let txid = tx.compute_txid();
61 let response = self
62 .http_client
63 .get("https://mempool.space/api/v1/services/accelerator/testnet4/accelerations")
64 .header("X-Mempool-Auth", api_key.clone())
65 .send()
66 .await
67 .map_err(|e| {
68 SendTxError::NetworkError(format!(
69 "Failed to get transaction history from mempool.space accelerator: {e}"
70 ))
71 })?;
72 if response.status().is_success() {
73 let text = response.text().await.unwrap_or_default();
76 let previously_sent_txs: serde_json::Value =
77 serde_json::from_str(&text).unwrap_or_else(|_| serde_json::json!([]));
78
79 for tx in previously_sent_txs.as_array().unwrap_or(&vec![]) {
81 let Some(response_txid) = tx.get("txid").and_then(|v| v.as_str()) else {
82 continue;
83 };
84
85 if response_txid == txid.to_string() && tx["status"] != "failed" {
86 tracing::debug!(
87 "Found {:?} with status {:?} in accelerator transaction history",
88 txid,
89 tx["status"]
90 );
91 let _ = self
92 .db
93 .update_tx_debug_sending_state(
94 try_to_send_id,
95 "nonstandard_testnet4_send_submitted",
96 false,
97 )
98 .await;
99 return Ok(()); }
101 }
102 } else {
103 let status = response.status();
104 let error_text = response.text().await.unwrap_or_default();
105 return Err(SendTxError::NetworkError(format!(
106 "Accelerator returned HTTP {status}: {error_text}"
107 )));
108 }
109
110 let tx_hex = hex::encode(serialize(tx));
112
113 let mut form_data = HashMap::new();
115 form_data.insert("txInput", tx_hex);
116 form_data.insert("label", format!("clementine-{}", tx.compute_txid()));
117
118 let response = self
120 .http_client
121 .post("https://mempool.space/api/v1/services/accelerator/testnet4/accelerate/hex")
122 .header("X-Mempool-Auth", api_key)
123 .form(&form_data)
124 .send()
125 .await
126 .map_err(|e| {
127 SendTxError::NetworkError(format!(
128 "Failed to submit transaction to mempool.space accelerator: {e}"
129 ))
130 })?;
131
132 if response.status().is_success() {
134 let response_text = response
135 .text()
136 .await
137 .map_err(|e| SendTxError::NetworkError(format!("Failed to read response: {e}")))?;
138
139 tracing::info!(
140 "Successfully submitted nonstandard transaction {:?} to mempool.space testnet4 accelerator: {}",
141 txid,
142 response_text
143 );
144
145 let _ = self
146 .db
147 .update_tx_debug_sending_state(
148 try_to_send_id,
149 "nonstandard_testnet4_send_success",
150 true,
151 )
152 .await;
153
154 Ok(())
155 } else {
156 let status = response.status();
157 let error_text = response
158 .text()
159 .await
160 .unwrap_or_else(|_| "Unknown error".to_string());
161
162 log_error_for_tx!(
163 self.db,
164 try_to_send_id,
165 format!(
166 "Failed to submit transaction to mempool.space. Status: {}, Error: {}",
167 status, error_text
168 )
169 );
170 let _ = self
171 .db
172 .update_tx_debug_sending_state(
173 try_to_send_id,
174 "nonstandard_testnet4_send_failed",
175 true,
176 )
177 .await;
178
179 Err(SendTxError::NetworkError(format!(
180 "Failed to submit transaction to mempool.space. Status: {status}, Error: {error_text}"
181 )))
182 }
183 }
184}