Skip to main content

smplx_sdk/transaction/
final_transaction.rs

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