pub mod error;
pub use error::*;
pub mod bind;
pub mod descriptors;
pub use descriptors::*;
use sapio_base::simp::CompiledObjectLT;
use sapio_base::simp::SIMPAttachableAt;
use sapio_base::Clause;
use serde_json::Value;
use crate::contract::abi::continuation::ContinuationPoint;
use crate::contract::CompilationError;
use crate::template::Template;
use crate::util::amountrange::AmountRange;
use crate::util::extended_address::ExtendedAddress;
use ::miniscript::*;
use bitcoin::hashes::sha256;
use bitcoin::util::amount::Amount;
use sapio_base::effects::EffectPath;
use sapio_base::effects::PathFragment;
use sapio_base::serialization_helpers::SArc;
use sapio_base::simp::SIMPError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::Arc;
#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug, PartialEq, Eq, Default)]
pub struct ObjectMetadata {
#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
pub simp: BTreeMap<i64, serde_json::Value>,
pub simps_for_guards: BTreeMap<Clause, BTreeMap<i64, Vec<serde_json::Value>>>,
}
impl ObjectMetadata {
pub fn is_empty(&self) -> bool {
*self == Default::default()
}
pub fn add_simp<S: SIMPAttachableAt<CompiledObjectLT>>(
mut self,
s: S,
) -> Result<Self, SIMPError> {
let old = self.simp.insert(s.get_protocol_number(), s.to_json()?);
if let Some(old) = old {
Err(SIMPError::AlreadyDefined(old))
} else {
Ok(self)
}
}
pub(crate) fn add_guard_simps(
mut self,
all_guard_simps: BTreeMap<
policy::Concrete<bitcoin::XOnlyPublicKey>,
Vec<Arc<dyn SIMPAttachableAt<sapio_base::simp::GuardLT>>>,
>,
) -> Result<ObjectMetadata, CompilationError> {
if self.simps_for_guards.is_empty() {
self.simps_for_guards = all_guard_simps
.into_iter()
.map(|(k, v)| {
Ok((
k,
v.into_iter().fold(
Ok(Default::default()),
|ra: Result<BTreeMap<_, Vec<Value>>, CompilationError>, b| {
let mut a = ra?;
a.entry(b.get_protocol_number()).or_default().push(
b.to_json().map_err(CompilationError::SerializationError)?,
);
Ok(a)
},
)?,
))
})
.collect::<Result<_, CompilationError>>()?;
Ok(self)
} else {
Err(Err(CompilationError::Custom(
"Failed to add guard simps".into(),
))?)
}
}
}
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
pub struct Object {
#[serde(
rename = "template_hash_to_template_map",
skip_serializing_if = "BTreeMap::is_empty",
default
)]
pub ctv_to_tx: BTreeMap<sha256::Hash, Template>,
#[serde(
rename = "suggested_template_hash_to_template_map",
skip_serializing_if = "BTreeMap::is_empty",
default
)]
pub suggested_txs: BTreeMap<sha256::Hash, Template>,
#[serde(
rename = "continuation_points",
skip_serializing_if = "BTreeMap::is_empty",
default
)]
pub continue_apis: BTreeMap<SArc<EffectPath>, ContinuationPoint>,
pub root_path: SArc<EffectPath>,
pub address: ExtendedAddress,
#[serde(
rename = "known_descriptor",
skip_serializing_if = "Option::is_none",
default
)]
pub descriptor: Option<SupportedDescriptors>,
pub amount_range: AmountRange,
pub metadata: ObjectMetadata,
}
impl Object {
pub fn from_address(address: bitcoin::Address, a: Option<AmountRange>) -> Object {
Object {
ctv_to_tx: BTreeMap::new(),
suggested_txs: BTreeMap::new(),
continue_apis: Default::default(),
root_path: SArc(EffectPath::push(
None,
PathFragment::Named(SArc(Arc::new("".into()))),
)),
address: address.into(),
descriptor: None,
amount_range: a.unwrap_or_else(|| {
let mut a = AmountRange::new();
a.update_range(Amount::min_value());
a.update_range(Amount::from_sat(21_000_000 * 100_000_000));
a
}),
metadata: Default::default(),
}
}
pub fn from_script(
script: bitcoin::Script,
a: Option<AmountRange>,
net: bitcoin::Network,
) -> Result<Object, ObjectError> {
bitcoin::Address::from_script(&script, net)
.ok_or_else(|| ObjectError::UnknownScriptType(script.clone()))
.map(|m| Object::from_address(m, a))
}
pub fn from_op_return<'a, I: ?Sized>(data: &'a I) -> Result<Object, ObjectError>
where
&'a [u8]: From<&'a I>,
{
Ok(Object {
ctv_to_tx: BTreeMap::new(),
suggested_txs: BTreeMap::new(),
continue_apis: Default::default(),
root_path: SArc(EffectPath::push(
None,
PathFragment::Named(SArc(Arc::new("".into()))),
)),
address: ExtendedAddress::make_op_return(data)?,
descriptor: None,
amount_range: AmountRange::new(),
metadata: Default::default(),
})
}
pub fn from_descriptor<T>(d: Descriptor<T>, a: Option<AmountRange>) -> Self
where
Descriptor<T>: Into<SupportedDescriptors>,
T: MiniscriptKey + ToPublicKey,
{
Object {
ctv_to_tx: BTreeMap::new(),
suggested_txs: BTreeMap::new(),
continue_apis: Default::default(),
root_path: SArc(EffectPath::push(
None,
PathFragment::Named(SArc(Arc::new("".into()))),
)),
address: d.address(bitcoin::Network::Bitcoin).unwrap().into(),
descriptor: Some(d.into()),
amount_range: a.unwrap_or_else(|| {
let mut a = AmountRange::new();
a.update_range(Amount::min_value());
a.update_range(Amount::from_sat(21_000_000 * 100_000_000));
a
}),
metadata: Default::default(),
}
}
}