use crate::imports::*;
use crate::result::Result;
use crate::{
Transaction, TransactionInput, TransactionInputInner, TransactionOutpoint, TransactionOutpointInner, TransactionOutput, UtxoEntry,
UtxoEntryId, UtxoEntryReference,
};
use ahash::AHashMap;
use cctx::VerifiableTransaction;
use kaspa_addresses::Address;
use kaspa_consensus_core::subnets::SubnetworkId;
use workflow_wasm::serde::{from_value, to_value};
pub type SignedTransactionIndexType = u32;
pub struct Options {
pub include_utxo: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SerializableUtxoEntry {
pub address: Option<Address>,
pub amount: String,
pub script_public_key: ScriptPublicKey,
pub block_daa_score: String,
pub is_coinbase: bool,
}
impl AsRef<SerializableUtxoEntry> for SerializableUtxoEntry {
fn as_ref(&self) -> &Self {
self
}
}
impl From<&UtxoEntryReference> for SerializableUtxoEntry {
fn from(utxo: &UtxoEntryReference) -> Self {
let utxo = utxo.utxo.as_ref();
Self {
address: utxo.address.clone(),
amount: utxo.amount.to_string(),
script_public_key: utxo.script_public_key.clone(),
block_daa_score: utxo.block_daa_score.to_string(),
is_coinbase: utxo.is_coinbase,
}
}
}
impl From<&cctx::UtxoEntry> for SerializableUtxoEntry {
fn from(utxo: &cctx::UtxoEntry) -> Self {
Self {
address: None,
amount: utxo.amount.to_string(),
script_public_key: utxo.script_public_key.clone(),
block_daa_score: utxo.block_daa_score.to_string(),
is_coinbase: utxo.is_coinbase,
}
}
}
impl TryFrom<&SerializableUtxoEntry> for cctx::UtxoEntry {
type Error = crate::error::Error;
fn try_from(utxo: &SerializableUtxoEntry) -> Result<Self> {
Ok(Self {
amount: utxo.amount.parse()?,
script_public_key: utxo.script_public_key.clone(),
block_daa_score: utxo.block_daa_score.parse()?,
is_coinbase: utxo.is_coinbase,
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SerializableTransactionInput {
pub transaction_id: TransactionId,
pub index: SignedTransactionIndexType,
pub sequence: String,
pub sig_op_count: u8,
#[serde(with = "hex::serde")]
pub signature_script: Vec<u8>,
pub utxo: SerializableUtxoEntry,
}
impl SerializableTransactionInput {
pub fn new(input: &cctx::TransactionInput, utxo: &cctx::UtxoEntry) -> Self {
let utxo = SerializableUtxoEntry::from(utxo);
Self {
transaction_id: input.previous_outpoint.transaction_id,
index: input.previous_outpoint.index,
signature_script: input.signature_script.clone(),
sequence: input.sequence.to_string(),
sig_op_count: input.sig_op_count,
utxo: utxo.clone(),
}
}
}
impl TryFrom<&SerializableTransactionInput> for UtxoEntryReference {
type Error = Error;
fn try_from(input: &SerializableTransactionInput) -> Result<Self> {
let outpoint = TransactionOutpoint::new(input.transaction_id, input.index);
let utxo = UtxoEntry {
outpoint,
address: input.utxo.address.clone(),
amount: input.utxo.amount.parse()?,
script_public_key: input.utxo.script_public_key.clone(),
block_daa_score: input.utxo.block_daa_score.parse()?,
is_coinbase: input.utxo.is_coinbase,
};
Ok(Self { utxo: Arc::new(utxo) })
}
}
impl TryFrom<SerializableTransactionInput> for cctx::TransactionInput {
type Error = Error;
fn try_from(signable_input: SerializableTransactionInput) -> Result<Self> {
Ok(Self {
previous_outpoint: cctx::TransactionOutpoint {
transaction_id: signable_input.transaction_id,
index: signable_input.index,
},
signature_script: signable_input.signature_script,
sequence: signable_input.sequence.parse()?,
sig_op_count: signable_input.sig_op_count,
})
}
}
impl TryFrom<&SerializableTransactionInput> for TransactionInput {
type Error = Error;
fn try_from(serializable_input: &SerializableTransactionInput) -> Result<Self> {
let utxo = UtxoEntryReference::try_from(serializable_input)?;
let previous_outpoint = TransactionOutpoint::new(serializable_input.transaction_id, serializable_input.index);
let inner = TransactionInputInner {
previous_outpoint,
signature_script: (!serializable_input.signature_script.is_empty()).then_some(serializable_input.signature_script.clone()),
sequence: serializable_input.sequence.parse()?,
sig_op_count: serializable_input.sig_op_count,
utxo: Some(utxo),
};
Ok(TransactionInput::new_with_inner(inner))
}
}
impl TryFrom<&TransactionInput> for SerializableTransactionInput {
type Error = Error;
fn try_from(input: &TransactionInput) -> Result<Self> {
let inner = input.inner();
let utxo = inner.utxo.as_ref().ok_or(Error::MissingUtxoEntry)?;
let utxo = SerializableUtxoEntry::from(utxo);
Ok(Self {
transaction_id: inner.previous_outpoint.transaction_id(),
index: inner.previous_outpoint.index(),
signature_script: inner.signature_script.clone().unwrap_or_default(),
sequence: inner.sequence.to_string(),
sig_op_count: inner.sig_op_count,
utxo,
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SerializableTransactionOutput {
pub value: String,
pub script_public_key: ScriptPublicKey,
}
impl From<cctx::TransactionOutput> for SerializableTransactionOutput {
fn from(output: cctx::TransactionOutput) -> Self {
Self { value: output.value.to_string(), script_public_key: output.script_public_key }
}
}
impl From<&cctx::TransactionOutput> for SerializableTransactionOutput {
fn from(output: &cctx::TransactionOutput) -> Self {
Self { value: output.value.to_string(), script_public_key: output.script_public_key.clone() }
}
}
impl TryFrom<SerializableTransactionOutput> for cctx::TransactionOutput {
type Error = Error;
fn try_from(output: SerializableTransactionOutput) -> Result<Self> {
Ok(Self { value: output.value.parse()?, script_public_key: output.script_public_key })
}
}
impl TryFrom<&SerializableTransactionOutput> for TransactionOutput {
type Error = Error;
fn try_from(output: &SerializableTransactionOutput) -> Result<Self> {
Ok(TransactionOutput::new(output.value.parse()?, output.script_public_key.clone()))
}
}
impl TryFrom<&TransactionOutput> for SerializableTransactionOutput {
type Error = Error;
fn try_from(output: &TransactionOutput) -> Result<Self> {
let inner = output.inner();
Ok(Self { value: inner.value.to_string(), script_public_key: inner.script_public_key.clone() })
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SerializableTransaction {
pub id: TransactionId,
pub version: u16,
pub inputs: Vec<SerializableTransactionInput>,
pub outputs: Vec<SerializableTransactionOutput>,
pub subnetwork_id: SubnetworkId,
pub lock_time: String,
pub gas: String,
#[serde(default)]
pub mass: String,
#[serde(with = "hex::serde")]
pub payload: Vec<u8>,
}
impl SerializableTransaction {
pub fn serialize_to_object(&self) -> Result<JsValue> {
Ok(to_value(self)?)
}
pub fn deserialize_from_object(object: JsValue) -> Result<Self> {
Ok(from_value(object)?)
}
pub fn serialize_to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
pub fn deserialize_from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
pub fn from_signable_transaction(tx: &cctx::SignableTransaction) -> Result<Self> {
let verifiable_tx = tx.as_verifiable();
let mut inputs = vec![];
let transaction = tx.as_ref();
for index in 0..transaction.inputs.len() {
let (input, utxo) = verifiable_tx.populated_input(index);
let input = SerializableTransactionInput::new(input, utxo);
inputs.push(input);
}
let outputs = transaction.outputs.clone();
Ok(Self {
id: transaction.id(),
inputs,
version: transaction.version,
outputs: outputs.into_iter().map(Into::into).collect(),
lock_time: transaction.lock_time.to_string(),
subnetwork_id: transaction.subnetwork_id.clone(),
gas: transaction.gas.to_string(),
mass: transaction.mass().to_string(),
payload: transaction.payload.clone(),
})
}
pub fn from_client_transaction(transaction: &Transaction) -> Result<Self> {
let inner = transaction.inner();
let inputs = inner.inputs.iter().map(TryFrom::try_from).collect::<Result<Vec<SerializableTransactionInput>>>()?;
let outputs = inner.outputs.iter().map(TryFrom::try_from).collect::<Result<Vec<SerializableTransactionOutput>>>()?;
Ok(Self {
inputs,
outputs,
version: inner.version,
lock_time: inner.lock_time.to_string(),
subnetwork_id: inner.subnetwork_id.clone(),
gas: inner.gas.to_string(),
mass: inner.mass.to_string(),
payload: inner.payload.clone(),
id: inner.id,
})
}
pub fn from_cctx_transaction(transaction: &cctx::Transaction, utxos: &AHashMap<UtxoEntryId, UtxoEntryReference>) -> Result<Self> {
let inputs = transaction
.inputs
.iter()
.map(|input| {
let id = TransactionOutpointInner::new(input.previous_outpoint.transaction_id, input.previous_outpoint.index);
let utxo = utxos.get(&id).ok_or(Error::MissingUtxoEntry)?;
let utxo = cctx::UtxoEntry::from(utxo);
let input = SerializableTransactionInput::new(input, &utxo);
Ok(input)
})
.collect::<Result<Vec<SerializableTransactionInput>>>()?;
let outputs = transaction.outputs.iter().map(Into::into).collect::<Vec<SerializableTransactionOutput>>();
Ok(Self {
id: transaction.id(),
version: transaction.version,
inputs,
outputs,
lock_time: transaction.lock_time.to_string(),
subnetwork_id: transaction.subnetwork_id.clone(),
gas: transaction.gas.to_string(),
mass: transaction.mass().to_string(),
payload: transaction.payload.clone(),
})
}
}
impl TryFrom<SerializableTransaction> for cctx::SignableTransaction {
type Error = Error;
fn try_from(signable: SerializableTransaction) -> Result<Self> {
let mut entries = vec![];
let mut inputs = vec![];
for input in signable.inputs {
entries.push(input.utxo.as_ref().try_into()?);
inputs.push(input.try_into()?);
}
let outputs = signable.outputs.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
let tx = cctx::Transaction::new(
signable.version,
inputs,
outputs,
signable.lock_time.parse()?,
signable.subnetwork_id,
signable.gas.parse()?,
signable.payload,
)
.with_mass(signable.mass.parse().unwrap_or_default());
Ok(Self::with_entries(tx, entries))
}
}
impl TryFrom<SerializableTransaction> for crate::Transaction {
type Error = Error;
fn try_from(tx: SerializableTransaction) -> Result<Self> {
let id = tx.id;
let inputs: Vec<TransactionInput> = tx.inputs.iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
let outputs: Vec<TransactionOutput> = tx.outputs.iter().map(TryInto::try_into).collect::<Result<Vec<_>>>()?;
Transaction::new(
Some(id),
tx.version,
inputs,
outputs,
tx.lock_time.parse()?,
tx.subnetwork_id,
tx.gas.parse()?,
tx.payload,
tx.mass.parse().unwrap_or_default(),
)
}
}