use ordinals_parser::{parse_inscriptions_from_transaction, Inscription};
use bitcoin::{consensus::deserialize, Transaction};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::env;
use reqwest::blocking::Client;
use serde_json::Value;
const MEMPOOL_API_BASE: &str = "https://mempool.space/api";
fn extract_txid_from_inscription_id(inscription_id: &str) -> Option<String> {
inscription_id.split('i').next().map(|s| s.to_string())
}
fn fetch_transaction_hex(txid: &str) -> Result<String, Box<dyn std::error::Error>> {
let client = Client::new();
let url = format!("{}/tx/{}/hex", MEMPOOL_API_BASE, txid);
println!("Fetching transaction from: {}", url);
let response = client.get(url).send()?;
if response.status().is_success() {
Ok(response.text()?)
} else {
Err(format!("Failed to fetch transaction: HTTP {}", response.status()).into())
}
}
fn fetch_inscription_info(inscription_id: &str) -> Result<Value, Box<dyn std::error::Error>> {
let client = Client::new();
let url = format!("https://ordinals.com/api/inscription/{}", inscription_id);
println!("Fetching inscription info from: {}", url);
let response = client.get(url).send()?;
if response.status().is_success() {
Ok(response.json()?)
} else {
Err(format!("Failed to fetch inscription info: HTTP {}", response.status()).into())
}
}
fn inspect_transaction(tx: &Transaction) {
println!("\n[DEBUG] Transaction inspection:");
println!("[DEBUG] Transaction has {} inputs", tx.input.len());
println!("[DEBUG] Transaction has {} outputs", tx.output.len());
for (i, input) in tx.input.iter().enumerate() {
println!("\n[DEBUG] Inspecting input #{}", i);
println!("[DEBUG] Previous output: {:?}", input.previous_output);
println!("[DEBUG] Witness stack size: {}", input.witness.len());
for j in 0..input.witness.len() {
if let Some(witness_item) = input.witness.nth(j) {
println!("[DEBUG] Witness item #{} - size: {} bytes", j, witness_item.len());
if witness_item.len() > 10 {
let ord_marker = b"ord\x01";
let has_ord_marker = witness_item.windows(ord_marker.len()).any(|window| window == ord_marker);
if has_ord_marker {
println!("[DEBUG] Witness item #{} CONTAINS potential ord marker bytes!", j);
} else {
println!("[DEBUG] Witness item #{} does NOT contain ord marker bytes", j);
}
if let Ok(text) = std::str::from_utf8(witness_item) {
let preview = if text.len() > 40 {
format!("{}...", &text[..40])
} else {
text.to_string()
};
println!("[DEBUG] Witness item #{} text preview: \"{}\"", j, preview);
}
}
} else {
println!("[DEBUG] Witness item #{} is None", j);
}
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
let tx_hex = if args.len() > 1 {
let path = PathBuf::from(&args[1]);
let mut file = File::open(path)?;
let mut hex = String::new();
file.read_to_string(&mut hex)?;
hex.trim().to_string()
} else if args.len() > 2 && args[1] == "--txid" {
let txid = &args[2];
println!("Using transaction ID: {}", txid);
fetch_transaction_hex(txid)?
} else if args.len() > 2 && args[1] == "--inscription" {
let inscription_id = &args[2];
println!("Using inscription ID: {}", inscription_id);
match fetch_inscription_info(inscription_id) {
Ok(info) => {
println!("Inscription info:");
println!(" Content type: {}", info.get("content_type").and_then(Value::as_str).unwrap_or("unknown"));
println!(" Content length: {}", info.get("content_length").and_then(Value::as_u64).unwrap_or(0));
println!(" Genesis timestamp: {}", info.get("timestamp").and_then(Value::as_u64).unwrap_or(0));
if let Some(content_type) = info.get("content_type").and_then(Value::as_str) {
if content_type.starts_with("text/") {
if let Some(content) = info.get("content").and_then(Value::as_str) {
let preview = if content.len() > 100 {
format!("{}...", &content[..100])
} else {
content.to_string()
};
println!(" Content preview: \"{}\"", preview);
}
}
}
},
Err(e) => println!("Could not fetch inscription info: {}", e),
}
let txid = extract_txid_from_inscription_id(inscription_id)
.ok_or("Could not extract transaction ID from inscription ID")?;
println!("Extracted transaction ID: {}", txid);
fetch_transaction_hex(&txid)?
} else {
let inscription_id = "787e0c399c13190ef00e4c8e85ddc2761bda18b31f9629919c8fe3c57e9b0d4fi0";
println!("Using text/plain inscription ID: {}", inscription_id);
match fetch_inscription_info(inscription_id) {
Ok(info) => {
println!("Inscription info:");
println!(" Content type: {}", info.get("content_type").and_then(Value::as_str).unwrap_or("unknown"));
println!(" Content length: {}", info.get("content_length").and_then(Value::as_u64).unwrap_or(0));
println!(" Genesis timestamp: {}", info.get("timestamp").and_then(Value::as_u64).unwrap_or(0));
if let Some(content_type) = info.get("content_type").and_then(Value::as_str) {
if content_type.starts_with("text/") {
if let Some(content) = info.get("content").and_then(Value::as_str) {
let preview = if content.len() > 100 {
format!("{}...", &content[..100])
} else {
content.to_string()
};
println!(" Content preview: \"{}\"", preview);
}
}
}
},
Err(e) => println!("Could not fetch inscription info: {}", e),
}
let txid = extract_txid_from_inscription_id(inscription_id)
.ok_or("Could not extract transaction ID from inscription ID")?;
println!("Extracted transaction ID: {}", txid);
fetch_transaction_hex(&txid)?
};
println!("\n[DEBUG] Decoding transaction hex of size: {} characters", tx_hex.len());
let tx_bytes = hex::decode(tx_hex.trim())?;
println!("[DEBUG] Transaction binary size: {} bytes", tx_bytes.len());
let tx: Transaction = deserialize(&tx_bytes)?;
println!("\nTransaction ID: {}", tx.txid());
inspect_transaction(&tx);
println!("\n[DEBUG] Calling parse_inscriptions_from_transaction...");
let inscriptions = parse_inscriptions_from_transaction(&tx);
println!("[DEBUG] Parsing complete. Found {} inscriptions", inscriptions.len());
if inscriptions.is_empty() {
println!("No inscriptions found in this transaction.");
return Ok(());
}
println!("Found {} inscription(s):", inscriptions.len());
for (idx, inscription) in inscriptions.iter().enumerate() {
println!("\nInscription #{}", idx + 1);
print_inscription_details(inscription);
}
Ok(())
}
fn print_inscription_details(inscription: &Inscription) {
println!(" Content Type: {:?}", inscription.content_type());
if let Some(content_encoding) = inscription.content_encoding() {
println!(" Content Encoding: {}", content_encoding);
}
println!(" Content Length: {:?} bytes", inscription.content_length());
if let Some(metaprotocol) = inscription.metaprotocol() {
println!(" Metaprotocol: {}", metaprotocol);
}
if !inscription.parents().is_empty() {
println!(" Parents:");
for parent in inscription.parents() {
println!(" {}", parent);
}
}
if let Some(delegate) = inscription.delegate() {
println!(" Delegate: {}", delegate);
}
if let Some(pointer) = inscription.pointer() {
println!(" Pointer: {}", pointer);
}
if let Some(content_type) = inscription.content_type() {
if let Some(body) = inscription.body() {
if let Ok(text) = std::str::from_utf8(body) {
if content_type == "application/json" ||
(content_type.starts_with("text/plain") && text.trim().starts_with('{') && text.trim().ends_with('}')) {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(text) {
if let Ok(pretty_json) = serde_json::to_string_pretty(&json_value) {
println!(" JSON Content:\n{}", pretty_json);
} else {
println!(" JSON Content: {}", text);
}
} else {
if content_type.starts_with("text/plain") {
let preview = if text.len() > 100 {
format!("{}...", &text[..100])
} else {
text.to_string()
};
println!(" Content Preview: \"{}\"", preview);
} else {
println!(" Content: [INVALID JSON]");
}
}
} else if content_type.starts_with("text/") {
let preview = if text.len() > 100 {
format!("{}...", &text[..100])
} else {
text.to_string()
};
println!(" Content Preview: \"{}\"", preview);
} else if content_type.starts_with("image/") {
println!(" Content: [IMAGE DATA]");
} else {
println!(" Content: [BINARY DATA]");
}
} else {
if content_type.starts_with("image/") {
println!(" Content: [IMAGE DATA]");
} else {
println!(" Content: [BINARY DATA]");
}
}
}
}
println!(" Flags:");
println!(" Duplicate Fields: {}", inscription.duplicate_field);
println!(" Incomplete Fields: {}", inscription.incomplete_field);
println!(" Unrecognized Even Fields: {}", inscription.unrecognized_even_field);
println!(" Hidden: {}", inscription.hidden());
}