1use super::invalid_input_mass_variant;
10use crate::error::Error;
11use crate::imports::*;
12use crate::result::Result;
13use crate::{
14 CovenantBinding, Transaction, TransactionInput, TransactionInputInner, TransactionOutpoint, TransactionOutpointInner,
15 TransactionOutput, UtxoEntry, UtxoEntryId, UtxoEntryReference,
16};
17use ahash::AHashMap;
18use cctx::VerifiableTransaction;
19use kaspa_addresses::Address;
20use kaspa_consensus_core::subnets::SubnetworkId;
21use kaspa_hashes::Hash;
22use workflow_wasm::serde::{from_value, to_value};
23
24pub type SignedTransactionIndexType = u32;
25
26pub struct Options {
27 pub include_utxo: bool,
28}
29
30#[derive(Debug, Serialize, Deserialize, Clone)]
31#[serde(rename_all = "camelCase")]
32pub struct SerializableUtxoEntry {
33 pub address: Option<Address>,
34 pub amount: u64,
35 pub script_public_key: ScriptPublicKey,
36 pub block_daa_score: u64,
37 pub is_coinbase: bool,
38 pub covenant_id: Option<Hash>,
39}
40
41impl AsRef<SerializableUtxoEntry> for SerializableUtxoEntry {
42 fn as_ref(&self) -> &Self {
43 self
44 }
45}
46
47impl From<&UtxoEntryReference> for SerializableUtxoEntry {
48 fn from(utxo: &UtxoEntryReference) -> Self {
49 let utxo = utxo.utxo.as_ref();
50 Self {
51 address: utxo.address.clone(),
52 amount: utxo.amount,
53 script_public_key: utxo.script_public_key.clone(),
54 block_daa_score: utxo.block_daa_score,
55 is_coinbase: utxo.is_coinbase,
56 covenant_id: utxo.covenant_id,
57 }
58 }
59}
60
61impl From<&cctx::UtxoEntry> for SerializableUtxoEntry {
62 fn from(utxo: &cctx::UtxoEntry) -> Self {
63 Self {
64 address: None,
65 amount: utxo.amount,
66 script_public_key: utxo.script_public_key.clone(),
67 block_daa_score: utxo.block_daa_score,
68 is_coinbase: utxo.is_coinbase,
69 covenant_id: utxo.covenant_id,
70 }
71 }
72}
73
74impl TryFrom<&SerializableUtxoEntry> for cctx::UtxoEntry {
75 type Error = crate::error::Error;
76 fn try_from(utxo: &SerializableUtxoEntry) -> Result<Self> {
77 Ok(Self {
78 amount: utxo.amount,
79 script_public_key: utxo.script_public_key.clone(),
80 block_daa_score: utxo.block_daa_score,
81 is_coinbase: utxo.is_coinbase,
82 covenant_id: utxo.covenant_id,
83 })
84 }
85}
86
87#[derive(Debug, Serialize, Deserialize, Clone)]
88#[serde(rename_all = "camelCase")]
89pub struct SerializableTransactionInput {
90 pub transaction_id: TransactionId,
91 pub index: SignedTransactionIndexType,
92 pub sequence: u64,
93 pub sig_op_count: u8,
94 #[serde(default)]
95 pub compute_budget: u16,
96 #[serde(with = "hex::serde")]
97 pub signature_script: Vec<u8>,
99 pub utxo: SerializableUtxoEntry,
100}
101
102impl SerializableTransactionInput {
103 pub fn new(input: &cctx::TransactionInput, utxo: &cctx::UtxoEntry) -> Self {
104 let utxo = SerializableUtxoEntry::from(utxo);
105
106 Self {
107 transaction_id: input.previous_outpoint.transaction_id,
108 index: input.previous_outpoint.index,
109 signature_script: input.signature_script.clone(),
112 sequence: input.sequence,
113 sig_op_count: input.compute_commit.sig_op_count().unwrap_or(0),
114 compute_budget: input.compute_commit.compute_budget().unwrap_or(0),
115 utxo: utxo.clone(),
116 }
117 }
118}
119
120impl TryFrom<&SerializableTransactionInput> for UtxoEntryReference {
121 type Error = Error;
122 fn try_from(input: &SerializableTransactionInput) -> Result<Self> {
123 let outpoint = TransactionOutpoint::new(input.transaction_id, input.index);
124
125 let utxo = UtxoEntry {
126 outpoint,
127 address: input.utxo.address.clone(),
128 amount: input.utxo.amount,
129 script_public_key: input.utxo.script_public_key.clone(),
130 block_daa_score: input.utxo.block_daa_score,
131 is_coinbase: input.utxo.is_coinbase,
132 covenant_id: input.utxo.covenant_id,
133 };
134
135 Ok(Self { utxo: Arc::new(utxo) })
136 }
137}
138
139struct SerializableInputWithVersion {
140 version: u16,
141 input: SerializableTransactionInput,
142}
143
144impl TryFrom<SerializableInputWithVersion> for cctx::TransactionInput {
145 type Error = Error;
146
147 fn try_from(value: SerializableInputWithVersion) -> Result<Self> {
148 let input = value.input;
149 Ok(Self {
150 previous_outpoint: cctx::TransactionOutpoint { transaction_id: input.transaction_id, index: input.index },
151 signature_script: input.signature_script,
152 sequence: input.sequence,
153 compute_commit: if cctx::ComputeCommit::version_expects_compute_budget_field(value.version) {
154 if input.sig_op_count != 0 {
155 return Err(invalid_input_mass_variant("sig_op_count", value.version));
156 }
157 cctx::ComputeCommit::ComputeBudget(input.compute_budget.into())
158 } else {
159 if input.compute_budget != 0 {
160 return Err(invalid_input_mass_variant("compute_budget", value.version));
161 }
162 cctx::ComputeCommit::SigopCount(input.sig_op_count.into())
163 },
164 })
165 }
166}
167
168impl TryFrom<&SerializableTransactionInput> for TransactionInput {
169 type Error = Error;
170 fn try_from(serializable_input: &SerializableTransactionInput) -> Result<Self> {
171 let utxo = UtxoEntryReference::try_from(serializable_input)?;
172
173 let previous_outpoint = TransactionOutpoint::new(serializable_input.transaction_id, serializable_input.index);
174 let inner = TransactionInputInner {
175 previous_outpoint,
176 signature_script: (!serializable_input.signature_script.is_empty()).then_some(serializable_input.signature_script.clone()),
178 sequence: serializable_input.sequence,
179 sig_op_count: serializable_input.sig_op_count,
180 compute_budget: serializable_input.compute_budget,
181 utxo: Some(utxo),
182 };
183
184 Ok(TransactionInput::new_with_inner(inner))
185 }
186}
187
188impl TryFrom<&TransactionInput> for SerializableTransactionInput {
189 type Error = Error;
190 fn try_from(input: &TransactionInput) -> Result<Self> {
191 let inner = input.inner();
192 let utxo = inner.utxo.as_ref().ok_or(Error::MissingUtxoEntry)?;
193 let utxo = SerializableUtxoEntry::from(utxo);
194 Ok(Self {
195 transaction_id: inner.previous_outpoint.transaction_id(),
196 index: inner.previous_outpoint.index(),
197 signature_script: inner.signature_script.clone().unwrap_or_default(),
199 sequence: inner.sequence,
200 sig_op_count: inner.sig_op_count,
201 compute_budget: inner.compute_budget,
202 utxo,
203 })
204 }
205}
206
207#[derive(Debug, Serialize, Deserialize, Clone)]
208#[serde(rename_all = "camelCase")]
209pub struct SerializableCovenantBinding {
210 authorizing_input: u16,
211 covenant_id: Hash,
212}
213
214impl From<cctx::CovenantBinding> for SerializableCovenantBinding {
215 fn from(covenant: cctx::CovenantBinding) -> Self {
216 Self { authorizing_input: covenant.authorizing_input, covenant_id: covenant.covenant_id }
217 }
218}
219
220impl From<CovenantBinding> for SerializableCovenantBinding {
221 fn from(covenant: CovenantBinding) -> Self {
222 Self { authorizing_input: covenant.get_authorizing_input(), covenant_id: covenant.get_covenant_id() }
223 }
224}
225
226impl TryFrom<SerializableCovenantBinding> for cctx::CovenantBinding {
227 type Error = Error;
228 fn try_from(covenant: SerializableCovenantBinding) -> Result<Self> {
229 Ok(Self { authorizing_input: covenant.authorizing_input, covenant_id: covenant.covenant_id })
230 }
231}
232
233#[derive(Debug, Serialize, Deserialize, Clone)]
234#[serde(rename_all = "camelCase")]
235pub struct SerializableTransactionOutput {
236 pub value: u64,
237 pub script_public_key: ScriptPublicKey,
238 pub covenant: Option<SerializableCovenantBinding>,
239}
240
241impl From<cctx::TransactionOutput> for SerializableTransactionOutput {
242 fn from(output: cctx::TransactionOutput) -> Self {
243 let covenant = output.covenant.map(SerializableCovenantBinding::from);
244 Self { value: output.value, script_public_key: output.script_public_key, covenant }
245 }
246}
247
248impl From<&cctx::TransactionOutput> for SerializableTransactionOutput {
249 fn from(output: &cctx::TransactionOutput) -> Self {
250 let covenant = output.covenant.map(SerializableCovenantBinding::from);
251 Self { value: output.value, script_public_key: output.script_public_key.clone(), covenant }
252 }
253}
254
255impl TryFrom<SerializableTransactionOutput> for cctx::TransactionOutput {
256 type Error = Error;
257 fn try_from(output: SerializableTransactionOutput) -> Result<Self> {
258 let covenant = match output.covenant {
259 Some(covenant) => Some(cctx::CovenantBinding::try_from(covenant)?),
260 None => None,
261 };
262
263 Ok(Self { value: output.value, script_public_key: output.script_public_key, covenant })
264 }
265}
266
267impl TryFrom<&SerializableTransactionOutput> for TransactionOutput {
268 type Error = Error;
269 fn try_from(output: &SerializableTransactionOutput) -> Result<Self> {
270 let covenant = output.covenant.as_ref().map(|covenant| CovenantBinding::new(covenant.authorizing_input, covenant.covenant_id));
271
272 Ok(TransactionOutput::new(output.value, output.script_public_key.clone(), covenant))
273 }
274}
275
276impl TryFrom<&TransactionOutput> for SerializableTransactionOutput {
277 type Error = Error;
278 fn try_from(output: &TransactionOutput) -> Result<Self> {
279 let inner = output.inner();
280 let covenant = inner.covenant.clone().map(SerializableCovenantBinding::from);
281 Ok(Self { value: inner.value, script_public_key: inner.script_public_key.clone(), covenant })
282 }
283}
284
285#[derive(Debug, Serialize, Deserialize, Clone)]
286#[serde(rename_all = "camelCase")]
287pub struct SerializableTransaction {
288 pub id: TransactionId,
290 pub version: u16,
291 pub inputs: Vec<SerializableTransactionInput>,
292 pub outputs: Vec<SerializableTransactionOutput>,
293 pub lock_time: u64,
294 pub gas: u64,
295 #[serde(default, alias = "mass")]
296 pub storage_mass: u64,
297 pub subnetwork_id: SubnetworkId,
298 #[serde(with = "hex::serde")]
299 pub payload: Vec<u8>,
300}
301
302impl SerializableTransaction {
303 pub fn serialize_to_object(&self) -> Result<JsValue> {
304 Ok(to_value(self)?)
305 }
306
307 pub fn deserialize_from_object(object: JsValue) -> Result<Self> {
308 Ok(from_value(object)?)
309 }
310
311 pub fn serialize_to_json(&self) -> Result<String> {
312 Ok(serde_json::to_string(self)?)
313 }
314
315 pub fn deserialize_from_json(json: &str) -> Result<Self> {
316 Ok(serde_json::from_str(json)?)
317 }
318
319 pub fn from_signable_transaction(tx: &cctx::SignableTransaction) -> Result<Self> {
320 let verifiable_tx = tx.as_verifiable();
321 let mut inputs = vec![];
322 let transaction = tx.as_ref();
323 for index in 0..transaction.inputs.len() {
324 let (input, utxo) = verifiable_tx.populated_input(index);
325 let input = SerializableTransactionInput::new(input, utxo);
326 inputs.push(input);
327 }
328
329 let outputs = transaction.outputs.clone();
330
331 Ok(Self {
332 inputs,
334 outputs: outputs.into_iter().map(Into::into).collect(),
335 version: transaction.version,
336 lock_time: transaction.lock_time,
337 subnetwork_id: transaction.subnetwork_id,
338 gas: transaction.gas,
339 storage_mass: transaction.storage_mass(),
340 payload: transaction.payload.clone(),
341 id: transaction.id(),
342 })
343 }
344
345 pub fn from_client_transaction(transaction: &Transaction) -> Result<Self> {
346 let inner = transaction.inner();
347
348 let inputs = inner.inputs.iter().map(TryFrom::try_from).collect::<Result<Vec<SerializableTransactionInput>>>()?;
349 let outputs = inner.outputs.iter().map(TryFrom::try_from).collect::<Result<Vec<SerializableTransactionOutput>>>()?;
350
351 Ok(Self {
352 inputs,
353 outputs,
354 version: inner.version,
355 lock_time: inner.lock_time,
356 subnetwork_id: inner.subnetwork_id,
357 gas: inner.gas,
358 payload: inner.payload.clone(),
359 storage_mass: inner.storage_mass,
360 id: inner.id,
361 })
362 }
363
364 pub fn from_cctx_transaction(transaction: &cctx::Transaction, utxos: &AHashMap<UtxoEntryId, UtxoEntryReference>) -> Result<Self> {
365 let inputs = transaction
366 .inputs
367 .iter()
368 .map(|input| {
369 let id = TransactionOutpointInner::new(input.previous_outpoint.transaction_id, input.previous_outpoint.index);
370 let utxo = utxos.get(&id).ok_or(Error::MissingUtxoEntry)?;
371 let utxo = cctx::UtxoEntry::from(utxo);
372 let input = SerializableTransactionInput::new(input, &utxo);
373 Ok(input)
374 })
375 .collect::<Result<Vec<SerializableTransactionInput>>>()?;
376
377 let outputs = transaction.outputs.iter().map(Into::into).collect::<Vec<SerializableTransactionOutput>>();
378
379 Ok(Self {
380 id: transaction.id(),
381 version: transaction.version,
382 inputs,
383 outputs,
384 lock_time: transaction.lock_time,
385 subnetwork_id: transaction.subnetwork_id,
386 gas: transaction.gas,
387 storage_mass: transaction.storage_mass(),
388 payload: transaction.payload.clone(),
389 })
390 }
391}
392
393impl TryFrom<SerializableTransaction> for cctx::SignableTransaction {
394 type Error = Error;
395 fn try_from(serializable: SerializableTransaction) -> Result<Self> {
396 let version = serializable.version;
397 let mut entries = vec![];
398 let mut inputs = vec![];
399 for input in serializable.inputs {
400 entries.push(input.utxo.as_ref().try_into()?);
401 inputs.push(SerializableInputWithVersion { version, input }.try_into()?);
402 }
403
404 let outputs = serializable.outputs.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
405
406 let tx = cctx::Transaction::new(
407 version,
408 inputs,
409 outputs,
410 serializable.lock_time,
411 serializable.subnetwork_id,
412 serializable.gas,
413 serializable.payload,
414 )
415 .with_storage_mass(serializable.storage_mass);
416
417 Ok(Self::with_entries(tx, entries))
418 }
419}
420
421impl TryFrom<SerializableTransaction> for Transaction {
422 type Error = Error;
423 fn try_from(tx: SerializableTransaction) -> Result<Self> {
424 let id = tx.id;
425 let inputs: Vec<TransactionInput> = tx.inputs.iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
426 let outputs: Vec<TransactionOutput> = tx.outputs.iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
427
428 Transaction::new(Some(id), tx.version, inputs, outputs, tx.lock_time, tx.subnetwork_id, tx.gas, tx.payload, tx.storage_mass)
429 }
430}