use super::input::InputMetadata;
pub use super::{Output, OutputMeta};
use super::{Template, TemplateMetadata};
use crate::contract::{CompilationError, Context};
use crate::util::extended_address::ExtendedAddress;
use bitcoin::util::amount::Amount;
use bitcoin::Witness;
use bitcoin::{Script, VarInt};
use sapio_base::effects::PathFragment;
use sapio_base::simp::SIMPAttachableAt;
use sapio_base::simp::TemplateInputLT;
use sapio_base::simp::TemplateLT;
use sapio_base::timelocks::*;
use sapio_base::CTVHash;
use sapio_base::Clause;
use std::convert::TryFrom;
pub struct Builder {
guards: Vec<Clause>,
sequences: Vec<Option<AnyRelTimeLock>>,
outputs: Vec<Output>,
inputs: Vec<InputMetadata>,
version: i32,
lock_time: Option<AnyAbsTimeLock>,
ctx: Context,
fees: Amount,
min_feerate: Option<Amount>,
metadata: TemplateMetadata,
}
impl Builder {
pub fn new(ctx: Context) -> Builder {
Builder {
guards: Vec::new(),
sequences: vec![None],
inputs: vec![InputMetadata::default()],
outputs: vec![],
version: 2,
lock_time: None,
metadata: TemplateMetadata::new(),
fees: Amount::from_sat(0),
min_feerate: None,
ctx,
}
}
pub fn ctx(&self) -> &Context {
&self.ctx
}
pub fn spend_amount(mut self, amount: Amount) -> Result<Self, CompilationError> {
self.ctx = self.ctx.spend_amount(amount)?;
Ok(self)
}
pub fn add_fees(self, amount: Amount) -> Result<Self, CompilationError> {
let mut c = self.spend_amount(amount)?;
c.fees += amount;
Ok(c)
}
pub fn add_output(
mut self,
amount: Amount,
contract: &dyn crate::contract::Compilable,
metadata: Option<OutputMeta>,
) -> Result<Self, CompilationError> {
let subctx = self
.ctx
.derive(PathFragment::Branch(self.outputs.len() as u64))?
.with_amount(amount)?;
let mut ret = self.spend_amount(amount)?;
ret.outputs.push(Output {
amount,
contract: contract.compile(subctx)?,
added_metadata: metadata.unwrap_or_default(),
});
Ok(ret)
}
pub fn add_amount(mut self, a: Amount) -> Self {
self.ctx = self.ctx.add_amount(a);
self
}
pub fn add_sequence(mut self) -> Self {
self.sequences.push(None);
self.inputs.push(Default::default());
self
}
pub fn set_sequence(mut self, ii: isize, s: AnyRelTimeLock) -> Result<Self, CompilationError> {
let i = if ii >= 0 {
ii
} else {
self.sequences.len() as isize + ii
} as usize;
match self.sequences.get_mut(i).as_mut() {
Some(Some(seq)) => match (*seq, s) {
(a @ AnyRelTimeLock::RH(_), b @ AnyRelTimeLock::RH(_)) => {
*seq = std::cmp::max(a, b);
}
(a @ AnyRelTimeLock::RT(_), b @ AnyRelTimeLock::RT(_)) => {
*seq = std::cmp::max(a, b);
}
_ => return Err(CompilationError::IncompatibleSequence),
},
Some(x @ None) => {
x.replace(s);
}
None => return Err(CompilationError::NoSuchSequence),
};
Ok(self)
}
pub fn add_simp_for_input<S: SIMPAttachableAt<TemplateInputLT>>(
mut self,
ii: isize,
s: S,
) -> Result<Self, CompilationError> {
let i = if ii >= 0 {
ii
} else {
self.sequences.len() as isize + ii
} as usize;
match self.inputs.get_mut(i) {
Some(r) => {
r.add_simp_inplace(s)?;
Ok(self)
}
None => Err(CompilationError::NoSuchSequence),
}
}
pub fn set_lock_time(mut self, lt_in: AnyAbsTimeLock) -> Result<Self, CompilationError> {
if let Some(lt) = self.lock_time.as_mut() {
match (*lt, lt_in) {
(a @ AnyAbsTimeLock::AH(_), b @ AnyAbsTimeLock::AH(_)) => {
*lt = std::cmp::max(a, b);
}
(a @ AnyAbsTimeLock::AT(_), b @ AnyAbsTimeLock::AT(_)) => {
*lt = std::cmp::max(a, b);
}
_ => return Err(CompilationError::IncompatibleSequence),
}
} else {
self.lock_time = Some(lt_in);
}
Ok(self)
}
pub fn set_label(mut self, label: String) -> Self {
self.metadata.label = Some(label);
self
}
pub fn set_color(mut self, color: String) -> Self {
self.metadata.color = Some(color);
self
}
pub fn set_extra_meta<I, J>(mut self, i: I, j: J) -> Result<Self, CompilationError>
where
I: Into<String>,
J: Into<serde_json::Value>,
{
self.metadata = self.metadata.set_extra(i, j)?;
Ok(self)
}
pub fn add_simp<S: SIMPAttachableAt<TemplateLT>>(
mut self,
s: S,
) -> Result<Self, CompilationError> {
self.metadata = self.metadata.add_simp(s)?;
Ok(self)
}
pub fn add_guard(mut self, guard: Clause) -> Self {
self.guards.push(guard);
self
}
pub fn get_tx(&self) -> bitcoin::Transaction {
let default_seq = RelTime::try_from(0).unwrap().into();
let default_nlt = AbsHeight::try_from(0).unwrap().into();
let input = self
.sequences
.iter()
.map(|sequence| bitcoin::TxIn {
previous_output: Default::default(),
script_sig: Default::default(),
sequence: sequence.unwrap_or(default_seq).get(),
witness: Witness::new(),
})
.collect();
let output = self
.outputs
.iter()
.map(|out| {
let value = out.amount.as_sat();
let script_pubkey: Script = From::<&ExtendedAddress>::from(&out.contract.address);
bitcoin::TxOut {
value,
script_pubkey,
}
})
.collect();
let t = bitcoin::Transaction {
version: self.version,
lock_time: self.lock_time.unwrap_or(default_nlt).get(),
input,
output,
};
t
}
pub fn set_min_feerate(mut self, a: Amount) -> Self {
let v: &mut Amount = self.min_feerate.get_or_insert(a);
*v = std::cmp::min(*v, a);
self
}
pub fn estimate_tx_size(&self) -> u64 {
let mut input_weight: u64 = 0;
let inputs_with_witnesses: u64 = self.sequences.len() as u64;
let scale_factor = 1u64;
for _seq in &self.sequences {
input_weight += scale_factor
* 32 + 4 + 4 + VarInt(0u64).len() as u64;
}
let mut output_size: u64 = 0;
for output in &self.outputs {
let spk = output
.contract
.descriptor
.as_ref()
.map(|d| d.script_pubkey().len() as u64);
output_size += 8 + (VarInt(spk.unwrap_or(0)).len() as u64) +
spk.unwrap_or(0);
}
let non_input_size : u64=
4 +
(VarInt(self.sequences.len() as u64).len() as u64 +
VarInt(self.outputs.len() as u64).len() as u64)+
output_size +
4;
if inputs_with_witnesses == 0 {
non_input_size * scale_factor + input_weight
} else {
non_input_size * scale_factor + input_weight + (self.sequences.len() as u64)
- inputs_with_witnesses
+ 2
}
}
}
impl From<Builder> for Template {
fn from(t: Builder) -> Template {
let tx = t.get_tx();
Template {
guards: t.guards,
outputs: t.outputs,
inputs: t.inputs,
ctv: tx.get_ctv_hash(0),
ctv_index: 0,
max: tx.total_amount() + t.fees,
min_feerate_sats_vbyte: t.min_feerate,
tx,
metadata_map_s2s: t.metadata,
}
}
}
impl From<Builder> for crate::contract::TxTmplIt {
fn from(t: Builder) -> Self {
Ok(Box::new(std::iter::once(Ok(t.into()))))
}
}