diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 771357211d..90763c10ea 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -474,7 +474,7 @@ impl KaspaCli { if matches.is_empty() { Err(Error::AccountNotFound(pat.to_string())) } else if matches.len() > 1 { - Err(Error::AmbigiousAccount(pat.to_string())) + Err(Error::AmbiguousAccount(pat.to_string())) } else { Ok(matches[0].clone()) } diff --git a/cli/src/error.rs b/cli/src/error.rs index 9a80d41f36..180f96d863 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -51,8 +51,8 @@ pub enum Error { #[error("account '{0}' not found")] AccountNotFound(String), - #[error("ambigious selection, pattern '{0}' matches too many accounts, please be more specific")] - AmbigiousAccount(String), + #[error("ambiguous selection, pattern '{0}' matches too many accounts, please be more specific")] + AmbiguousAccount(String), #[error("please create a wallet")] WalletDoesNotExist, @@ -109,7 +109,7 @@ pub enum Error { Dom(#[from] workflow_dom::error::Error), #[error(transparent)] - NetworkId(#[from] kaspa_consensus_core::networktype::NetworkIdError), + NetworkId(#[from] kaspa_consensus_core::network::NetworkIdError), #[error(transparent)] Bip32(#[from] kaspa_bip32::Error), diff --git a/cli/src/imports.rs b/cli/src/imports.rs index 5c955fadf3..e9a8009fb6 100644 --- a/cli/src/imports.rs +++ b/cli/src/imports.rs @@ -9,10 +9,10 @@ pub use borsh::{BorshDeserialize, BorshSerialize}; pub use cfg_if::cfg_if; pub use futures::stream::{Stream, StreamExt, TryStreamExt}; pub use futures::{future::FutureExt, select, Future}; +pub use kaspa_consensus_core::network::{NetworkId, NetworkType}; pub use kaspa_daemon::DaemonEvent; pub use kaspa_utils::hex::*; pub use kaspa_wallet_core::derivation::gen0::import::*; -pub use kaspa_wallet_core::network::{NetworkId, NetworkType}; pub use kaspa_wallet_core::storage::interface::{AccessContext, Interface}; pub use kaspa_wallet_core::storage::{AccessContextT, AccountKind, IdT, PrvKeyDataId, PrvKeyDataInfo}; pub use kaspa_wallet_core::tx::PaymentOutputs; diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index 9131ab593b..f0975cc3fd 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -4,7 +4,7 @@ pub use super::{ genesis::{GenesisBlock, DEVNET_GENESIS, GENESIS, SIMNET_GENESIS, TESTNET11_GENESIS, TESTNET_GENESIS}, }; use crate::{ - networktype::{NetworkId, NetworkType}, + network::{NetworkId, NetworkType}, BlockLevel, KType, }; use kaspa_addresses::Prefix; @@ -223,7 +223,7 @@ impl Params { } pub fn network_name(&self) -> String { - self.net.name() + self.net.to_prefixed() } pub fn prefix(&self) -> Prefix { diff --git a/consensus/core/src/lib.rs b/consensus/core/src/lib.rs index 5fcf7b5179..ae47654839 100644 --- a/consensus/core/src/lib.rs +++ b/consensus/core/src/lib.rs @@ -22,7 +22,7 @@ pub mod header; pub mod mass; pub mod merkle; pub mod muhash; -pub mod networktype; +pub mod network; pub mod pruning; pub mod sign; pub mod subnets; diff --git a/consensus/core/src/networktype.rs b/consensus/core/src/network.rs similarity index 55% rename from consensus/core/src/networktype.rs rename to consensus/core/src/network.rs index cda8280576..2ce3f105d5 100644 --- a/consensus/core/src/networktype.rs +++ b/consensus/core/src/network.rs @@ -1,11 +1,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use kaspa_addresses::Prefix; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; use std::str::FromStr; use wasm_bindgen::prelude::*; use workflow_core::enums::u8_try_from; +use workflow_wasm::abi::ref_from_abi; #[derive(thiserror::Error, PartialEq, Eq, Debug, Clone)] pub enum NetworkTypeError { @@ -115,6 +116,13 @@ impl Display for NetworkType { impl TryFrom for NetworkType { type Error = NetworkTypeError; fn try_from(js_value: JsValue) -> Result { + NetworkType::try_from(&js_value) + } +} + +impl TryFrom<&JsValue> for NetworkType { + type Error = NetworkTypeError; + fn try_from(js_value: &JsValue) -> Result { if let Some(network_type) = js_value.as_string() { Self::from_str(&network_type) } else if let Some(v) = js_value.as_f64() { @@ -138,30 +146,49 @@ pub enum NetworkIdError { #[error("Unexpected extra token: {0}.")] UnexpectedExtraToken(String), + + #[error("Missing network suffix: '{0}'")] + MissingNetworkSuffix(String), + + #[error("Network suffix required for network type: '{0}'")] + NetworkSuffixRequired(String), + + #[error("Invalid network id: '{0}'")] + InvalidNetworkId(String), } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq)] +#[wasm_bindgen(inspectable)] pub struct NetworkId { + #[wasm_bindgen(js_name = "type")] pub network_type: NetworkType, + #[wasm_bindgen(js_name = "suffix")] pub suffix: Option, } impl NetworkId { pub const fn new(network_type: NetworkType) -> Self { + if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) { + panic!("network suffix required for this network type"); + } + Self { network_type, suffix: None } } - pub const fn with_suffix(network_type: NetworkType, suffix: u32) -> Self { - Self { network_type, suffix: Some(suffix) } + pub fn try_new(network_type: NetworkType) -> Result { + if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) { + return Err(NetworkIdError::NetworkSuffixRequired(network_type.to_string())); + } + + Ok(Self { network_type, suffix: None }) } - pub fn name(&self) -> String { - self.to_string() + pub const fn with_suffix(network_type: NetworkType, suffix: u32) -> Self { + Self { network_type, suffix: Some(suffix) } } - pub fn as_type(&self) -> &NetworkType { - &self.network_type + pub fn network_type(&self) -> NetworkType { + self.network_type } pub fn suffix(&self) -> Option { @@ -195,6 +222,19 @@ impl NetworkId { ]; NETWORK_IDS.iter().copied() } + + /// Returns a textual description of the network prefixed with `kaspa-` + pub fn to_prefixed(&self) -> String { + format!("kaspa-{}", self) + } + + pub fn from_prefixed(prefixed: &str) -> Result { + if let Some(stripped) = prefixed.strip_prefix("kaspa-") { + Self::from_str(stripped) + } else { + Err(NetworkIdError::InvalidPrefix(prefixed.to_string())) + } + } } impl Deref for NetworkId { @@ -205,9 +245,10 @@ impl Deref for NetworkId { } } -impl From for NetworkId { - fn from(value: NetworkType) -> Self { - Self::new(value) +impl TryFrom for NetworkId { + type Error = NetworkIdError; + fn try_from(value: NetworkType) -> Result { + Self::try_new(value) } } @@ -227,12 +268,14 @@ impl FromStr for NetworkId { type Err = NetworkIdError; fn from_str(network_name: &str) -> Result { let mut parts = network_name.split('-').fuse(); - let prefix = parts.next().unwrap_or_default(); - if prefix != "kaspa" { - return Err(NetworkIdError::InvalidPrefix(prefix.to_string())); - } let network_type = NetworkType::from_str(parts.next().unwrap_or_default())?; let suffix = parts.next().map(|x| u32::from_str(x).map_err(|_| NetworkIdError::InvalidSuffix(x.to_string()))).transpose()?; + // Disallow testnet network without suffix. + // Lack of suffix makes it impossible to distinguish between + // multiple testnet networks + if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) && suffix.is_none() { + return Err(NetworkIdError::MissingNetworkSuffix(network_name.to_string())); + } match parts.next() { Some(extra_token) => Err(NetworkIdError::UnexpectedExtraToken(extra_token.to_string())), None => Ok(Self { network_type, suffix }), @@ -243,9 +286,120 @@ impl FromStr for NetworkId { impl Display for NetworkId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if let Some(suffix) = self.suffix { - write!(f, "kaspa-{}-{}", self.network_type, suffix) + write!(f, "{}-{}", self.network_type, suffix) + } else { + write!(f, "{}", self.network_type) + } + } +} + +impl Serialize for NetworkId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +struct NetworkIdVisitor; + +impl<'de> de::Visitor<'de> for NetworkIdVisitor { + type Value = NetworkId; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string containing network_type and optional suffix separated by a '-'") + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: de::Error, + { + NetworkId::from_str(value).map_err(|err| de::Error::custom(err.to_string())) + } +} + +impl<'de> Deserialize<'de> for NetworkId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(NetworkIdVisitor) + } +} + +#[wasm_bindgen] +impl NetworkId { + #[wasm_bindgen(constructor)] + pub fn ctor(value: JsValue) -> Result { + value.try_into().map_err(|err: NetworkIdError| err.to_string()) + } + + #[wasm_bindgen(getter, js_name = "id")] + pub fn js_id(&self) -> String { + self.to_string() + } + + #[wasm_bindgen(js_name = "toString")] + pub fn js_to_string(&self) -> String { + self.to_string() + } + + #[wasm_bindgen(js_name = "addressPrefix")] + pub fn js_address_prefix(&self) -> Result { + Ok(Prefix::from(self.network_type).to_string()) + } +} + +impl TryFrom for NetworkId { + type Error = NetworkIdError; + fn try_from(js_value: JsValue) -> Result { + NetworkId::try_from(&js_value) + } +} + +impl TryFrom<&JsValue> for NetworkId { + type Error = NetworkIdError; + fn try_from(js_value: &JsValue) -> Result { + if let Some(network_id) = js_value.as_string() { + NetworkId::from_str(&network_id) + } else if let Ok(network_id) = ref_from_abi!(NetworkId, js_value) { + Ok(network_id) } else { - write!(f, "kaspa-{}", self.network_type) + Err(NetworkIdError::InvalidNetworkId(format!("{:?}", js_value))) + } + } +} + +pub mod wasm { + use super::*; + + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(js_name = "Network", typescript_type = "NetworkType | NetworkId | string")] + pub type Network; + } + + impl TryFrom for NetworkType { + type Error = String; + fn try_from(network: Network) -> std::result::Result { + let js_value = JsValue::from(network); + if let Ok(network_id) = ref_from_abi!(NetworkId, &js_value) { + Ok(network_id.network_type()) + } else if let Ok(network_id) = NetworkId::try_from(&js_value) { + Ok(network_id.network_type()) + } else if let Ok(network_type) = NetworkType::try_from(&js_value) { + Ok(network_type) + } else { + Err(format!("Invalid network value: {:?}", js_value)) + } + } + } + + impl TryFrom for Prefix { + type Error = String; + fn try_from(value: Network) -> Result { + NetworkType::try_from(value).map(Into::into) } } } @@ -257,14 +411,16 @@ mod tests { #[test] fn test_network_id_parse_roundtrip() { for nt in NetworkType::iter() { - let ni = NetworkId::from(nt); + if matches!(nt, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) { + let ni = NetworkId::try_from(nt).expect("failed to create network id"); + assert_eq!(nt, *NetworkId::from_str(ni.to_string().as_str()).unwrap()); + assert_eq!(ni, NetworkId::from_str(ni.to_string().as_str()).unwrap()); + } let nis = NetworkId::with_suffix(nt, 1); - assert_eq!(nt, *NetworkId::from_str(ni.to_string().as_str()).unwrap()); - assert_eq!(ni, NetworkId::from_str(ni.to_string().as_str()).unwrap()); assert_eq!(nt, *NetworkId::from_str(nis.to_string().as_str()).unwrap()); assert_eq!(nis, NetworkId::from_str(nis.to_string().as_str()).unwrap()); - assert_eq!(nis, NetworkId::from_str(nis.name().as_str()).unwrap()); + assert_eq!(nis, NetworkId::from_str(nis.to_string().as_str()).unwrap()); } } @@ -277,24 +433,18 @@ mod tests { } let tests = vec![ - Test { name: "Valid mainnet", expr: "kaspa-mainnet", expected: Ok(NetworkId::new(NetworkType::Mainnet)) }, - Test { name: "Valid testnet", expr: "kaspa-testnet-88", expected: Ok(NetworkId::with_suffix(NetworkType::Testnet, 88)) }, - Test { name: "Missing prefix", expr: "testnet", expected: Err(NetworkIdError::InvalidPrefix("testnet".to_string())) }, - Test { name: "Invalid prefix", expr: "K-testnet", expected: Err(NetworkIdError::InvalidPrefix("K".to_string())) }, - Test { - name: "Missing network", - expr: "kaspa-", - expected: Err(NetworkTypeError::InvalidNetworkType("".to_string()).into()), - }, + Test { name: "Valid mainnet", expr: "mainnet", expected: Ok(NetworkId::new(NetworkType::Mainnet)) }, + Test { name: "Valid testnet", expr: "testnet-88", expected: Ok(NetworkId::with_suffix(NetworkType::Testnet, 88)) }, + Test { name: "Missing network", expr: "", expected: Err(NetworkTypeError::InvalidNetworkType("".to_string()).into()) }, Test { name: "Invalid network", - expr: "kaspa-gamenet", + expr: "gamenet", expected: Err(NetworkTypeError::InvalidNetworkType("gamenet".to_string()).into()), }, - Test { name: "Invalid suffix", expr: "kaspa-testnet-x", expected: Err(NetworkIdError::InvalidSuffix("x".to_string())) }, + Test { name: "Invalid suffix", expr: "testnet-x", expected: Err(NetworkIdError::InvalidSuffix("x".to_string())) }, Test { name: "Unexpected extra token", - expr: "kaspa-testnet-10-x", + expr: "testnet-10-x", expected: Err(NetworkIdError::UnexpectedExtraToken("x".to_string())), }, ]; diff --git a/consensus/src/processes/coinbase.rs b/consensus/src/processes/coinbase.rs index 7e72b84531..4e3c36b797 100644 --- a/consensus/src/processes/coinbase.rs +++ b/consensus/src/processes/coinbase.rs @@ -275,7 +275,7 @@ mod tests { use kaspa_consensus_core::{ config::params::{Params, TESTNET11_PARAMS}, constants::SOMPI_PER_KASPA, - networktype::NetworkId, + network::NetworkId, tx::scriptvec, }; diff --git a/consensus/wasm/src/error.rs b/consensus/wasm/src/error.rs index f0866e3172..cb76a6764d 100644 --- a/consensus/wasm/src/error.rs +++ b/consensus/wasm/src/error.rs @@ -33,9 +33,12 @@ pub enum Error { #[error(transparent)] SerdeWasmBindgen(JsErrorData), - // SerdeWasmBindgen(String), + #[error(transparent)] AddressError(#[from] kaspa_addresses::AddressError), + + #[error(transparent)] + NetworkTypeError(#[from] kaspa_consensus_core::network::NetworkTypeError), } // unsafe impl Send for Error {} diff --git a/consensus/wasm/src/keypair.rs b/consensus/wasm/src/keypair.rs index 1f00bcb95e..9779ec2b57 100644 --- a/consensus/wasm/src/keypair.rs +++ b/consensus/wasm/src/keypair.rs @@ -21,7 +21,7 @@ use crate::error::Error; use crate::result::Result; use js_sys::{Array, Uint8Array}; use kaspa_addresses::{Address, Version as AddressVersion}; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::wasm::Network; use secp256k1::{Secp256k1, XOnlyPublicKey}; use serde_wasm_bindgen::to_value; use std::str::FromStr; @@ -65,9 +65,10 @@ impl Keypair { /// Receives a [`NetworkType`] to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddress)] - pub fn to_address(&self, network_type: NetworkType) -> Result
{ + // pub fn to_address(&self, network_type: NetworkType) -> Result
{ + pub fn to_address(&self, network: Network) -> Result
{ let pk = PublicKey { xonly_public_key: self.xonly_public_key, source: self.public_key.to_string() }; - let address = pk.to_address(network_type).unwrap(); + let address = pk.to_address(network)?; Ok(address) } @@ -75,9 +76,9 @@ impl Keypair { /// Receives a [`NetworkType`] to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddressECDSA)] - pub fn to_address_ecdsa(&self, network_type: NetworkType) -> Result
{ + pub fn to_address_ecdsa(&self, network: Network) -> Result
{ let pk = PublicKey { xonly_public_key: self.xonly_public_key, source: self.public_key.to_string() }; - let address = pk.to_address_ecdsa(network_type).unwrap(); + let address = pk.to_address_ecdsa(network)?; Ok(address) } @@ -203,13 +204,18 @@ impl PublicKey { } } + #[wasm_bindgen(js_name = "toString")] + pub fn js_to_string(&self) -> String { + self.source.clone() + } + /// Get the [`Address`] of this PublicKey. /// Receives a [`NetworkType`] to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddress)] - pub fn to_address(&self, network_type: NetworkType) -> Result
{ + pub fn to_address(&self, network: Network) -> Result
{ let payload = &self.xonly_public_key.serialize(); - let address = Address::new(network_type.into(), AddressVersion::PubKey, payload); + let address = Address::new(network.try_into()?, AddressVersion::PubKey, payload); Ok(address) } @@ -217,9 +223,9 @@ impl PublicKey { /// Receives a [`NetworkType`] to determine the prefix of the address. /// JavaScript: `let address = keypair.toAddress(NetworkType.MAINNET);`. #[wasm_bindgen(js_name = toAddressECDSA)] - pub fn to_address_ecdsa(&self, network_type: NetworkType) -> Result
{ + pub fn to_address_ecdsa(&self, network: Network) -> Result
{ let payload = &self.xonly_public_key.serialize(); - let address = Address::new(network_type.into(), AddressVersion::PubKeyECDSA, payload); + let address = Address::new(network.try_into()?, AddressVersion::PubKeyECDSA, payload); Ok(address) } } diff --git a/consensus/wasm/src/signable.rs b/consensus/wasm/src/signable.rs index ebd096a933..f4210fc785 100644 --- a/consensus/wasm/src/signable.rs +++ b/consensus/wasm/src/signable.rs @@ -8,7 +8,7 @@ use std::str::FromStr; /// Represents a generic mutable transaction #[derive(Clone, Debug, Serialize, Deserialize)] -#[wasm_bindgen] +#[wasm_bindgen(inspectable)] pub struct SignableTransaction { tx: Arc>, /// UTXO entry data @@ -23,9 +23,9 @@ impl SignableTransaction { Self { tx: Arc::new(Mutex::new(tx.clone())), entries: entries.clone() } } - #[wasm_bindgen(getter=id)] - pub fn id_string(&self) -> String { - self.tx.lock().unwrap().id_string() + #[wasm_bindgen(getter=tx)] + pub fn tx_getter(&self) -> Transaction { + self.tx.lock().unwrap().clone() } #[wasm_bindgen(js_name=toJSON)] @@ -55,7 +55,7 @@ impl SignableTransaction { let tx = locked.as_mut().unwrap(); if signatures.len() != tx.inner().inputs.len() { - return Err(Error::Custom("Signature counts dont match input counts".to_string()).into()); + return Err(Error::Custom("Signature counts don't match input counts".to_string()).into()); } let len = tx.inner().inputs.len(); for (i, signature) in signatures.into_iter().enumerate().take(len) { @@ -132,3 +132,23 @@ impl TryFrom<(tx::SignableTransaction, UtxoEntries)> for SignableTransaction { Ok(Self { tx: Arc::new(Mutex::new(value.0.tx.into())), entries: value.1 }) } } + +impl From for Transaction { + fn from(mtx: SignableTransaction) -> Self { + mtx.tx.lock().unwrap().clone() + } +} + +impl TryFrom for SignableTransaction { + type Error = Error; + fn try_from(js_value: JsValue) -> Result { + SignableTransaction::try_from(&js_value) + } +} + +impl TryFrom<&JsValue> for SignableTransaction { + type Error = Error; + fn try_from(js_value: &JsValue) -> Result { + Ok(ref_from_abi!(SignableTransaction, js_value)?) + } +} diff --git a/consensus/wasm/src/signer.rs b/consensus/wasm/src/signer.rs index 27fd4c05a3..98c288f249 100644 --- a/consensus/wasm/src/signer.rs +++ b/consensus/wasm/src/signer.rs @@ -10,59 +10,12 @@ use kaspa_consensus_core::{ }; use kaspa_hashes::Hash; use serde_wasm_bindgen::from_value; -use workflow_log::log_trace; - -// pub trait SignerTrait { -// fn sign(&self, _mtx: SignableTransaction) -> Result; -// } - -// /// `Signer` is a type capable of signing transactions. -// #[derive(Clone, Debug)] -// #[wasm_bindgen] -// pub struct Signer { -// private_keys: Vec, -// pub verify: bool, -// } - -// impl Signer { -// pub fn new(private_keys: Vec) -> Result { -// Ok(Self { private_keys, verify: true }) -// } -// fn private_keys(&self) -> Vec<[u8; 32]> { -// self.private_keys.iter().map(|k| k.into()).collect::>() -// } -// } - -// #[wasm_bindgen] -// impl Signer { -// #[wasm_bindgen(constructor)] -// pub fn js_ctor(private_keys: PrivateKeyArray) -> Result { -// Ok(Self { private_keys: private_keys.try_into()?, verify: true }) -// } - -// #[wasm_bindgen(js_name = "signTransaction")] -// pub fn sign_transaction(&self, mtx: SignableTransaction, verify_sig: bool) -> Result { -// sign_mutable_transaction(mtx, self.private_keys(), verify_sig).map_err(|err| Error::Custom(err.to_string())) -// } -// } - -// impl SignerTrait for Signer { -// fn sign(&self, mtx: SignableTransaction) -> Result { -// let mtx = sign_transaction(mtx, self.private_keys(), self.verify).map_err(|err| Error::Custom(err.to_string()))?; - -// Ok(mtx) -// } -// } #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = js_sys::Array, is_type_of = Array::is_array, typescript_type = "PrivateKey[]")] #[derive(Clone, Debug, PartialEq, Eq)] pub type PrivateKeyArray; - - // #[wasm_bindgen(extends = js_sys::Object, typescript_type = "PrivateKey[] | Signer")] - // #[derive(Clone, Debug, PartialEq, Eq)] - // pub type PrivateKeyArrayOrSigner; } impl TryFrom for Vec { @@ -92,11 +45,6 @@ pub fn js_sign_transaction(mtx: SignableTransaction, signer: PrivateKeyArray, ve } else { Err(Error::custom("signTransaction() requires an array of signatures")) } - // } else { - // let signer = Signer::try_from(&JsValue::from(signer)).map_err(|_| Error::Custom("Unable to cast Signer".to_string()))?; - // // log_trace!("\nSigning via Signer: {signer:?}....\n"); - // signer.sign_transaction(mtx, verify_sig) - // } } pub fn sign_transaction(mtx: SignableTransaction, private_keys: Vec<[u8; 32]>, verify_sig: bool) -> Result { @@ -113,11 +61,7 @@ fn sign_transaction_impl( ) -> Result { let mtx = sign(mtx, private_keys)?; if verify_sig { - // let mtx_clone = mtx.clone(); - log_trace!("sign_transaction mtx: {mtx:?}"); - // let tx_verifiable = mtx_clone.as_verifiable(); let tx_verifiable = mtx.as_verifiable(); - log_trace!("verify..."); verify(&tx_verifiable)?; } Ok(mtx) @@ -126,32 +70,6 @@ fn sign_transaction_impl( /// Sign a transaction using schnorr, returns a new transaction with the signatures added. pub fn sign(mutable_tx: tx::SignableTransaction, privkeys: Vec<[u8; 32]>) -> Result { Ok(sign_with_multiple_v2(mutable_tx, privkeys)) - - // let mut map = BTreeMap::new(); - // for privkey in privkeys { - // let schnorr_key = secp256k1::KeyPair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; - // let schnorr_public_key = schnorr_key.public_key().x_only_public_key().0; - // let script_pub_key_script = once(0x20).chain(schnorr_public_key.serialize().into_iter()).chain(once(0xac)).collect_vec(); // TODO: Use script builder when available to create p2pk properly - // //map.insert(schnorr_public_key.serialize(), schnorr_key); - // map.insert(script_pub_key_script.to_hex(), schnorr_key); - // //println!("schnorr_key.public_key().serialize(): {:x?}", schnorr_public_key.serialize()) - // } - // // for i in 0..mutable_tx.tx.inputs.len() { - // // mutable_tx.tx.inputs[i].sig_op_count = 1; - // // } - - // let mut reused_values = SigHashReusedValues::new(); - // for i in 0..mutable_tx.tx.inputs.len() { - // let script = mutable_tx.entries[i].as_ref().unwrap().script_public_key.script().to_hex(); - // if let Some(schnorr_key) = map.get(&script) { - // let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &mut reused_values); - // let msg = secp256k1::Message::from_slice(sig_hash.as_bytes().as_slice()).unwrap(); - // let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); - // // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) - // mutable_tx.tx.inputs[i].signature_script = std::iter::once(65u8).chain(sig).chain([SIG_HASH_ALL.to_u8()]).collect(); - // } - // } - // Ok(mutable_tx) } #[wasm_bindgen(js_name=signScriptHash)] diff --git a/consensus/wasm/src/transaction.rs b/consensus/wasm/src/transaction.rs index f00fa97551..83b7b205b4 100644 --- a/consensus/wasm/src/transaction.rs +++ b/consensus/wasm/src/transaction.rs @@ -183,49 +183,43 @@ impl Transaction { impl TryFrom for Transaction { type Error = Error; fn try_from(js_value: JsValue) -> std::result::Result { - if let Some(object) = Object::try_from(&js_value) { - let version = object.get_u16("version")?; - // workflow_log::log_trace!("JsValue->Transaction: version: {version:?}"); - let lock_time = object.get_u64("lockTime")?; - let gas = object.get_u64("gas")?; - let payload = object.get_vec_u8("payload")?; - let subnetwork_id = object.get_vec_u8("subnetworkId")?; - if subnetwork_id.len() != subnets::SUBNETWORK_ID_SIZE { - return Err(Error::Custom("subnetworkId must be 20 bytes long".into())); + Transaction::try_from(&js_value) + } +} + +impl TryFrom<&JsValue> for Transaction { + type Error = Error; + fn try_from(js_value: &JsValue) -> std::result::Result { + if let Ok(tx) = ref_from_abi!(Transaction, js_value) { + Ok(tx) + } else if let Some(object) = Object::try_from(js_value) { + if let Some(tx) = object.try_get_value("tx")? { + Transaction::try_from(&tx) + } else { + let version = object.get_u16("version")?; + let lock_time = object.get_u64("lockTime")?; + let gas = object.get_u64("gas")?; + let payload = object.get_vec_u8("payload")?; + let subnetwork_id = object.get_vec_u8("subnetworkId")?; + if subnetwork_id.len() != subnets::SUBNETWORK_ID_SIZE { + return Err(Error::Custom("subnetworkId must be 20 bytes long".into())); + } + let subnetwork_id: SubnetworkId = subnetwork_id + .as_slice() + .try_into() + .map_err(|err| Error::Custom(format!("`subnetworkId` property error: `{err}`")))?; + let inputs = object + .get_vec("inputs")? + .into_iter() + .map(|jsv| jsv.try_into()) + .collect::, Error>>()?; + let outputs: Vec = object + .get_vec("outputs")? + .into_iter() + .map(|jsv| jsv.try_into()) + .collect::, Error>>()?; + Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload) } - let subnetwork_id: SubnetworkId = - subnetwork_id.as_slice().try_into().map_err(|err| Error::Custom(format!("`subnetworkId` property error: `{err}`")))?; - // workflow_log::log_trace!("JsValue->Transaction: subnetwork_id: {subnetwork_id:?}"); - let inputs = object - .get_vec("inputs")? - .into_iter() - .map(|jsv| jsv.try_into()) - .collect::, Error>>()?; - // workflow_log::log_trace!("JsValue->Transaction: inputs.len(): {:?}", inputs.len()); - // let jsv_outputs = object.get("outputs")?; - // let outputs: Vec = if !jsv_outputs.is_array() { - // let outputs: PaymentOutputs = jsv_outputs.try_into()?; - // outputs.into() - // } else { - // object - // .get_vec("outputs")? - // .into_iter() - // .map(|jsv| { - // // workflow_log::log_trace!("JsValue->Transaction: output : {jsv:?}"); - // jsv.try_into() - // }) - // .collect::, Error>>()? - // }; - let outputs: Vec = object - .get_vec("outputs")? - .into_iter() - .map(|jsv| { - // workflow_log::log_trace!("JsValue->Transaction: output : {jsv:?}"); - jsv.try_into() - }) - .collect::, Error>>()?; - // workflow_log::log_trace!("JsValue->Transaction: outputs: {outputs:?}"); - Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload) } else { Err("Transaction must be an object".into()) } diff --git a/daemon/src/imports.rs b/daemon/src/imports.rs index fcecce3394..2e0a072423 100644 --- a/daemon/src/imports.rs +++ b/daemon/src/imports.rs @@ -5,7 +5,7 @@ pub use async_trait::async_trait; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use downcast_rs::{impl_downcast, DowncastSync}; pub use kaspa_addresses::Address; -pub use kaspa_wallet_core::network::{NetworkId, NetworkType}; +pub use kaspa_consensus_core::network::{NetworkId, NetworkType}; pub use serde::{Deserialize, Serialize}; pub use std::path::{Path, PathBuf}; pub use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/kaspad/src/args.rs b/kaspad/src/args.rs index 506e085618..bc9828f5be 100644 --- a/kaspad/src/args.rs +++ b/kaspad/src/args.rs @@ -4,7 +4,7 @@ use clap::{arg, command, Arg, Command}; use kaspa_consensus_core::{ config::Config, - networktype::{NetworkId, NetworkType}, + network::{NetworkId, NetworkType}, }; use kaspa_core::kaspad_env::version; use kaspa_utils::networking::{ContextualNetAddress, IpAddress}; @@ -97,10 +97,10 @@ impl Args { pub fn network(&self) -> NetworkId { match (self.testnet, self.devnet, self.simnet) { - (false, false, false) => NetworkType::Mainnet.into(), + (false, false, false) => NetworkId::new(NetworkType::Mainnet), (true, false, false) => NetworkId::with_suffix(NetworkType::Testnet, self.testnet_suffix), - (false, true, false) => NetworkType::Devnet.into(), - (false, false, true) => NetworkType::Simnet.into(), + (false, true, false) => NetworkId::new(NetworkType::Devnet), + (false, false, true) => NetworkId::new(NetworkType::Simnet), _ => panic!("only a single net should be activated"), } } diff --git a/kaspad/src/daemon.rs b/kaspad/src/daemon.rs index 6e51af6730..19c8b5f586 100644 --- a/kaspad/src/daemon.rs +++ b/kaspad/src/daemon.rs @@ -109,7 +109,8 @@ impl Runtime { // Logs directory is usually under the application directory, unless otherwise specified let log_dir = args.logdir.clone().unwrap_or_default().replace('~', get_home_dir().as_path().to_str().unwrap()); - let log_dir = if log_dir.is_empty() { app_dir.join(network.name()).join(DEFAULT_LOG_DIR) } else { PathBuf::from(log_dir) }; + let log_dir = + if log_dir.is_empty() { app_dir.join(network.to_prefixed()).join(DEFAULT_LOG_DIR) } else { PathBuf::from(log_dir) }; let log_dir = if args.no_log_files { None } else { log_dir.to_str() }; // Initialize the logger @@ -141,7 +142,7 @@ pub fn create_core_with_runtime(runtime: &Runtime, args: &Args) -> Arc { } let app_dir = get_app_dir_from_args(args); - let db_dir = app_dir.join(network.name()).join(DEFAULT_DATA_DIR); + let db_dir = app_dir.join(network.to_prefixed()).join(DEFAULT_DATA_DIR); // Print package name and version info!("{} v{}", env!("CARGO_PKG_NAME"), version()); diff --git a/kos/src/imports.rs b/kos/src/imports.rs index bf95347cba..7a256d482e 100644 --- a/kos/src/imports.rs +++ b/kos/src/imports.rs @@ -9,7 +9,7 @@ pub use async_trait::async_trait; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use futures::{future::join_all, select, select_biased, stream::StreamExt, FutureExt, Stream}; pub use kaspa_cli::{KaspaCli, Options as KaspaCliOptions}; -pub use kaspa_consensus_core::networktype::NetworkType; +pub use kaspa_consensus_core::network::NetworkType; pub use kaspa_daemon::{ CpuMiner, CpuMinerConfig, CpuMinerCtl, DaemonEvent, DaemonKind, DaemonStatus, Daemons, Kaspad, KaspadConfig, KaspadCtl, Result as DaemonResult, diff --git a/mining/src/mempool/check_transaction_standard.rs b/mining/src/mempool/check_transaction_standard.rs index 54774119bc..8550c34689 100644 --- a/mining/src/mempool/check_transaction_standard.rs +++ b/mining/src/mempool/check_transaction_standard.rs @@ -229,7 +229,7 @@ mod tests { use kaspa_consensus_core::{ config::params::Params, constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASPA, TX_VERSION}, - networktype::NetworkType, + network::NetworkType, subnets::SUBNETWORK_ID_NATIVE, tx::{ScriptPublicKey, ScriptVec, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}, }; diff --git a/rpc/core/src/error.rs b/rpc/core/src/error.rs index 927d5e8ae4..7f39735a54 100644 --- a/rpc/core/src/error.rs +++ b/rpc/core/src/error.rs @@ -77,10 +77,10 @@ pub enum RpcError { AddressError(#[from] kaspa_addresses::AddressError), #[error(transparent)] - NetworkTypeError(#[from] kaspa_consensus_core::networktype::NetworkTypeError), + NetworkTypeError(#[from] kaspa_consensus_core::network::NetworkTypeError), #[error(transparent)] - NetworkIdError(#[from] kaspa_consensus_core::networktype::NetworkIdError), + NetworkIdError(#[from] kaspa_consensus_core::network::NetworkIdError), #[error(transparent)] NotificationError(#[from] kaspa_notify::error::Error), diff --git a/rpc/core/src/model/mod.rs b/rpc/core/src/model/mod.rs index aa3fe23901..fd07a109ee 100644 --- a/rpc/core/src/model/mod.rs +++ b/rpc/core/src/model/mod.rs @@ -6,7 +6,7 @@ pub mod header; pub mod hex_cnv; pub mod mempool; pub mod message; -pub mod networktype; +pub mod network; pub mod peer; pub mod script_class; pub mod subnets; @@ -20,7 +20,7 @@ pub use header::*; pub use hex_cnv::*; pub use mempool::*; pub use message::*; -pub use networktype::*; +pub use network::*; pub use peer::*; pub use subnets::*; pub use tx::*; diff --git a/rpc/core/src/model/networktype.rs b/rpc/core/src/model/network.rs similarity index 53% rename from rpc/core/src/model/networktype.rs rename to rpc/core/src/model/network.rs index 86632ec909..e7db30d14c 100644 --- a/rpc/core/src/model/networktype.rs +++ b/rpc/core/src/model/network.rs @@ -1,4 +1,4 @@ -use kaspa_consensus_core::networktype::{NetworkId, NetworkType}; +use kaspa_consensus_core::network::{NetworkId, NetworkType}; pub type RpcNetworkType = NetworkType; pub type RpcNetworkId = NetworkId; diff --git a/rpc/grpc/core/src/convert/message.rs b/rpc/grpc/core/src/convert/message.rs index c860edf43a..16ddbc22b2 100644 --- a/rpc/grpc/core/src/convert/message.rs +++ b/rpc/grpc/core/src/convert/message.rs @@ -257,7 +257,7 @@ from!(item: RpcResult<&kaspa_rpc_core::GetBlockCountResponse>, protowire::GetBlo from!(&kaspa_rpc_core::GetBlockDagInfoRequest, protowire::GetBlockDagInfoRequestMessage); from!(item: RpcResult<&kaspa_rpc_core::GetBlockDagInfoResponse>, protowire::GetBlockDagInfoResponseMessage, { Self { - network_name: item.network.to_string(), + network_name: item.network.to_prefixed(), block_count: item.block_count, header_count: item.header_count, tip_hashes: item.tip_hashes.iter().map(|x| x.to_string()).collect(), @@ -581,7 +581,7 @@ try_from!(item: &protowire::GetBlockCountResponseMessage, RpcResult, { Self { - network: kaspa_rpc_core::RpcNetworkId::from_str(&item.network_name)?, + network: kaspa_rpc_core::RpcNetworkId::from_prefixed(&item.network_name)?, block_count: item.block_count, header_count: item.header_count, tip_hashes: item.tip_hashes.iter().map(|x| RpcHash::from_str(x)).collect::, _>>()?, diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index 6631b423af..d584ce7776 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -9,7 +9,7 @@ use kaspa_consensus_core::{ coinbase::MinerData, config::Config, constants::MAX_SOMPI, - networktype::NetworkType, + network::NetworkType, tx::{Transaction, COINBASE_TRANSACTION_INDEX}, }; use kaspa_consensus_notify::{ diff --git a/rpc/wrpc/client/src/client.rs b/rpc/wrpc/client/src/client.rs index 51cbc0bf45..3375311fc1 100644 --- a/rpc/wrpc/client/src/client.rs +++ b/rpc/wrpc/client/src/client.rs @@ -1,7 +1,7 @@ use crate::error::Error; use crate::imports::*; use crate::parse::parse_host; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use kaspa_rpc_core::notify::collector::{RpcCoreCollector, RpcCoreConverter}; pub use kaspa_rpc_macros::build_wrpc_client_interface; use std::fmt::Debug; diff --git a/rpc/wrpc/client/src/error.rs b/rpc/wrpc/client/src/error.rs index c095b2c642..86d5c9358f 100644 --- a/rpc/wrpc/client/src/error.rs +++ b/rpc/wrpc/client/src/error.rs @@ -40,7 +40,7 @@ pub enum Error { ToValue(String), #[error("invalid network type: {0}")] - NetworkType(#[from] kaspa_consensus_core::networktype::NetworkTypeError), + NetworkType(#[from] kaspa_consensus_core::network::NetworkTypeError), #[error(transparent)] ConsensusWasm(#[from] kaspa_consensus_wasm::error::Error), @@ -52,6 +52,18 @@ impl Error { } } +impl From for Error { + fn from(err: String) -> Self { + Self::Custom(err) + } +} + +impl From<&str> for Error { + fn from(err: &str) -> Self { + Self::Custom(err.to_string()) + } +} + impl From> for Error { fn from(err: ChannelError) -> Self { Error::ChannelError(err.to_string()) diff --git a/rpc/wrpc/client/src/wasm.rs b/rpc/wrpc/client/src/wasm.rs index 4a2960d3a4..09484035cc 100644 --- a/rpc/wrpc/client/src/wasm.rs +++ b/rpc/wrpc/client/src/wasm.rs @@ -3,13 +3,12 @@ use crate::imports::*; use crate::result::Result; use js_sys::Array; use kaspa_addresses::{Address, AddressList}; -use kaspa_consensus_core::networktype::NetworkType; -use kaspa_consensus_wasm::Transaction; +use kaspa_consensus_core::network::{wasm::Network, NetworkType}; +use kaspa_consensus_wasm::{SignableTransaction, Transaction}; use kaspa_notify::notification::Notification as NotificationT; pub use kaspa_rpc_macros::{build_wrpc_wasm_bindgen_interface, build_wrpc_wasm_bindgen_subscriptions}; pub use serde_wasm_bindgen::from_value; pub use workflow_wasm::serde::to_value; -// use kaspa_rpc_core::wasm::*; struct NotificationSink(Function); unsafe impl Send for NotificationSink {} @@ -38,7 +37,7 @@ pub struct RpcClient { impl RpcClient { /// Create a new RPC client with [`Encoding`] and a `url`. #[wasm_bindgen(constructor)] - pub fn new(encoding: Encoding, url: &str, network_type: Option) -> Result { + pub fn new(url: &str, encoding: Encoding, network_type: Option) -> Result { let url = if let Some(network_type) = network_type { Self::parse_url(url, encoding, network_type)? } else { url.to_string() }; let rpc_client = RpcClient { @@ -170,7 +169,8 @@ impl RpcClient { #[wasm_bindgen] impl RpcClient { #[wasm_bindgen(js_name = "defaultPort")] - pub fn default_port(encoding: WrpcEncoding, network_type: NetworkType) -> Result { + pub fn default_port(encoding: WrpcEncoding, network: Network) -> Result { + let network_type = NetworkType::try_from(network)?; match encoding { WrpcEncoding::Borsh => Ok(network_type.default_borsh_rpc_port()), WrpcEncoding::SerdeJson => Ok(network_type.default_json_rpc_port()), @@ -187,8 +187,8 @@ impl RpcClient { /// * `network_type` - Network type /// #[wasm_bindgen(js_name = parseUrl)] - pub fn parse_url(url: &str, encoding: Encoding, network_type: NetworkType) -> Result { - let url_ = KaspaRpcClient::parse_url(Some(url.to_string()), encoding, network_type)?; + pub fn parse_url(url: &str, encoding: Encoding, network: Network) -> Result { + let url_ = KaspaRpcClient::parse_url(Some(url.to_string()), encoding, network.try_into()?)?; let url_ = url_.ok_or(Error::custom(format!("received a malformed URL: {url}")))?; Ok(url_) } @@ -333,7 +333,15 @@ build_wrpc_wasm_bindgen_interface!( impl RpcClient { #[wasm_bindgen(js_name = submitTransaction)] pub async fn js_submit_transaction(&self, js_value: JsValue, allow_orphan: Option) -> Result { - let transaction = RpcTransaction::from(Transaction::try_from(js_value)?); + let transaction = if let Ok(signable) = SignableTransaction::try_from(&js_value) { + Transaction::from(signable) + } else if let Ok(transaction) = Transaction::try_from(js_value) { + transaction + } else { + return Err(Error::custom("invalid transaction data")); + }; + + let transaction = RpcTransaction::from(transaction); let request = SubmitTransactionRequest { transaction, allow_orphan: allow_orphan.unwrap_or(false) }; diff --git a/rpc/wrpc/proxy/src/main.rs b/rpc/wrpc/proxy/src/main.rs index 5d9c7167d2..a0a5b78fb5 100644 --- a/rpc/wrpc/proxy/src/main.rs +++ b/rpc/wrpc/proxy/src/main.rs @@ -2,7 +2,7 @@ mod error; mod result; use clap::Parser; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use kaspa_rpc_core::api::ops::RpcApiOps; use kaspa_wrpc_core::ServerCounters as WrpcServerCounters; use kaspa_wrpc_server::{ diff --git a/rpc/wrpc/server/src/address.rs b/rpc/wrpc/server/src/address.rs index 2bfd981d11..7a00295abd 100644 --- a/rpc/wrpc/server/src/address.rs +++ b/rpc/wrpc/server/src/address.rs @@ -1,5 +1,5 @@ use crate::service::WrpcEncoding; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use kaspa_utils::networking::ContextualNetAddress; use std::{net::AddrParseError, str::FromStr}; diff --git a/testing/integration/src/integration_tests.rs b/testing/integration/src/integration_tests.rs index 1b4e35c9c4..8d5559bcde 100644 --- a/testing/integration/src/integration_tests.rs +++ b/testing/integration/src/integration_tests.rs @@ -27,7 +27,7 @@ use kaspa_consensus_core::blockstatus::BlockStatus; use kaspa_consensus_core::constants::BLOCK_VERSION; use kaspa_consensus_core::errors::block::{BlockProcessResult, RuleError}; use kaspa_consensus_core::header::Header; -use kaspa_consensus_core::networktype::{NetworkId, NetworkType::Mainnet}; +use kaspa_consensus_core::network::{NetworkId, NetworkType::Mainnet}; use kaspa_consensus_core::subnets::SubnetworkId; use kaspa_consensus_core::trusted::{ExternalGhostdagData, TrustedBlock}; use kaspa_consensus_core::tx::{ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry}; diff --git a/wallet/bip32/src/error.rs b/wallet/bip32/src/error.rs index 4d63ad3c3e..497309af4b 100644 --- a/wallet/bip32/src/error.rs +++ b/wallet/bip32/src/error.rs @@ -89,6 +89,9 @@ pub enum Error { #[error("Poison error -> {0:?}")] PoisonError(String), + + #[error(transparent)] + WorkflowWasm(#[from] workflow_wasm::error::Error), } impl From for Error { diff --git a/wallet/bip32/src/mnemonic/phrase.rs b/wallet/bip32/src/mnemonic/phrase.rs index 9710033e33..7c9e1caddb 100644 --- a/wallet/bip32/src/mnemonic/phrase.rs +++ b/wallet/bip32/src/mnemonic/phrase.rs @@ -81,8 +81,9 @@ impl Mnemonic { } #[wasm_bindgen(js_name = toSeed)] - pub fn create_seed(&self, password: &str) -> String { - self.to_seed(password).as_bytes().to_vec().to_hex() + pub fn create_seed(&self, password: Option) -> String { + let password = password.unwrap_or_default(); + self.to_seed(password.as_str()).as_bytes().to_vec().to_hex() } } diff --git a/wallet/bip32/src/wasm/derivation_path.rs b/wallet/bip32/src/wasm/derivation_path.rs index a654105aaf..1b89eac6d8 100644 --- a/wallet/bip32/src/wasm/derivation_path.rs +++ b/wallet/bip32/src/wasm/derivation_path.rs @@ -1,10 +1,9 @@ use crate::{ChildNumber, Error, Result}; -use js_sys::Object; use std::str::FromStr; -use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; use workflow_wasm::prelude::*; +#[derive(Clone)] #[wasm_bindgen] pub struct DerivationPath { inner: crate::DerivationPath, @@ -57,11 +56,7 @@ impl TryFrom for DerivationPath { return Self::new(&path); } - let api = Object::from(value).get_u64("ptr").map_err(|e| Error::String(e.to_string()))?; - - let path = unsafe { Self::from_abi(api as u32) }; - - Ok(path) + Ok(ref_from_abi!(DerivationPath, &value)?) } } diff --git a/wallet/core/src/derivation/mod.rs b/wallet/core/src/derivation/mod.rs index 387735fc7a..c4bbcfcd60 100644 --- a/wallet/core/src/derivation/mod.rs +++ b/wallet/core/src/derivation/mod.rs @@ -15,7 +15,7 @@ use crate::secret::Secret; use crate::storage::PrvKeyDataId; use crate::Result; use kaspa_bip32::{AddressType, DerivationPath, ExtendedPrivateKey, ExtendedPublicKey, Language, Mnemonic, SecretKeyExt}; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use kaspa_utils::hex::ToHex; use std::collections::HashMap; use std::sync::{Arc, Mutex, MutexGuard}; diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index 9907e8eb3c..8050ca45fa 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -86,10 +86,10 @@ pub enum Error { NetworkTypeConnected, #[error("{0}")] - NetworkType(#[from] kaspa_consensus_core::networktype::NetworkTypeError), + NetworkType(#[from] kaspa_consensus_core::network::NetworkTypeError), #[error("{0}")] - NetworkId(#[from] kaspa_consensus_core::networktype::NetworkIdError), + NetworkId(#[from] kaspa_consensus_core::network::NetworkIdError), #[error("The server UTXO index is not enabled")] MissingUtxoIndex, diff --git a/wallet/core/src/imports.rs b/wallet/core/src/imports.rs index 96bfc3f7e6..4b96f2a059 100644 --- a/wallet/core/src/imports.rs +++ b/wallet/core/src/imports.rs @@ -1,6 +1,5 @@ pub use crate::error::Error; pub use crate::events::{Events, SyncState}; -pub use crate::network::NetworkId; pub use crate::utxo::scan::{Scan, ScanExtent}; pub use crate::DynRpcApi; pub use crate::{runtime, storage, utils, utxo}; @@ -13,7 +12,7 @@ pub use futures::future::join_all; pub use futures::{select, stream, FutureExt, Stream, StreamExt, TryStreamExt}; pub use js_sys::{Array, BigInt, Object}; pub use kaspa_addresses::{Address, Prefix}; -pub use kaspa_consensus_core::networktype::NetworkType; +pub use kaspa_consensus_core::network::{NetworkId, NetworkType}; pub use kaspa_consensus_core::subnets; pub use kaspa_consensus_core::subnets::SubnetworkId; pub use kaspa_consensus_core::tx as cctx; diff --git a/wallet/core/src/lib.rs b/wallet/core/src/lib.rs index c37b32b7ea..d9ca5e3584 100644 --- a/wallet/core/src/lib.rs +++ b/wallet/core/src/lib.rs @@ -6,7 +6,6 @@ pub mod encryption; pub mod error; pub mod events; mod imports; -pub mod network; pub mod result; pub mod runtime; pub mod secret; diff --git a/wallet/core/src/network.rs b/wallet/core/src/network.rs deleted file mode 100644 index 9e2c913c4d..0000000000 --- a/wallet/core/src/network.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::imports::*; -use kaspa_addresses::Prefix; -pub use kaspa_consensus_core::networktype::NetworkType; -use serde::{de, Deserializer, Serializer}; -use std::{ops::Deref, str::FromStr}; - -#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq)] -pub struct NetworkId { - pub network_type: NetworkType, - pub suffix: Option, -} - -impl NetworkId { - pub const fn new(network_type: NetworkType) -> Self { - Self { network_type, suffix: None } - } - - pub const fn with_suffix(network_type: NetworkType, suffix: u32) -> Self { - Self { network_type, suffix: Some(suffix) } - } - - pub fn name(&self) -> String { - self.to_string() - } - - pub fn iter() -> impl Iterator { - static NETWORK_IDS: [NetworkId; 5] = [ - NetworkId::new(NetworkType::Mainnet), - NetworkId::with_suffix(NetworkType::Testnet, 10), - NetworkId::with_suffix(NetworkType::Testnet, 11), - NetworkId::new(NetworkType::Devnet), - NetworkId::new(NetworkType::Simnet), - ]; - NETWORK_IDS.iter().copied() - } -} - -impl Deref for NetworkId { - type Target = NetworkType; - - fn deref(&self) -> &Self::Target { - &self.network_type - } -} - -impl From for NetworkId { - fn from(value: NetworkType) -> Self { - Self::new(value) - } -} - -impl From for Prefix { - fn from(net: NetworkId) -> Self { - (*net).into() - } -} - -impl From for NetworkType { - fn from(net: NetworkId) -> Self { - *net - } -} - -impl From for kaspa_consensus_core::networktype::NetworkId { - fn from(net: NetworkId) -> Self { - kaspa_consensus_core::networktype::NetworkId { network_type: net.network_type, suffix: net.suffix } - } -} - -impl From for NetworkId { - fn from(net: kaspa_consensus_core::networktype::NetworkId) -> Self { - NetworkId { network_type: net.network_type, suffix: net.suffix } - } -} - -impl FromStr for NetworkId { - type Err = Error; - fn from_str(network_name: &str) -> Result { - let mut parts = network_name.split('-').fuse(); - let network_type = NetworkType::from_str(parts.next().unwrap_or_default())?; - let suffix = parts.next().map(|x| u32::from_str(x).map_err(|_| Error::InvalidNetworkSuffix(x.to_string()))).transpose()?; - // diallow network types without suffix (other than mainnet) - // lack of suffix makes it impossible to distinguish between - // multiple testnet networks - if !matches!(network_type, NetworkType::Mainnet) && suffix.is_none() { - return Err(Error::MissingNetworkSuffix(network_name.to_string())); - } - match parts.next() { - Some(extra_token) => Err(Error::UnexpectedExtraSuffixToken(extra_token.to_string())), - None => Ok(Self { network_type, suffix }), - } - } -} - -impl std::fmt::Display for NetworkId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(suffix) = self.suffix { - write!(f, "{}-{}", self.network_type, suffix) - } else { - write!(f, "{}", self.network_type) - } - } -} - -impl Serialize for NetworkId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -struct NetworkIdVisitor; - -impl<'de> de::Visitor<'de> for NetworkIdVisitor { - type Value = NetworkId; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string containing network_type and optional suffix separated by a '-'") - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: de::Error, - { - NetworkId::from_str(value).map_err(|err| de::Error::custom(err.to_string())) - } -} - -impl<'de> Deserialize<'de> for NetworkId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(NetworkIdVisitor) - } -} - -impl TryFrom for NetworkId { - type Error = Error; - - fn try_from(value: JsValue) -> Result { - let network_name = value.as_string().ok_or_else(|| Error::InvalidNetworkId(format!("{value:?}")))?; - NetworkId::from_str(&network_name) - } -} diff --git a/wallet/core/src/runtime/balance.rs b/wallet/core/src/runtime/balance.rs index f312e3f180..0d76aa7114 100644 --- a/wallet/core/src/runtime/balance.rs +++ b/wallet/core/src/runtime/balance.rs @@ -1,5 +1,5 @@ use crate::imports::*; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; pub enum DeltaStyle { Mature, diff --git a/wallet/core/src/tx/consensus.rs b/wallet/core/src/tx/consensus.rs index 2a43b381e0..85877b34f7 100644 --- a/wallet/core/src/tx/consensus.rs +++ b/wallet/core/src/tx/consensus.rs @@ -1,7 +1,7 @@ use kaspa_addresses::{Address, Prefix}; use kaspa_consensus_core::{ config::params::{Params, DEVNET_PARAMS, MAINNET_PARAMS, SIMNET_PARAMS, TESTNET_PARAMS}, - networktype::NetworkType, + network::NetworkType, }; /// find Consensus parameters for given Address diff --git a/wallet/core/src/tx/generator/settings.rs b/wallet/core/src/tx/generator/settings.rs index 0679f94279..c432da7ec4 100644 --- a/wallet/core/src/tx/generator/settings.rs +++ b/wallet/core/src/tx/generator/settings.rs @@ -4,7 +4,7 @@ use crate::tx::{Fees, PaymentDestination}; use crate::utxo::{UtxoContext, UtxoEntryReference, UtxoIterator}; use crate::Events; use kaspa_addresses::Address; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use std::sync::Arc; use workflow_core::channel::Multiplexer; diff --git a/wallet/core/src/tx/generator/summary.rs b/wallet/core/src/tx/generator/summary.rs index d24c81e100..b12035279a 100644 --- a/wallet/core/src/tx/generator/summary.rs +++ b/wallet/core/src/tx/generator/summary.rs @@ -1,5 +1,5 @@ use crate::utils::*; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use kaspa_consensus_core::tx::TransactionId; use std::fmt; diff --git a/wallet/core/src/utils.rs b/wallet/core/src/utils.rs index 1e289334c8..636082cd41 100644 --- a/wallet/core/src/utils.rs +++ b/wallet/core/src/utils.rs @@ -1,7 +1,7 @@ use crate::result::Result; use kaspa_addresses::Address; use kaspa_consensus_core::constants::*; -use kaspa_consensus_core::networktype::NetworkType; +use kaspa_consensus_core::network::NetworkType; use separator::Separatable; use workflow_log::style; diff --git a/wallet/core/src/utxo/processor.rs b/wallet/core/src/utxo/processor.rs index 5ab1d46b38..60efd8a797 100644 --- a/wallet/core/src/utxo/processor.rs +++ b/wallet/core/src/utxo/processor.rs @@ -263,7 +263,6 @@ impl UtxoProcessor { let kaspa_rpc_core::GetBlockDagInfoResponse { virtual_daa_score, network: server_network_id, .. } = self.rpc().get_block_dag_info().await?; - let server_network_id = NetworkId::from(server_network_id); let network_id = self.network_id()?; if network_id != server_network_id { return Err(Error::InvalidNetworkType(network_id.to_string(), server_network_id.to_string())); diff --git a/wallet/core/src/wasm/tx/consensus.rs b/wallet/core/src/wasm/tx/consensus.rs index e2f085ae3c..62e2852791 100644 --- a/wallet/core/src/wasm/tx/consensus.rs +++ b/wallet/core/src/wasm/tx/consensus.rs @@ -1,6 +1,6 @@ use crate::tx::consensus as core; use kaspa_addresses::Address; -use kaspa_consensus_core::{config::params::Params, networktype::NetworkType}; +use kaspa_consensus_core::{config::params::Params, network::NetworkType}; use wasm_bindgen::prelude::*; #[wasm_bindgen] diff --git a/wallet/core/src/wasm/wallet/wallet.rs b/wallet/core/src/wasm/wallet/wallet.rs index d27eb0fb1b..66dbb63b96 100644 --- a/wallet/core/src/wasm/wallet/wallet.rs +++ b/wallet/core/src/wasm/wallet/wallet.rs @@ -31,8 +31,8 @@ impl Wallet { let store = Arc::new(LocalStore::try_new(resident)?); let rpc = RpcClient::new( - encoding.unwrap_or(WrpcEncoding::Borsh), url.unwrap_or("wrpc://127.0.0.1:17110".to_string()).as_str(), + encoding.unwrap_or(WrpcEncoding::Borsh), None, )?; let wallet = Arc::new(runtime::Wallet::try_with_rpc(Some(rpc.client().clone()), store, network_id)?); diff --git a/wasm/nodejs/addresses.js b/wasm/nodejs/addresses.js index 699f7e43e8..1735ca639a 100644 --- a/wasm/nodejs/addresses.js +++ b/wasm/nodejs/addresses.js @@ -6,9 +6,10 @@ let { createAddress, NetworkType, } = kaspa; + kaspa.initConsolePanicHook(); -(async ()=>{ +(async () => { /*** Common Use-cases ***/ demoGenerateAddressFromPrivateKeyHexString(); demoGenerateAddressFromPublicKeyHexString(); @@ -26,13 +27,13 @@ kaspa.initConsolePanicHook(); // Generates the first 10 Receive Public keys and their addresses let compressedPublicKeys = await xpub.receivePubkeys(0, 10); console.log("receive address compressedPublicKeys", compressedPublicKeys); - let addresses = compressedPublicKeys.map(key=>createAddress(key, NetworkType.Mainnet).toString()); + let addresses = compressedPublicKeys.map(key => createAddress(key, NetworkType.Mainnet).toString()); console.log("receive addresses", addresses); // Generates the first 10 Change Public keys and their addresses compressedPublicKeys = await xpub.changePubkeys(0, 10); console.log("change address compressedPublicKeys", compressedPublicKeys) - addresses = compressedPublicKeys.map(key=>createAddress(key, NetworkType.Mainnet).toString()); + addresses = compressedPublicKeys.map(key => createAddress(key, NetworkType.Mainnet).toString()); console.log("change addresses", addresses); })(); @@ -59,9 +60,9 @@ function demoGenerateAddressFromPublicKeyHexString() { console.info(xOnlyPublicKey.toAddress(NetworkType.Mainnet).toString()); console.info(xOnlyPublicKey.toAddress(NetworkType.Mainnet).toString() == 'kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva'); - // Given full DER public key: '0421EB0C4270128B16C93C5F0DAC48D56051A6237DAE997B58912695052818E348B0A895CBD0C93A11EE7AFAC745929D96A4642A71831F54A7377893AF71A2E2AE' - const fullDERPublicKey = new PublicKey('0421EB0C4270128B16C93C5F0DAC48D56051A6237DAE997B58912695052818E348B0A895CBD0C93A11EE7AFAC745929D96A4642A71831F54A7377893AF71A2E2AE'); - console.info("Given x-only public key: '0421EB0C4270128B16C93C5F0DAC48D56051A6237DAE997B58912695052818E348B0A895CBD0C93A11EE7AFAC745929D96A4642A71831F54A7377893AF71A2E2AE'"); + // Given full DER public key: '0421eb0c4270128b16c93c5f0dac48d56051a6237dae997b58912695052818e348b0a895cbd0c93a11ee7afac745929d96a4642a71831f54a7377893af71a2e2ae' + const fullDERPublicKey = new PublicKey('0421eb0c4270128b16c93c5f0dac48d56051a6237dae997b58912695052818e348b0a895cbd0c93a11ee7afac745929d96a4642a71831f54a7377893af71a2e2ae'); + console.info("Given x-only public key: '0421eb0c4270128b16c93c5f0dac48d56051a6237dae997b58912695052818e348b0a895cbd0c93a11ee7afac745929d96a4642a71831f54a7377893af71a2e2ae'"); console.info(fullDERPublicKey.toString()); console.info(fullDERPublicKey.toAddress(NetworkType.Mainnet).toString()); console.info(fullDERPublicKey.toAddress(NetworkType.Mainnet).toString() == 'kaspa:qqs7krzzwqfgk9kf830smtzg64s9rf3r0khfj76cjynf2pfgrr35saatu88xq'); diff --git a/wasm/nodejs/demo.js b/wasm/nodejs/demo.js index b6ac177760..d8b15564c3 100644 --- a/wasm/nodejs/demo.js +++ b/wasm/nodejs/demo.js @@ -3,30 +3,22 @@ globalThis.WebSocket = require("websocket").w3cwebsocket; const { PrivateKey, - Address, RpcClient, - Encoding, - NetworkType, createTransaction, signTransaction, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); -const {parseArgs} = require("./utils"); initConsolePanicHook(); -(async ()=>{ +// command line arguments --network=(mainnet|testnet-) --encoding=borsh (default) +const { networkId, encoding } = require("./utils").parseArgs(); - const args = parseArgs({}); - - // Either NetworkType.Mainnet or NetworkType.Testnet - const networkType = args.networkType; - // Either Encoding.Borsh or Encoding.SerdeJson - const encoding = args.encoding; +(async () => { // Create secret key from BIP0340 - const sk = new PrivateKey('b99d75736a0fd0ae2da658959813d680474f5a740a9c970a7da867141596178f'); - const keypair = sk.toKeypair(); + const privateKey = new PrivateKey('b99d75736a0fd0ae2da658959813d680474f5a740a9c970a7da867141596178f'); + const keypair = privateKey.toKeypair(); // For example dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659 console.info(keypair.xOnlyPublicKey); @@ -34,16 +26,14 @@ initConsolePanicHook(); console.info(keypair.publicKey); // An address such as kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva - const address = keypair.toAddress(networkType); + const address = keypair.toAddress(networkId); console.info(`Full kaspa address: ${address}`); console.info(address); - const rpcHost = "127.0.0.1"; - // Parse the url to automatically determine the port for the given host - const rpcUrl = RpcClient.parseUrl(rpcHost, encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); - + const rpc = new RpcClient("127.0.0.1", encoding, networkId); + console.log(`Connecting to ${rpc.url}`); await rpc.connect(); + console.log(`Connected to ${rpc.url}`); let { isSynced } = await rpc.getServerInfo(); if (!isSynced) { console.error("Please wait for the node to sync"); @@ -53,7 +43,7 @@ initConsolePanicHook(); try { - const utxos = await rpc.getUtxosByAddresses({addresses: [address]}); + const utxos = await rpc.getUtxosByAddresses([address]); console.info(utxos); @@ -80,8 +70,10 @@ initConsolePanicHook(); console.info(tx); - const transaction = signTransaction(tx, [sk], true); - console.info(JSON.stringify(transaction, null, 4)); + const transaction = signTransaction(tx, [privateKey], true); + + console.log("Transaction:", transaction); + // console.info(JSON.stringify(transaction, null, 4)); let result = await rpc.submitTransaction(transaction); diff --git a/wasm/nodejs/derivation.js b/wasm/nodejs/derivation.js new file mode 100644 index 0000000000..46aed07ee1 --- /dev/null +++ b/wasm/nodejs/derivation.js @@ -0,0 +1,33 @@ +const kaspa = require('./kaspa/kaspa_wasm'); +const { + Mnemonic, + XPrv, + DerivationPath +} = kaspa; + +kaspa.initConsolePanicHook(); + +(async () => { + + const mnemonic = Mnemonic.random(); + console.log("mnemonic:", mnemonic); + const seed = mnemonic.toSeed("my_password"); + console.log("seed:", seed); + + // --- + + const xPrv = new XPrv(seed); + console.log("xPrv", xPrv.intoString("xprv")) + + console.log("xPrv", xPrv.derivePath("m/1'/2'/3").intoString("xprv")) + + const path = new DerivationPath("m/1'"); + path.push(2, true); + path.push(3, false); + console.log(`path: ${path}`); + + console.log("xPrv", xPrv.derivePath(path).intoString("xprv")) + + const xPub = xPrv.publicKey(); + console.log("xPub", xPub.derivePath("m/1").intoString("xpub")); +})(); diff --git a/wasm/nodejs/estimate.js b/wasm/nodejs/estimate.js index 4dc9b7b05c..7caa264b16 100644 --- a/wasm/nodejs/estimate.js +++ b/wasm/nodejs/estimate.js @@ -3,45 +3,27 @@ globalThis.WebSocket = require("websocket").w3cwebsocket; const { PrivateKey, - Address, + Generator, RpcClient, - Encoding, - NetworkType, - UtxoEntries, kaspaToSompi, - createTransactions, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); initConsolePanicHook(); -(async () => { +const { encoding, networkId } = require("./utils").parseArgs(); - let args = process.argv.slice(2); - let destination = args.shift() || "kaspatest:qqkl0ct62rv6dz74pff2kx5sfyasl4z28uekevau23g877r5gt6userwyrmtt"; - console.log("using destination address:", destination); +(async () => { - // --- - // network type - let network = NetworkType.Testnet; - // RPC encoding - let encoding = Encoding.Borsh; - // --- + // console.log("using destination address:", destinationAddress); // From BIP0340 - const sk = new PrivateKey('b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef'); - - const kaspaAddress = sk.toKeypair().toAddress(network).toString(); - // Full kaspa address: kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva - console.info(`Full kaspa address: ${kaspaAddress}`); + const privateKey = new PrivateKey('b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef'); - const address = new Address(kaspaAddress); - console.info(address); - console.info(sk.toKeypair().xOnlyPublicKey); // dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659 - console.info(sk.toKeypair().publicKey); // 02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659 + const sourceAddress = privateKey.toKeypair().toAddress(networkId); + console.info(`Full kaspa address: ${sourceAddress}`); - let rpcUrl = RpcClient.parseUrl("127.0.0.1", encoding, network); - const rpc = new RpcClient(encoding, rpcUrl, network); + const rpc = new RpcClient("127.0.0.1", encoding, networkId); console.log(`Connecting to ${rpc.url}`); await rpc.connect(); @@ -52,10 +34,10 @@ initConsolePanicHook(); return; } - let entries = await rpc.getUtxosByAddresses([address]); + let entries = await rpc.getUtxosByAddresses([sourceAddress]); if (!entries.length) { - console.error(`No UTXOs found for address ${address}`); + console.error(`No UTXOs found for address ${sourceAddress}`); } else { console.info(entries); @@ -66,7 +48,7 @@ initConsolePanicHook(); // entries: an array of UtxoEntry // outputs: an array of [address, amount] // - // priorityFee: a priodityFee value in Sompi + // priorityFee: a priorityFee value in Sompi // NOTE: The priorityFee applies only to the final transaction // // changeAddress: a change address @@ -77,22 +59,22 @@ initConsolePanicHook(); // to the change address. // // If the requested amount is greater than the Kaspa - // transactoin mass, the Generator will create multiple + // transaction mass, the Generator will create multiple // transactions where each transaction will forward // UTXOs to the change address, until the requested // amount is reached. It will then create a final // transaction according to the supplied outputs. let generator = new Generator({ entries, - outputs: [[destination, kaspaToSompi(0.2)]], + outputs: [[sourceAddress, kaspaToSompi(0.2)]], priorityFee: 0, - changeAddress: address, + changeAddress: sourceAddress, }); // provides a generator summary by simulating // transaction creation and returning the // `GeneratorSummary` object - let estimate = generator.estimate(); + let estimate = await generator.estimate(); console.log(estimate); } diff --git a/wasm/nodejs/generator.js b/wasm/nodejs/generator.js index 44012e3a4a..7dc23248e6 100644 --- a/wasm/nodejs/generator.js +++ b/wasm/nodejs/generator.js @@ -3,40 +3,29 @@ globalThis.WebSocket = require("websocket").w3cwebsocket; const { PrivateKey, - Address, RpcClient, - Encoding, - NetworkType, - UtxoEntries, + Generator, kaspaToSompi, - createTransactions, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); initConsolePanicHook(); -(async () => { - - let args = process.argv.slice(2); - let destination = args.shift() || "kaspatest:qqkl0ct62rv6dz74pff2kx5sfyasl4z28uekevau23g877r5gt6userwyrmtt"; - console.log("using destination address:", destination); +const { encoding, networkId, destinationAddress: destinationAddressArg } = require("./utils").parseArgs(); - // --- - // network type - let network = NetworkType.Testnet; - // RPC encoding - let encoding = Encoding.Borsh; - // --- +(async () => { // From BIP0340 const privateKey = new PrivateKey('b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef'); - const address = sk.toKeypair().toAddress(network); - // Full kaspa address: kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva - console.info(`Kaspa address: ${address}`); + const sourceAddress = privateKey.toKeypair().toAddress(networkId); + console.info(`Source address: ${sourceAddress}`); + + // if not destination address is supplied, send funds to source address + const destinationAddress = destinationAddressArg || sourceAddress; + console.info(`Destination address: ${destinationAddress}`); - let rpcUrl = RpcClient.parseUrl("127.0.0.1", encoding, network); - const rpc = new RpcClient(encoding, rpcUrl, network); + const rpc = new RpcClient("127.0.0.1", encoding, networkId); console.log(`Connecting to ${rpc.url}`); await rpc.connect(); @@ -48,10 +37,10 @@ initConsolePanicHook(); } - let entries = await rpc.getUtxosByAddresses([address]); + let entries = await rpc.getUtxosByAddresses([sourceAddress]); if (!entries.length) { - console.error(`No UTXOs found for address ${address}`); + console.error(`No UTXOs found for address ${sourceAddress}`); } else { console.info(entries); @@ -80,9 +69,9 @@ initConsolePanicHook(); // transaction according to the supplied outputs. let generator = new Generator({ entries, - outputs: [[destination, kaspaToSompi(0.2)]], + outputs: [[destinationAddress, kaspaToSompi(0.2)]], priorityFee: 0, - changeAddress: address, + changeAddress: sourceAddress, }); // transaction generator creates a diff --git a/wasm/nodejs/mnemonic.js b/wasm/nodejs/mnemonic.js new file mode 100644 index 0000000000..9d16bd74b7 --- /dev/null +++ b/wasm/nodejs/mnemonic.js @@ -0,0 +1,33 @@ +const kaspa = require('./kaspa/kaspa_wasm'); +const { + Mnemonic, +} = kaspa; + +kaspa.initConsolePanicHook(); + +(async () => { + + const mnemonic1 = Mnemonic.random(); + console.log("mnemonic1:", mnemonic1); + + const mnemonic2 = new Mnemonic(mnemonic1.phrase); + console.log("mnemonic2:", mnemonic2); + + // create a seed with a recovery password ("25th word") + const seed1 = mnemonic1.toSeed("my_password"); + console.log("seed1:", seed1); + + const seed2 = mnemonic2.toSeed("my_password"); + console.log("seed2:", seed2); + + if (seed1 !== seed2) { + throw Error("mnemonic restore failure"); + } else { + console.log("mnemonic restore success"); + } + + // create a seed without a recovery password + const seed3 = mnemonic1.toSeed(); + console.log("seed3 (no recovery password):", seed3); + +})(); diff --git a/wasm/nodejs/refactoring/hd-wallet.js b/wasm/nodejs/refactoring/hd-wallet.js deleted file mode 100644 index 3376ce0767..0000000000 --- a/wasm/nodejs/refactoring/hd-wallet.js +++ /dev/null @@ -1,45 +0,0 @@ -const kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs} = require("../utils"); -const { - Mnemonic, - XPrv, - DerivationPath -} = kaspa; - -kaspa.init_console_panic_hook(); - -(async () => { - const {} = parseArgs(); - - const mnemonic1 = Mnemonic.random(); - console.log("mnemonic1", mnemonic1); - - const mnemonic2 = new Mnemonic(mnemonic1.phrase); - console.log("mnemonic2", mnemonic2); - - const seed1 = mnemonic1.toSeed("my_password"); - console.log("seed1", seed1); - - const seed2 = mnemonic2.toSeed("my_password"); - console.log("seed2", seed2); - - if (seed1 !== seed2) { - throw Error("mnemonic restore dont works"); - } - - const xPrv = new XPrv(seed1); - - console.log("xPrv", xPrv.intoString("xprv")) - - console.log("xPrv", xPrv.derivePath("m/1'/2'/3").intoString("xprv")) - - const path = new DerivationPath("m/1'"); - path.push(2, true); - path.push(3, false); - console.log("path", path + ""); - - console.log("xPrv", xPrv.derivePath(path).intoString("xprv")) - - const xPub = xPrv.publicKey(); - console.log("xPub", xPub.derivePath("m/1").intoString("xpub")); -})(); diff --git a/wasm/nodejs/refactoring/test.js b/wasm/nodejs/refactoring/test.js deleted file mode 100644 index b1d5437788..0000000000 --- a/wasm/nodejs/refactoring/test.js +++ /dev/null @@ -1,27 +0,0 @@ -globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim - -const kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs} = require("../utils"); -kaspa.init_console_panic_hook(); - -(async () => { - const {} = parseArgs(); - - const iter = kaspa.get_async_iter(); - console.log("iter ->", iter); - for await (const item of iter) { - console.log("item ->", item); - } - - // let URL = "ws://127.0.0.1:17110"; - // let rpc = new RpcClient(Encoding.Borsh,URL); - - // console.log(`# connecting to ${URL}`) - // await rpc.connect(); - - // let info = await rpc.getBlockDagInfo(); - // console.log("info:", info); - - // await rpc.disconnect(); - -})(); diff --git a/wasm/nodejs/refactoring/tx-create.js b/wasm/nodejs/refactoring/tx-create.js index 7b9bd6d1db..4d6e82d0f0 100644 --- a/wasm/nodejs/refactoring/tx-create.js +++ b/wasm/nodejs/refactoring/tx-create.js @@ -1,7 +1,7 @@ globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim const kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs, guardRpcIsSynced} = require("../utils"); +const { parseArgs, guardRpcIsSynced } = require("../utils"); const { RpcClient, UtxoSet, Address, Encoding, UtxoOrdering, PaymentOutputs, PaymentOutput, @@ -25,12 +25,9 @@ kaspa.init_console_panic_hook(); networkType, } = parseArgs(); - const rpcHost = "127.0.0.1"; - // Parse the url to automatically determine the port for the given host - const rpcUrl = RpcClient.parseUrl(rpcHost, encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); + const rpc = new RpcClient("127.0.0.1", encoding, networkType); - console.log(`# connecting to ${URL}`) + console.log(`Connecting to ${rpc.url}`) await rpc.connect(); await guardRpcIsSynced(rpc); @@ -141,7 +138,7 @@ kaspa.init_console_panic_hook(); console.log("before submit mtx.id", mtx.id) transaction = mtx.toRpcTransaction(); - let result = await rpc.submitTransaction({transaction, allowOrphan: false}); + let result = await rpc.submitTransaction({ transaction, allowOrphan: false }); console.log("result", result) diff --git a/wasm/nodejs/refactoring/tx-misc.js b/wasm/nodejs/refactoring/tx-misc.js deleted file mode 100644 index 6beaf9d7b8..0000000000 --- a/wasm/nodejs/refactoring/tx-misc.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - -BigInt.prototype["toJSON1"] = function(){ - return this.toString() -} - -// let {RpcClient,Encoding,init_console_panic_hook,defer} = require('./kaspa'); -let kaspa = require('./kaspa/kaspa_wasm'); -kaspa.init_console_panic_hook(); - -// let txid = new kaspa.Hash("880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3"); -let txid = "880eb9819a31821d9d2399e2f35e2433b72637e393d71ecc9b8d0250f49153c3"; - -let keypair1 = kaspa.generate_random_keypair_not_secure(); -//console.log("keypair:",keypair1); -let keypair2 = kaspa.generate_random_keypair_not_secure(); -//console.log("keypair2:",keypair2); -let keypair3 = kaspa.generate_random_keypair_not_secure(); -//console.log("keypair3:",keypair3); - -//let scriptPubKey1Bytes = keypair1.publicKey;// '20'+keypair.xOnlyPublicKey+'ac'; -//console.log("scriptPubKeyBytes:",scriptPubKey1Bytes); -//console.log("scriptPubKeyBytes:",scriptPubKey1Bytes); - -let inputs = [ - new kaspa.TransactionInput({ - previousOutpoint: { transactionId: txid, index: 0 }, - signatureScript: [], - sequence: 0, - sigOpCount: 0 - }), - new kaspa.TransactionInput({ - previousOutpoint: { transactionId: txid, index: 1 }, - signatureScript: [], - sequence: 1, - sigOpCount: 0 - }), - new kaspa.TransactionInput({ - previousOutpoint: { transactionId: txid, index: 2 }, - signatureScript: [], - sequence: 2, - sigOpCount: 0 - }), -]; - -//console.log("inputs:",inputs); - -// console.log("scriptPubKey:",scriptPubKeyBytes, typeof scriptPubKey); -let scriptPublicKey1 = new kaspa.ScriptPublicKey(0, keypair1.publicKey); -console.log("scriptPublicKey1:",scriptPublicKey1); -let scriptPublicKey2 = new kaspa.ScriptPublicKey(0, keypair2.publicKey); -console.log("scriptPublicKey2:",scriptPublicKey2); - -let utxos = [ - new kaspa.UtxoEntry(18446744073709551615n, scriptPublicKey1, 0n, false), - new kaspa.UtxoEntry(340282366920938463463374607431768211455n, scriptPublicKey2, 0n, false), - //new kaspa.UtxoEntry(310n, scriptPublicKey1, 0n, false), - { - amount: 310n, - scriptPublicKey: { - version: 0, - script: keypair1.publicKey - }, - blockDaaScore: 0n, - isCoinbase: false - } -]; - -console.log("utxos", utxos) - -let utxoEntries = new kaspa.UtxoEntries(utxos); -//console.log("utxoEntries:", utxoEntries.items); - -let outputs = [ - new kaspa.TransactionOutput(300n, new kaspa.ScriptPublicKey(0, keypair3.publicKey)), - { - value: 300n, - scriptPublicKey : new kaspa.ScriptPublicKey(0, keypair3.publicKey) - }, -]; - -//console.log("outputs:",outputs); - -let transaction = new kaspa.Transaction({ - inputs, - outputs, - lockTime: 1615462089000, - subnetworkId: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - version: 1, - gas: 0, - payload: [], -}); - -// transaction.inputs.push(new kaspa.TransactionInput({ -// previousOutpoint: { transactionId: txid, index: 2 }, -// signatureScript: [], -// sequence: 2, -// sigOpCount: 0 -// })); -// transaction.inputs.push(new kaspa.TransactionInput({ -// previousOutpoint: { transactionId: txid, index: 2 }, -// signatureScript: [], -// sequence: 2, -// sigOpCount: 0 -// })); - -// console.log("transaction.inputs.length", transaction.inputs.length) - -let signableTx = new kaspa.MutableTransaction(transaction, utxoEntries); -let json = signableTx.toJSON(); -console.log("\nsignableTx.toJSON()", json); -console.log("\nsignableTx.getScriptHashes()", signableTx.getScriptHashes()); - -signableTx = kaspa.MutableTransaction.fromJSON(json); -// console.log("\nJSON casting test: ", signableTx.toJSON()==json); - -// console.log("\njson parse direct", JSON.parse(json)); -// console.log("\njson parse via helper", JSON.parse(json, (key, value, c)=>{ -// if (["amount"].includes(key)){ -// return BigInt(value) -// } -// console.log("#######", {key, value, c}) -// return value; -// })); - -//console.log("signableTx.entries.items", signableTx.entries.items) -let keys = [ - keypair2.privateKey, - //keypair1.privateKey -] - -console.log("\nkeys", keys) - -transaction = kaspa.signTransaction(signableTx, keys, false); - -console.log("\ntransaction:", transaction); - -let signer = new kaspa.Signer([ - keypair1.privateKey -]); - -console.log("\nsigner:", signer) - -// Option 1 -// transaction = signer.signTransaction(transaction, true); - -// Option 2 -transaction = kaspa.signTransaction(transaction, signer, true); - -console.log("\ntransaction:", transaction); - - - - - -//console.log("transaction:", JSON.stringify(transaction, null, "\t")); -// console.log("transaction (JSON):", JSON.stringify(transaction,(k,v) => { -// console.log(k,v,typeof v); -// if (typeof v == 'bigint') { -// return v.toString(); -// } else { -// return v; -// } -// },"\t")); - -// TODO sign - -*/ \ No newline at end of file diff --git a/wasm/nodejs/refactoring/tx-script-sign.js b/wasm/nodejs/refactoring/tx-script-sign.js index da6edee23c..b4f6bc40b4 100644 --- a/wasm/nodejs/refactoring/tx-script-sign.js +++ b/wasm/nodejs/refactoring/tx-script-sign.js @@ -1,7 +1,7 @@ globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim let kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs, guardRpcIsSynced} = require("../utils"); +const { parseArgs, guardRpcIsSynced } = require("../utils"); let { RpcClient, UtxoSet, Address, Encoding, UtxoOrdering, PaymentOutputs, PaymentOutput, @@ -23,10 +23,7 @@ kaspa.init_console_panic_hook(); // The kaspa address that was passed as an argument or a default one const address = args.address ?? "kaspatest:qz7ulu4c25dh7fzec9zjyrmlhnkzrg4wmf89q7gzr3gfrsj3uz6xjceef60sd"; - const rpcHost = "127.0.0.1"; - // Parse the url to automatically determine the port for the given host - const rpcUrl = RpcClient.parseUrl(rpcHost, encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); + const rpc = new RpcClient("127.0.0.1", encoding, networkType); console.log(`# connecting to ${URL}`) await rpc.connect(); @@ -43,7 +40,7 @@ kaspa.init_console_panic_hook(); console.log("\nJSON.stringify(addresses):", JSON.stringify(addresses)); console.log("\ngetting UTXOs..."); - const utxosByAddress = await rpc.getUtxosByAddresses({addresses}); + const utxosByAddress = await rpc.getUtxosByAddresses({ addresses }); console.log("Creating UtxoSet..."); //console.log("utxos_by_address", utxos_by_address) const utxoSet = UtxoSet.from(utxosByAddress); @@ -88,7 +85,7 @@ kaspa.init_console_panic_hook(); console.log("transaction", transaction) //let transaction = tx.toRpcTransaction(); - const result = await rpc.submitTransaction({transaction, allowOrphan: false}); + const result = await rpc.submitTransaction(transaction); console.log("result", result) diff --git a/wasm/nodejs/refactoring/tx-send.js b/wasm/nodejs/refactoring/tx-send.js deleted file mode 100644 index 677e28a22c..0000000000 --- a/wasm/nodejs/refactoring/tx-send.js +++ /dev/null @@ -1,102 +0,0 @@ -globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim - -const kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs, guardRpcIsSynced} = require("../utils"); -const { - RpcClient, UtxoSet, Address, Encoding, UtxoOrdering, - PaymentOutputs, PaymentOutput, - XPrivateKey, - VirtualTransaction, - createTransaction, - signTransaction, - Person, - Address1, - Location -} = kaspa; -kaspa.init_console_panic_hook(); - -(async () => { - const args = parseArgs({}); - - // Either NetworkType.Mainnet or NetworkType.Testnet - const networkType = args.networkType; - // Either Encoding.Borsh or Encoding.SerdeJson - const encoding = args.encoding; - // The kaspa address that was passed as an argument or a default one - const address = args.address ?? "kaspatest:qz7ulu4c25dh7fzec9zjyrmlhnkzrg4wmf89q7gzr3gfrsj3uz6xjceef60sd"; - - const rpcHost = "127.0.0.1"; - // Parse the url to automatically determine the port for the given host - const rpcUrl = RpcClient.parseUrl(rpcHost, encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); - - console.log(`# connecting to ${URL}`) - await rpc.connect(); - await guardRpcIsSynced(rpc); - - const info = await rpc.getInfo(); - console.log("info", info); - - const addresses = [ - address, - //new Address("kaspatest:qz7ulu4c25dh7fzec9zjyrmlhnkzrg4wmf89q7gzr3gfrsj3uz6xjceef60sd") - ]; - - //let addresses = ["kaspatest:qz7ulu4c25dh7fzec9zjyrmlhnkzrg4wmf89q7gzr3gfrsj3uz6xjceef60sd"]; - //console.log("\naddresses:", addresses); - console.log("\nJSON.stringify(addresses):", JSON.stringify(addresses)); - //console.log("\naddresses.toString():", addresses.toString()); - // console.log(addresses.toString()); - - console.log("\ngetting UTXOs..."); - const utxosByAddress = await rpc.getUtxosByAddresses({addresses}); - console.log("Creating UtxoSet..."); - //console.log("utxos_by_address", utxos_by_address) - const utxoSet = UtxoSet.from(utxosByAddress); - - //console.log("utxos_by_address", utxos_by_address) - - const amount = 1000n; - - const utxoSelection = await utxoSet.select(amount + 100n, UtxoOrdering.AscendingAmount); - - console.log("utxo_selection.amount", utxoSelection.amount) - console.log("utxo_selection.totalAmount", utxoSelection.totalAmount) - const utxos = utxoSelection.utxos; - console.log("utxos", utxos) - console.log("utxos.*.data.entry", utxos.map(a => a.data.entry)) - - - const outputs = [ - [ - address, - amount, - ] - ]; - - console.log("outputs", outputs) - - const changeAddress = address; - - const priorityFee = 0; - const tx = createTransaction(utxoSelection, outputs, changeAddress, priorityFee); - console.log("tx", tx) - - const xKey = new XPrivateKey( - "kprv5y2qurMHCsXYrNfU3GCihuwG3vMqFji7PZXajMEqyBkNh9UZUJgoHYBLTKu1eM4MvUtomcXPQ3Sw9HZ5ebbM4byoUciHo1zrPJBQfqpLorQ", - false, - 0n - ); - - const private_key = xKey.receiveKey(0); - - console.log("tx.inputs", tx.inputs) - - let transaction = signTransaction(tx, [private_key], true); - transaction = transaction.toRpcTransaction(); - const result = await rpc.submitTransaction({transaction, allowOrphan: false}); - - console.log("result", result) - - await rpc.disconnect(); -})(); diff --git a/wasm/nodejs/refactoring/utxo-split.js b/wasm/nodejs/refactoring/utxo-split.js deleted file mode 100644 index 3c85b69b7d..0000000000 --- a/wasm/nodejs/refactoring/utxo-split.js +++ /dev/null @@ -1,135 +0,0 @@ -globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim - -const kaspa = require('../kaspa/kaspa_wasm'); -const {parseArgs, guardRpcIsSynced} = require("../utils"); -const { - RpcClient, - UtxoSet, - Address, - Encoding, - UtxoOrdering, - PaymentOutputs, - PaymentOutput, - XPrivateKey, - VirtualTransaction, - createTransaction, - signTransaction, - calculateTransactionMass, - Person, - Location, - NetworkType, - UtxoEntries, - LimitCalcStrategy, - Abortable -} = kaspa; -kaspa.init_console_panic_hook(); - -(async () => { - const args = parseArgs({ - additionalParseArgs: { - address2: { - type: 'string', - }, - }, - additionalHelpOutput: '[--address2
]' - }); - const address2Arg = args.tokens.find((token) => token.name === 'address2')?.value; - - // Either NetworkType.Mainnet or NetworkType.Testnet - const networkType = args.networkType; - // Either Encoding.Borsh or Encoding.SerdeJson - const encoding = args.encoding; - - const rpcHost = "127.0.0.1"; - // Parse the url to automatically determine the port for the given host - const rpcUrl = RpcClient.parseUrl(rpcHost, encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); - - console.log(`# connecting to ${URL}`) - await rpc.connect(); - await guardRpcIsSynced(rpc); - - const info = await rpc.getInfo(); - console.log("info", info); - - const addr1 = args.address ?? "kaspa:qq5dawejjdzp22jsgtn2mdr3lg45j7pq0yaq8he8t8269lvg87cuwl7ze7djh"; - const addr2 = address2Arg ?? "kaspa:qzewpzt0rx6jmvy0eea82lpnf0t7f7frmqavqcaawmt4wk70puazcp8zljgx5"; - const addresses = [ - addr1, - addr2, - ]; - - console.log("\ngetting UTXOs..."); - const utxosByAddress = await rpc.getUtxosByAddresses({addresses}); - console.log("\nCreating UtxoSet..."); - const utxoSet = UtxoSet.from(utxosByAddress); - - //console.log("utxos_by_address", utxos_by_address) - const count = 90; - - const amount = 10000n; - - const utxo_selection = await utxoSet.select(amount * BigInt(count), UtxoOrdering.AscendingAmount); - - console.log("utxo_selection.amount", utxo_selection.amount) - console.log("utxo_selection.totalAmount", utxo_selection.totalAmount) - //const utxos = utxo_selection.utxos; - - let outputs = []; - for (let i = 0; i < count; i++) { - outputs.push([addr1, amount]); - } - const priorityFee = 0n; - - //console.log("outputs", outputs) - - const xKey = new XPrivateKey( - "kprv...", - false, - 0n - ); - - const private_keys = []; - private_keys.push(xKey.changeKey(0)); - private_keys.push(xKey.receiveKey(0)); - - const change_address = new Address("kaspa:qq5dawejjdzp22jsgtn2mdr3lg45j7pq0yaq8he8t8269lvg87cuwl7ze7djh"); - let result = []; - - if (false) { - const tx = createTransaction(1, utxo_selection, outputs, change_address, 1, priorityFee); - //console.log("tx", tx) - const transaction = signTransaction(tx, private_keys, true); - result = [await rpc.submitTransaction({transaction, allowOrphan: false})]; - // console.log("result", result) - } else { - const vt = await new VirtualTransaction( - 1, - 1, - utxo_selection, - outputs, - change_address, - priorityFee, - [], - LimitCalcStrategy.calculated(), - new Abortable() - ); - vt.sign(private_keys, true); - //txs = vt.transactions(); - //result = await vt.submit(rpc, false); - } - - // console.log("txs.length", txs.length) - // for(transaction of txs){ - // console.log("inputs length", transaction.inputs.length) - // const mass = transaction.mass(NetworkType.Mainnet, false, 1); - // console.log("mass after sign", mass); - // transaction = transaction.toRpcTransaction(); - // // const result = await rpc.submitTransaction({transaction, allowOrphan:false}); - // // console.log("result", result) - // } - - console.log("result", result) - - await rpc.disconnect(); -})(); diff --git a/wasm/nodejs/rpc.js b/wasm/nodejs/rpc.js index 59be5a1b2f..f4ff483290 100644 --- a/wasm/nodejs/rpc.js +++ b/wasm/nodejs/rpc.js @@ -1,24 +1,26 @@ globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim const kaspa = require('./kaspa/kaspa_wasm'); -const {parseArgs} = require("./utils"); +const { parseArgs } = require("./utils"); const { RpcClient } = kaspa; + kaspa.initConsolePanicHook(); +const { + networkId, + encoding, +} = parseArgs(); + (async () => { - const { - networkType, - encoding, - } = parseArgs(); - const rpc = new RpcClient(encoding, "127.0.0.1", networkType); - console.log(`# connecting to ${rpc.url}`) + const rpc = new RpcClient("127.0.0.1", encoding, networkId); + console.log(`Connecting to ${rpc.url}`) await rpc.connect(); const info = await rpc.getBlockDagInfo(); - console.log("info:", info); + console.log("GetBlockDagInfo response:", info); await rpc.disconnect(); })(); diff --git a/wasm/nodejs/simple-transaction.js b/wasm/nodejs/simple-transaction.js index 34ef98a95a..5f035e922e 100644 --- a/wasm/nodejs/simple-transaction.js +++ b/wasm/nodejs/simple-transaction.js @@ -9,34 +9,26 @@ const { createTransactions, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); -const { parseArgs } = require('./utils'); + +const { encoding, networkId, destinationAddress: destinationAddressArg } = require("./utils").parseArgs(); initConsolePanicHook(); -(async ()=>{ +(async () => { - let { - address: destinationAddress, - networkType, - encoding, - } = parseArgs(); - destinationAddress = destinationAddress ?? "kaspatest:qqkl0ct62rv6dz74pff2kx5sfyasl4z28uekevau23g877r5gt6userwyrmtt"; - console.log("using destination address:", destinationAddress); // From BIP0340 const privateKey = new PrivateKey('b99d75736a0fd0ae2da658959813d680474f5a740a9c970a7da867141596178f'); - const kaspaAddress = privateKey.toKeypair().toAddress(networkType).toString(); - // Full kaspa address: kaspa:qr0lr4ml9fn3chekrqmjdkergxl93l4wrk3dankcgvjq776s9wn9jkdskewva - console.info(`Full kaspa address: ${kaspaAddress}`); + const sourceAddress = privateKey.toKeypair().toAddress(networkId); + console.info(`Source address: ${sourceAddress}`); - console.info(kaspaAddress); - const keypair = privateKey.toKeypair(); - console.info(keypair.xOnlyPublicKey); // dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659 - console.info(keypair.publicKey); // 02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659 + // if not destination address is supplied, send funds to source address + const destinationAddress = destinationAddressArg || sourceAddress; + console.log(`Destination address: ${destinationAddress}`); - let rpcUrl = RpcClient.parseUrl("127.0.0.1", encoding, networkType); - const rpc = new RpcClient(encoding, rpcUrl, networkType); + // let rpcUrl = RpcClient.parseUrl("127.0.0.1", encoding, networkType); + const rpc = new RpcClient("127.0.0.1", encoding, networkId); console.log(`Connecting to ${rpc.url}`); await rpc.connect(); @@ -47,7 +39,7 @@ initConsolePanicHook(); return; } - let entries = await rpc.getUtxosByAddresses([kaspaAddress]); + let entries = await rpc.getUtxosByAddresses([sourceAddress]); if (!entries.length) { console.error("No UTXOs found for address"); @@ -58,18 +50,18 @@ initConsolePanicHook(); entries.sort((a, b) => a.utxoEntry.amount > b.utxoEntry.amount || -(a.utxoEntry.amount < b.utxoEntry.amount)); let { transactions, summary } = await createTransactions({ - entries, - outputs : [[destinationAddress, kaspaToSompi(0.00012)]], + entries, + outputs: [[destinationAddress, kaspaToSompi(0.00012)]], priorityFee: 0, - changeAddress: skAddress, + changeAddress: sourceAddress, }); console.log("Summary:", summary); for (let pending of transactions) { console.log("Pending transaction:", pending); - console.log("Signing tx with secret key:",sk.toString()); - await pending.sign([sk]); + console.log("Signing tx with secret key:", privateKey.toString()); + await pending.sign([privateKey]); console.log("Submitting pending tx to RPC ...") let txid = await pending.submit(rpc); console.log("Node responded with txid:", txid); diff --git a/wasm/nodejs/utils.js b/wasm/nodejs/utils.js index 34c0169cc5..fb1321ce3a 100644 --- a/wasm/nodejs/utils.js +++ b/wasm/nodejs/utils.js @@ -1,11 +1,11 @@ const path = require('path'); const nodeUtil = require('node:util'); -const {parseArgs: nodeParseArgs} = nodeUtil; +const { parseArgs: nodeParseArgs } = nodeUtil; const { Address, Encoding, - NetworkType, + NetworkId, } = require('./kaspa/kaspa_wasm'); /** @@ -38,10 +38,13 @@ function parseArgs(options = { network: { type: 'string', }, + encoding: { + type: 'string', + }, }, tokens: true, allowPositionals: true }); if (values.help) { - console.log(`Usage: node ${script} [address] [mainnet|testnet] [--destination
] [--network ] [--json] ${options.additionalHelpOutput}`); + console.log(`Usage: node ${script} [address] [mainnet|testnet] [--destination
] [--network ] [--encoding ] ${options.additionalHelpOutput}`); process.exit(0); } @@ -49,17 +52,22 @@ function parseArgs(options = { const addressArg = values.address ?? positionals.find((positional) => addressRegex.test(positional)) ?? null; const destinationAddress = addressArg === null ? null : new Address(addressArg); - let networkType = addressArg?.startsWith('kaspa:') ? NetworkType.Mainnet : NetworkType.Testnet; - const networkArg = values.network ?? positionals.find((positional) => positional === 'mainnet' || positional === 'testnet') ?? null; - if (networkArg !== null) { - networkType = networkArg === 'mainnet' ? NetworkType.Mainnet : NetworkType.Testnet; + const networkArg = values.network ?? positionals.find((positional) => positional.match(/^(testnet|mainnet|simnet|devnet)-\d+$/)) ?? null; + if (!networkArg) { + console.error('Network id must be specified: --network=(mainnet|testnet-)'); + process.exit(1); } + const networkId = new NetworkId(networkArg); - const encoding = values.json ? Encoding.SerdeJson : Encoding.Borsh; + const encodingArg = values.encoding ?? positionals.find((positional) => positional.match(/^(borsh|json)$/)) ?? null; + let encoding = Encoding.Borsh; + if (encodingArg == "json") { + encoding = Encoding.SerdeJson; + } return { destinationAddress, - networkType, + networkId, encoding, tokens, }; diff --git a/wasm/nodejs/utxo-context-generator.js b/wasm/nodejs/utxo-context-generator.js index 09c8a690e4..d76d65c8d4 100644 --- a/wasm/nodejs/utxo-context-generator.js +++ b/wasm/nodejs/utxo-context-generator.js @@ -11,31 +11,28 @@ const { createTransactions, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); -const { parseArgs } = require('./utils'); initConsolePanicHook(); +const { encoding, networkId, destinationAddress } = require("./utils").parseArgs(); + + (async () => { - let { - destinationAddress, - networkType, - encoding, - } = parseArgs(); - const privateKey = new PrivateKey('b99d75736a0fd0ae2da658959813d680474f5a740a9c970a7da867141596178f'); - const kaspaAddress = privateKey.toKeypair().toAddress(networkType); + const sourceAddress = privateKey.toKeypair().toAddress(networkType); + console.log(`Source address: ${sourceAddress}`); // if not destination is specified, send back to ourselves - destinationAddress = destinationAddress ?? kaspaAddress; - console.log("using destination address:", destinationAddress.toString()); + destinationAddress = destinationAddress ?? sourceAddress; + console.log(`Destination address: ${destinationAddress}`); // 1) Initialize RPC - const rpc = new RpcClient(encoding, "127.0.0.1", networkType); + const rpc = new RpcClient("127.0.0.1", encoding, networkId); - // - TODO - network id // 2) Create UtxoProcessor, passing RPC to it - let processor = await new UtxoProcessor({ rpc, networkId: "testnet-10" }); + let processor = await new UtxoProcessor({ rpc, networkId }); + // 3) Create one of more UtxoContext, passing UtxoProcessor to it // you can create UtxoContext objects as needed to monitor different // address sets. @@ -59,7 +56,7 @@ initConsolePanicHook(); } // 6) Register the address list with the UtxoContext - await context.trackAddresses([kaspaAddress]); + await context.trackAddresses([sourceAddress]); // 7) Check balance, if there are enough funds, send a transaction if (context.balance.mature > kaspaToSompi(0.2) + 1000n) { @@ -69,7 +66,7 @@ initConsolePanicHook(); context, outputs: [[destinationAddress, kaspaToSompi(0.2)]], priorityFee: 0, - changeAddress: address, + changeAddress: sourceAddress, }); while (pending = await generator.next()) { @@ -83,7 +80,7 @@ initConsolePanicHook(); } else { console.log("Not enough funds to send transaction"); } - + await processor.shutdown(); await rpc.disconnect(); diff --git a/wasm/nodejs/utxo-context-listener.js b/wasm/nodejs/utxo-context-listener.js index 8972d388ad..5aa5b6f2e1 100644 --- a/wasm/nodejs/utxo-context-listener.js +++ b/wasm/nodejs/utxo-context-listener.js @@ -11,26 +11,27 @@ const { createTransactions, initConsolePanicHook } = require('./kaspa/kaspa_wasm'); -const { parseArgs } = require('./utils'); initConsolePanicHook(); +const { encoding, networkId, destinationAddress } = require("./utils").parseArgs(); + (async () => { - let { - networkType, - encoding, - } = parseArgs(); - const privateKey = new PrivateKey('b99d75736a0fd0ae2da658959813d680474f5a740a9c970a7da867141596178f'); - const kaspaAddress = privateKey.toKeypair().toAddress(networkType); + const sourceAddress = privateKey.toKeypair().toAddress(networkType); + console.info(`Source address: ${sourceAddress}`); + + // if not destination is specified, send back to ourselves + destinationAddress = destinationAddress ?? sourceAddress; + console.info(`Destination address: ${destinationAddress}`); // 1) Initialize RPC - const rpc = new RpcClient(encoding, "127.0.0.1", networkType); + const rpc = new RpcClient("127.0.0.1", encoding, networkType); - // - TODO - network id // 2) Create UtxoProcessor, passing RPC to it - let processor = await new UtxoProcessor({ rpc, networkId: "testnet-10" }); + let processor = await new UtxoProcessor({ rpc, networkId }); + // 3) Create one of more UtxoContext, passing UtxoProcessor to it // you can create UtxoContext objects as needed to monitor different // address sets. @@ -54,6 +55,6 @@ initConsolePanicHook(); } // 6) Register the address list with the UtxoContext - await context.trackAddresses([kaspaAddress]); + await context.trackAddresses([sourceAddress]); })(); \ No newline at end of file