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