smplx_sdk/transaction/
final_transaction.rs1use std::collections::HashMap;
2
3use simplicityhl::elements::pset::PartiallySignedTransaction;
4use simplicityhl::elements::{
5 AssetId, TxOutSecrets,
6 confidential::{AssetBlindingFactor, ValueBlindingFactor},
7};
8
9use crate::provider::SimplicityNetwork;
10use crate::utils::asset_entropy;
11
12use super::partial_input::{IssuanceInput, PartialInput, ProgramInput, RequiredSignature};
13use super::partial_output::PartialOutput;
14
15pub const WITNESS_SCALE_FACTOR: usize = 4;
16
17#[derive(Clone)]
18pub struct FinalInput {
19 pub partial_input: PartialInput,
20 pub program_input: Option<ProgramInput>,
21 pub issuance_input: Option<IssuanceInput>,
22 pub required_sig: RequiredSignature,
23}
24
25#[derive(Clone)]
26pub struct FinalTransaction {
27 inputs: Vec<FinalInput>,
28 outputs: Vec<PartialOutput>,
29}
30
31impl FinalTransaction {
32 #[allow(clippy::new_without_default)]
33 pub fn new() -> Self {
34 Self {
35 inputs: Vec::new(),
36 outputs: Vec::new(),
37 }
38 }
39
40 pub fn add_input(&mut self, partial_input: PartialInput, required_sig: RequiredSignature) {
41 if let RequiredSignature::Witness(_) = required_sig {
42 panic!("Requested signature is not NativeEcdsa or None");
43 }
44
45 self.inputs.push(FinalInput {
46 partial_input,
47 program_input: None,
48 issuance_input: None,
49 required_sig,
50 });
51 }
52
53 pub fn add_program_input(
54 &mut self,
55 partial_input: PartialInput,
56 program_input: ProgramInput,
57 required_sig: RequiredSignature,
58 ) {
59 if let RequiredSignature::NativeEcdsa = required_sig {
60 panic!("Requested signature is not Witness or None");
61 }
62
63 self.inputs.push(FinalInput {
64 partial_input,
65 program_input: Some(program_input),
66 issuance_input: None,
67 required_sig,
68 });
69 }
70
71 pub fn add_issuance_input(
72 &mut self,
73 partial_input: PartialInput,
74 issuance_input: IssuanceInput,
75 required_sig: RequiredSignature,
76 ) -> AssetId {
77 if let RequiredSignature::Witness(_) = required_sig {
78 panic!("Requested signature is not NativeEcdsa or None");
79 }
80
81 let asset_id = AssetId::from_entropy(asset_entropy(&partial_input.outpoint(), issuance_input.asset_entropy));
82
83 self.inputs.push(FinalInput {
84 partial_input,
85 program_input: None,
86 issuance_input: Some(issuance_input),
87 required_sig,
88 });
89
90 asset_id
91 }
92
93 pub fn add_program_issuance_input(
94 &mut self,
95 partial_input: PartialInput,
96 program_input: ProgramInput,
97 issuance_input: IssuanceInput,
98 required_sig: RequiredSignature,
99 ) -> AssetId {
100 if let RequiredSignature::NativeEcdsa = required_sig {
101 panic!("Requested signature is not Witness or None");
102 }
103
104 let asset_id = AssetId::from_entropy(asset_entropy(&partial_input.outpoint(), issuance_input.asset_entropy));
105
106 self.inputs.push(FinalInput {
107 partial_input,
108 program_input: Some(program_input),
109 issuance_input: Some(issuance_input),
110 required_sig,
111 });
112
113 asset_id
114 }
115
116 pub fn remove_input(&mut self, index: usize) -> Option<FinalInput> {
117 if self.inputs.get(index).is_some() {
118 return Some(self.inputs.remove(index));
119 }
120
121 None
122 }
123
124 pub fn add_output(&mut self, partial_output: PartialOutput) {
125 self.outputs.push(partial_output);
126 }
127
128 pub fn remove_output(&mut self, index: usize) -> Option<PartialOutput> {
129 if self.outputs.get(index).is_some() {
130 return Some(self.outputs.remove(index));
131 }
132
133 None
134 }
135
136 pub fn inputs(&self) -> &[FinalInput] {
137 &self.inputs
138 }
139
140 pub fn inputs_mut(&mut self) -> &mut [FinalInput] {
141 &mut self.inputs
142 }
143
144 pub fn outputs(&self) -> &[PartialOutput] {
145 &self.outputs
146 }
147
148 pub fn outputs_mut(&mut self) -> &mut [PartialOutput] {
149 &mut self.outputs
150 }
151
152 pub fn n_inputs(&self) -> usize {
153 self.inputs.len()
154 }
155
156 pub fn n_outputs(&self) -> usize {
157 self.outputs.len()
158 }
159
160 pub fn needs_blinding(&self) -> bool {
161 self.outputs.iter().any(|el| el.blinding_key.is_some())
162 }
163
164 pub fn calculate_fee_delta(&self, network: &SimplicityNetwork) -> i64 {
165 let mut available_amount = 0;
166
167 for input in &self.inputs {
168 match input.partial_input.secrets {
169 Some(secrets) => {
171 if secrets.asset == network.policy_asset() {
172 available_amount += secrets.value;
173 }
174 }
175 None => {
177 if input.partial_input.asset.unwrap() == network.policy_asset() {
178 available_amount += input.partial_input.amount.unwrap();
179 }
180 }
181 }
182 }
183
184 let consumed_amount = self
185 .outputs
186 .iter()
187 .filter(|output| output.asset == network.policy_asset())
188 .fold(0_u64, |acc, output| acc + output.amount);
189
190 available_amount as i64 - consumed_amount as i64
191 }
192
193 pub fn calculate_fee(&self, weight: usize, fee_rate: f32) -> u64 {
194 let vsize = weight.div_ceil(WITNESS_SCALE_FACTOR);
195
196 (vsize as f32 * fee_rate / 1000.0).ceil() as u64
197 }
198
199 pub fn extract_pst(&self) -> (PartiallySignedTransaction, HashMap<usize, TxOutSecrets>) {
200 let mut input_secrets = HashMap::new();
201 let mut pst = PartiallySignedTransaction::new_v2();
202
203 for i in 0..self.inputs.len() {
204 let final_input = &self.inputs[i];
205 let mut pst_input = final_input.partial_input.to_input();
206
207 if final_input.issuance_input.is_some() {
209 let issue = final_input.issuance_input.clone().unwrap().to_input();
210
211 pst_input.issuance_value_amount = issue.issuance_value_amount;
212 pst_input.issuance_asset_entropy = issue.issuance_asset_entropy;
213 pst_input.issuance_inflation_keys = issue.issuance_inflation_keys;
214 pst_input.blinded_issuance = issue.blinded_issuance;
215 }
216
217 match final_input.partial_input.secrets {
218 Some(secrets) => input_secrets.insert(i, secrets),
220 None => input_secrets.insert(
222 i,
223 TxOutSecrets {
224 asset: pst_input.asset.unwrap(),
225 asset_bf: AssetBlindingFactor::zero(),
226 value: pst_input.amount.unwrap(),
227 value_bf: ValueBlindingFactor::zero(),
228 },
229 ),
230 };
231
232 pst.add_input(pst_input);
233 }
234
235 self.outputs.iter().for_each(|el| {
236 pst.add_output(el.to_output());
237 });
238
239 (pst, input_secrets)
240 }
241}