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