hal_simplicity/actions/simplicity/pset/
create.rs1use std::collections::HashMap;
5
6use elements::confidential;
7use elements::pset::PartiallySignedTransaction;
8use elements::{Address, AssetId, OutPoint, Transaction, TxIn, TxOut, Txid};
9use serde::Deserialize;
10
11use super::{PsetError, UpdatedPset};
12
13#[derive(Debug, thiserror::Error)]
14pub enum PsetCreateError {
15 #[error(transparent)]
16 SharedError(#[from] PsetError),
17
18 #[error("invalid inputs JSON: {0}")]
19 InputsJsonParse(serde_json::Error),
20
21 #[error("invalid outputs JSON: {0}")]
22 OutputsJsonParse(serde_json::Error),
23
24 #[error("invalid amount: {0}")]
25 AmountParse(elements::bitcoin::amount::ParseAmountError),
26
27 #[error("invalid address: {0}")]
28 AddressParse(elements::address::AddressError),
29
30 #[error("confidential addresses are not yet supported")]
31 ConfidentialAddressNotSupported,
32}
33
34#[derive(Deserialize)]
35struct InputSpec {
36 txid: Txid,
37 vout: u32,
38 #[serde(default)]
39 sequence: Option<u32>,
40}
41
42#[derive(Deserialize)]
43struct FlattenedOutputSpec {
44 address: String,
45 asset: AssetId,
46 #[serde(with = "elements::bitcoin::amount::serde::as_btc")]
47 amount: elements::bitcoin::Amount,
48}
49
50#[derive(Deserialize)]
51#[serde(untagged)]
52enum OutputSpec {
53 Explicit {
54 address: String,
55 asset: AssetId,
56 #[serde(with = "elements::bitcoin::amount::serde::as_btc")]
57 amount: elements::bitcoin::Amount,
58 },
59 Map(HashMap<String, f64>),
60}
61
62impl OutputSpec {
63 fn flatten(self) -> Box<dyn Iterator<Item = Result<FlattenedOutputSpec, PsetCreateError>>> {
64 match self {
65 Self::Map(map) => Box::new(map.into_iter().map(|(address, amount)| {
66 let default_asset = AssetId::from_slice(&[
68 0x49, 0x9a, 0x81, 0x85, 0x45, 0xf6, 0xba, 0xe3, 0x9f, 0xc0, 0x3b, 0x63, 0x7f,
69 0x2a, 0x4e, 0x1e, 0x64, 0xe5, 0x90, 0xca, 0xc1, 0xbc, 0x3a, 0x6f, 0x6d, 0x71,
70 0xaa, 0x44, 0x43, 0x65, 0x4c, 0x14,
71 ])
72 .expect("valid asset id");
73
74 Ok(FlattenedOutputSpec {
75 address,
76 asset: default_asset,
77 amount: elements::bitcoin::Amount::from_btc(amount)
78 .map_err(PsetCreateError::AmountParse)?,
79 })
80 })),
81 Self::Explicit {
82 address,
83 asset,
84 amount,
85 } => Box::new(
86 Some(Ok(FlattenedOutputSpec {
87 address,
88 asset,
89 amount,
90 }))
91 .into_iter(),
92 ),
93 }
94 }
95}
96
97pub fn pset_create(inputs_json: &str, outputs_json: &str) -> Result<UpdatedPset, PsetCreateError> {
99 let input_specs: Vec<InputSpec> =
101 serde_json::from_str(inputs_json).map_err(PsetCreateError::InputsJsonParse)?;
102
103 let output_specs: Vec<OutputSpec> =
105 serde_json::from_str(outputs_json).map_err(PsetCreateError::OutputsJsonParse)?;
106
107 let mut inputs = Vec::new();
109 for input_spec in &input_specs {
110 let outpoint = OutPoint::new(input_spec.txid, input_spec.vout);
111 let sequence = elements::Sequence(input_spec.sequence.unwrap_or(0xffffffff));
112
113 inputs.push(TxIn {
114 previous_output: outpoint,
115 script_sig: elements::Script::new(),
116 sequence,
117 asset_issuance: Default::default(),
118 witness: Default::default(),
119 is_pegin: false,
120 });
121 }
122
123 let mut outputs = Vec::new();
125 for output_spec in output_specs.into_iter().flat_map(OutputSpec::flatten) {
126 let output_spec = output_spec?; let script_pubkey = match output_spec.address.as_str() {
129 "fee" => elements::Script::new(),
130 x => {
131 let addr = x.parse::<Address>().map_err(PsetCreateError::AddressParse)?;
132 if addr.is_blinded() {
133 return Err(PsetCreateError::ConfidentialAddressNotSupported);
134 }
135 addr.script_pubkey()
136 }
137 };
138
139 outputs.push(TxOut {
140 asset: confidential::Asset::Explicit(output_spec.asset),
141 value: confidential::Value::Explicit(output_spec.amount.to_sat()),
142 nonce: elements::confidential::Nonce::Null,
143 script_pubkey,
144 witness: elements::TxOutWitness::empty(),
145 });
146 }
147
148 let tx = Transaction {
150 version: 2,
151 lock_time: elements::LockTime::ZERO,
152 input: inputs,
153 output: outputs,
154 };
155
156 let pset = PartiallySignedTransaction::from_tx(tx);
158
159 Ok(UpdatedPset {
160 pset: pset.to_string(),
161 updated_values: vec![
162 ],
166 })
167}