use serde::Serialize;
use crate::hash::hash160;
use crate::pubkey::PubKey;
use crate::pushbytes::{parse_push_bytes, push_bytes};
use crate::script::{ALL_FORMATS, Script};
#[derive(Debug, Clone, Serialize)]
pub struct Out {
pub name: String,
pub script: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub flags: Vec<String>,
#[serde(skip)]
pub(crate) raw: Vec<u8>,
}
impl Out {
pub fn make(name: &str, script: Vec<u8>, flags: &[&str]) -> Out {
Out {
name: name.to_string(),
script: hex::encode(&script),
flags: flags.iter().map(|s| s.to_string()).collect(),
raw: script,
}
}
pub fn bytes(&self) -> &[u8] {
&self.raw
}
pub fn base_name(&self) -> &str {
match self.name.split_once(':') {
Some((base, _)) => base,
None => &self.name,
}
}
pub fn hash(&self) -> Option<Vec<u8>> {
match self.name.as_str() {
"p2wpkh" | "p2tr" => parse_push_bytes(&self.raw[1..]).map(|(d, _)| d.to_vec()),
"p2pkh" | "p2pukh" => parse_push_bytes(&self.raw[2..]).map(|(d, _)| d.to_vec()),
"p2pk" | "p2puk" => parse_push_bytes(&self.raw).map(|(pub_, _)| hash160(pub_).to_vec()),
"p2sh" => parse_push_bytes(&self.raw[1..]).map(|(d, _)| d.to_vec()),
"eth" | "massa" | "solana" => Some(self.raw.clone()),
_ => None,
}
}
}
impl core::fmt::Display for Out {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}:{}", self.name, self.script)
}
}
pub fn guess_out(script: &[u8], pubkey_hint: Option<&PubKey>) -> Out {
if script.is_empty() {
return Out::make("empty", script.to_vec(), &["invalid"]);
}
if script[0] == 0 {
return match script.len() {
22 => Out::make("p2wpkh", script.to_vec(), &[]),
34 => Out::make("p2wsh", script.to_vec(), &[]),
_ => Out::make("invalid", script.to_vec(), &[]),
};
}
if script[0] == 0x51 {
if script.len() == 34 {
return Out::make("p2tr", script.to_vec(), &[]);
}
return Out::make("invalid", script.to_vec(), &[]);
}
if script[0] == 0x6a {
return Out::make("op_return", script.to_vec(), &[]);
}
let last = script[script.len() - 1];
if last == 0xac {
if script.len() == 25
&& script.starts_with(&[0x76, 0xa9, 0x14])
&& script.ends_with(&[0x88, 0xac])
{
if let Some(hint) = pubkey_hint {
let s = Script::new(hint.clone());
for e in ["p2pkh", "p2pukh"] {
if let Ok(buf) = s.generate(e)
&& buf == script
{
return Out::make(e, script.to_vec(), &[]);
}
}
}
return Out::make("p2pkh", script.to_vec(), &[]);
}
if let Some((v, _)) = parse_push_bytes(script) {
let mut rebuilt = push_bytes(v);
rebuilt.push(0xac);
if rebuilt == script {
match v.len() {
33 => return Out::make("p2pk", script.to_vec(), &[]),
65 => return Out::make("p2puk", script.to_vec(), &[]),
_ => {}
}
}
}
} else if last == 0x87 {
if let Some((v, _)) = parse_push_bytes(&script[1..]) {
let mut rebuilt = vec![0xa9];
rebuilt.extend_from_slice(&push_bytes(v));
rebuilt.push(0x87);
if rebuilt == script {
if let Some(hint) = pubkey_hint {
let s = Script::new(hint.clone());
for e in [
"p2sh:p2pk",
"p2sh:p2pkh",
"p2sh:p2puk",
"p2sh:p2pukh",
"p2sh:p2wpkh",
] {
if let Ok(buf) = s.generate(e)
&& buf == script
{
return Out::make(e, script.to_vec(), &[]);
}
}
}
return Out::make("p2sh", script.to_vec(), &[]);
}
}
}
Out::make("invalid", script.to_vec(), &[])
}
pub fn get_outs(pubkey: impl Into<PubKey>) -> Vec<Out> {
let s = Script::new(pubkey);
let mut outs = Vec::new();
for name in ALL_FORMATS {
if let Ok(out) = s.out(name) {
outs.push(out);
}
}
outs
}