1#![allow(
2 clippy::large_enum_variant,
3 clippy::result_large_err,
4 clippy::too_many_arguments,
5 clippy::type_complexity,
6 mismatched_lifetime_syntaxes
7)]
8#![deny(
9 clippy::cast_lossless,
10 clippy::cast_possible_truncation,
11 clippy::cast_possible_wrap,
12 clippy::cast_sign_loss
13)]
14
15use {
16 self::{
17 arguments::Arguments,
18 blocktime::Blocktime,
19 decimal::Decimal,
20 deserialize_from_str::DeserializeFromStr,
21 fund_raw_transaction::fund_raw_transaction,
22 index::BitcoinCoreRpcResultExt,
23 inscriptions::{
24 inscription_id,
25 media::{self, ImageRendering, Media},
26 teleburn,
27 },
28 into_u64::IntoU64,
29 into_usize::IntoUsize,
30 option_ext::OptionExt,
31 outgoing::Outgoing,
32 representation::Representation,
33 satscard::Satscard,
34 settings::Settings,
35 signer::Signer,
36 subcommand::{OutputFormat, Subcommand, SubcommandResult},
37 tally::Tally,
38 },
39 anyhow::{Context, Error, anyhow, bail, ensure},
40 bip39::Mnemonic,
41 bitcoin::{
42 Amount, Block, KnownHrp, Network, OutPoint, Psbt, Script, ScriptBuf, Sequence, SignedAmount,
43 Transaction, TxIn, TxOut, Txid, Witness,
44 address::{Address, NetworkUnchecked},
45 blockdata::{
46 constants::{DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL},
47 locktime::absolute::LockTime,
48 },
49 consensus::{self, Decodable, Encodable},
50 hash_types::{BlockHash, TxMerkleNode},
51 hashes::Hash,
52 policy::MAX_STANDARD_TX_WEIGHT,
53 script,
54 secp256k1::{self, Secp256k1},
55 transaction::Version,
56 },
57 bitcoincore_rpc::{Client, RpcApi},
58 chrono::{DateTime, TimeZone, Utc},
59 ciborium::Value,
60 clap::{ArgGroup, Parser},
61 error::{ResultExt, SnafuError},
62 html_escaper::{Escape, Trusted},
63 ordinals::{
64 Artifact, Charm, Edict, Epoch, Etching, Height, Pile, Rarity, Rune, RuneId, Runestone, Sat,
65 SatPoint, SpacedRune, Terms, varint,
66 },
67 regex::Regex,
68 reqwest::{StatusCode, Url, header::HeaderMap},
69 serde::{Deserialize, Deserializer, Serialize},
70 serde_with::{DeserializeFromStr, SerializeDisplay},
71 snafu::{Backtrace, ErrorCompat, Snafu},
72 std::{
73 backtrace::BacktraceStatus,
74 cmp,
75 collections::{BTreeMap, BTreeSet, HashSet},
76 env,
77 ffi::OsString,
78 fmt::{self, Display, Formatter},
79 fs::{self, File},
80 io::{self, BufReader, Cursor, Read},
81 mem,
82 net::ToSocketAddrs,
83 path::{Path, PathBuf},
84 process::{self, Command, Stdio},
85 str::FromStr,
86 sync::{
87 Arc, LazyLock, Mutex,
88 atomic::{self, AtomicBool},
89 },
90 thread,
91 time::{Duration, Instant, SystemTime},
92 },
93 sysinfo::System,
94 tokio::{runtime::Runtime, task},
95};
96
97pub use self::{
98 chain::Chain,
99 fee_rate::FeeRate,
100 index::{Index, RuneEntry},
101 inscriptions::{Envelope, Inscription, InscriptionId, ParsedEnvelope, RawEnvelope},
102 object::Object,
103 options::Options,
104 properties::{Attributes, Item, Properties, Trait, Traits},
105 wallet::transaction_builder::{Target, TransactionBuilder},
106};
107
108#[cfg(test)]
109#[macro_use]
110mod test;
111
112#[cfg(test)]
113use self::test::*;
114
115pub mod api;
116pub mod arguments;
117mod blocktime;
118pub mod chain;
119pub mod decimal;
120mod deserialize_from_str;
121mod error;
122mod fee_rate;
123mod fund_raw_transaction;
124pub mod index;
125mod inscriptions;
126mod into_u64;
127mod into_usize;
128mod macros;
129mod object;
130mod option_ext;
131pub mod options;
132pub mod outgoing;
133mod properties;
134mod re;
135mod representation;
136pub mod runes;
137mod satscard;
138pub mod settings;
139mod signer;
140pub mod subcommand;
141mod tally;
142pub mod templates;
143pub mod wallet;
144
145type Result<T = (), E = Error> = std::result::Result<T, E>;
146type SnafuResult<T = (), E = SnafuError> = std::result::Result<T, E>;
147
148const MAX_STANDARD_OP_RETURN_SIZE: usize = 83;
149const TARGET_POSTAGE: Amount = Amount::from_sat(10_000);
150
151static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
152static LISTENERS: Mutex<Vec<axum_server::Handle>> = Mutex::new(Vec::new());
153static INDEXER: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);
154
155#[doc(hidden)]
156#[derive(Deserialize, Serialize)]
157pub struct SimulateRawTransactionResult {
158 #[serde(with = "bitcoin::amount::serde::as_btc")]
159 pub balance_change: SignedAmount,
160}
161
162#[doc(hidden)]
163#[derive(Deserialize, Serialize)]
164pub struct SimulateRawTransactionOptions {
165 include_watchonly: bool,
166}
167
168pub fn timestamp(seconds: u64) -> DateTime<Utc> {
169 Utc
170 .timestamp_opt(seconds.try_into().unwrap_or(i64::MAX), 0)
171 .unwrap()
172}
173
174fn target_as_block_hash(target: bitcoin::Target) -> BlockHash {
175 BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes()))
176}
177
178pub fn unbound_outpoint() -> OutPoint {
179 OutPoint {
180 txid: Hash::all_zeros(),
181 vout: 0,
182 }
183}
184
185fn uncheck(address: &Address) -> Address<NetworkUnchecked> {
186 address.to_string().parse().unwrap()
187}
188
189pub fn base64_encode(data: &[u8]) -> String {
190 use base64::Engine;
191 base64::engine::general_purpose::STANDARD.encode(data)
192}
193
194pub fn base64_decode(s: &str) -> Result<Vec<u8>> {
195 use base64::Engine;
196 Ok(base64::engine::general_purpose::STANDARD.decode(s)?)
197}
198
199fn default<T: Default>() -> T {
200 Default::default()
201}
202
203pub fn parse_ord_server_args(args: &str) -> (Settings, subcommand::server::Server) {
204 match Arguments::try_parse_from(args.split_whitespace()) {
205 Ok(arguments) => match arguments.subcommand {
206 Subcommand::Server(server) => (
207 Settings::merge(
208 arguments.options,
209 vec![("INTEGRATION_TEST".into(), "1".into())]
210 .into_iter()
211 .collect(),
212 )
213 .unwrap(),
214 server,
215 ),
216 subcommand => panic!("unexpected subcommand: {subcommand:?}"),
217 },
218 Err(err) => panic!("error parsing arguments: {err}"),
219 }
220}
221
222pub fn cancel_shutdown() {
223 SHUTTING_DOWN.store(false, atomic::Ordering::Relaxed);
224}
225
226pub fn shut_down() {
227 SHUTTING_DOWN.store(true, atomic::Ordering::Relaxed);
228}
229
230fn gracefully_shut_down_indexer() {
231 if let Some(indexer) = INDEXER.lock().unwrap().take() {
232 shut_down();
233 log::info!("Waiting for index thread to finish...");
234 if indexer.join().is_err() {
235 log::warn!("Index thread panicked; join failed");
236 }
237 }
238}
239
240fn unversioned_leaf_script_from_witness(witness: &Witness) -> Option<&Script> {
247 #[allow(deprecated)]
248 witness.tapscript()
249}
250
251pub fn main() {
252 env_logger::init();
253
254 ctrlc::set_handler(move || {
255 if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) {
256 process::exit(1);
257 }
258
259 eprintln!("Shutting down gracefully. Press <CTRL-C> again to shutdown immediately.");
260
261 LISTENERS
262 .lock()
263 .unwrap()
264 .iter()
265 .for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100))));
266
267 gracefully_shut_down_indexer();
268 })
269 .expect("Error setting <CTRL-C> handler");
270
271 let args = Arguments::parse();
272
273 let format = args.options.format;
274
275 match args.run() {
276 Err(err) => {
277 eprintln!("error: {err}");
278
279 if let SnafuError::Anyhow { err } = err {
280 for (i, err) in err.chain().skip(1).enumerate() {
281 if i == 0 {
282 eprintln!();
283 eprintln!("because:");
284 }
285
286 eprintln!("- {err}");
287 }
288
289 if env::var_os("RUST_BACKTRACE")
290 .map(|val| val == "1")
291 .unwrap_or_default()
292 {
293 eprintln!("{}", err.backtrace());
294 }
295 } else {
296 for (i, err) in err.iter_chain().skip(1).enumerate() {
297 if i == 0 {
298 eprintln!();
299 eprintln!("because:");
300 }
301
302 eprintln!("- {err}");
303 }
304
305 if let Some(backtrace) = err.backtrace()
306 && backtrace.status() == BacktraceStatus::Captured
307 {
308 eprintln!("backtrace:");
309 eprintln!("{backtrace}");
310 }
311 }
312
313 gracefully_shut_down_indexer();
314
315 process::exit(1);
316 }
317 Ok(output) => {
318 if let Some(output) = output {
319 output.print(format.unwrap_or_default());
320 }
321 gracefully_shut_down_indexer();
322 }
323 }
324}