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: 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        match required_sig {
43            RequiredSignature::Witness(_) => {
44                return Err(TransactionError::SignatureRequest(
45                    "Requested signature is not NativeEcdsa or None".to_string(),
46                ));
47            }
48            _ => {}
49        }
50
51        self.inputs.push(FinalInput {
52            partial_input: partial_input,
53            program_input: None,
54            issuance_input: None,
55            required_sig: required_sig,
56        });
57
58        Ok(())
59    }
60
61    pub fn add_program_input(
62        &mut self,
63        partial_input: PartialInput,
64        program_input: ProgramInput,
65        required_sig: RequiredSignature,
66    ) -> Result<(), TransactionError> {
67        match required_sig {
68            RequiredSignature::NativeEcdsa => {
69                return Err(TransactionError::SignatureRequest(
70                    "Requested signature is not Witness or None".to_string(),
71                ));
72            }
73            _ => {}
74        }
75
76        self.inputs.push(FinalInput {
77            partial_input: partial_input,
78            program_input: Some(program_input),
79            issuance_input: None,
80            required_sig: required_sig,
81        });
82
83        Ok(())
84    }
85
86    pub fn add_issuance_input(
87        &mut self,
88        partial_input: PartialInput,
89        issuance_input: IssuanceInput,
90        required_sig: RequiredSignature,
91    ) -> Result<AssetId, TransactionError> {
92        match required_sig {
93            RequiredSignature::Witness(_) => {
94                return Err(TransactionError::SignatureRequest(
95                    "Requested signature is not NativeEcdsa or None".to_string(),
96                ));
97            }
98            _ => {}
99        }
100
101        let asset_id = AssetId::from_entropy(asset_entropy(&partial_input.outpoint(), issuance_input.asset_entropy));
102
103        self.inputs.push(FinalInput {
104            partial_input: partial_input,
105            program_input: None,
106            issuance_input: Some(issuance_input),
107            required_sig: required_sig,
108        });
109
110        Ok(asset_id)
111    }
112
113    pub fn add_program_issuance_input(
114        &mut self,
115        partial_input: PartialInput,
116        program_input: ProgramInput,
117        issuance_input: IssuanceInput,
118        required_sig: RequiredSignature,
119    ) -> Result<AssetId, TransactionError> {
120        match required_sig {
121            RequiredSignature::NativeEcdsa => {
122                return Err(TransactionError::SignatureRequest(
123                    "Requested signature is not Witness or None".to_string(),
124                ));
125            }
126            _ => {}
127        }
128
129        let asset_id = AssetId::from_entropy(asset_entropy(&partial_input.outpoint(), issuance_input.asset_entropy));
130
131        self.inputs.push(FinalInput {
132            partial_input: partial_input,
133            program_input: Some(program_input),
134            issuance_input: Some(issuance_input),
135            required_sig: required_sig,
136        });
137
138        Ok(asset_id)
139    }
140
141    pub fn remove_input(&mut self, index: usize) -> Option<FinalInput> {
142        if self.inputs.get(index).is_some() {
143            return Some(self.inputs.remove(index));
144        }
145
146        None
147    }
148
149    pub fn add_output(&mut self, partial_output: PartialOutput) {
150        self.outputs.push(partial_output);
151    }
152
153    pub fn remove_output(&mut self, index: usize) -> Option<PartialOutput> {
154        if self.outputs.get(index).is_some() {
155            return Some(self.outputs.remove(index));
156        }
157
158        None
159    }
160
161    pub fn inputs(&self) -> &[FinalInput] {
162        &self.inputs
163    }
164
165    pub fn inputs_mut(&mut self) -> &mut [FinalInput] {
166        &mut self.inputs
167    }
168
169    pub fn outputs(&self) -> &[PartialOutput] {
170        &self.outputs
171    }
172
173    pub fn outputs_mut(&mut self) -> &mut [PartialOutput] {
174        &mut self.outputs
175    }
176
177    pub fn n_inputs(&self) -> usize {
178        self.inputs.len()
179    }
180
181    pub fn n_outputs(&self) -> usize {
182        self.outputs.len()
183    }
184
185    pub fn calculate_fee_delta(&self) -> i64 {
186        let available_amount = self
187            .inputs
188            .iter()
189            .filter(|input| input.partial_input.asset.clone().unwrap() == self.network.policy_asset())
190            .fold(0 as u64, |acc, input| acc + input.partial_input.amount.clone().unwrap());
191
192        let consumed_amount = self
193            .outputs
194            .iter()
195            .filter(|output| output.asset == self.network.policy_asset())
196            .fold(0 as u64, |acc, output| acc + output.amount);
197
198        available_amount as i64 - consumed_amount as i64
199    }
200
201    pub fn calculate_fee(&self, weight: usize, fee_rate: f32) -> u64 {
202        let vsize = weight.div_ceil(WITNESS_SCALE_FACTOR);
203
204        (vsize as f32 * fee_rate / 1000.0).ceil() as u64
205    }
206
207    pub fn extract_pst(&self) -> PartiallySignedTransaction {
208        let mut pst = PartiallySignedTransaction::new_v2();
209
210        self.inputs.iter().for_each(|el| {
211            let mut input = el.partial_input.input();
212
213            // populate the input manually since `input.merge` is private
214            if el.issuance_input.is_some() {
215                let issue = el.issuance_input.clone().unwrap().input();
216
217                input.issuance_value_amount = issue.issuance_value_amount;
218                input.issuance_asset_entropy = issue.issuance_asset_entropy;
219                input.issuance_inflation_keys = issue.issuance_inflation_keys;
220                input.blinded_issuance = issue.blinded_issuance;
221            }
222
223            pst.add_input(input);
224        });
225
226        self.outputs.iter().for_each(|el| {
227            pst.add_output(el.to_output());
228        });
229
230        pst
231    }
232}