From bfdb12ede02a6df6f1e2a2d370e854195f3f8178 Mon Sep 17 00:00:00 2001 From: William Wills Date: Fri, 10 Oct 2025 23:10:04 -0400 Subject: [PATCH 01/12] it certainly possibly might do something --- Cargo.lock | 155 ++++--- Cargo.toml | 2 +- napi/Cargo.toml | 2 +- napi/src/conversions.rs | 910 ++++++++++++++++++++-------------------- napi/src/napi_lib.rs | 2 +- src/lib.rs | 26 +- src/server_coin.rs | 7 +- src/wallet.rs | 127 +++++- 8 files changed, 667 insertions(+), 564 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0db60b..3c44ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -91,9 +91,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -101,7 +101,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-link", ] [[package]] @@ -242,9 +242,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", @@ -394,9 +394,9 @@ dependencies = [ [[package]] name = "chia-sdk-client" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f44eebc9bb4a596f0c770822facc117416a02db9c78d10b451956597011a3e" +checksum = "09a9ab7609f5c98ff34fc5ba33ad50bfdecca771165640eb88e8054ccef18916" dependencies = [ "chia-protocol", "chia-sdk-types", @@ -404,7 +404,7 @@ dependencies = [ "chia-traits 0.26.0", "futures-util", "native-tls", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-tungstenite", "tracing", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "chia-sdk-coinset" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cac592b1eece9eb9e46db9027d58f4c3ec9b549c5419e249998c53ed7795bd" +checksum = "bb55e723e4b847fdbb9e729fef1a0f21c624b618bda8a17c9c205ff9db64a1e1" dependencies = [ "chia-protocol", "hex", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "chia-sdk-derive" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7ac9360740b8af343eadcb60f3155d6f9f56bef2f56d79b71e3e6be2a5d529" +checksum = "add9e60902df26dfe987d87f1b19ab7e359c6269fa4f222a9d79c02a9dba969d" dependencies = [ "convert_case 0.8.0", "quote", @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "chia-sdk-driver" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91b1e0ec55edbe736cf9f7a33b492ca05c635ccb022fe9511be83b6ba3e235" +checksum = "260c97751f8986e7f82091e3bee504044efb79857f26bfcd2b5d37cba674f79e" dependencies = [ "bigdecimal", "bip39", @@ -465,14 +465,14 @@ dependencies = [ "num-bigint", "rand", "rand_chacha", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "chia-sdk-signer" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd70865cfd9881f5282d4a9c5e42905f96f88b2ec35efda8cf23c944e258d20" +checksum = "d71037389cc3bdebc47c9c6cfebc898244393e4e7246b486d64922a9c23bbac6" dependencies = [ "chia-bls 0.26.0", "chia-consensus", @@ -483,14 +483,14 @@ dependencies = [ "clvm-traits", "clvmr", "k256", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "chia-sdk-test" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c459f50deea22e03ab715868d9ed0c11922d7325a0dd8d328d07b3ad5df63e5c" +checksum = "365b95611176b670cd61455ce6578cccf740a66b7d27cd3b31395c12f4073bc2" dependencies = [ "anyhow", "bip39", @@ -518,7 +518,7 @@ dependencies = [ "serde", "serde_json", "signature", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-tungstenite", "tracing", @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "chia-sdk-types" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14123f03a5ccb66c245b216481436749d11c8f394c0fe7a2bf1e35f2a582f6f" +checksum = "298b92f005cd884861678808813cb49dca3545231b6b442d691e01535ed86825" dependencies = [ "chia-bls 0.26.0", "chia-consensus", @@ -543,21 +543,21 @@ dependencies = [ "clvm_tools_rs", "clvmr", "hex-literal", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "chia-sdk-utils" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca897c36475cbc6832271cacdc4c5d13f5963ef61f99cebec246fd91e981306f" +checksum = "738bc2a53dcece78184aca4de617df666e984af0c86560131213039889d927fc" dependencies = [ "bech32", "chia-protocol", "indexmap", "rand", "rand_chacha", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "chia-wallet-sdk" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae886c1c212efcbd43564e8039920d3bd94a8db35e8fbd649e6b01ebe8c167e" +checksum = "94547c50b141ad9ae84f9bcc26c0bf467b7570f0354c906fb203eeea69886a4c" dependencies = [ "chia-bls 0.26.0", "chia-protocol", @@ -1077,7 +1077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1098,9 +1098,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "fnv" @@ -1236,9 +1236,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -1644,9 +1644,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" @@ -1694,11 +1694,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1935,9 +1934,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1997,9 +1996,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.2+3.5.2" +version = "300.5.3+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" +checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" dependencies = [ "cc", ] @@ -2031,9 +2030,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2041,15 +2040,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -2194,9 +2193,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -2259,9 +2258,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -2411,7 +2410,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2441,7 +2440,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2495,9 +2494,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -2505,18 +2504,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2658,9 +2657,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "subtle" @@ -2721,7 +2720,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2746,11 +2745,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2766,9 +2765,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -3015,9 +3014,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" @@ -3229,9 +3228,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -3253,9 +3252,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -3459,9 +3458,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 7f89e4d..4de5b31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ chia-puzzles = "0.20.1" thiserror = "1.0.61" clvmr = "0.14.0" tokio = "1.39.3" -chia-wallet-sdk = { version = "0.29.0", features = ["chip-0035", "native-tls", "peer-simulator"] } +chia-wallet-sdk = { version = "0.30.0", features = ["chip-0035", "native-tls", "peer-simulator"] } hex-literal = "0.4.1" num-bigint = "0.4.6" hex = "0.4.3" diff --git a/napi/Cargo.toml b/napi/Cargo.toml index fa8a762..42cd40f 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -23,7 +23,7 @@ napi-derive = "2.12.2" chia = "0.26.0" thiserror = "1.0.61" tokio = "1.39.3" -chia-wallet-sdk = { version = "0.29.0", features = ["chip-0035", "native-tls", "peer-simulator"] } +chia-wallet-sdk = { version = "0.30.0", features = ["chip-0035", "native-tls", "peer-simulator", "action-layer"] } hex = "0.4.3" rand = "0.8" futures-util = "0.3" diff --git a/napi/src/conversions.rs b/napi/src/conversions.rs index 32a8a07..1e00bc8 100644 --- a/napi/src/conversions.rs +++ b/napi/src/conversions.rs @@ -1,455 +1,455 @@ -use chia::{ - bls::{PublicKey, SecretKey, Signature}, - protocol::{Bytes, Bytes32, Program}, -}; -use napi::bindgen_prelude::*; -use napi::Result; -use thiserror::Error; - -use crate::{js, rust}; - -#[derive(Error, Debug)] -pub enum ConversionError { - #[error("Expected different byte length {0}")] - DifferentLength(u32), - - #[error("Invalid public key")] - InvalidPublicKey, - - #[error("Invalid private key")] - InvalidPrivateKey, - - #[error("Invalid signature")] - InvalidSignature, - - #[error("Missing proof")] - MissingProof, - - #[error("Missing delegated puzzle info")] - MissingDelegatedPuzzleInfo, - - #[error("Invalid URI: {0}")] - InvalidUri(String), -} - -pub trait FromJs { - fn from_js(value: T) -> Result - where - Self: Sized; -} - -pub trait ToJs { - fn to_js(&self) -> Result; -} - -impl ToJs for String { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.clone())) - } -} - -impl FromJs for String { - fn from_js(value: Buffer) -> Result { - let string = String::from_utf8(value.to_vec()) - .map_err(|e| Error::from_reason(format!("Invalid UTF-8: {}", e)))?; - Ok(string) - } -} - -impl FromJs for Bytes32{ - fn from_js(value: Buffer) -> Result { - Self::try_from(value.as_ref().to_vec()) - .map_err(|_| js::err(ConversionError::DifferentLength(32))) - } -} - -impl ToJs for Bytes32 { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_vec())) - } -} - -impl FromJs for Program { - fn from_js(value: Buffer) -> Result { - Ok(Self::from(value.to_vec())) - } -} - -impl ToJs for Program { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_vec())) - } -} - -impl FromJs for Bytes { - fn from_js(value: Buffer) -> Result { - Ok(Self::new(value.to_vec())) - } -} - -impl ToJs for Bytes { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_vec())) - } -} - -impl FromJs for PublicKey { - fn from_js(value: Buffer) -> Result { - Self::from_bytes( - &<[u8; 48]>::try_from(value.to_vec()) - .map_err(|_| js::err(ConversionError::DifferentLength(48)))?, - ) - .map_err(|_| js::err(ConversionError::InvalidPublicKey)) - } -} - -impl ToJs for PublicKey { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_bytes().to_vec())) - } -} - -impl FromJs for SecretKey { - fn from_js(value: Buffer) -> Result { - Self::from_bytes( - &<[u8; 32]>::try_from(value.to_vec()) - .map_err(|_| js::err(ConversionError::DifferentLength(32)))?, - ) - .map_err(|_| js::err(ConversionError::InvalidPrivateKey)) - } -} - -impl ToJs for SecretKey { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_bytes().to_vec())) - } -} - -impl FromJs for Signature { - fn from_js(value: Buffer) -> Result { - Self::from_bytes( - &<[u8; 96]>::try_from(value.to_vec()) - .map_err(|_| js::err(ConversionError::DifferentLength(96)))?, - ) - .map_err(|_| js::err(ConversionError::InvalidSignature)) - } -} - -impl ToJs for Signature { - fn to_js(&self) -> Result { - Ok(Buffer::from(self.to_bytes().to_vec())) - } -} - -impl FromJs for u64 { - fn from_js(value: BigInt) -> Result { - Ok(value.get_u64().1) - } -} - -impl ToJs for u64 { - fn to_js(&self) -> Result { - Ok(BigInt::from(*self)) - } -} - -impl FromJs for rust::Coin { - fn from_js(value: js::Coin) -> Result { - Ok(Self { - parent_coin_info: Bytes32::from_js(value.parent_coin_info)?, - puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, - amount: u64::from_js(value.amount)?, - }) - } -} - -impl ToJs for rust::Coin { - fn to_js(&self) -> Result { - Ok(js::Coin { - parent_coin_info: self.parent_coin_info.to_js()?, - puzzle_hash: self.puzzle_hash.to_js()?, - amount: self.amount.to_js()?, - }) - } -} - -impl FromJs for rust::SimulatorPuzzle { - fn from_js(value: js::SimulatorPuzzle) -> Result { - Ok(Self { - puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, - puzzle_reveal: Program::from_js(value.puzzle_reveal)?, - }) - } -} - -impl ToJs for rust::SimulatorPuzzle { - fn to_js(&self) -> Result { - Ok(js::SimulatorPuzzle { - puzzle_reveal: self.puzzle_reveal.to_js()?, - puzzle_hash: self.puzzle_hash.to_js()?, - }) - } -} - -impl FromJs for rust::BlsPair { - fn from_js(value: js::BlsPair) -> Result { - Ok(Self { - puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, - pk: PublicKey::from_js(value.pk)?, - sk: SecretKey::from_js(value.sk)?, - }) - } -} - -impl ToJs for rust::BlsPair { - fn to_js(&self) -> Result { - Ok(js::BlsPair { - puzzle_hash: self.puzzle_hash.to_js()?, - pk: self.pk.to_js()?, - sk: self.sk.to_js()?, - }) - } -} - -impl FromJs for rust::CoinState { - fn from_js(value: js::CoinState) -> Result { - Ok(Self { - coin: rust::Coin::from_js(value.coin)?, - spent_height: value - .spent_height - .map(|height| { - u64::from_js(height).and_then(|height| { - height.try_into().map_err(|_| js::err("height exceeds u32")) - }) - }) - .transpose()?, - created_height: value - .created_height - .map(|height| { - u64::from_js(height).and_then(|height| { - height.try_into().map_err(|_| js::err("height exceeds u32")) - }) - }) - .transpose()?, - }) - } -} - -impl ToJs for rust::CoinState { - fn to_js(&self) -> Result { - Ok(js::CoinState { - coin: self.coin.to_js()?, - spent_height: self - .spent_height - .map(|height| (height as u64).to_js()) - .transpose()?, - created_height: self - .created_height - .map(|height| (height as u64).to_js()) - .transpose()?, - }) - } -} - -impl FromJs for rust::CoinSpend { - fn from_js(value: js::CoinSpend) -> Result { - Ok(Self { - coin: rust::Coin::from_js(value.coin)?, - puzzle_reveal: Program::from_js(value.puzzle_reveal)?, - solution: Program::from_js(value.solution)?, - }) - } -} - -impl ToJs for rust::CoinSpend { - fn to_js(&self) -> Result { - Ok(js::CoinSpend { - coin: self.coin.to_js()?, - puzzle_reveal: self.puzzle_reveal.to_js()?, - solution: self.solution.to_js()?, - }) - } -} - -impl FromJs for rust::LineageProof { - fn from_js(value: js::LineageProof) -> Result { - Ok(Self { - parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, - parent_inner_puzzle_hash: Bytes32::from_js(value.parent_inner_puzzle_hash)?, - parent_amount: u64::from_js(value.parent_amount)?, - }) - } -} - -impl ToJs for rust::LineageProof { - fn to_js(&self) -> Result { - Ok(js::LineageProof { - parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, - parent_inner_puzzle_hash: self.parent_inner_puzzle_hash.to_js()?, - parent_amount: self.parent_amount.to_js()?, - }) - } -} - -impl FromJs for rust::EveProof { - fn from_js(value: js::EveProof) -> Result { - Ok(rust::EveProof { - parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, - parent_amount: u64::from_js(value.parent_amount)?, - }) - } -} - -impl ToJs for rust::EveProof { - fn to_js(&self) -> Result { - Ok(js::EveProof { - parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, - parent_amount: self.parent_amount.to_js()?, - }) - } -} - -impl FromJs for rust::Proof { - fn from_js(value: js::Proof) -> Result { - if let Some(lineage_proof) = value.lineage_proof { - Ok(rust::Proof::Lineage(rust::LineageProof::from_js( - lineage_proof, - )?)) - } else if let Some(eve_proof) = value.eve_proof { - Ok(rust::Proof::Eve(rust::EveProof::from_js(eve_proof)?)) - } else { - Err(js::err(ConversionError::MissingProof)) - } - } -} - -impl ToJs for rust::Proof { - fn to_js(&self) -> Result { - Ok(match self { - rust::Proof::Lineage(lineage_proof) => js::Proof { - lineage_proof: Some(lineage_proof.to_js()?), - eve_proof: None, - }, - rust::Proof::Eve(eve_proof) => js::Proof { - lineage_proof: None, - eve_proof: Some(eve_proof.to_js()?), - }, - }) - } -} - -impl FromJs for rust::ServerCoin { - fn from_js(value: js::ServerCoin) -> Result { - Ok(Self { - coin: rust::Coin::from_js(value.coin)?, - p2_puzzle_hash: Bytes32::from_js(value.p2_puzzle_hash)?, - memo_urls: value.memo_urls, - }) - } -} - -impl ToJs for rust::ServerCoin { - fn to_js(&self) -> Result { - Ok(js::ServerCoin { - coin: self.coin.to_js()?, - p2_puzzle_hash: self.p2_puzzle_hash.to_js()?, - memo_urls: self.memo_urls.clone(), - }) - } -} - -impl FromJs for rust::SpendBundle { - fn from_js(value: js::SpendBundle) -> Result { - Ok(Self { - coin_spends: value - .coin_spends - .into_iter() - .map(rust::CoinSpend::from_js) - .collect::>>()?, - aggregated_signature: Signature::from_js(value.aggregated_signature)?, - }) - } -} - -impl ToJs for rust::SpendBundle { - fn to_js(&self) -> Result { - Ok(js::SpendBundle { - coin_spends: self - .coin_spends - .iter() - .map(rust::CoinSpend::to_js) - .collect::>>()?, - aggregated_signature: self.aggregated_signature.to_js()?, - }) - } -} - -impl FromJs for chia::puzzles::nft::NftMetadata { - fn from_js(value: js::NftMetadata) -> Result { - Ok(chia::puzzles::nft::NftMetadata { - data_uris: value.data_uris, - data_hash: if let Some(hash) = value.data_hash { - Some(Bytes32::from_js(hash)?) - } else { - None - }, - metadata_uris: value.metadata_uris, - metadata_hash: if let Some(hash) = value.metadata_hash { - Some(Bytes32::from_js(hash)?) - } else { - None - }, - license_uris: value.license_uris, - license_hash: if let Some(hash) = value.license_hash { - Some(Bytes32::from_js(hash)?) - } else { - None - }, - edition_number: if let Some(num) = value.edition_number { - u64::from_js(num)? - } else { - 0 - }, - edition_total: if let Some(total) = value.edition_total { - u64::from_js(total)? - } else { - 0 - }, - }) - } -} - -impl ToJs for chia::puzzles::nft::NftMetadata { - fn to_js(&self) -> Result { - Ok(js::NftMetadata { - data_uris: self.data_uris.clone(), - data_hash: if let Some(hash) = self.data_hash { - Some(hash.to_js()?) - } else { - None - }, - metadata_uris: self.metadata_uris.clone(), - metadata_hash: if let Some(hash) = self.metadata_hash { - Some(hash.to_js()?) - } else { - None - }, - license_uris: self.license_uris.clone(), - license_hash: if let Some(hash) = self.license_hash { - Some(hash.to_js()?) - } else { - None - }, - edition_number: if self.edition_number > 0 { - Some(self.edition_number.to_js()?) - } else { - None - }, - edition_total: if self.edition_total > 0 { - Some(self.edition_total.to_js()?) - } else { - None - }, - }) - } -} +use chia::{ + bls::{PublicKey, SecretKey, Signature}, + protocol::{Bytes, Bytes32, Program}, +}; +use napi::bindgen_prelude::*; +use napi::Result; +use thiserror::Error; + +use crate::{js, rust}; + +#[derive(Error, Debug)] +pub enum ConversionError { + #[error("Expected different byte length {0}")] + DifferentLength(u32), + + #[error("Invalid public key")] + InvalidPublicKey, + + #[error("Invalid private key")] + InvalidPrivateKey, + + #[error("Invalid signature")] + InvalidSignature, + + #[error("Missing proof")] + MissingProof, + + #[error("Missing delegated puzzle info")] + MissingDelegatedPuzzleInfo, + + #[error("Invalid URI: {0}")] + InvalidUri(String), +} + +pub trait FromJs { + fn from_js(value: T) -> Result + where + Self: Sized; +} + +pub trait ToJs { + fn to_js(&self) -> Result; +} + +impl ToJs for String { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.clone())) + } +} + +impl FromJs for String { + fn from_js(value: Buffer) -> Result { + let string = String::from_utf8(value.to_vec()) + .map_err(|e| Error::from_reason(format!("Invalid UTF-8: {}", e)))?; + Ok(string) + } +} + +impl FromJs for Bytes32 { + fn from_js(value: Buffer) -> Result { + Self::try_from(value.as_ref().to_vec()) + .map_err(|_| js::err(ConversionError::DifferentLength(32))) + } +} + +impl ToJs for Bytes32 { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_vec())) + } +} + +impl FromJs for Program { + fn from_js(value: Buffer) -> Result { + Ok(Self::from(value.to_vec())) + } +} + +impl ToJs for Program { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_vec())) + } +} + +impl FromJs for Bytes { + fn from_js(value: Buffer) -> Result { + Ok(Self::new(value.to_vec())) + } +} + +impl ToJs for Bytes { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_vec())) + } +} + +impl FromJs for PublicKey { + fn from_js(value: Buffer) -> Result { + Self::from_bytes( + &<[u8; 48]>::try_from(value.to_vec()) + .map_err(|_| js::err(ConversionError::DifferentLength(48)))?, + ) + .map_err(|_| js::err(ConversionError::InvalidPublicKey)) + } +} + +impl ToJs for PublicKey { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_bytes().to_vec())) + } +} + +impl FromJs for SecretKey { + fn from_js(value: Buffer) -> Result { + Self::from_bytes( + &<[u8; 32]>::try_from(value.to_vec()) + .map_err(|_| js::err(ConversionError::DifferentLength(32)))?, + ) + .map_err(|_| js::err(ConversionError::InvalidPrivateKey)) + } +} + +impl ToJs for SecretKey { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_bytes().to_vec())) + } +} + +impl FromJs for Signature { + fn from_js(value: Buffer) -> Result { + Self::from_bytes( + &<[u8; 96]>::try_from(value.to_vec()) + .map_err(|_| js::err(ConversionError::DifferentLength(96)))?, + ) + .map_err(|_| js::err(ConversionError::InvalidSignature)) + } +} + +impl ToJs for Signature { + fn to_js(&self) -> Result { + Ok(Buffer::from(self.to_bytes().to_vec())) + } +} + +impl FromJs for u64 { + fn from_js(value: BigInt) -> Result { + Ok(value.get_u64().1) + } +} + +impl ToJs for u64 { + fn to_js(&self) -> Result { + Ok(BigInt::from(*self)) + } +} + +impl FromJs for rust::Coin { + fn from_js(value: js::Coin) -> Result { + Ok(Self { + parent_coin_info: Bytes32::from_js(value.parent_coin_info)?, + puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, + amount: u64::from_js(value.amount)?, + }) + } +} + +impl ToJs for rust::Coin { + fn to_js(&self) -> Result { + Ok(js::Coin { + parent_coin_info: self.parent_coin_info.to_js()?, + puzzle_hash: self.puzzle_hash.to_js()?, + amount: self.amount.to_js()?, + }) + } +} + +impl FromJs for rust::SimulatorPuzzle { + fn from_js(value: js::SimulatorPuzzle) -> Result { + Ok(Self { + puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, + puzzle_reveal: Program::from_js(value.puzzle_reveal)?, + }) + } +} + +impl ToJs for rust::SimulatorPuzzle { + fn to_js(&self) -> Result { + Ok(js::SimulatorPuzzle { + puzzle_reveal: self.puzzle_reveal.to_js()?, + puzzle_hash: self.puzzle_hash.to_js()?, + }) + } +} + +impl FromJs for rust::BlsPair { + fn from_js(value: js::BlsPair) -> Result { + Ok(Self { + puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, + pk: PublicKey::from_js(value.pk)?, + sk: SecretKey::from_js(value.sk)?, + }) + } +} + +impl ToJs for rust::BlsPair { + fn to_js(&self) -> Result { + Ok(js::BlsPair { + puzzle_hash: self.puzzle_hash.to_js()?, + pk: self.pk.to_js()?, + sk: self.sk.to_js()?, + }) + } +} + +impl FromJs for rust::CoinState { + fn from_js(value: js::CoinState) -> Result { + Ok(Self { + coin: rust::Coin::from_js(value.coin)?, + spent_height: value + .spent_height + .map(|height| { + u64::from_js(height).and_then(|height| { + height.try_into().map_err(|_| js::err("height exceeds u32")) + }) + }) + .transpose()?, + created_height: value + .created_height + .map(|height| { + u64::from_js(height).and_then(|height| { + height.try_into().map_err(|_| js::err("height exceeds u32")) + }) + }) + .transpose()?, + }) + } +} + +impl ToJs for rust::CoinState { + fn to_js(&self) -> Result { + Ok(js::CoinState { + coin: self.coin.to_js()?, + spent_height: self + .spent_height + .map(|height| (height as u64).to_js()) + .transpose()?, + created_height: self + .created_height + .map(|height| (height as u64).to_js()) + .transpose()?, + }) + } +} + +impl FromJs for rust::CoinSpend { + fn from_js(value: js::CoinSpend) -> Result { + Ok(Self { + coin: rust::Coin::from_js(value.coin)?, + puzzle_reveal: Program::from_js(value.puzzle_reveal)?, + solution: Program::from_js(value.solution)?, + }) + } +} + +impl ToJs for rust::CoinSpend { + fn to_js(&self) -> Result { + Ok(js::CoinSpend { + coin: self.coin.to_js()?, + puzzle_reveal: self.puzzle_reveal.to_js()?, + solution: self.solution.to_js()?, + }) + } +} + +impl FromJs for rust::LineageProof { + fn from_js(value: js::LineageProof) -> Result { + Ok(Self { + parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, + parent_inner_puzzle_hash: Bytes32::from_js(value.parent_inner_puzzle_hash)?, + parent_amount: u64::from_js(value.parent_amount)?, + }) + } +} + +impl ToJs for rust::LineageProof { + fn to_js(&self) -> Result { + Ok(js::LineageProof { + parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, + parent_inner_puzzle_hash: self.parent_inner_puzzle_hash.to_js()?, + parent_amount: self.parent_amount.to_js()?, + }) + } +} + +impl FromJs for rust::EveProof { + fn from_js(value: js::EveProof) -> Result { + Ok(rust::EveProof { + parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, + parent_amount: u64::from_js(value.parent_amount)?, + }) + } +} + +impl ToJs for rust::EveProof { + fn to_js(&self) -> Result { + Ok(js::EveProof { + parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, + parent_amount: self.parent_amount.to_js()?, + }) + } +} + +impl FromJs for rust::Proof { + fn from_js(value: js::Proof) -> Result { + if let Some(lineage_proof) = value.lineage_proof { + Ok(rust::Proof::Lineage(rust::LineageProof::from_js( + lineage_proof, + )?)) + } else if let Some(eve_proof) = value.eve_proof { + Ok(rust::Proof::Eve(rust::EveProof::from_js(eve_proof)?)) + } else { + Err(js::err(ConversionError::MissingProof)) + } + } +} + +impl ToJs for rust::Proof { + fn to_js(&self) -> Result { + Ok(match self { + rust::Proof::Lineage(lineage_proof) => js::Proof { + lineage_proof: Some(lineage_proof.to_js()?), + eve_proof: None, + }, + rust::Proof::Eve(eve_proof) => js::Proof { + lineage_proof: None, + eve_proof: Some(eve_proof.to_js()?), + }, + }) + } +} + +impl FromJs for rust::ServerCoin { + fn from_js(value: js::ServerCoin) -> Result { + Ok(Self { + coin: rust::Coin::from_js(value.coin)?, + p2_puzzle_hash: Bytes32::from_js(value.p2_puzzle_hash)?, + memo_urls: value.memo_urls, + }) + } +} + +impl ToJs for rust::ServerCoin { + fn to_js(&self) -> Result { + Ok(js::ServerCoin { + coin: self.coin.to_js()?, + p2_puzzle_hash: self.p2_puzzle_hash.to_js()?, + memo_urls: self.memo_urls.clone(), + }) + } +} + +impl FromJs for rust::SpendBundle { + fn from_js(value: js::SpendBundle) -> Result { + Ok(Self { + coin_spends: value + .coin_spends + .into_iter() + .map(rust::CoinSpend::from_js) + .collect::>>()?, + aggregated_signature: Signature::from_js(value.aggregated_signature)?, + }) + } +} + +impl ToJs for rust::SpendBundle { + fn to_js(&self) -> Result { + Ok(js::SpendBundle { + coin_spends: self + .coin_spends + .iter() + .map(rust::CoinSpend::to_js) + .collect::>>()?, + aggregated_signature: self.aggregated_signature.to_js()?, + }) + } +} + +impl FromJs for chia::puzzles::nft::NftMetadata { + fn from_js(value: js::NftMetadata) -> Result { + Ok(chia::puzzles::nft::NftMetadata { + data_uris: value.data_uris, + data_hash: if let Some(hash) = value.data_hash { + Some(Bytes32::from_js(hash)?) + } else { + None + }, + metadata_uris: value.metadata_uris, + metadata_hash: if let Some(hash) = value.metadata_hash { + Some(Bytes32::from_js(hash)?) + } else { + None + }, + license_uris: value.license_uris, + license_hash: if let Some(hash) = value.license_hash { + Some(Bytes32::from_js(hash)?) + } else { + None + }, + edition_number: if let Some(num) = value.edition_number { + u64::from_js(num)? + } else { + 0 + }, + edition_total: if let Some(total) = value.edition_total { + u64::from_js(total)? + } else { + 0 + }, + }) + } +} + +impl ToJs for chia::puzzles::nft::NftMetadata { + fn to_js(&self) -> Result { + Ok(js::NftMetadata { + data_uris: self.data_uris.clone(), + data_hash: if let Some(hash) = self.data_hash { + Some(hash.to_js()?) + } else { + None + }, + metadata_uris: self.metadata_uris.clone(), + metadata_hash: if let Some(hash) = self.metadata_hash { + Some(hash.to_js()?) + } else { + None + }, + license_uris: self.license_uris.clone(), + license_hash: if let Some(hash) = self.license_hash { + Some(hash.to_js()?) + } else { + None + }, + edition_number: if self.edition_number > 0 { + Some(self.edition_number.to_js()?) + } else { + None + }, + edition_total: if self.edition_total > 0 { + Some(self.edition_total.to_js()?) + } else { + None + }, + }) + } +} diff --git a/napi/src/napi_lib.rs b/napi/src/napi_lib.rs index 67f4d1b..bb0ef8e 100644 --- a/napi/src/napi_lib.rs +++ b/napi/src/napi_lib.rs @@ -644,7 +644,7 @@ impl Peer { let peer = sim.lock().await; let inner = peer.lock().await; Ok(inner.height()) - }, + } None => Err(crate::js::err( "Simulator is not available for this peer type", )), diff --git a/src/lib.rs b/src/lib.rs index 2160d6f..a7de2ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,13 +35,11 @@ pub mod wallet; pub use rust::{BlsPair, SimulatorPuzzle, UnspentCoinsResponse}; pub use server_coin::{morph_launcher_id, ServerCoin}; pub use wallet::{ - create_simple_did, generate_did_proof, - generate_did_proof_from_chain, generate_did_proof_manual, get_fee_estimate, - get_header_hash, get_store_creation_height, get_unspent_coin_states, is_coin_spent, - look_up_possible_launchers, mint_nft, - spend_server_coins, subscribe_to_coin_states, - sync_store, sync_store_using_launcher_id, unsubscribe_from_coin_states, - verify_signature, DataStoreInnerSpend, NewServerCoin, + create_simple_did, generate_did_proof, generate_did_proof_from_chain, + generate_did_proof_manual, get_fee_estimate, get_header_hash, get_store_creation_height, + get_unspent_coin_states, is_coin_spent, look_up_possible_launchers, mint_nft, + spend_server_coins, subscribe_to_coin_states, sync_store, sync_store_using_launcher_id, + unsubscribe_from_coin_states, verify_signature, DataStoreInnerSpend, NewServerCoin, PossibleLaunchersResponse, SuccessResponse, SyncStoreResponse, TargetNetwork, UnspentCoinStates, }; @@ -146,7 +144,7 @@ pub fn send_xch( fee: u64, ) -> Result> { let outputs: Vec<(Bytes32, u64, Vec)> = outputs - .iter() + .iter() .map(|output| (output.puzzle_hash, output.amount, output.memos.clone())) .collect(); @@ -182,14 +180,14 @@ pub fn add_fee( pub fn sign_coin_spends( coin_spends: &[CoinSpend], private_keys: &[SecretKey], - for_testnet: bool, + for_testnet: bool, ) -> Result { Ok(wallet::sign_coin_spends( coin_spends.to_vec(), private_keys.to_vec(), - if for_testnet { + if for_testnet { wallet::TargetNetwork::Testnet11 - } else { + } else { wallet::TargetNetwork::Mainnet }, )?) @@ -306,7 +304,7 @@ pub fn melt_store(store: DataStore, owner_pk: PublicKey) -> Result, + selected_coins: Vec, hint: Bytes32, uris: Vec, amount: u64, @@ -467,7 +465,7 @@ pub mod async_api { pub async fn mint_nft( peer: &Peer, synthetic_key: PublicKey, - selected_coins: Vec, + selected_coins: Vec, did_string: &str, recipient_puzzle_hash: Bytes32, metadata: chia::puzzles::nft::NftMetadata, @@ -515,7 +513,7 @@ pub mod async_api { /// Creates a simple DID (Rust API version). pub fn create_simple_did( synthetic_key: PublicKey, - selected_coins: Vec, + selected_coins: Vec, fee: u64, ) -> Result<(Vec, Coin)> { Ok(wallet::create_simple_did( diff --git a/src/server_coin.rs b/src/server_coin.rs index bd6e69e..bc10471 100644 --- a/src/server_coin.rs +++ b/src/server_coin.rs @@ -1,7 +1,10 @@ -use std::borrow::Cow; -use chia_wallet_sdk::prelude::{Allocator, Bytes, Bytes32, Coin, Condition, CreateCoin, CurriedProgram, Mod, ToTreeHash, TreeHash, Memos, ToClvm, FromClvm}; +use chia_wallet_sdk::prelude::{ + Allocator, Bytes, Bytes32, Coin, Condition, CreateCoin, CurriedProgram, FromClvm, Memos, Mod, + ToClvm, ToTreeHash, TreeHash, +}; use hex_literal::hex; use num_bigint::BigInt; +use std::borrow::Cow; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ServerCoin { diff --git a/src/wallet.rs b/src/wallet.rs index edabc37..158be52 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -4,11 +4,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use chia::bls::{sign, verify, PublicKey, SecretKey, Signature}; use chia::clvm_traits::{clvm_tuple, FromClvm, ToClvm}; -use chia::clvm_utils::tree_hash; -use chia::consensus::{ - consensus_constants::ConsensusConstants, -}; +use chia::clvm_utils::{tree_hash, ToTreeHash}; +use chia::consensus::consensus_constants::ConsensusConstants; use chia::consensus::flags::{DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; +use chia::consensus::opcodes::CREATE_COIN; use chia::consensus::owned_conditions::OwnedSpendBundleConditions; use chia::consensus::run_block_generator::run_block_generator; use chia::consensus::solution_generator::solution_generator; @@ -23,9 +22,15 @@ use chia::puzzles::{ standard::{StandardArgs, StandardSolution}, DeriveSynthetic, }; +use chia::ssl::Error::DateRange; use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::{ClientError, Peer}; -use chia_wallet_sdk::driver::{get_merkle_tree, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, SpendContext, SpendWithConditions, StandardLayer, WriterLayer}; +use chia_wallet_sdk::driver::{ + get_merkle_tree, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, + DriverError, HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, + P2ParentCoin, P2ParentLayer, Puzzle, SpendContext, SpendWithConditions, StandardLayer, + WriterLayer, +}; // Import proof types from our own crate's rust module use crate::rust::{EveProof, LineageProof, Proof}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; @@ -35,14 +40,14 @@ use chia_wallet_sdk::types::{ Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS, }; use chia_wallet_sdk::utils::{self, CoinSelectionError}; -use clvmr::Allocator; +use clvm_traits::clvm_quote; +use clvmr::{Allocator, NodePtr}; use hex_literal::hex; use thiserror::Error; use crate::rust::ServerCoin; use crate::server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution}; - /* echo -n 'datastore' | sha256sum */ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " @@ -50,6 +55,10 @@ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " )); +pub const DIG_COIN_ASSET_ID: Bytes32 = Bytes32::new(hex!( + "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81" +)); + #[derive(Clone, Debug)] pub struct SuccessResponse { pub coin_spends: Vec, @@ -237,6 +246,44 @@ pub fn send_xch( Ok(ctx.take()) } +pub fn create_dig_collateral_coin_spend( + collateral_dig_coins: Vec, + store: DataStore, + synthetic_key: PublicKey, + fee: u64, +) -> Result, WalletError> { + let mut collateral_amount = 0_u64; + for cat in &collateral_dig_coins { + if cat.info.asset_id != DIG_COIN_ASSET_ID { + return Err(WalletError::Driver(DriverError::InvalidAssetId)); + } + collateral_amount += cat.coin.amount; + } + let p2_tail_hash_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); + + let p2 = StandardLayer::new(synthetic_key); + let p2_hash = p2.tree_hash(); + + let mut ctx = SpendContext::new(); + let memos_node_ptr = + ctx.alloc::<(Bytes32, Bytes32)>(&clvm_tuple!(p2_hash.into(), store.coin.coin_id()))?; + let memos = Memos::Some(memos_node_ptr); + + let conditions = Conditions::new() + .create_coin(p2_tail_hash_hash.into(), collateral_amount, memos) + .reserve_fee(fee); + let cat_inner_spend = + StandardLayer::new(synthetic_key).spend_with_conditions(&mut ctx, conditions)?; + let cat_spends: Vec = collateral_dig_coins + .into_iter() + .map(|cat_coin| CatSpend::new(cat_coin, cat_inner_spend)) + .collect(); + + Cat::spend_all(&mut ctx, &cat_spends)?; + + Ok(ctx.take()) +} + pub fn create_server_coin( synthetic_key: PublicKey, selected_coins: Vec, @@ -465,7 +512,7 @@ pub fn mint_store( label, description, bytes, - size_proof + size_proof, }, owner_puzzle_hash.into(), delegated_puzzles, @@ -494,7 +541,6 @@ pub fn mint_store( .collect::, WalletError>>()?, ); - let lead_coin_conditions = if total_amount_from_coins > total_amount { let hint = ctx.hint(minter_puzzle_hash)?; @@ -837,7 +883,7 @@ pub fn update_store_metadata( label: new_label, description: new_description, bytes: new_bytes, - size_proof: new_size_proof + size_proof: new_size_proof, }; let mut new_metadata_condition = Conditions::new().with( DataStore::::new_metadata_condition(ctx, new_metadata)?, @@ -1223,6 +1269,58 @@ pub async fn look_up_possible_launchers( }) } +pub async fn prove_dig_cat_coin( + peer: &Peer, + allocator: &mut Allocator, + coin: &Coin, + coin_created_height: u32, +) -> Result<(Cat, Puzzle, NodePtr), WalletError> { + // 1) Request parent coin state + let parent_state_response = peer + .request_coin_state( + vec![coin.parent_coin_info], + None, + MAINNET_CONSTANTS.genesis_challenge, + false, + ) + .await?; + + let parent_state = parent_state_response.map_err(|_| WalletError::RejectCoinState)?; + + // 2) Request parent puzzle and solution + let parent_puzzle_and_solution_response = peer + .request_puzzle_and_solution(parent_state.coin_ids[0], coin_created_height) + .await?; + + let parent_puzzle_and_solution = + parent_puzzle_and_solution_response.map_err(|_| WalletError::RejectPuzzleSolution)?; + + // 3) Convert puzzle to CLVM + let parent_puzzle_ptr = parent_puzzle_and_solution.puzzle.to_clvm(allocator)?; + let parent_puzzle = Puzzle::parse(&allocator, parent_puzzle_ptr); + + // 4) Convert solution to CLVM + let parent_solution = parent_puzzle_and_solution.solution.to_clvm(allocator)?; + + // 5) Parse CAT + let (cat, puzzle, node_ptr) = Cat::parse( + allocator, + parent_state.coin_states[0].coin, + parent_puzzle, + parent_solution, + )? + .ok_or(WalletError::UnknownCoin)?; + + // 6) Prove lineage + cat.lineage_proof.ok_or(WalletError::UnknownCoin)?; + + if cat.info.asset_id != DIG_COIN_ASSET_ID { + return Err(WalletError::UnknownCoin); + } + + Ok((cat, puzzle, node_ptr)) +} + pub async fn subscribe_to_coin_states( peer: &Peer, coin_id: Bytes32, @@ -1308,8 +1406,13 @@ pub async fn mint_nft( let mut meta_data_allocator = Allocator::new(); let node_metadata = metadata.to_clvm(&mut meta_data_allocator)?; let metadata_hashed_ptr = HashedPtr::from_ptr(&meta_data_allocator, node_metadata); - let did_info: DidInfo = - DidInfo::new(did_coin.coin_id(), None, 1, metadata_hashed_ptr, public_key_hash.into()); + let did_info: DidInfo = DidInfo::new( + did_coin.coin_id(), + None, + 1, + metadata_hashed_ptr, + public_key_hash.into(), + ); let did = Did::new(did_coin, did_proof, did_info); From 08bcee256bf83a19c34b9137313d0cd7e2bde8c0 Mon Sep 17 00:00:00 2001 From: William Wills Date: Mon, 13 Oct 2025 20:17:29 -0400 Subject: [PATCH 02/12] feat: revise p2 parent creation logic WIP --- .github/workflows/CI.yml | 2 +- src/wallet.rs | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 684343d..85981e6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -43,7 +43,7 @@ jobs: cargo machete - name: Fmt - run: rustup component add rustfmt && cargo fmt --all + run: rustup component add rustfmt && cargo fmt --all -- --check build: needs: rust-checks diff --git a/src/wallet.rs b/src/wallet.rs index 158be52..c733ae7 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -39,6 +39,7 @@ use chia_wallet_sdk::types::{ conditions::{CreateCoin, MeltSingleton, Memos, UpdateDataStoreMerkleRoot}, Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS, }; +use chia_wallet_sdk::types::Condition::CreateCoin; use chia_wallet_sdk::utils::{self, CoinSelectionError}; use clvm_traits::clvm_quote; use clvmr::{Allocator, NodePtr}; @@ -246,7 +247,7 @@ pub fn send_xch( Ok(ctx.take()) } -pub fn create_dig_collateral_coin_spend( +pub fn create_dig_collateral_coin( collateral_dig_coins: Vec, store: DataStore, synthetic_key: PublicKey, @@ -259,26 +260,29 @@ pub fn create_dig_collateral_coin_spend( } collateral_amount += cat.coin.amount; } - let p2_tail_hash_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); - let p2 = StandardLayer::new(synthetic_key); - let p2_hash = p2.tree_hash(); + let tail_hash_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2_parent_dig_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2 = StandardLayer::new(synthetic_key); let mut ctx = SpendContext::new(); - let memos_node_ptr = - ctx.alloc::<(Bytes32, Bytes32)>(&clvm_tuple!(p2_hash.into(), store.coin.coin_id()))?; - let memos = Memos::Some(memos_node_ptr); + let hint = ctx.hint(store.coin.coin_id())?; - let conditions = Conditions::new() - .create_coin(p2_tail_hash_hash.into(), collateral_amount, memos) - .reserve_fee(fee); - let cat_inner_spend = - StandardLayer::new(synthetic_key).spend_with_conditions(&mut ctx, conditions)?; - let cat_spends: Vec = collateral_dig_coins + let p2_parent_creation_conditions = Conditions::new() + .create_coin(tail_hash_hash.into(), collateral_amount, hint); + + let p2_parent_creation_spend = p2.spend_with_conditions(&mut ctx, p2_parent_creation_conditions)?; + let p2_parent_dig_spend = CatSpend::new(collateral_dig_coins[0].clone(), p2_parent_creation_spend); + + let mut all_dig_collateral_spends: Vec = collateral_dig_coins[1..] .into_iter() - .map(|cat_coin| CatSpend::new(cat_coin, cat_inner_spend)) + .map(|cat_coin| { + + let spend = p2.spend_with_conditions(&mut ctx,) + }) .collect(); + Cat::spend_all(&mut ctx, &cat_spends)?; Ok(ctx.take()) From b9983570051efc93552a92b8696bd86f435a4f6b Mon Sep 17 00:00:00 2001 From: William Wills Date: Tue, 14 Oct 2025 15:12:05 -0400 Subject: [PATCH 03/12] feat: properly aggregate spends - change not created - fee not added --- src/wallet.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/wallet.rs b/src/wallet.rs index c733ae7..03dad80 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -7,7 +7,6 @@ use chia::clvm_traits::{clvm_tuple, FromClvm, ToClvm}; use chia::clvm_utils::{tree_hash, ToTreeHash}; use chia::consensus::consensus_constants::ConsensusConstants; use chia::consensus::flags::{DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; -use chia::consensus::opcodes::CREATE_COIN; use chia::consensus::owned_conditions::OwnedSpendBundleConditions; use chia::consensus::run_block_generator::run_block_generator; use chia::consensus::solution_generator::solution_generator; @@ -25,12 +24,7 @@ use chia::puzzles::{ use chia::ssl::Error::DateRange; use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::{ClientError, Peer}; -use chia_wallet_sdk::driver::{ - get_merkle_tree, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, - DriverError, HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, - P2ParentCoin, P2ParentLayer, Puzzle, SpendContext, SpendWithConditions, StandardLayer, - WriterLayer, -}; +use chia_wallet_sdk::driver::{get_merkle_tree, Asset, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, SpendContext, SpendWithConditions, StandardLayer, WriterLayer}; // Import proof types from our own crate's rust module use crate::rust::{EveProof, LineageProof, Proof}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; @@ -39,7 +33,6 @@ use chia_wallet_sdk::types::{ conditions::{CreateCoin, MeltSingleton, Memos, UpdateDataStoreMerkleRoot}, Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS, }; -use chia_wallet_sdk::types::Condition::CreateCoin; use chia_wallet_sdk::utils::{self, CoinSelectionError}; use clvm_traits::clvm_quote; use clvmr::{Allocator, NodePtr}; @@ -262,28 +255,28 @@ pub fn create_dig_collateral_coin( } let tail_hash_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); - let p2_parent_dig_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); let p2 = StandardLayer::new(synthetic_key); let mut ctx = SpendContext::new(); let hint = ctx.hint(store.coin.coin_id())?; - let p2_parent_creation_conditions = Conditions::new() + let conditions = Conditions::new() .create_coin(tail_hash_hash.into(), collateral_amount, hint); - let p2_parent_creation_spend = p2.spend_with_conditions(&mut ctx, p2_parent_creation_conditions)?; - let p2_parent_dig_spend = CatSpend::new(collateral_dig_coins[0].clone(), p2_parent_creation_spend); + let p2_parent_creation_spend = p2.spend_with_conditions(&mut ctx, conditions)?; + let mut p2_parent_dig_spend = vec![CatSpend::new(collateral_dig_coins[0].clone(), p2_parent_creation_spend)]; - let mut all_dig_collateral_spends: Vec = collateral_dig_coins[1..] + let p2_spend = p2.spend_with_conditions(&mut ctx, Conditions::new())?; + let dig_cat_spends: Vec = collateral_dig_coins[1..] .into_iter() .map(|cat_coin| { - - let spend = p2.spend_with_conditions(&mut ctx,) + CatSpend::new(cat_coin.clone(), p2_spend) }) .collect(); + p2_parent_dig_spend.extend(dig_cat_spends); - Cat::spend_all(&mut ctx, &cat_spends)?; + Cat::spend_all(&mut ctx, &p2_parent_dig_spend)?; Ok(ctx.take()) } From 17446f31ee1f811f521e1fd85e167d5518fe89a9 Mon Sep 17 00:00:00 2001 From: William Wills Date: Wed, 15 Oct 2025 13:37:09 -0400 Subject: [PATCH 04/12] feat: action based p2 parent creation --- Cargo.lock | 1 + Cargo.toml | 1 + src/wallet.rs | 50 +++++++++++++++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c44ddb..20a10a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -889,6 +889,7 @@ dependencies = [ "futures-util", "hex", "hex-literal", + "indexmap", "num-bigint", "openssl", "openssl-sys", diff --git a/Cargo.toml b/Cargo.toml index 4de5b31..5c0c0d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ hex = "0.4.3" rand = "0.8" futures-util = "0.3" clvm-traits = "0.26.0" +indexmap = "2.11.4" [target.aarch64-unknown-linux-gnu.dependencies] openssl = { version = "0.10.64", features = ["vendored"] } diff --git a/src/wallet.rs b/src/wallet.rs index 03dad80..d39c5ca 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; +use indexmap::indexmap; use chia::bls::{sign, verify, PublicKey, SecretKey, Signature}; use chia::clvm_traits::{clvm_tuple, FromClvm, ToClvm}; @@ -24,7 +25,7 @@ use chia::puzzles::{ use chia::ssl::Error::DateRange; use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::{ClientError, Peer}; -use chia_wallet_sdk::driver::{get_merkle_tree, Asset, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, SpendContext, SpendWithConditions, StandardLayer, WriterLayer}; +use chia_wallet_sdk::driver::{get_merkle_tree, Action, Asset, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, StandardLayer, WriterLayer}; // Import proof types from our own crate's rust module use crate::rust::{EveProof, LineageProof, Proof}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; @@ -38,7 +39,6 @@ use clvm_traits::clvm_quote; use clvmr::{Allocator, NodePtr}; use hex_literal::hex; use thiserror::Error; - use crate::rust::ServerCoin; use crate::server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution}; @@ -242,8 +242,9 @@ pub fn send_xch( pub fn create_dig_collateral_coin( collateral_dig_coins: Vec, - store: DataStore, + morphed_store_id: Bytes32, synthetic_key: PublicKey, + fee_coins: Vec, fee: u64, ) -> Result, WalletError> { let mut collateral_amount = 0_u64; @@ -254,29 +255,40 @@ pub fn create_dig_collateral_coin( collateral_amount += cat.coin.amount; } - let tail_hash_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2_parent_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2_parent_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); - let p2 = StandardLayer::new(synthetic_key); let mut ctx = SpendContext::new(); - let hint = ctx.hint(store.coin.coin_id())?; - let conditions = Conditions::new() - .create_coin(tail_hash_hash.into(), collateral_amount, hint); + let p2_parent_layer = P2ParentLayer::cat(p2_parent_hash); + p2_parent_layer.construct_puzzle(&mut ctx)?; - let p2_parent_creation_spend = p2.spend_with_conditions(&mut ctx, conditions)?; - let mut p2_parent_dig_spend = vec![CatSpend::new(collateral_dig_coins[0].clone(), p2_parent_creation_spend)]; + //todo add store id morph + let hint = ctx.hint(morphed_store_id)?; - let p2_spend = p2.spend_with_conditions(&mut ctx, Conditions::new())?; - let dig_cat_spends: Vec = collateral_dig_coins[1..] - .into_iter() - .map(|cat_coin| { - CatSpend::new(cat_coin.clone(), p2_spend) - }) - .collect(); + let actions = &[ + Action::fee(fee), + Action::send(Id::Existing(DIG_COIN_ASSET_ID), p2_parent_puzzle_hash.into(), collateral_amount, hint), + ]; + + let p2_layer = StandardLayer::new(synthetic_key); + let p2_puzzle_hash: Bytes32 = p2_layer.tree_hash().into(); + let mut spends = Spends::new(p2_puzzle_hash); + + // add collateral coins to spends + for dig_cat in collateral_dig_coins { + spends.add(dig_cat); + } + + // add fee coins to spends + for fee_xch_coin in fee_coins { + spends.add(fee_xch_coin); + } - p2_parent_dig_spend.extend(dig_cat_spends); + let deltas = spends.apply(&mut ctx, actions)?; + let index_map = indexmap!{p2_puzzle_hash => synthetic_key}; - Cat::spend_all(&mut ctx, &p2_parent_dig_spend)?; + let _outputs = spends.finish_with_keys(&mut ctx, &deltas, Relation::AssertConcurrent, &index_map)?; Ok(ctx.take()) } From d1e9e1292cc8d0f29e1339a01b7e1b8b5cde9e4b Mon Sep 17 00:00:00 2001 From: William Wills Date: Wed, 15 Oct 2025 13:59:24 -0400 Subject: [PATCH 05/12] chore: clippy --- .github/workflows/CI.yml | 2 +- Cargo.toml | 2 +- src/wallet.rs | 32 +++++++++++++++++++++----------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 85981e6..fdee10f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -35,7 +35,7 @@ jobs: toolchain: stable - name: Clippy - run: rustup component add clippy && cargo clippy --workspace --all-features --all-targets + run: rustup component add clippy && cargo clippy --all-targets --all-features -- -D warnings - name: Unused dependencies run: | diff --git a/Cargo.toml b/Cargo.toml index 5c0c0d9..0e3959c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ chia-puzzles = "0.20.1" thiserror = "1.0.61" clvmr = "0.14.0" tokio = "1.39.3" -chia-wallet-sdk = { version = "0.30.0", features = ["chip-0035", "native-tls", "peer-simulator"] } +chia-wallet-sdk = { version = "0.30.0", features = ["chip-0035", "native-tls", "peer-simulator", "action-layer"] } hex-literal = "0.4.1" num-bigint = "0.4.6" hex = "0.4.3" diff --git a/src/wallet.rs b/src/wallet.rs index d39c5ca..d6789ab 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,7 +1,7 @@ #![allow(clippy::result_large_err)] +use indexmap::indexmap; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; -use indexmap::indexmap; use chia::bls::{sign, verify, PublicKey, SecretKey, Signature}; use chia::clvm_traits::{clvm_tuple, FromClvm, ToClvm}; @@ -22,12 +22,19 @@ use chia::puzzles::{ standard::{StandardArgs, StandardSolution}, DeriveSynthetic, }; -use chia::ssl::Error::DateRange; + use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::{ClientError, Peer}; -use chia_wallet_sdk::driver::{get_merkle_tree, Action, Asset, Cat, CatSpend, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, StandardLayer, WriterLayer}; +use chia_wallet_sdk::driver::{ + get_merkle_tree, Action, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, + Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, + OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, Relation, SpendContext, SpendWithConditions, + Spends, StandardLayer, WriterLayer, +}; // Import proof types from our own crate's rust module +use crate::rust::ServerCoin; use crate::rust::{EveProof, LineageProof, Proof}; +use crate::server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; use chia_wallet_sdk::types::{ announcement_id, @@ -35,12 +42,9 @@ use chia_wallet_sdk::types::{ Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS, }; use chia_wallet_sdk::utils::{self, CoinSelectionError}; -use clvm_traits::clvm_quote; use clvmr::{Allocator, NodePtr}; use hex_literal::hex; use thiserror::Error; -use crate::rust::ServerCoin; -use crate::server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution}; /* echo -n 'datastore' | sha256sum */ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( @@ -256,7 +260,7 @@ pub fn create_dig_collateral_coin( } let p2_parent_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); - let p2_parent_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2_parent_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); let mut ctx = SpendContext::new(); @@ -268,7 +272,12 @@ pub fn create_dig_collateral_coin( let actions = &[ Action::fee(fee), - Action::send(Id::Existing(DIG_COIN_ASSET_ID), p2_parent_puzzle_hash.into(), collateral_amount, hint), + Action::send( + Id::Existing(DIG_COIN_ASSET_ID), + p2_parent_puzzle_hash.into(), + collateral_amount, + hint, + ), ]; let p2_layer = StandardLayer::new(synthetic_key); @@ -286,9 +295,10 @@ pub fn create_dig_collateral_coin( } let deltas = spends.apply(&mut ctx, actions)?; - let index_map = indexmap!{p2_puzzle_hash => synthetic_key}; + let index_map = indexmap! {p2_puzzle_hash => synthetic_key}; - let _outputs = spends.finish_with_keys(&mut ctx, &deltas, Relation::AssertConcurrent, &index_map)?; + let _outputs = + spends.finish_with_keys(&mut ctx, &deltas, Relation::AssertConcurrent, &index_map)?; Ok(ctx.take()) } @@ -1306,7 +1316,7 @@ pub async fn prove_dig_cat_coin( // 3) Convert puzzle to CLVM let parent_puzzle_ptr = parent_puzzle_and_solution.puzzle.to_clvm(allocator)?; - let parent_puzzle = Puzzle::parse(&allocator, parent_puzzle_ptr); + let parent_puzzle = Puzzle::parse(allocator, parent_puzzle_ptr); // 4) Convert solution to CLVM let parent_solution = parent_puzzle_and_solution.solution.to_clvm(allocator)?; From 6bb0529237bf4c9e209cf04b3348bd2dcb9439a7 Mon Sep 17 00:00:00 2001 From: William Wills Date: Mon, 20 Oct 2025 12:38:06 -0400 Subject: [PATCH 06/12] chore: improvements from PR review --- src/lib.rs | 25 + src/lib_original.rs | 1934 ------------------------------------------- src/napi_lib.rs | 1904 ------------------------------------------ src/wallet.rs | 69 +- 4 files changed, 55 insertions(+), 3877 deletions(-) delete mode 100644 src/lib_original.rs delete mode 100644 src/napi_lib.rs diff --git a/src/lib.rs b/src/lib.rs index a7de2ec..46ac8a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ // Re-export core types from dependencies pub use chia::bls::{master_to_wallet_unhardened, PublicKey, SecretKey, Signature}; +use chia::consensus::solution_generator::solution_generator; pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle}; pub use chia::puzzles::{EveProof, LineageProof, Proof}; pub use chia_wallet_sdk::client::Peer; @@ -44,11 +45,24 @@ pub use wallet::{ UnspentCoinStates, }; +use hex_literal::hex; + // Type aliases for convenience pub type Result = std::result::Result>; // Helper functions for common conversions use chia::puzzles::{standard::StandardArgs, DeriveSynthetic}; +use chia_wallet_sdk::prelude::ToTreeHash; + +pub const DIG_MIN_HEIGHT: u32 = 5777842; +pub const DIG_MIN_HEIGHT_HEADER_HASH: Bytes32 = Bytes32::new(hex!("b29a4daac2434fd17a36e15ba1aac5d65012d4a66f99bed0bf2b5342e92e562c")); + +pub const DIG_STORE_LAUNCHER_ID_MORPH: &str = "DIG_STORE"; + +/// Morphs a DIG store launcher ID into the DIG namespace. Store launcher IDs should be morphed when hinted on coins +pub fn morph_store_launcher_id(store_launcher_id: Bytes32) -> Bytes32 { + (store_launcher_id, DIG_STORE_LAUNCHER_ID_MORPH).tree_hash().into() +} /// Converts a master public key to a wallet synthetic key. pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey { @@ -323,6 +337,9 @@ pub fn create_server_coin( /// Async functions for blockchain interaction (Rust API versions) pub mod async_api { use super::*; + use chia_wallet_sdk::driver::Puzzle; + use chia_wallet_sdk::prelude::Cat; + use clvmr::{Allocator, NodePtr}; use futures_util::stream::{FuturesUnordered, StreamExt}; use rand::seq::SliceRandom; use std::net::SocketAddr; @@ -596,6 +613,14 @@ pub mod async_api { ) -> Result { Ok(wallet::broadcast_spend_bundle(peer, spend_bundle).await?) } + + pub async fn prove_dig_cat_coin( + peer: &Peer, + coin: &Coin, + coin_created_height: u32, + ) -> Result { + Ok(wallet::prove_dig_cat_coin(peer, coin, coin_created_height).await?) + } } /// Constants for different networks diff --git a/src/lib_original.rs b/src/lib_original.rs deleted file mode 100644 index cb98282..0000000 --- a/src/lib_original.rs +++ /dev/null @@ -1,1934 +0,0 @@ -#![allow(unexpected_cfgs)] - -#[cfg(feature = "napi")] -mod conversions; -#[cfg(feature = "napi")] -mod js; -mod rust; -mod server_coin; -mod wallet; - -// Pure Rust API module -#[cfg(not(feature = "napi"))] -pub mod api; - -use chia::bls::{ - master_to_wallet_unhardened, PublicKey as RustPublicKey, SecretKey as RustSecretKey, - Signature as RustSignature, -}; - -use chia::protocol::{ - Bytes as RustBytes, Bytes32 as RustBytes32, Coin as RustCoin, CoinSpend as RustCoinSpend, - CoinStateUpdate, NewPeakWallet, ProtocolMessageTypes, SpendBundle as RustSpendBundle, -}; -use chia::puzzles::{standard::StandardArgs, DeriveSynthetic, Proof as RustProof}; -use chia::traits::Streamable; -use chia_wallet_sdk::client::{ - connect_peer, create_native_tls_connector, load_ssl_cert, Connector, PeerOptions, -}; -use chia_wallet_sdk::prelude::AggSigMe; -use chia_wallet_sdk::test::{to_program, to_puzzle, PeerSimulator}; -use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS}; -use chia_wallet_sdk::utils::Address; -use chia_wallet_sdk::{ - client::Peer as RustPeer, - driver::{ - DataStore as RustDataStore, DataStoreInfo as RustDataStoreInfo, - DataStoreMetadata as RustDataStoreMetadata, DelegatedPuzzle as RustDelegatedPuzzle, - }, -}; -#[cfg(feature = "napi")] -use conversions::{ConversionError, FromJs, ToJs}; -use futures_util::stream::{FuturesUnordered, StreamExt}; -#[cfg(feature = "napi")] -use js::{Coin, CoinSpend, CoinState, EveProof, NftMetadata, Proof, ServerCoin, SpendBundle}; -#[cfg(feature = "napi")] -use napi::bindgen_prelude::*; -#[cfg(feature = "napi")] -use napi::Result; -use rand::seq::SliceRandom; -use std::collections::HashMap; -use std::{net::SocketAddr, sync::Arc}; -use tokio::net::lookup_host; -use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; -use tokio::sync::Mutex; -use tokio::time::{timeout, Duration}; -use wallet::{ - PossibleLaunchersResponse as RustPossibleLaunchersResponse, - SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse, -}; - -pub use wallet::*; - -// Re-export core types for Rust users (when used as a crate) -// These are the same types that the NAPI bindings wrap, just re-exported with cleaner names -pub use chia::bls::{PublicKey, SecretKey, Signature}; -pub use chia::protocol::{Bytes, Bytes32, Coin as ChiaCoin, CoinSpend as ChiaCoinSpend, SpendBundle as ChiaSpendBundle}; -pub use chia::puzzles::Proof as ChiaProof; -pub use chia_wallet_sdk::{ - client::Peer as ChiaPeer, - driver::{DataStore as ChiaDataStore, DataStoreInfo, DataStoreMetadata as ChiaDataStoreMetadata, DelegatedPuzzle as ChiaDelegatedPuzzle}, -}; - -#[cfg(feature = "napi")] -#[macro_use] -extern crate napi_derive; - -// DNS introducers and default ports for connecting to random peers. -const MAINNET_DNS_INTRODUCERS: &[&str] = &[ - "dns-introducer.chia.net", - "chia.ctrlaltdel.ch", - "seeder.dexie.space", - "chia.hoffmang.com", -]; -const TESTNET11_DNS_INTRODUCERS: &[&str] = &["dns-introducer-testnet11.chia.net"]; -const MAINNET_DEFAULT_PORT: u16 = 8444; -const TESTNET11_DEFAULT_PORT: u16 = 58444; - -// When not using NAPI, export the pure Rust API -#[cfg(not(feature = "napi"))] -pub use api::*; - -// All NAPI bindings below this point -#[cfg(feature = "napi")] -#[napi] -/// Creates a new lineage proof. -/// -/// @param {LineageProof} lineageProof - The lineage proof. -/// @returns {Proof} The new proof. -pub fn new_lineage_proof(lineage_proof: js::LineageProof) -> js::Proof { - js::Proof { - lineage_proof: Some(lineage_proof), - eve_proof: None, - } -} - -#[napi] -/// Creates a new eve proof. -/// -/// @param {EveProof} eveProof - The eve proof. -/// @returns {Proof} The new proof. -pub fn new_eve_proof(eve_proof: EveProof) -> Proof { - Proof { - lineage_proof: None, - eve_proof: Some(eve_proof), - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents metadata for a data store. -/// -/// @property {Buffer} rootHash - Root hash. -/// @property {Option} label - Label (optional). -/// @property {Option} description - Description (optional). -/// @property {Option} bytes - Size of the store in bytes (optional). -pub struct DataStoreMetadata { - pub root_hash: Buffer, - pub label: Option, - pub description: Option, - pub bytes: Option, -} - -impl FromJs for RustDataStoreMetadata { - fn from_js(value: DataStoreMetadata) -> Result { - Ok(RustDataStoreMetadata { - root_hash: RustBytes32::from_js(value.root_hash)?, - label: value.label, - description: value.description, - bytes: if let Some(bytes) = value.bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - }) - } -} - -impl ToJs for RustDataStoreMetadata { - fn to_js(&self) -> Result { - Ok(DataStoreMetadata { - root_hash: self.root_hash.to_js()?, - label: self.label.clone(), - description: self.description.clone(), - bytes: if let Some(bytes) = self.bytes { - Some(bytes.to_js()?) - } else { - None - }, - }) - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents information about a delegated puzzle. Note that this struct can represent all three types of delegated puzzles, but only represents one at a time. -/// -/// @property {Option} adminInnerPuzzleHash - Admin inner puzzle hash, if this is an admin delegated puzzle. -/// @property {Option} writerInnerPuzzleHash - Writer inner puzzle hash, if this is a writer delegated puzzle. -/// @property {Option} oraclePaymentPuzzleHash - Oracle payment puzzle hash, if this is an oracle delegated puzzle. -/// @property {Option} oracleFee - Oracle fee, if this is an oracle delegated puzzle. -pub struct DelegatedPuzzle { - pub admin_inner_puzzle_hash: Option, - pub writer_inner_puzzle_hash: Option, - pub oracle_payment_puzzle_hash: Option, - pub oracle_fee: Option, -} - -impl FromJs for RustDelegatedPuzzle { - fn from_js(value: DelegatedPuzzle) -> Result { - Ok( - if let Some(admin_inner_puzzle_hash) = value.admin_inner_puzzle_hash { - RustDelegatedPuzzle::Admin(RustBytes32::from_js(admin_inner_puzzle_hash)?.into()) - } else if let Some(writer_inner_puzzle_hash) = value.writer_inner_puzzle_hash { - RustDelegatedPuzzle::Writer(RustBytes32::from_js(writer_inner_puzzle_hash)?.into()) - } else if let (Some(oracle_payment_puzzle_hash), Some(oracle_fee)) = - (value.oracle_payment_puzzle_hash, value.oracle_fee) - { - RustDelegatedPuzzle::Oracle( - RustBytes32::from_js(oracle_payment_puzzle_hash)?, - u64::from_js(oracle_fee)?, - ) - } else { - return Err(js::err(ConversionError::MissingDelegatedPuzzleInfo)); - }, - ) - } -} - -impl ToJs for RustDelegatedPuzzle { - fn to_js(&self) -> Result { - match self { - RustDelegatedPuzzle::Admin(admin_inner_puzzle_hash) => { - let admin_inner_puzzle_hash: RustBytes32 = (*admin_inner_puzzle_hash).into(); - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: Some(admin_inner_puzzle_hash.to_js()?), - writer_inner_puzzle_hash: None, - oracle_payment_puzzle_hash: None, - oracle_fee: None, - }) - } - RustDelegatedPuzzle::Writer(writer_inner_puzzle_hash) => { - let writer_inner_puzzle_hash: RustBytes32 = (*writer_inner_puzzle_hash).into(); - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: None, - writer_inner_puzzle_hash: Some(writer_inner_puzzle_hash.to_js()?), - oracle_payment_puzzle_hash: None, - oracle_fee: None, - }) - } - RustDelegatedPuzzle::Oracle(oracle_payment_puzzle_hash, oracle_fee) => { - let oracle_payment_puzzle_hash: RustBytes32 = *oracle_payment_puzzle_hash; - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: None, - writer_inner_puzzle_hash: None, - oracle_payment_puzzle_hash: Some(oracle_payment_puzzle_hash.to_js()?), - oracle_fee: Some(oracle_fee.to_js()?), - }) - } - } - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents information about a data store. This information can be used to spend the store. It is recommended that this struct is stored in a database to avoid syncing it every time. -/// -/// @property {Coin} coin - The coin associated with the data store. -/// @property {Buffer} launcherId - The store's launcher/singleton ID. -/// @property {Proof} proof - Proof that can be used to spend this store. -/// @property {DataStoreMetadata} metadata - This store's metadata. -/// @property {Buffer} ownerPuzzleHash - The puzzle hash of the owner puzzle. -/// @property {Vec} delegatedPuzzles - This store's delegated puzzles. An empty list usually indicates a 'vanilla' store. -pub struct DataStore { - pub coin: Coin, - // singleton layer - pub launcher_id: Buffer, - pub proof: Proof, - // NFT state layer - pub metadata: DataStoreMetadata, - // inner puzzle (either p2 or delegation_layer + p2) - pub owner_puzzle_hash: Buffer, - pub delegated_puzzles: Vec, // if empty, there is no delegation layer -} - -impl FromJs for RustDataStore { - fn from_js(value: DataStore) -> Result { - Ok(RustDataStore { - coin: RustCoin::from_js(value.coin)?, - proof: RustProof::from_js(value.proof)?, - - info: RustDataStoreInfo { - launcher_id: RustBytes32::from_js(value.launcher_id)?, - metadata: RustDataStoreMetadata::from_js(value.metadata)?, - owner_puzzle_hash: RustBytes32::from_js(value.owner_puzzle_hash)?, - delegated_puzzles: value - .delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - }, - }) - } -} - -impl ToJs for RustDataStore { - fn to_js(&self) -> Result { - Ok(DataStore { - coin: self.coin.to_js()?, - proof: self.proof.to_js()?, - - launcher_id: self.info.launcher_id.to_js()?, - metadata: self.info.metadata.to_js()?, - owner_puzzle_hash: self.info.owner_puzzle_hash.to_js()?, - delegated_puzzles: self - .info - .delegated_puzzles - .iter() - .map(RustDelegatedPuzzle::to_js) - .collect::>>()?, - }) - } -} - -#[napi(object)] -// Represents a driver response indicating success. -/// -/// @property {Vec} coinSpends - Coin spends that can be used to spend the provided store. -/// @property {DataStore} newStore - New data store information after the spend is confirmed. -pub struct SuccessResponse { - pub coin_spends: Vec, - pub new_store: DataStore, -} - -impl FromJs for RustSuccessResponse { - fn from_js(value: SuccessResponse) -> Result { - Ok(RustSuccessResponse { - coin_spends: value - .coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - new_datastore: RustDataStore::from_js(value.new_store)?, - }) - } -} - -impl ToJs for RustSuccessResponse { - fn to_js(&self) -> Result { - Ok(SuccessResponse { - coin_spends: self - .coin_spends - .iter() - .map(RustCoinSpend::to_js) - .collect::>>()?, - new_store: self.new_datastore.to_js()?, - }) - } -} - -#[napi(object)] -/// Represents a response from synchronizing a store. -/// -/// @property {DataStore} latestStore - Latest data store information. -/// @property {Option>} rootHashes - When synced with whistory, this list will contain all of the store's previous root hashes. Otherwise null. -/// @property {Option>} rootHashesTimestamps - Timestamps of the root hashes (see `rootHashes`). -/// @property {u32} latestHeight - Latest sync height. -pub struct SyncStoreResponse { - pub latest_store: DataStore, - pub root_hashes: Option>, - pub root_hashes_timestamps: Option>, - pub latest_height: u32, -} - -impl FromJs for RustSyncStoreResponse { - fn from_js(value: SyncStoreResponse) -> Result { - let mut root_hash_history = None; - - if let (Some(root_hashes), Some(root_hashes_timestamps)) = - (value.root_hashes, value.root_hashes_timestamps) - { - let mut v = vec![]; - - for (root_hash, timestamp) in root_hashes - .into_iter() - .zip(root_hashes_timestamps.into_iter()) - { - v.push((RustBytes32::from_js(root_hash)?, u64::from_js(timestamp)?)); - } - - root_hash_history = Some(v); - } - - Ok(RustSyncStoreResponse { - latest_store: RustDataStore::from_js(value.latest_store)?, - latest_height: value.latest_height, - root_hash_history, - }) - } -} -impl ToJs for RustSyncStoreResponse { - fn to_js(&self) -> Result { - let root_hashes = self - .root_hash_history - .as_ref() - .map(|v| { - v.iter() - .map(|(rh, _)| rh.to_js()) - .collect::>>() - }) - .transpose()?; - - let root_hashes_timestamps = self - .root_hash_history - .as_ref() - .map(|v| { - v.iter() - .map(|(_, ts)| ts.to_js()) - .collect::>>() - }) - .transpose()?; - - Ok(SyncStoreResponse { - latest_store: self.latest_store.to_js()?, - latest_height: self.latest_height, - root_hashes, - root_hashes_timestamps, - }) - } -} - -#[napi(object)] -/// Represents a response containing unspent coins. -/// -/// @property {Vec} coins - Unspent coins. -/// @property {u32} lastHeight - Last height. -/// @property {Buffer} lastHeaderHash - Last header hash. -pub struct UnspentCoinsResponse { - pub coins: Vec, - pub last_height: u32, - pub last_header_hash: Buffer, -} - -impl FromJs for rust::UnspentCoinsResponse { - fn from_js(value: UnspentCoinsResponse) -> Result { - Ok(rust::UnspentCoinsResponse { - coins: value - .coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - last_height: value.last_height, - last_header_hash: RustBytes32::from_js(value.last_header_hash)?, - }) - } -} - -impl ToJs for rust::UnspentCoinsResponse { - fn to_js(&self) -> Result { - Ok(UnspentCoinsResponse { - coins: self - .coins - .iter() - .map(RustCoin::to_js) - .collect::>>()?, - last_height: self.last_height, - last_header_hash: self.last_header_hash.to_js()?, - }) - } -} - -#[napi(object)] -/// Represents a response containing possible launcher ids for datastores. -/// -/// @property {Vec} launcher_ids - Launcher ids of coins that might be datastores. -/// @property {u32} lastHeight - Last height. -/// @property {Buffer} lastHeaderHash - Last header hash. -pub struct PossibleLaunchersResponse { - pub launcher_ids: Vec, - pub last_height: u32, - pub last_header_hash: Buffer, -} - -impl FromJs for RustPossibleLaunchersResponse { - fn from_js(value: PossibleLaunchersResponse) -> Result { - Ok(RustPossibleLaunchersResponse { - last_header_hash: RustBytes32::from_js(value.last_header_hash)?, - last_height: value.last_height, - launcher_ids: value - .launcher_ids - .into_iter() - .map(RustBytes32::from_js) - .collect::>>()?, - }) - } -} - -impl ToJs for RustPossibleLaunchersResponse { - fn to_js(&self) -> Result { - Ok(PossibleLaunchersResponse { - last_header_hash: self.last_header_hash.to_js()?, - last_height: self.last_height, - launcher_ids: self - .launcher_ids - .iter() - .map(RustBytes32::to_js) - .collect::>>()?, - }) - } -} - -#[napi] -pub struct Tls(Connector); - -#[napi] -impl Tls { - #[napi(constructor)] - /// Creates a new TLS connector. - /// - /// @param {String} certPath - Path to the certificate file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.crt'). - /// @param {String} keyPath - Path to the key file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.key'). - pub fn new(cert_path: String, key_path: String) -> napi::Result { - let cert = load_ssl_cert(&cert_path, &key_path).map_err(js::err)?; - let tls = create_native_tls_connector(&cert).map_err(js::err)?; - Ok(Self(tls)) - } -} - -#[napi] -pub struct Peer { - inner: Arc, - peak: Arc>>, - coin_listeners: Arc>>>, - sim: Option>>, -} - -#[napi] -#[derive(PartialEq, Eq)] -pub enum PeerType { - Mainnet, - Testnet11, - Simulator, -} - -impl PeerType { - pub fn as_str(&self) -> &'static str { - match self { - PeerType::Mainnet => "mainnet", - PeerType::Testnet11 => "testnet11", - PeerType::Simulator => "simulator", - } - } -} - -#[napi] -impl Peer { - #[napi(factory)] - /// Creates a new Peer instance. - /// - /// @param {String} nodeUri - URI of the node (e.g., '127.0.0.1:58444'). - /// @param {PeerType} peerType - Network type: 'mainnet', 'testnet11', or 'simulator'. - /// @param {Tls} tls - TLS connector. - /// @returns {Promise} A new Peer instance. - pub async fn new(node_uri: String, peer_type: PeerType, tls: &Tls) -> napi::Result { - let (peer, mut receiver); - let sim: Option>>; - if peer_type == PeerType::Simulator { - let simulator = PeerSimulator::new().await.map_err(js::err)?; - let (p, r) = simulator.connect_raw().await.map_err(js::err)?; - peer = p; - receiver = r; - sim = Some(Arc::new(Mutex::new(simulator))); - } else { - let (p, r) = connect_peer( - peer_type.as_str().to_string(), - tls.0.clone(), - if let Ok(socket_addr) = node_uri.parse::() { - socket_addr - } else { - return Err(js::err(ConversionError::InvalidUri(node_uri))); - }, - PeerOptions::default(), - ) - .await - .map_err(js::err)?; - peer = p; - receiver = r; - sim = None; - } - - let inner = Arc::new(peer); - let peak = Arc::new(Mutex::new(None)); - let coin_listeners = Arc::new(Mutex::new( - HashMap::>::new(), - )); - - let peak_clone = peak.clone(); - let coin_listeners_clone = coin_listeners.clone(); - tokio::spawn(async move { - while let Some(message) = receiver.recv().await { - if message.msg_type == ProtocolMessageTypes::NewPeakWallet { - if let Ok(new_peak) = NewPeakWallet::from_bytes(&message.data) { - let mut peak_guard = peak_clone.lock().await; - *peak_guard = Some(new_peak); - } - } - - if message.msg_type == ProtocolMessageTypes::CoinStateUpdate { - if let Ok(coin_state_update) = CoinStateUpdate::from_bytes(&message.data) { - let mut listeners = coin_listeners_clone.lock().await; - - for coin_state_update_item in coin_state_update.items { - if coin_state_update_item.spent_height.is_none() { - continue; - } - - if let Some(listener) = - listeners.get(&coin_state_update_item.coin.coin_id()) - { - let _ = listener.send(()); - listeners.remove(&coin_state_update_item.coin.coin_id()); - } - } - } - } - } - }); - - Ok(Self { - inner, - peak, - coin_listeners, - sim, - }) - } - - #[napi] - /// Retrieves all coins that are unspent on the chain. Note that coins part of spend bundles that are pending in the mempool will also be included. - /// - /// @param {Buffer} puzzleHash - Puzzle hash of the wallet. - /// @param {Option} previousHeight - Previous height that was spent. If null, sync will be done from the genesis block. - /// @param {Buffer} previousHeaderHash - Header hash corresponding to the previous height. If previousHeight is null, this should be the genesis challenge of the current chain. - /// @returns {Promise} The unspent coins response. - pub async fn get_all_unspent_coins( - &self, - puzzle_hash: Buffer, - previous_height: Option, - previous_header_hash: Buffer, - ) -> napi::Result { - let resp: rust::UnspentCoinsResponse = get_unspent_coin_states( - &self.inner.clone(), - RustBytes32::from_js(puzzle_hash)?, - previous_height, - RustBytes32::from_js(previous_header_hash)?, - false, - ) - .await - .map_err(js::err)? - .into(); - - resp.to_js() - } - - #[napi] - /// Creates a new coin with the specified puzzle hash and amount using the simulator. - /// - /// @param {Buffer} puzzleHash - The puzzle hash for the new coin. - /// @param {BigInt} amount - The amount for the new coin. - /// @returns {Promise} The newly created coin. - pub async fn simulator_new_coin( - &self, - puzzle_hash: Buffer, - amount: BigInt, - ) -> napi::Result { - let puzzle_hash = RustBytes32::from_js(puzzle_hash)?; - let amount = u64::from_js(amount)?; - match &self.sim { - Some(sim) => { - let coin = sim.lock().await.mint_coin(puzzle_hash, amount).await; - coin.to_js() - } - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the current height of the simulator. - /// - /// @returns {Promise} The current height. - pub async fn simulator_height(&self) -> napi::Result { - match &self.sim { - Some(sim) => Ok(sim.lock().await.height().await), - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the coin state for a given coin ID. - /// - /// @param {Buffer} coinId - The coin ID to look up. - /// @returns {Promise} The coin state if found. - pub async fn simulator_coin_state(&self, coin_id: Buffer) -> napi::Result> { - let coin_id = RustBytes32::from_js(coin_id)?; - - match &self.sim { - Some(sim) => match sim.lock().await.coin_state(coin_id).await { - Some(state) => Ok(Some(state.to_js()?)), - None => Ok(None), - }, - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the header hash at the specified height. - /// - /// @param {u32} height - The height to get the header hash for. - /// @returns {Promise} The header hash. - pub async fn header_hash(&self, height: u32) -> napi::Result { - match &self.sim { - Some(sim) => sim.lock().await.header_hash(height).await.to_js(), - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Retrieves all hinted coin states that are unspent on the chain. Note that coins part of spend bundles that are pending in the mempool will also be included. - /// - /// @param {Buffer} puzzleHash - Puzzle hash to lookup hinted coins for. - /// @param {bool} forTestnet - True for testnet, false for mainnet. - /// @returns {Promise>} The unspent coins response. - pub async fn get_hinted_coin_states( - &self, - puzzle_hash: Buffer, - for_testnet: bool, - ) -> napi::Result> { - let resp = get_unspent_coin_states( - &self.inner.clone(), - RustBytes32::from_js(puzzle_hash)?, - None, - if for_testnet { - TESTNET11_CONSTANTS.genesis_challenge - } else { - MAINNET_CONSTANTS.genesis_challenge - }, - true, - ) - .await - .map_err(js::err)?; - - resp.coin_states - .into_iter() - .map(|c| c.to_js()) - .collect::>>() - } - - #[napi] - /// Fetches the server coin from a given coin state. - /// - /// @param {CoinState} coinState - The coin state. - /// @param {BigInt} maxCost - The maximum cost to use when parsing the coin. For example, `11_000_000_000`. - /// @returns {Promise} The server coin. - pub async fn fetch_server_coin( - &self, - coin_state: CoinState, - max_cost: BigInt, - ) -> napi::Result { - let coin = wallet::fetch_server_coin( - &self.inner.clone(), - rust::CoinState::from_js(coin_state)?, - u64::from_js(max_cost)?, - ) - .await - .map_err(js::err)?; - - coin.to_js() - } - - #[napi] - /// Synchronizes a datastore. - /// - /// @param {DataStore} store - Data store. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @param {bool} withHistory - Whether to return the root hash history of the store. - /// @returns {Promise} The sync store response. - pub async fn sync_store( - &self, - store: DataStore, - last_height: Option, - last_header_hash: Buffer, - with_history: bool, - ) -> napi::Result { - let res = sync_store( - &self.inner.clone(), - &RustDataStore::from_js(store)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - with_history, - ) - .await - .map_err(js::err)?; - - res.to_js() - } - - #[napi] - /// Synchronizes a store using its launcher ID. - /// - /// @param {Buffer} launcherId - The store's launcher/singleton ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @param {bool} withHistory - Whether to return the root hash history of the store. - /// @returns {Promise} The sync store response. - pub async fn sync_store_from_launcher_id( - &self, - launcher_id: Buffer, - last_height: Option, - last_header_hash: Buffer, - with_history: bool, - ) -> napi::Result { - let res = sync_store_using_launcher_id( - &self.inner.clone(), - RustBytes32::from_js(launcher_id)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - with_history, - ) - .await - .map_err(js::err)?; - - res.to_js() - } - - #[napi] - /// Fetch a store's creation height. - /// - /// @param {Buffer} launcherId - The store's launcher/singleton ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} The store's creation height. - pub async fn get_store_creation_height( - &self, - launcher_id: Buffer, - last_height: Option, - last_header_hash: Buffer, - ) -> napi::Result { - let res = wallet::get_store_creation_height( - &self.inner.clone(), - RustBytes32::from_js(launcher_id)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - ) - .await - .map_err(js::err)?; - - (res as u64).to_js() - } - - #[napi] - /// Broadcasts a spend bundle to the mempool. - /// - /// @param {Vec} coinSpends - The coin spends to be included in the bundle. - /// @param {Vec} sigs - The signatures to be aggregated and included in the bundle. - /// @returns {Promise} The broadcast error. If '', the broadcast was successful. - pub async fn broadcast_spend( - &self, - coin_spends: Vec, - sigs: Vec, - ) -> napi::Result { - let mut agg_sig = RustSignature::default(); - for sig in sigs.into_iter() { - agg_sig += &RustSignature::from_js(sig)?; - } - - let spend_bundle = RustSpendBundle::new( - coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - agg_sig, - ); - - Ok( - wallet::broadcast_spend_bundle(&self.inner.clone(), spend_bundle) - .await - .map_err(js::err)? - .error - .unwrap_or(String::default()), - ) - } - - #[napi] - /// Checks if a coin is spent on-chain. - /// - /// @param {Buffer} coinId - The coin ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Whether the coin is spent on-chain. - pub async fn is_coin_spent( - &self, - coin_id: Buffer, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - is_coin_spent( - &self.inner.clone(), - RustBytes32::from_js(coin_id)?, - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err) - } - - #[napi] - /// Retrieves the current header hash corresponding to a given height. - /// - /// @param {u32} height - The height. - /// @returns {Promise} The header hash. - pub async fn get_header_hash(&self, height: u32) -> napi::Result { - get_header_hash(&self.inner.clone(), height) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Retrieves the fee estimate for a given target time. - /// - /// @param {Peer} peer - The peer connection to the Chia node. - /// @param {BigInt} targetTimeSeconds - Time delta: The target time in seconds from the current time for the fee estimate. - /// @returns {Promise} The estimated fee in mojos per CLVM cost. - pub async fn get_fee_estimate(&self, target_time_seconds: BigInt) -> napi::Result { - wallet::get_fee_estimate(&self.inner.clone(), u64::from_js(target_time_seconds)?) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Retrieves the peer's peak. - /// - /// @returns {Option} A tuple consiting of the latest synced block's height, as reported by the peer. Null if the peer has not yet reported a peak. - pub async fn get_peak(&self) -> napi::Result> { - let peak_guard = self.peak.lock().await; - let peak: Option = peak_guard.clone(); - Ok(peak.map(|p| p.height)) - } - - /// Spends the mirror coins to make them unusable in the future. - /// - /// @param {Buffer} syntheticKey - The synthetic key used by the wallet. - /// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that the server coins will count towards the fee. - /// @param {BigInt} fee - The fee to use for the transaction. - /// @param {bool} forTestnet - True for testnet, false for mainnet. - #[napi] - pub async fn lookup_and_spend_server_coins( - &self, - synthetic_key: Buffer, - selected_coins: Vec, - fee: BigInt, - for_testnet: bool, - ) -> napi::Result> { - let coin = wallet::spend_server_coins( - &self.inner, - RustPublicKey::from_js(synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - u64::from_js(fee)?, - if for_testnet { - TargetNetwork::Testnet11 - } else { - TargetNetwork::Mainnet - }, - ) - .await - .map_err(js::err)?; - - coin.into_iter() - .map(|c| c.to_js()) - .collect::>>() - } - - #[napi] - /// Looks up possible datastore launchers by searching for singleton launchers created with a DL-specific hint. - /// - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Possible launcher ids for datastores, as well as a height + header hash combo to use for the next call. - pub async fn look_up_possible_launchers( - &self, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - wallet::look_up_possible_launchers( - &self.inner.clone(), - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Waits for a coin to be spent on-chain. - /// - /// @param {Buffer} coin_id - Id of coin to track. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Promise that resolves when the coin is spent (returning the coin id). - pub async fn wait_for_coin_to_be_spent( - &self, - coin_id: Buffer, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - let rust_coin_id = RustBytes32::from_js(coin_id)?; - let spent_height = wallet::subscribe_to_coin_states( - &self.inner.clone(), - rust_coin_id, - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err)?; - - if spent_height.is_none() { - let (sender, mut receiver) = unbounded_channel::<()>(); - - { - let mut listeners = self.coin_listeners.lock().await; - listeners.insert(rust_coin_id, sender); - } - - receiver - .recv() - .await - .ok_or_else(|| js::err("Failed to receive spent notification"))?; - } - - wallet::unsubscribe_from_coin_states(&self.inner.clone(), rust_coin_id) - .await - .map_err(js::err)?; - - rust_coin_id.to_js() - } - - #[napi(factory)] - /// Connects to a random peer on the specified network (mainnet or testnet11). - /// - /// The function performs DNS lookups using the network's introducers, picks a random - /// address from the returned list, and attempts to establish a connection. It will - /// try every resolved address until a connection succeeds. - /// - /// @param {PeerType} peerType - Network type: 'mainnet' or 'testnet11'. 'simulator' is not supported. - /// @param {Tls} tls - TLS connector. - /// @returns {Promise} A connected Peer instance. - pub async fn connect_random(peer_type: PeerType, tls: &Tls) -> napi::Result { - if peer_type == PeerType::Simulator { - return Peer::new("".to_string(), peer_type, tls).await; - } - - // Introducers and default port per network - let (introducers, default_port) = match peer_type { - PeerType::Mainnet => (MAINNET_DNS_INTRODUCERS, MAINNET_DEFAULT_PORT), - PeerType::Testnet11 => (TESTNET11_DNS_INTRODUCERS, TESTNET11_DEFAULT_PORT), - PeerType::Simulator => unreachable!(), - }; - - // Resolve all introducers to socket addresses - let mut addrs = Vec::new(); - for introducer in introducers { - if let Ok(iter) = lookup_host((*introducer, default_port)).await { - addrs.extend(iter); - } - } - - if addrs.is_empty() { - return Err(js::err( - "Failed to resolve any peer addresses from introducers", - )); - } - - // Shuffle for randomness so every call has different order - { - let mut rng = rand::thread_rng(); - addrs.shuffle(&mut rng); - } - - // Try to connect in concurrent batches with timeout logic similar to peer_discovery.rs - const BATCH_SIZE: usize = 10; - const CONNECT_TIMEOUT: Duration = Duration::from_secs(8); - - for chunk in addrs.chunks(BATCH_SIZE) { - let mut futures = FuturesUnordered::new(); - for addr in chunk { - let uri = addr.to_string(); - let pt = peer_type.clone(); - // Spawn connection attempt with timeout - futures - .push(async move { timeout(CONNECT_TIMEOUT, Peer::new(uri, pt, tls)).await }); - } - - while let Some(result) = futures.next().await { - match result { - Ok(Ok(peer)) => return Ok(peer), - _ => { - // Either timed out or failed; continue with others - } - } - } - } - - Err(js::err("Unable to connect to any discovered peer")) - } -} - -/// Selects coins using the knapsack algorithm. -/// -/// @param {Vec} allCoins - Array of available coins (coins to select from). -/// @param {BigInt} totalAmount - Amount needed for the transaction, including fee. -/// @returns {Vec} Array of selected coins. -#[napi] -pub fn select_coins(all_coins: Vec, total_amount: BigInt) -> napi::Result> { - let coins: Vec = all_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?; - let selected_coins = - wallet::select_coins(coins, u64::from_js(total_amount)?).map_err(js::err)?; - - selected_coins - .into_iter() - .map(|c| c.to_js()) - .collect::>>() -} - -/// An output puzzle hash and amount. -#[napi(object)] -pub struct Output { - pub puzzle_hash: Buffer, - pub amount: BigInt, - pub memos: Vec, -} - -/// Sends XCH to a given set of puzzle hashes. -/// -/// @param {Buffer} syntheticKey - The synthetic key used by the wallet. -/// @param {Vec} selectedCoins - Coins to be spent, as retured by `select_coins`. -/// @param {Vec} outputs - The output amounts to create. -/// @param {BigInt} fee - The fee to use for the transaction. -#[napi] -pub fn send_xch( - synthetic_key: Buffer, - selected_coins: Vec, - outputs: Vec, - fee: BigInt, -) -> napi::Result> { - let mut items = Vec::new(); - - for output in outputs { - items.push(( - RustBytes32::from_js(output.puzzle_hash)?, - u64::from_js(output.amount)?, - output - .memos - .into_iter() - .map(RustBytes::from_js) - .collect::>>()?, - )); - } - - let coin_spends = wallet::send_xch( - RustPublicKey::from_js(synthetic_key)?, - &selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - &items, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - coin_spends - .into_iter() - .map(|c| c.to_js()) - .collect::>>() -} - -/// Adds an offset to a launcher id to make it deterministically unique from the original. -/// -/// @param {Buffer} launcherId - The original launcher id. -/// @param {BigInt} offset - The offset to add. -#[napi] -pub fn morph_launcher_id(launcher_id: Buffer, offset: BigInt) -> napi::Result { - server_coin::morph_launcher_id( - RustBytes32::from_js(launcher_id)?, - &u64::from_js(offset)?.into(), - ) - .to_js() -} - -/// The new server coin and coin spends to create it. -#[napi(object)] -pub struct NewServerCoin { - pub server_coin: ServerCoin, - pub coin_spends: Vec, -} - -/// Creates a new mirror coin with the given URLs. -/// -/// @param {Buffer} syntheticKey - The synthetic key used by the wallet. -/// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that, besides the fee, 1 mojo will be used to create the mirror coin. -/// @param {Buffer} hint - The hint for the mirror coin, usually the original or morphed launcher id. -/// @param {Vec} uris - The URIs of the mirrors. -/// @param {BigInt} amount - The amount to use for the created coin. -/// @param {BigInt} fee - The fee to use for the transaction. -#[napi] -pub fn create_server_coin( - synthetic_key: Buffer, - selected_coins: Vec, - hint: Buffer, - uris: Vec, - amount: BigInt, - fee: BigInt, -) -> napi::Result { - let (coin_spends, server_coin) = wallet::create_server_coin( - RustPublicKey::from_js(synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustBytes32::from_js(hint)?, - uris, - u64::from_js(amount)?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - Ok(NewServerCoin { - coin_spends: coin_spends - .into_iter() - .map(|c| c.to_js()) - .collect::>>()?, - server_coin: server_coin.to_js()?, - }) -} - -#[allow(clippy::too_many_arguments)] -#[napi] -/// Mints a new datastore. -/// -/// @param {Buffer} minterSyntheticKey - Minter synthetic key. -/// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that, besides the fee, 1 mojo will be used to create the new store. -/// @param {Buffer} rootHash - Root hash of the store. -/// @param {Option} label - Store label (optional). -/// @param {Option} description - Store description (optional). -/// @param {Option} bytes - Store size in bytes (optional). -/// @param {Buffer} ownerPuzzleHash - Owner puzzle hash. -/// @param {Vec} delegatedPuzzles - Delegated puzzles. -/// @param {BigInt} fee - Fee to use for the transaction. Total amount - 1 - fee will be sent back to the minter. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn mint_store( - minter_synthetic_key: Buffer, - selected_coins: Vec, - root_hash: Buffer, - label: Option, - description: Option, - bytes: Option, - owner_puzzle_hash: Buffer, - delegated_puzzles: Vec, - fee: BigInt, -) -> napi::Result { - let response = wallet::mint_store( - RustPublicKey::from_js(minter_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustBytes32::from_js(root_hash)?, - label, - description, - if let Some(bytes) = bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - RustBytes32::from_js(owner_puzzle_hash)?, - delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - u64::from_js(fee).map_err(js::err)?, - ) - .map_err(js::err)?; - - response.to_js() -} - -#[napi] -/// Spends a store in oracle mode. -/// -/// @param {Buffer} spenderSyntheticKey - Spender synthetic key. -/// @param {Vec} selectedCoins - Selected coins, as returned by `select_coins`. -/// @param {DataStore} store - Up-to-daye store information. -/// @param {BigInt} fee - Transaction fee to use. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn oracle_spend( - spender_synthetic_key: Buffer, - selected_coins: Vec, - store: DataStore, - fee: BigInt, -) -> napi::Result { - let response = wallet::oracle_spend( - RustPublicKey::from_js(spender_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustDataStore::from_js(store)?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - response.to_js() -} - -#[napi] -/// Adds a fee to any transaction. Change will be sent to spender. -/// -/// @param {Buffer} spenderSyntheticKey - Synthetic key of spender. -/// @param {Vec} selectedCoins - Selected coins, as returned by `select_coins`. -/// @param {Vec} assertCoinIds - IDs of coins that need to be spent for the fee to be paid. Usually all coin ids in the original transaction. -/// @param {BigInt} fee - Fee to add. -/// @returns {Vec} The coin spends to be added to the original transaction. -pub fn add_fee( - spender_synthetic_key: Buffer, - selected_coins: Vec, - assert_coin_ids: Vec, - fee: BigInt, -) -> napi::Result> { - let response = wallet::add_fee( - RustPublicKey::from_js(spender_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - assert_coin_ids - .into_iter() - .map(RustBytes32::from_js) - .collect::>>()?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - response - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Converts a master public key to a wallet synthetic key. -/// -/// @param {Buffer} publicKey - Master public key. -/// @returns {Buffer} The (first) wallet synthetic key. -pub fn master_public_key_to_wallet_synthetic_key(public_key: Buffer) -> napi::Result { - let public_key = RustPublicKey::from_js(public_key)?; - let wallet_pk = master_to_wallet_unhardened(&public_key, 0).derive_synthetic(); - wallet_pk.to_js() -} - -#[napi] -/// Converts a master public key to the first puzzle hash. -/// -/// @param {Buffer} publicKey - Master public key. -/// @returns {Buffer} The first wallet puzzle hash. -pub fn master_public_key_to_first_puzzle_hash(public_key: Buffer) -> napi::Result { - let public_key = RustPublicKey::from_js(public_key)?; - let wallet_pk = master_to_wallet_unhardened(&public_key, 0).derive_synthetic(); - - let puzzle_hash: RustBytes32 = StandardArgs::curry_tree_hash(wallet_pk).into(); - - puzzle_hash.to_js() -} - -#[napi] -/// Converts a master secret key to a wallet synthetic secret key. -/// -/// @param {Buffer} secretKey - Master secret key. -/// @returns {Buffer} The (first) wallet synthetic secret key. -pub fn master_secret_key_to_wallet_synthetic_secret_key( - secret_key: Buffer, -) -> napi::Result { - let secret_key = RustSecretKey::from_js(secret_key)?; - let wallet_sk = master_to_wallet_unhardened(&secret_key, 0).derive_synthetic(); - wallet_sk.to_js() -} - -#[napi] -/// Converts a secret key to its corresponding public key. -/// -/// @param {Buffer} secretKey - The secret key. -/// @returns {Buffer} The public key. -pub fn secret_key_to_public_key(secret_key: Buffer) -> napi::Result { - let secret_key = RustSecretKey::from_js(secret_key)?; - secret_key.public_key().to_js() -} - -#[napi] -/// Converts a puzzle hash to an address by encoding it using bech32m. -/// -/// @param {Buffer} puzzleHash - The puzzle hash. -/// @param {String} prefix - Address prefix (e.g., 'txch'). -/// @returns {Promise} The converted address. -pub fn puzzle_hash_to_address(puzzle_hash: Buffer, prefix: String) -> napi::Result { - let puzzle_hash = RustBytes32::from_js(puzzle_hash)?; - Address::new(puzzle_hash, prefix).encode().map_err(js::err) -} - -#[napi] -/// Converts an address to a puzzle hash using bech32m. -/// -/// @param {String} address - The address. -/// @returns {Promise} The puzzle hash. -pub fn address_to_puzzle_hash(address: String) -> napi::Result { - Address::decode(&address) - .map_err(js::err)? - .puzzle_hash - .to_js() -} - -#[napi] -/// Creates an admin delegated puzzle for a given key. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// @returns {Promise} The delegated puzzle. -pub fn admin_delegated_puzzle_from_key(synthetic_key: Buffer) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - - RustDelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(synthetic_key)).to_js() -} - -#[napi] -/// Creates a writer delegated puzzle from a given key. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// /// @returns {Promise} The delegated puzzle. -pub fn writer_delegated_puzzle_from_key(synthetic_key: Buffer) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - - RustDelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(synthetic_key)).to_js() -} - -#[napi] -// Creates an oracle delegated puzzle. -/// -/// @param {Buffer} oraclePuzzleHash - The oracle puzzle hash (corresponding to the wallet where fees should be paid). -/// @param {BigInt} oracleFee - The oracle fee (i.e., XCH amount to be paid for every oracle spend). This amount MUST be even. -/// @returns {Promise} The delegated puzzle. -pub fn oracle_delegated_puzzle( - oracle_puzzle_hash: Buffer, - oracle_fee: BigInt, -) -> napi::Result { - let oracle_puzzle_hash = RustBytes32::from_js(oracle_puzzle_hash)?; - let oracle_fee = u64::from_js(oracle_fee)?; - - RustDelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee).to_js() -} - -#[napi] -/// Partially or fully signs coin spends using a list of keys. -/// -/// @param {Vec} coinSpends - The coin spends to sign. -/// @param {Vec} privateKeys - The private/secret keys to be used for signing. -/// @param {Buffer} forTestnet - Set to true to sign spends for testnet11, false for mainnet. -/// @returns {Promise} The signature. -pub fn sign_coin_spends( - coin_spends: Vec, - private_keys: Vec, - for_testnet: bool, -) -> napi::Result { - let coin_spends = coin_spends - .iter() - .map(|cs| RustCoinSpend::from_js(cs.clone())) - .collect::>>()?; - let private_keys = private_keys - .iter() - .map(|sk| RustSecretKey::from_js(sk.clone())) - .collect::>>()?; - - let sig = wallet::sign_coin_spends( - coin_spends, - private_keys, - if for_testnet { - TargetNetwork::Testnet11 - } else { - TargetNetwork::Mainnet - }, - ) - .map_err(js::err)?; - - sig.to_js() -} - -#[napi] -pub fn hex_spend_bundle_to_coin_spends(hex: String) -> napi::Result> { - let bytes = hex::decode(hex).map_err(js::err)?; - let spend_bundle = RustSpendBundle::from_bytes(&bytes).map_err(js::err)?; - spend_bundle - .coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -pub fn spend_bundle_to_hex(spend_bundle: SpendBundle) -> napi::Result { - let rust_spend_bundle = RustSpendBundle::from_js(spend_bundle)?; - let bytes = rust_spend_bundle.to_bytes().map_err(js::err)?; - Ok(hex::encode(bytes)) -} - -#[napi] -/// Computes the ID (name) of a coin. -/// -/// @param {Coin} coin - The coin. -/// @returns {Buffer} The coin ID. -pub fn get_coin_id(coin: Coin) -> napi::Result { - RustCoin::from_js(coin)?.coin_id().to_js() -} - -#[allow(clippy::too_many_arguments)] -#[napi] -/// Updates the metadata of a store. Either the owner, admin, or writer public key must be provided. -/// -/// @param {DataStore} store - Current store information. -/// @param {Buffer} newRootHash - New root hash. -/// @param {Option} newLabel - New label (optional). -/// @param {Option} newDescription - New description (optional). -/// @param {Option} newBytes - New size in bytes (optional). -/// @param {Option} ownerPublicKey - Owner public key. -/// @param {Option} adminPublicKey - Admin public key. -/// @param {Option} writerPublicKey - Writer public key. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn update_store_metadata( - store: DataStore, - new_root_hash: Buffer, - new_label: Option, - new_description: Option, - new_bytes: Option, - owner_public_key: Option, - admin_public_key: Option, - writer_public_key: Option, -) -> napi::Result { - let inner_spend_info = match (owner_public_key, admin_public_key, writer_public_key) { - (Some(owner_public_key), None, None) => { - DataStoreInnerSpend::Owner(RustPublicKey::from_js(owner_public_key)?) - } - (None, Some(admin_public_key), None) => { - DataStoreInnerSpend::Admin(RustPublicKey::from_js(admin_public_key)?) - } - (None, None, Some(writer_public_key)) => { - DataStoreInnerSpend::Writer(RustPublicKey::from_js(writer_public_key)?) - } - _ => return Err(js::err( - "Exactly one of owner_public_key, admin_public_key, writer_public_key must be provided", - )), - }; - - let res = wallet::update_store_metadata( - RustDataStore::from_js(store)?, - RustBytes32::from_js(new_root_hash)?, - new_label, - new_description, - if let Some(bytes) = new_bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - inner_spend_info, - ) - .map_err(js::err)?; - - res.to_js() -} - -#[napi] -/// Updates the ownership of a store. Either the admin or owner public key must be provided. -/// -/// @param {DataStore} store - Store information. -/// @param {Option} newOwnerPuzzleHash - New owner puzzle hash. -/// @param {Vec} newDelegatedPuzzles - New delegated puzzles. -/// @param {Option} ownerPublicKey - Owner public key. -/// @param {Option} adminPublicKey - Admin public key. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn update_store_ownership( - store: DataStore, - new_owner_puzzle_hash: Option, - new_delegated_puzzles: Vec, - owner_public_key: Option, - admin_public_key: Option, -) -> napi::Result { - let store = RustDataStore::from_js(store)?; - let new_owner_puzzle_hash = new_owner_puzzle_hash - .map(RustBytes32::from_js) - .unwrap_or_else(|| Ok(store.info.owner_puzzle_hash))?; - - let inner_spend_info = match (owner_public_key, admin_public_key) { - (Some(owner_public_key), None) => { - DataStoreInnerSpend::Owner(RustPublicKey::from_js(owner_public_key)?) - } - (None, Some(admin_public_key)) => { - DataStoreInnerSpend::Admin(RustPublicKey::from_js(admin_public_key)?) - } - _ => { - return Err(js::err( - "Exactly one of owner_public_key, admin_public_key must be provided", - )) - } - }; - - let res = wallet::update_store_ownership( - store, - new_owner_puzzle_hash, - new_delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - inner_spend_info, - ) - .map_err(js::err)?; - - res.to_js() -} - -#[napi] -/// Melts a store. The 1 mojo change will be used as a fee. -/// -/// @param {DataStore} store - Store information. -/// @param {Buffer} ownerPublicKey - Owner's public key. -/// @returns {Vec} The coin spends that the owner can sign to melt the store. -pub fn melt_store(store: DataStore, owner_public_key: Buffer) -> napi::Result> { - let res = wallet::melt_store( - RustDataStore::from_js(store)?, - RustPublicKey::from_js(owner_public_key)?, - ) - .map_err(js::err)?; - - res.into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Signs a message using the provided private key. -/// -/// @param {Buffer} message - Message to sign, as bytes. "Chia Signed Message" will be prepended automatically, as per CHIP-2 - no need to add it before calling this function. -/// @param {Buffer} private_key - Private key to sign the message with. No derivation is done. -/// @returns {Buffer} The signature. -pub fn sign_message(message: Buffer, private_key: Buffer) -> napi::Result { - wallet::sign_message( - RustBytes::from_js(message)?, - RustSecretKey::from_js(private_key)?, - ) - .map_err(js::err)? - .to_js() -} - -#[napi] -/// Verifies a signed message using the provided public key. -/// -/// @param {Buffer} signature - Th signature to be verified. -/// @param {Buffer} public_key - Public key corresponding to the private key that was used to sign the message. -/// @param {Buffer} message - Message that was signed, as bytes. "Chia Signed Message" will be prepended automatically, as per CHIP-2 - no need to add it before calling this function. -/// @returns {Buffer} Boolean - true indicates that the signature is valid, while false indicates that it is not. -pub fn verify_signed_message( - signature: Buffer, - public_key: Buffer, - message: Buffer, -) -> napi::Result { - wallet::verify_signature( - RustBytes::from_js(message)?, - RustPublicKey::from_js(public_key)?, - RustSignature::from_js(signature)?, - ) - .map_err(js::err) -} - -#[napi] -/// Converts a synthetic key to its corresponding standard puzzle hash. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// @returns {Buffer} The standard puzzle (puzzle) hash. -pub fn synthetic_key_to_puzzle_hash(synthetic_key: Buffer) -> napi::Result { - let puzzle_hash: RustBytes32 = - StandardArgs::curry_tree_hash(RustPublicKey::from_js(synthetic_key)?).into(); - - puzzle_hash.to_js() -} - -#[napi] -/// Calculates the total cost of a given array of coin spends/ -/// -/// @param {Vec} CoinSpend - Coin spends. -/// @returns {BigInt} The cost of the coin spends. -pub fn get_cost(coin_spends: Vec) -> napi::Result { - wallet::get_cost( - coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - ) - .map_err(js::err)? - .to_js() -} - -#[napi] -/// Returns the mainnet genesis challenge. -/// -/// @returns {Buffer} The mainnet genesis challenge. -pub fn get_mainnet_genesis_challenge() -> napi::Result { - MAINNET_CONSTANTS.genesis_challenge.to_js() -} - -#[napi] -/// Returns the testnet11 genesis challenge. -/// -/// @returns {Buffer} The testnet11 genesis challenge. -pub fn get_testnet11_genesis_challenge() -> napi::Result { - TESTNET11_CONSTANTS.genesis_challenge.to_js() -} - -#[napi] -/// Mints a new NFT using a DID string. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Buffer} syntheticKey - The synthetic key of the wallet -/// @param {Vec} selectedCoins - Coins to spend for the transaction -/// @param {string} didString - The DID string (e.g., "did:chia:1s8j4pquxfu5mhlldzu357qfqkwa9r35mdx5a0p0ehn76dr4ut4tqs0n6kv") -/// @param {Buffer} recipientPuzzleHash - The puzzle hash to send the NFT to -/// @param {NftMetadata} metadata - The NFT metadata -/// @param {Buffer} royaltyPuzzleHash - Optional royalty puzzle hash (defaults to recipient if None) -/// @param {number} royaltyBasisPoints - Royalty percentage in basis points (e.g., 300 = 3%) -/// @param {BigInt} fee - Transaction fee -/// @param {boolean} forTestnet - Whether to use testnet or mainnet (defaults to mainnet) -/// @returns {Promise>} A vector of coin spends that mint the NFT -pub async fn mint_nft( - peer: &Peer, - synthetic_key: Buffer, - selected_coins: Vec, - did_string: String, - recipient_puzzle_hash: Buffer, - metadata: NftMetadata, - royalty_puzzle_hash: Option, - royalty_basis_points: u32, - fee: BigInt, - for_testnet: Option, -) -> napi::Result> { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - let selected_coins = selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>() - .map_err(js::err)?; - let recipient_puzzle_hash = RustBytes32::from_js(recipient_puzzle_hash)?; - let metadata = chia::puzzles::nft::NftMetadata::from_js(metadata)?; - let royalty_puzzle_hash = if let Some(hash) = royalty_puzzle_hash { - Some(RustBytes32::from_js(hash)?) - } else { - None - }; - let fee = u64::from_js(fee)?; - let network = if for_testnet.unwrap_or(false) { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let coin_spends = wallet::mint_nft( - &peer.inner, - synthetic_key, - selected_coins, - &did_string, - recipient_puzzle_hash, - metadata, - royalty_puzzle_hash, - royalty_basis_points as u16, - fee, - network, - ) - .await - .map_err(js::err)?; - - coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Generates a DID proof for a DID coin by analyzing its parent automatically. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Coin} didCoin - The DID coin to generate proof for -/// @param {boolean} forTestnet - Whether to use testnet or mainnet -/// @returns {Promise} An object containing the proof and the DID coin -pub async fn generate_did_proof( - peer: &Peer, - did_coin: Coin, - for_testnet: bool, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let network = if for_testnet { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let (proof, coin) = wallet::generate_did_proof(&peer.inner, did_coin, network) - .await - .map_err(js::err)?; - - Ok(js::DidProofResult { - proof: proof.to_js()?, - did_coin: coin.to_js()?, - }) -} - -#[napi] -/// Generates a DID proof manually when you have the parent information. -/// -/// @param {Coin} didCoin - The current DID coin -/// @param {Coin} parentCoin - The parent coin of the DID (null for eve proof) -/// @param {Buffer} parentInnerPuzzleHash - The parent's inner puzzle hash (for lineage proof) -/// @returns {Proof} A DID proof that can be used to spend the DID coin -pub fn generate_did_proof_manual( - did_coin: Coin, - parent_coin: Option, - parent_inner_puzzle_hash: Option, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let parent_coin = if let Some(coin) = parent_coin { - Some(rust::Coin::from_js(coin)?) - } else { - None - }; - let parent_inner_puzzle_hash = if let Some(hash) = parent_inner_puzzle_hash { - Some(RustBytes32::from_js(hash)?) - } else { - None - }; - - let proof = wallet::generate_did_proof_manual(did_coin, parent_coin, parent_inner_puzzle_hash) - .map_err(js::err)?; - - proof.to_js() -} - -#[napi] -/// Generates a DID proof from the blockchain by analyzing the parent spend. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Coin} didCoin - The DID coin to generate proof for -/// @param {boolean} forTestnet - Whether to use testnet or mainnet -/// @returns {Promise} A DID proof that can be used to spend the DID coin -pub async fn generate_did_proof_from_chain( - peer: &Peer, - did_coin: Coin, - for_testnet: bool, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let network = if for_testnet { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let proof = wallet::generate_did_proof_from_chain(&peer.inner, did_coin, network) - .await - .map_err(js::err)?; - - proof.to_js() -} - -#[napi] -/// Creates a simple DID from a private key and selected coins. -/// -/// @param {Buffer} syntheticKey - The synthetic key that will control the DID -/// @param {Vec} selectedCoins - Coins to spend for creating the DID -/// @param {BigInt} fee - Transaction fee -/// @returns {CreateDidResult} An object containing coinSpends and the created DID coin -pub fn create_simple_did( - synthetic_key: Buffer, - selected_coins: Vec, - fee: BigInt, -) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - let selected_coins = selected_coins - .into_iter() - .map(rust::Coin::from_js) - .collect::>>() - .map_err(js::err)?; - let fee = u64::from_js(fee)?; - - let (coin_spends, did_coin) = - wallet::create_simple_did(synthetic_key, selected_coins, fee).map_err(js::err)?; - - let coin_spends_js = coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>()?; - - Ok(js::CreateDidResult { - coin_spends: coin_spends_js, - did_coin: did_coin.to_js()?, - }) -} - -#[napi] -/// Creates a new puzzle and its hash using the simulator. -/// -/// @param {BigInt} value - The value to use for the puzzle. -/// @returns {Promise} The puzzle hash and reveal. -pub async fn simulator_new_puzzle(value: BigInt) -> napi::Result { - let value = u64::from_js(value)?; - let (puzzle_hash, puzzle_reveal) = to_puzzle(value).map_err(js::err)?; - Ok(js::SimulatorPuzzle { - puzzle_hash: puzzle_hash.to_js()?, - puzzle_reveal: puzzle_reveal.to_js()?, - }) -} - -#[napi] -/// Creates a new BlsPair. -/// -/// @param {BigInt} value - The value to use to initialize the pair. -/// @returns {Promise} The BlsPair. -pub fn simulator_new_blspair(value: BigInt) -> napi::Result { - let value = u64::from_js(value)?; - let pair = chia_wallet_sdk::test::BlsPair::new(value); - Ok(js::BlsPair { - puzzle_hash: pair.puzzle_hash.to_js()?, - sk: pair.sk.to_js()?, - pk: pair.pk.to_js()?, - }) -} - -#[napi] -/// Creates a new simulator program. -/// -/// @returns {Promise} The program. -pub fn simulator_new_program(pk: Buffer) -> napi::Result { - let pk = RustPublicKey::from_js(pk)?; - let program = - to_program([AggSigMe::new(pk, b"Hello, world!".to_vec().into())]).map_err(js::err)?; - program.to_js() -} diff --git a/src/napi_lib.rs b/src/napi_lib.rs deleted file mode 100644 index 44f9f29..0000000 --- a/src/napi_lib.rs +++ /dev/null @@ -1,1904 +0,0 @@ -#![allow(unexpected_cfgs)] - -mod conversions; -mod js; -mod rust; -mod server_coin; -mod wallet; - -use chia::bls::{ - master_to_wallet_unhardened, PublicKey as RustPublicKey, SecretKey as RustSecretKey, - Signature as RustSignature, -}; - -use chia::protocol::{ - Bytes as RustBytes, Bytes32 as RustBytes32, Coin as RustCoin, CoinSpend as RustCoinSpend, - CoinStateUpdate, NewPeakWallet, ProtocolMessageTypes, SpendBundle as RustSpendBundle, -}; -use chia::puzzles::{standard::StandardArgs, DeriveSynthetic, Proof as RustProof}; -use chia::traits::Streamable; -use chia_wallet_sdk::client::{ - connect_peer, create_native_tls_connector, load_ssl_cert, Connector, PeerOptions, -}; -use chia_wallet_sdk::prelude::AggSigMe; -use chia_wallet_sdk::test::{to_program, to_puzzle, PeerSimulator}; -use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS}; -use chia_wallet_sdk::utils::Address; -use chia_wallet_sdk::{ - client::Peer as RustPeer, - driver::{ - DataStore as RustDataStore, DataStoreInfo as RustDataStoreInfo, - DataStoreMetadata as RustDataStoreMetadata, DelegatedPuzzle as RustDelegatedPuzzle, - }, -}; -use conversions::{ConversionError, FromJs, ToJs}; -use futures_util::stream::{FuturesUnordered, StreamExt}; -use js::{Coin, CoinSpend, CoinState, EveProof, NftMetadata, Proof, ServerCoin, SpendBundle}; -use napi::bindgen_prelude::*; -use napi::Result; -use rand::seq::SliceRandom; -use std::collections::HashMap; -use std::{net::SocketAddr, sync::Arc}; -use tokio::net::lookup_host; -use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; -use tokio::sync::Mutex; -use tokio::time::{timeout, Duration}; -use wallet::{ - PossibleLaunchersResponse as RustPossibleLaunchersResponse, - SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse, -}; - -pub use wallet::*; - -// DNS introducers and default ports for connecting to random peers. -const MAINNET_DNS_INTRODUCERS: &[&str] = &[ - "dns-introducer.chia.net", - "chia.ctrlaltdel.ch", - "seeder.dexie.space", - "chia.hoffmang.com", -]; -const TESTNET11_DNS_INTRODUCERS: &[&str] = &["dns-introducer-testnet11.chia.net"]; -const MAINNET_DEFAULT_PORT: u16 = 8444; -const TESTNET11_DEFAULT_PORT: u16 = 58444; - -#[napi] -/// Creates a new lineage proof. -/// -/// @param {LineageProof} lineageProof - The lineage proof. -/// @returns {Proof} The new proof. -pub fn new_lineage_proof(lineage_proof: js::LineageProof) -> js::Proof { - js::Proof { - lineage_proof: Some(lineage_proof), - eve_proof: None, - } -} - -#[napi] -/// Creates a new eve proof. -/// -/// @param {EveProof} eveProof - The eve proof. -/// @returns {Proof} The new proof. -pub fn new_eve_proof(eve_proof: EveProof) -> Proof { - Proof { - lineage_proof: None, - eve_proof: Some(eve_proof), - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents metadata for a data store. -/// -/// @property {Buffer} rootHash - Root hash. -/// @property {Option} label - Label (optional). -/// @property {Option} description - Description (optional). -/// @property {Option} bytes - Size of the store in bytes (optional). -pub struct DataStoreMetadata { - pub root_hash: Buffer, - pub label: Option, - pub description: Option, - pub bytes: Option, -} - -impl FromJs for RustDataStoreMetadata { - fn from_js(value: DataStoreMetadata) -> Result { - Ok(RustDataStoreMetadata { - root_hash: RustBytes32::from_js(value.root_hash)?, - label: value.label, - description: value.description, - bytes: if let Some(bytes) = value.bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - }) - } -} - -impl ToJs for RustDataStoreMetadata { - fn to_js(&self) -> Result { - Ok(DataStoreMetadata { - root_hash: self.root_hash.to_js()?, - label: self.label.clone(), - description: self.description.clone(), - bytes: if let Some(bytes) = self.bytes { - Some(bytes.to_js()?) - } else { - None - }, - }) - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents information about a delegated puzzle. Note that this struct can represent all three types of delegated puzzles, but only represents one at a time. -/// -/// @property {Option} adminInnerPuzzleHash - Admin inner puzzle hash, if this is an admin delegated puzzle. -/// @property {Option} writerInnerPuzzleHash - Writer inner puzzle hash, if this is a writer delegated puzzle. -/// @property {Option} oraclePaymentPuzzleHash - Oracle payment puzzle hash, if this is an oracle delegated puzzle. -/// @property {Option} oracleFee - Oracle fee, if this is an oracle delegated puzzle. -pub struct DelegatedPuzzle { - pub admin_inner_puzzle_hash: Option, - pub writer_inner_puzzle_hash: Option, - pub oracle_payment_puzzle_hash: Option, - pub oracle_fee: Option, -} - -impl FromJs for RustDelegatedPuzzle { - fn from_js(value: DelegatedPuzzle) -> Result { - Ok( - if let Some(admin_inner_puzzle_hash) = value.admin_inner_puzzle_hash { - RustDelegatedPuzzle::Admin(RustBytes32::from_js(admin_inner_puzzle_hash)?.into()) - } else if let Some(writer_inner_puzzle_hash) = value.writer_inner_puzzle_hash { - RustDelegatedPuzzle::Writer(RustBytes32::from_js(writer_inner_puzzle_hash)?.into()) - } else if let (Some(oracle_payment_puzzle_hash), Some(oracle_fee)) = - (value.oracle_payment_puzzle_hash, value.oracle_fee) - { - RustDelegatedPuzzle::Oracle( - RustBytes32::from_js(oracle_payment_puzzle_hash)?, - u64::from_js(oracle_fee)?, - ) - } else { - return Err(js::err(ConversionError::MissingDelegatedPuzzleInfo)); - }, - ) - } -} - -impl ToJs for RustDelegatedPuzzle { - fn to_js(&self) -> Result { - match self { - RustDelegatedPuzzle::Admin(admin_inner_puzzle_hash) => { - let admin_inner_puzzle_hash: RustBytes32 = (*admin_inner_puzzle_hash).into(); - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: Some(admin_inner_puzzle_hash.to_js()?), - writer_inner_puzzle_hash: None, - oracle_payment_puzzle_hash: None, - oracle_fee: None, - }) - } - RustDelegatedPuzzle::Writer(writer_inner_puzzle_hash) => { - let writer_inner_puzzle_hash: RustBytes32 = (*writer_inner_puzzle_hash).into(); - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: None, - writer_inner_puzzle_hash: Some(writer_inner_puzzle_hash.to_js()?), - oracle_payment_puzzle_hash: None, - oracle_fee: None, - }) - } - RustDelegatedPuzzle::Oracle(oracle_payment_puzzle_hash, oracle_fee) => { - let oracle_payment_puzzle_hash: RustBytes32 = *oracle_payment_puzzle_hash; - - Ok(DelegatedPuzzle { - admin_inner_puzzle_hash: None, - writer_inner_puzzle_hash: None, - oracle_payment_puzzle_hash: Some(oracle_payment_puzzle_hash.to_js()?), - oracle_fee: Some(oracle_fee.to_js()?), - }) - } - } - } -} - -#[napi(object)] -#[derive(Clone)] -/// Represents information about a data store. This information can be used to spend the store. It is recommended that this struct is stored in a database to avoid syncing it every time. -/// -/// @property {Coin} coin - The coin associated with the data store. -/// @property {Buffer} launcherId - The store's launcher/singleton ID. -/// @property {Proof} proof - Proof that can be used to spend this store. -/// @property {DataStoreMetadata} metadata - This store's metadata. -/// @property {Buffer} ownerPuzzleHash - The puzzle hash of the owner puzzle. -/// @property {Vec} delegatedPuzzles - This store's delegated puzzles. An empty list usually indicates a 'vanilla' store. -pub struct DataStore { - pub coin: Coin, - // singleton layer - pub launcher_id: Buffer, - pub proof: Proof, - // NFT state layer - pub metadata: DataStoreMetadata, - // inner puzzle (either p2 or delegation_layer + p2) - pub owner_puzzle_hash: Buffer, - pub delegated_puzzles: Vec, // if empty, there is no delegation layer -} - -impl FromJs for RustDataStore { - fn from_js(value: DataStore) -> Result { - Ok(RustDataStore { - coin: RustCoin::from_js(value.coin)?, - proof: RustProof::from_js(value.proof)?, - - info: RustDataStoreInfo { - launcher_id: RustBytes32::from_js(value.launcher_id)?, - metadata: RustDataStoreMetadata::from_js(value.metadata)?, - owner_puzzle_hash: RustBytes32::from_js(value.owner_puzzle_hash)?, - delegated_puzzles: value - .delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - }, - }) - } -} - -impl ToJs for RustDataStore { - fn to_js(&self) -> Result { - Ok(DataStore { - coin: self.coin.to_js()?, - proof: self.proof.to_js()?, - - launcher_id: self.info.launcher_id.to_js()?, - metadata: self.info.metadata.to_js()?, - owner_puzzle_hash: self.info.owner_puzzle_hash.to_js()?, - delegated_puzzles: self - .info - .delegated_puzzles - .iter() - .map(RustDelegatedPuzzle::to_js) - .collect::>>()?, - }) - } -} - -#[napi(object)] -// Represents a driver response indicating success. -/// -/// @property {Vec} coinSpends - Coin spends that can be used to spend the provided store. -/// @property {DataStore} newStore - New data store information after the spend is confirmed. -pub struct SuccessResponse { - pub coin_spends: Vec, - pub new_store: DataStore, -} - -impl FromJs for RustSuccessResponse { - fn from_js(value: SuccessResponse) -> Result { - Ok(RustSuccessResponse { - coin_spends: value - .coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - new_datastore: RustDataStore::from_js(value.new_store)?, - }) - } -} - -impl ToJs for RustSuccessResponse { - fn to_js(&self) -> Result { - Ok(SuccessResponse { - coin_spends: self - .coin_spends - .iter() - .map(RustCoinSpend::to_js) - .collect::>>()?, - new_store: self.new_datastore.to_js()?, - }) - } -} - -#[napi(object)] -/// Represents a response from synchronizing a store. -/// -/// @property {DataStore} latestStore - Latest data store information. -/// @property {Option>} rootHashes - When synced with whistory, this list will contain all of the store's previous root hashes. Otherwise null. -/// @property {Option>} rootHashesTimestamps - Timestamps of the root hashes (see `rootHashes`). -/// @property {u32} latestHeight - Latest sync height. -pub struct SyncStoreResponse { - pub latest_store: DataStore, - pub root_hashes: Option>, - pub root_hashes_timestamps: Option>, - pub latest_height: u32, -} - -impl FromJs for RustSyncStoreResponse { - fn from_js(value: SyncStoreResponse) -> Result { - let mut root_hash_history = None; - - if let (Some(root_hashes), Some(root_hashes_timestamps)) = - (value.root_hashes, value.root_hashes_timestamps) - { - let mut v = vec![]; - - for (root_hash, timestamp) in root_hashes - .into_iter() - .zip(root_hashes_timestamps.into_iter()) - { - v.push((RustBytes32::from_js(root_hash)?, u64::from_js(timestamp)?)); - } - - root_hash_history = Some(v); - } - - Ok(RustSyncStoreResponse { - latest_store: RustDataStore::from_js(value.latest_store)?, - latest_height: value.latest_height, - root_hash_history, - }) - } -} -impl ToJs for RustSyncStoreResponse { - fn to_js(&self) -> Result { - let root_hashes = self - .root_hash_history - .as_ref() - .map(|v| { - v.iter() - .map(|(rh, _)| rh.to_js()) - .collect::>>() - }) - .transpose()?; - - let root_hashes_timestamps = self - .root_hash_history - .as_ref() - .map(|v| { - v.iter() - .map(|(_, ts)| ts.to_js()) - .collect::>>() - }) - .transpose()?; - - Ok(SyncStoreResponse { - latest_store: self.latest_store.to_js()?, - latest_height: self.latest_height, - root_hashes, - root_hashes_timestamps, - }) - } -} - -#[napi(object)] -/// Represents a response containing unspent coins. -/// -/// @property {Vec} coins - Unspent coins. -/// @property {u32} lastHeight - Last height. -/// @property {Buffer} lastHeaderHash - Last header hash. -pub struct UnspentCoinsResponse { - pub coins: Vec, - pub last_height: u32, - pub last_header_hash: Buffer, -} - -impl FromJs for rust::UnspentCoinsResponse { - fn from_js(value: UnspentCoinsResponse) -> Result { - Ok(rust::UnspentCoinsResponse { - coins: value - .coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - last_height: value.last_height, - last_header_hash: RustBytes32::from_js(value.last_header_hash)?, - }) - } -} - -impl ToJs for rust::UnspentCoinsResponse { - fn to_js(&self) -> Result { - Ok(UnspentCoinsResponse { - coins: self - .coins - .iter() - .map(RustCoin::to_js) - .collect::>>()?, - last_height: self.last_height, - last_header_hash: self.last_header_hash.to_js()?, - }) - } -} - -#[napi(object)] -/// Represents a response containing possible launcher ids for datastores. -/// -/// @property {Vec} launcher_ids - Launcher ids of coins that might be datastores. -/// @property {u32} lastHeight - Last height. -/// @property {Buffer} lastHeaderHash - Last header hash. -pub struct PossibleLaunchersResponse { - pub launcher_ids: Vec, - pub last_height: u32, - pub last_header_hash: Buffer, -} - -impl FromJs for RustPossibleLaunchersResponse { - fn from_js(value: PossibleLaunchersResponse) -> Result { - Ok(RustPossibleLaunchersResponse { - last_header_hash: RustBytes32::from_js(value.last_header_hash)?, - last_height: value.last_height, - launcher_ids: value - .launcher_ids - .into_iter() - .map(RustBytes32::from_js) - .collect::>>()?, - }) - } -} - -impl ToJs for RustPossibleLaunchersResponse { - fn to_js(&self) -> Result { - Ok(PossibleLaunchersResponse { - last_header_hash: self.last_header_hash.to_js()?, - last_height: self.last_height, - launcher_ids: self - .launcher_ids - .iter() - .map(RustBytes32::to_js) - .collect::>>()?, - }) - } -} - -#[napi] -pub struct Tls(Connector); - -#[napi] -impl Tls { - #[napi(constructor)] - /// Creates a new TLS connector. - /// - /// @param {String} certPath - Path to the certificate file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.crt'). - /// @param {String} keyPath - Path to the key file (usually '~/.chia/mainnet/config/ssl/wallet/wallet_node.key'). - pub fn new(cert_path: String, key_path: String) -> napi::Result { - let cert = load_ssl_cert(&cert_path, &key_path).map_err(js::err)?; - let tls = create_native_tls_connector(&cert).map_err(js::err)?; - Ok(Self(tls)) - } -} - -#[napi] -pub struct Peer { - inner: Arc, - peak: Arc>>, - coin_listeners: Arc>>>, - sim: Option>>, -} - -#[napi] -#[derive(PartialEq, Eq)] -pub enum PeerType { - Mainnet, - Testnet11, - Simulator, -} - -impl PeerType { - pub fn as_str(&self) -> &'static str { - match self { - PeerType::Mainnet => "mainnet", - PeerType::Testnet11 => "testnet11", - PeerType::Simulator => "simulator", - } - } -} - -#[napi] -impl Peer { - #[napi(factory)] - /// Creates a new Peer instance. - /// - /// @param {String} nodeUri - URI of the node (e.g., '127.0.0.1:58444'). - /// @param {PeerType} peerType - Network type: 'mainnet', 'testnet11', or 'simulator'. - /// @param {Tls} tls - TLS connector. - /// @returns {Promise} A new Peer instance. - pub async fn new(node_uri: String, peer_type: PeerType, tls: &Tls) -> napi::Result { - let (peer, mut receiver); - let sim: Option>>; - if peer_type == PeerType::Simulator { - let simulator = PeerSimulator::new().await.map_err(js::err)?; - let (p, r) = simulator.connect_raw().await.map_err(js::err)?; - peer = p; - receiver = r; - sim = Some(Arc::new(Mutex::new(simulator))); - } else { - let (p, r) = connect_peer( - peer_type.as_str().to_string(), - tls.0.clone(), - if let Ok(socket_addr) = node_uri.parse::() { - socket_addr - } else { - return Err(js::err(ConversionError::InvalidUri(node_uri))); - }, - PeerOptions::default(), - ) - .await - .map_err(js::err)?; - peer = p; - receiver = r; - sim = None; - } - - let inner = Arc::new(peer); - let peak = Arc::new(Mutex::new(None)); - let coin_listeners = Arc::new(Mutex::new( - HashMap::>::new(), - )); - - let peak_clone = peak.clone(); - let coin_listeners_clone = coin_listeners.clone(); - tokio::spawn(async move { - while let Some(message) = receiver.recv().await { - if message.msg_type == ProtocolMessageTypes::NewPeakWallet { - if let Ok(new_peak) = NewPeakWallet::from_bytes(&message.data) { - let mut peak_guard = peak_clone.lock().await; - *peak_guard = Some(new_peak); - } - } - - if message.msg_type == ProtocolMessageTypes::CoinStateUpdate { - if let Ok(coin_state_update) = CoinStateUpdate::from_bytes(&message.data) { - let mut listeners = coin_listeners_clone.lock().await; - - for coin_state_update_item in coin_state_update.items { - if coin_state_update_item.spent_height.is_none() { - continue; - } - - if let Some(listener) = - listeners.get(&coin_state_update_item.coin.coin_id()) - { - let _ = listener.send(()); - listeners.remove(&coin_state_update_item.coin.coin_id()); - } - } - } - } - } - }); - - Ok(Self { - inner, - peak, - coin_listeners, - sim, - }) - } - - #[napi] - /// Retrieves all coins that are unspent on the chain. Note that coins part of spend bundles that are pending in the mempool will also be included. - /// - /// @param {Buffer} puzzleHash - Puzzle hash of the wallet. - /// @param {Option} previousHeight - Previous height that was spent. If null, sync will be done from the genesis block. - /// @param {Buffer} previousHeaderHash - Header hash corresponding to the previous height. If previousHeight is null, this should be the genesis challenge of the current chain. - /// @returns {Promise} The unspent coins response. - pub async fn get_all_unspent_coins( - &self, - puzzle_hash: Buffer, - previous_height: Option, - previous_header_hash: Buffer, - ) -> napi::Result { - let resp: rust::UnspentCoinsResponse = get_unspent_coin_states( - &self.inner.clone(), - RustBytes32::from_js(puzzle_hash)?, - previous_height, - RustBytes32::from_js(previous_header_hash)?, - false, - ) - .await - .map_err(js::err)? - .into(); - - resp.to_js() - } - - #[napi] - /// Creates a new coin with the specified puzzle hash and amount using the simulator. - /// - /// @param {Buffer} puzzleHash - The puzzle hash for the new coin. - /// @param {BigInt} amount - The amount for the new coin. - /// @returns {Promise} The newly created coin. - pub async fn simulator_new_coin( - &self, - puzzle_hash: Buffer, - amount: BigInt, - ) -> napi::Result { - let puzzle_hash = RustBytes32::from_js(puzzle_hash)?; - let amount = u64::from_js(amount)?; - match &self.sim { - Some(sim) => { - let coin = sim.lock().await.mint_coin(puzzle_hash, amount).await; - coin.to_js() - } - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the current height of the simulator. - /// - /// @returns {Promise} The current height. - pub async fn simulator_height(&self) -> napi::Result { - match &self.sim { - Some(sim) => Ok(sim.lock().await.height().await), - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the coin state for a given coin ID. - /// - /// @param {Buffer} coinId - The coin ID to look up. - /// @returns {Promise} The coin state if found. - pub async fn simulator_coin_state(&self, coin_id: Buffer) -> napi::Result> { - let coin_id = RustBytes32::from_js(coin_id)?; - - match &self.sim { - Some(sim) => match sim.lock().await.coin_state(coin_id).await { - Some(state) => Ok(Some(state.to_js()?)), - None => Ok(None), - }, - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Gets the header hash at the specified height. - /// - /// @param {u32} height - The height to get the header hash for. - /// @returns {Promise} The header hash. - pub async fn header_hash(&self, height: u32) -> napi::Result { - match &self.sim { - Some(sim) => sim.lock().await.header_hash(height).await.to_js(), - None => Err(js::err("Simulator is not available for this peer type")), - } - } - - #[napi] - /// Retrieves all hinted coin states that are unspent on the chain. Note that coins part of spend bundles that are pending in the mempool will also be included. - /// - /// @param {Buffer} puzzleHash - Puzzle hash to lookup hinted coins for. - /// @param {bool} forTestnet - True for testnet, false for mainnet. - /// @returns {Promise>} The unspent coins response. - pub async fn get_hinted_coin_states( - &self, - puzzle_hash: Buffer, - for_testnet: bool, - ) -> napi::Result> { - let resp = get_unspent_coin_states( - &self.inner.clone(), - RustBytes32::from_js(puzzle_hash)?, - None, - if for_testnet { - TESTNET11_CONSTANTS.genesis_challenge - } else { - MAINNET_CONSTANTS.genesis_challenge - }, - true, - ) - .await - .map_err(js::err)?; - - resp.coin_states - .into_iter() - .map(|c| c.to_js()) - .collect::>>() - } - - #[napi] - /// Fetches the server coin from a given coin state. - /// - /// @param {CoinState} coinState - The coin state. - /// @param {BigInt} maxCost - The maximum cost to use when parsing the coin. For example, `11_000_000_000`. - /// @returns {Promise} The server coin. - pub async fn fetch_server_coin( - &self, - coin_state: CoinState, - max_cost: BigInt, - ) -> napi::Result { - let coin = wallet::fetch_server_coin( - &self.inner.clone(), - rust::CoinState::from_js(coin_state)?, - u64::from_js(max_cost)?, - ) - .await - .map_err(js::err)?; - - coin.to_js() - } - - #[napi] - /// Synchronizes a datastore. - /// - /// @param {DataStore} store - Data store. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @param {bool} withHistory - Whether to return the root hash history of the store. - /// @returns {Promise} The sync store response. - pub async fn sync_store( - &self, - store: DataStore, - last_height: Option, - last_header_hash: Buffer, - with_history: bool, - ) -> napi::Result { - let res = sync_store( - &self.inner.clone(), - &RustDataStore::from_js(store)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - with_history, - ) - .await - .map_err(js::err)?; - - res.to_js() - } - - #[napi] - /// Synchronizes a store using its launcher ID. - /// - /// @param {Buffer} launcherId - The store's launcher/singleton ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @param {bool} withHistory - Whether to return the root hash history of the store. - /// @returns {Promise} The sync store response. - pub async fn sync_store_from_launcher_id( - &self, - launcher_id: Buffer, - last_height: Option, - last_header_hash: Buffer, - with_history: bool, - ) -> napi::Result { - let res = sync_store_using_launcher_id( - &self.inner.clone(), - RustBytes32::from_js(launcher_id)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - with_history, - ) - .await - .map_err(js::err)?; - - res.to_js() - } - - #[napi] - /// Fetch a store's creation height. - /// - /// @param {Buffer} launcherId - The store's launcher/singleton ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} lastHeaderHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} The store's creation height. - pub async fn get_store_creation_height( - &self, - launcher_id: Buffer, - last_height: Option, - last_header_hash: Buffer, - ) -> napi::Result { - let res = wallet::get_store_creation_height( - &self.inner.clone(), - RustBytes32::from_js(launcher_id)?, - last_height, - RustBytes32::from_js(last_header_hash)?, - ) - .await - .map_err(js::err)?; - - (res as u64).to_js() - } - - #[napi] - /// Broadcasts a spend bundle to the mempool. - /// - /// @param {Vec} coinSpends - The coin spends to be included in the bundle. - /// @param {Vec} sigs - The signatures to be aggregated and included in the bundle. - /// @returns {Promise} The broadcast error. If '', the broadcast was successful. - pub async fn broadcast_spend( - &self, - coin_spends: Vec, - sigs: Vec, - ) -> napi::Result { - let mut agg_sig = RustSignature::default(); - for sig in sigs.into_iter() { - agg_sig += &RustSignature::from_js(sig)?; - } - - let spend_bundle = RustSpendBundle::new( - coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - agg_sig, - ); - - Ok( - wallet::broadcast_spend_bundle(&self.inner.clone(), spend_bundle) - .await - .map_err(js::err)? - .error - .unwrap_or(String::default()), - ) - } - - #[napi] - /// Checks if a coin is spent on-chain. - /// - /// @param {Buffer} coinId - The coin ID. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Whether the coin is spent on-chain. - pub async fn is_coin_spent( - &self, - coin_id: Buffer, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - is_coin_spent( - &self.inner.clone(), - RustBytes32::from_js(coin_id)?, - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err) - } - - #[napi] - /// Retrieves the current header hash corresponding to a given height. - /// - /// @param {u32} height - The height. - /// @returns {Promise} The header hash. - pub async fn get_header_hash(&self, height: u32) -> napi::Result { - get_header_hash(&self.inner.clone(), height) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Retrieves the fee estimate for a given target time. - /// - /// @param {Peer} peer - The peer connection to the Chia node. - /// @param {BigInt} targetTimeSeconds - Time delta: The target time in seconds from the current time for the fee estimate. - /// @returns {Promise} The estimated fee in mojos per CLVM cost. - pub async fn get_fee_estimate(&self, target_time_seconds: BigInt) -> napi::Result { - wallet::get_fee_estimate(&self.inner.clone(), u64::from_js(target_time_seconds)?) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Retrieves the peer's peak. - /// - /// @returns {Option} A tuple consiting of the latest synced block's height, as reported by the peer. Null if the peer has not yet reported a peak. - pub async fn get_peak(&self) -> napi::Result> { - let peak_guard = self.peak.lock().await; - let peak: Option = peak_guard.clone(); - Ok(peak.map(|p| p.height)) - } - - /// Spends the mirror coins to make them unusable in the future. - /// - /// @param {Buffer} syntheticKey - The synthetic key used by the wallet. - /// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that the server coins will count towards the fee. - /// @param {BigInt} fee - The fee to use for the transaction. - /// @param {bool} forTestnet - True for testnet, false for mainnet. - #[napi] - pub async fn lookup_and_spend_server_coins( - &self, - synthetic_key: Buffer, - selected_coins: Vec, - fee: BigInt, - for_testnet: bool, - ) -> napi::Result> { - let coin = wallet::spend_server_coins( - &self.inner, - RustPublicKey::from_js(synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - u64::from_js(fee)?, - if for_testnet { - TargetNetwork::Testnet11 - } else { - TargetNetwork::Mainnet - }, - ) - .await - .map_err(js::err)?; - - coin.into_iter() - .map(|c| c.to_js()) - .collect::>>() - } - - #[napi] - /// Looks up possible datastore launchers by searching for singleton launchers created with a DL-specific hint. - /// - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Possible launcher ids for datastores, as well as a height + header hash combo to use for the next call. - pub async fn look_up_possible_launchers( - &self, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - wallet::look_up_possible_launchers( - &self.inner.clone(), - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err)? - .to_js() - } - - #[napi] - /// Waits for a coin to be spent on-chain. - /// - /// @param {Buffer} coin_id - Id of coin to track. - /// @param {Option} lastHeight - Min. height to search records from. If null, sync will be done from the genesis block. - /// @param {Buffer} headerHash - Header hash corresponding to `lastHeight`. If null, this should be the genesis challenge of the current chain. - /// @returns {Promise} Promise that resolves when the coin is spent (returning the coin id). - pub async fn wait_for_coin_to_be_spent( - &self, - coin_id: Buffer, - last_height: Option, - header_hash: Buffer, - ) -> napi::Result { - let rust_coin_id = RustBytes32::from_js(coin_id)?; - let spent_height = wallet::subscribe_to_coin_states( - &self.inner.clone(), - rust_coin_id, - last_height, - RustBytes32::from_js(header_hash)?, - ) - .await - .map_err(js::err)?; - - if spent_height.is_none() { - let (sender, mut receiver) = unbounded_channel::<()>(); - - { - let mut listeners = self.coin_listeners.lock().await; - listeners.insert(rust_coin_id, sender); - } - - receiver - .recv() - .await - .ok_or_else(|| js::err("Failed to receive spent notification"))?; - } - - wallet::unsubscribe_from_coin_states(&self.inner.clone(), rust_coin_id) - .await - .map_err(js::err)?; - - rust_coin_id.to_js() - } - - #[napi(factory)] - /// Connects to a random peer on the specified network (mainnet or testnet11). - /// - /// The function performs DNS lookups using the network's introducers, picks a random - /// address from the returned list, and attempts to establish a connection. It will - /// try every resolved address until a connection succeeds. - /// - /// @param {PeerType} peerType - Network type: 'mainnet' or 'testnet11'. 'simulator' is not supported. - /// @param {Tls} tls - TLS connector. - /// @returns {Promise} A connected Peer instance. - pub async fn connect_random(peer_type: PeerType, tls: &Tls) -> napi::Result { - if peer_type == PeerType::Simulator { - return Peer::new("".to_string(), peer_type, tls).await; - } - - // Introducers and default port per network - let (introducers, default_port) = match peer_type { - PeerType::Mainnet => (MAINNET_DNS_INTRODUCERS, MAINNET_DEFAULT_PORT), - PeerType::Testnet11 => (TESTNET11_DNS_INTRODUCERS, TESTNET11_DEFAULT_PORT), - PeerType::Simulator => unreachable!(), - }; - - // Resolve all introducers to socket addresses - let mut addrs = Vec::new(); - for introducer in introducers { - if let Ok(iter) = lookup_host((*introducer, default_port)).await { - addrs.extend(iter); - } - } - - if addrs.is_empty() { - return Err(js::err( - "Failed to resolve any peer addresses from introducers", - )); - } - - // Shuffle for randomness so every call has different order - { - let mut rng = rand::thread_rng(); - addrs.shuffle(&mut rng); - } - - // Try to connect in concurrent batches with timeout logic similar to peer_discovery.rs - const BATCH_SIZE: usize = 10; - const CONNECT_TIMEOUT: Duration = Duration::from_secs(8); - - for chunk in addrs.chunks(BATCH_SIZE) { - let mut futures = FuturesUnordered::new(); - for addr in chunk { - let uri = addr.to_string(); - let pt = peer_type.clone(); - // Spawn connection attempt with timeout - futures - .push(async move { timeout(CONNECT_TIMEOUT, Peer::new(uri, pt, tls)).await }); - } - - while let Some(result) = futures.next().await { - match result { - Ok(Ok(peer)) => return Ok(peer), - _ => { - // Either timed out or failed; continue with others - } - } - } - } - - Err(js::err("Unable to connect to any discovered peer")) - } -} - -/// Selects coins using the knapsack algorithm. -/// -/// @param {Vec} allCoins - Array of available coins (coins to select from). -/// @param {BigInt} totalAmount - Amount needed for the transaction, including fee. -/// @returns {Vec} Array of selected coins. -#[napi] -pub fn select_coins(all_coins: Vec, total_amount: BigInt) -> napi::Result> { - let coins: Vec = all_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?; - let selected_coins = - wallet::select_coins(coins, u64::from_js(total_amount)?).map_err(js::err)?; - - selected_coins - .into_iter() - .map(|c| c.to_js()) - .collect::>>() -} - -/// An output puzzle hash and amount. -#[napi(object)] -pub struct Output { - pub puzzle_hash: Buffer, - pub amount: BigInt, - pub memos: Vec, -} - -/// Sends XCH to a given set of puzzle hashes. -/// -/// @param {Buffer} syntheticKey - The synthetic key used by the wallet. -/// @param {Vec} selectedCoins - Coins to be spent, as retured by `select_coins`. -/// @param {Vec} outputs - The output amounts to create. -/// @param {BigInt} fee - The fee to use for the transaction. -#[napi] -pub fn send_xch( - synthetic_key: Buffer, - selected_coins: Vec, - outputs: Vec, - fee: BigInt, -) -> napi::Result> { - let mut items = Vec::new(); - - for output in outputs { - items.push(( - RustBytes32::from_js(output.puzzle_hash)?, - u64::from_js(output.amount)?, - output - .memos - .into_iter() - .map(RustBytes::from_js) - .collect::>>()?, - )); - } - - let coin_spends = wallet::send_xch( - RustPublicKey::from_js(synthetic_key)?, - &selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - &items, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - coin_spends - .into_iter() - .map(|c| c.to_js()) - .collect::>>() -} - -/// Adds an offset to a launcher id to make it deterministically unique from the original. -/// -/// @param {Buffer} launcherId - The original launcher id. -/// @param {BigInt} offset - The offset to add. -#[napi] -pub fn morph_launcher_id(launcher_id: Buffer, offset: BigInt) -> napi::Result { - server_coin::morph_launcher_id( - RustBytes32::from_js(launcher_id)?, - &u64::from_js(offset)?.into(), - ) - .to_js() -} - -/// The new server coin and coin spends to create it. -#[napi(object)] -pub struct NewServerCoin { - pub server_coin: ServerCoin, - pub coin_spends: Vec, -} - -/// Creates a new mirror coin with the given URLs. -/// -/// @param {Buffer} syntheticKey - The synthetic key used by the wallet. -/// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that, besides the fee, 1 mojo will be used to create the mirror coin. -/// @param {Buffer} hint - The hint for the mirror coin, usually the original or morphed launcher id. -/// @param {Vec} uris - The URIs of the mirrors. -/// @param {BigInt} amount - The amount to use for the created coin. -/// @param {BigInt} fee - The fee to use for the transaction. -#[napi] -pub fn create_server_coin( - synthetic_key: Buffer, - selected_coins: Vec, - hint: Buffer, - uris: Vec, - amount: BigInt, - fee: BigInt, -) -> napi::Result { - let result = wallet::create_server_coin( - RustPublicKey::from_js(synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustBytes32::from_js(hint)?, - uris, - u64::from_js(amount)?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - Ok(NewServerCoin { - coin_spends: result.coin_spends - .into_iter() - .map(|c| c.to_js()) - .collect::>>()?, - server_coin: result.server_coin.to_js()?, - }) -} - -#[allow(clippy::too_many_arguments)] -#[napi] -/// Mints a new datastore. -/// -/// @param {Buffer} minterSyntheticKey - Minter synthetic key. -/// @param {Vec} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that, besides the fee, 1 mojo will be used to create the new store. -/// @param {Buffer} rootHash - Root hash of the store. -/// @param {Option} label - Store label (optional). -/// @param {Option} description - Store description (optional). -/// @param {Option} bytes - Store size in bytes (optional). -/// @param {Buffer} ownerPuzzleHash - Owner puzzle hash. -/// @param {Vec} delegatedPuzzles - Delegated puzzles. -/// @param {BigInt} fee - Fee to use for the transaction. Total amount - 1 - fee will be sent back to the minter. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn mint_store( - minter_synthetic_key: Buffer, - selected_coins: Vec, - root_hash: Buffer, - label: Option, - description: Option, - bytes: Option, - owner_puzzle_hash: Buffer, - delegated_puzzles: Vec, - fee: BigInt, -) -> napi::Result { - let response = wallet::mint_store( - RustPublicKey::from_js(minter_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustBytes32::from_js(root_hash)?, - label, - description, - if let Some(bytes) = bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - RustBytes32::from_js(owner_puzzle_hash)?, - delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - u64::from_js(fee).map_err(js::err)?, - ) - .map_err(js::err)?; - - response.to_js() -} - -#[napi] -/// Spends a store in oracle mode. -/// -/// @param {Buffer} spenderSyntheticKey - Spender synthetic key. -/// @param {Vec} selectedCoins - Selected coins, as returned by `select_coins`. -/// @param {DataStore} store - Up-to-daye store information. -/// @param {BigInt} fee - Transaction fee to use. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn oracle_spend( - spender_synthetic_key: Buffer, - selected_coins: Vec, - store: DataStore, - fee: BigInt, -) -> napi::Result { - let response = wallet::oracle_spend( - RustPublicKey::from_js(spender_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - RustDataStore::from_js(store)?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - response.to_js() -} - -#[napi] -/// Adds a fee to any transaction. Change will be sent to spender. -/// -/// @param {Buffer} spenderSyntheticKey - Synthetic key of spender. -/// @param {Vec} selectedCoins - Selected coins, as returned by `select_coins`. -/// @param {Vec} assertCoinIds - IDs of coins that need to be spent for the fee to be paid. Usually all coin ids in the original transaction. -/// @param {BigInt} fee - Fee to add. -/// @returns {Vec} The coin spends to be added to the original transaction. -pub fn add_fee( - spender_synthetic_key: Buffer, - selected_coins: Vec, - assert_coin_ids: Vec, - fee: BigInt, -) -> napi::Result> { - let response = wallet::add_fee( - RustPublicKey::from_js(spender_synthetic_key)?, - selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>()?, - assert_coin_ids - .into_iter() - .map(RustBytes32::from_js) - .collect::>>()?, - u64::from_js(fee)?, - ) - .map_err(js::err)?; - - response - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Converts a master public key to a wallet synthetic key. -/// -/// @param {Buffer} publicKey - Master public key. -/// @returns {Buffer} The (first) wallet synthetic key. -pub fn master_public_key_to_wallet_synthetic_key(public_key: Buffer) -> napi::Result { - let public_key = RustPublicKey::from_js(public_key)?; - let wallet_pk = master_to_wallet_unhardened(&public_key, 0).derive_synthetic(); - wallet_pk.to_js() -} - -#[napi] -/// Converts a master public key to the first puzzle hash. -/// -/// @param {Buffer} publicKey - Master public key. -/// @returns {Buffer} The first wallet puzzle hash. -pub fn master_public_key_to_first_puzzle_hash(public_key: Buffer) -> napi::Result { - let public_key = RustPublicKey::from_js(public_key)?; - let wallet_pk = master_to_wallet_unhardened(&public_key, 0).derive_synthetic(); - - let puzzle_hash: RustBytes32 = StandardArgs::curry_tree_hash(wallet_pk).into(); - - puzzle_hash.to_js() -} - -#[napi] -/// Converts a master secret key to a wallet synthetic secret key. -/// -/// @param {Buffer} secretKey - Master secret key. -/// @returns {Buffer} The (first) wallet synthetic secret key. -pub fn master_secret_key_to_wallet_synthetic_secret_key( - secret_key: Buffer, -) -> napi::Result { - let secret_key = RustSecretKey::from_js(secret_key)?; - let wallet_sk = master_to_wallet_unhardened(&secret_key, 0).derive_synthetic(); - wallet_sk.to_js() -} - -#[napi] -/// Converts a secret key to its corresponding public key. -/// -/// @param {Buffer} secretKey - The secret key. -/// @returns {Buffer} The public key. -pub fn secret_key_to_public_key(secret_key: Buffer) -> napi::Result { - let secret_key = RustSecretKey::from_js(secret_key)?; - secret_key.public_key().to_js() -} - -#[napi] -/// Converts a puzzle hash to an address by encoding it using bech32m. -/// -/// @param {Buffer} puzzleHash - The puzzle hash. -/// @param {String} prefix - Address prefix (e.g., 'txch'). -/// @returns {Promise} The converted address. -pub fn puzzle_hash_to_address(puzzle_hash: Buffer, prefix: String) -> napi::Result { - let puzzle_hash = RustBytes32::from_js(puzzle_hash)?; - Address::new(puzzle_hash, prefix).encode().map_err(js::err) -} - -#[napi] -/// Converts an address to a puzzle hash using bech32m. -/// -/// @param {String} address - The address. -/// @returns {Promise} The puzzle hash. -pub fn address_to_puzzle_hash(address: String) -> napi::Result { - Address::decode(&address) - .map_err(js::err)? - .puzzle_hash - .to_js() -} - -#[napi] -/// Creates an admin delegated puzzle for a given key. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// @returns {Promise} The delegated puzzle. -pub fn admin_delegated_puzzle_from_key(synthetic_key: Buffer) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - - RustDelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(synthetic_key)).to_js() -} - -#[napi] -/// Creates a writer delegated puzzle from a given key. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// /// @returns {Promise} The delegated puzzle. -pub fn writer_delegated_puzzle_from_key(synthetic_key: Buffer) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - - RustDelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(synthetic_key)).to_js() -} - -#[napi] -// Creates an oracle delegated puzzle. -/// -/// @param {Buffer} oraclePuzzleHash - The oracle puzzle hash (corresponding to the wallet where fees should be paid). -/// @param {BigInt} oracleFee - The oracle fee (i.e., XCH amount to be paid for every oracle spend). This amount MUST be even. -/// @returns {Promise} The delegated puzzle. -pub fn oracle_delegated_puzzle( - oracle_puzzle_hash: Buffer, - oracle_fee: BigInt, -) -> napi::Result { - let oracle_puzzle_hash = RustBytes32::from_js(oracle_puzzle_hash)?; - let oracle_fee = u64::from_js(oracle_fee)?; - - RustDelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee).to_js() -} - -#[napi] -/// Partially or fully signs coin spends using a list of keys. -/// -/// @param {Vec} coinSpends - The coin spends to sign. -/// @param {Vec} privateKeys - The private/secret keys to be used for signing. -/// @param {Buffer} forTestnet - Set to true to sign spends for testnet11, false for mainnet. -/// @returns {Promise} The signature. -pub fn sign_coin_spends( - coin_spends: Vec, - private_keys: Vec, - for_testnet: bool, -) -> napi::Result { - let coin_spends = coin_spends - .iter() - .map(|cs| RustCoinSpend::from_js(cs.clone())) - .collect::>>()?; - let private_keys = private_keys - .iter() - .map(|sk| RustSecretKey::from_js(sk.clone())) - .collect::>>()?; - - let sig = wallet::sign_coin_spends( - coin_spends, - private_keys, - if for_testnet { - TargetNetwork::Testnet11 - } else { - TargetNetwork::Mainnet - }, - ) - .map_err(js::err)?; - - sig.to_js() -} - -#[napi] -pub fn hex_spend_bundle_to_coin_spends(hex: String) -> napi::Result> { - let bytes = hex::decode(hex).map_err(js::err)?; - let spend_bundle = RustSpendBundle::from_bytes(&bytes).map_err(js::err)?; - spend_bundle - .coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -pub fn spend_bundle_to_hex(spend_bundle: SpendBundle) -> napi::Result { - let rust_spend_bundle = RustSpendBundle::from_js(spend_bundle)?; - let bytes = rust_spend_bundle.to_bytes().map_err(js::err)?; - Ok(hex::encode(bytes)) -} - -#[napi] -/// Computes the ID (name) of a coin. -/// -/// @param {Coin} coin - The coin. -/// @returns {Buffer} The coin ID. -pub fn get_coin_id(coin: Coin) -> napi::Result { - RustCoin::from_js(coin)?.coin_id().to_js() -} - -#[allow(clippy::too_many_arguments)] -#[napi] -/// Updates the metadata of a store. Either the owner, admin, or writer public key must be provided. -/// -/// @param {DataStore} store - Current store information. -/// @param {Buffer} newRootHash - New root hash. -/// @param {Option} newLabel - New label (optional). -/// @param {Option} newDescription - New description (optional). -/// @param {Option} newBytes - New size in bytes (optional). -/// @param {Option} ownerPublicKey - Owner public key. -/// @param {Option} adminPublicKey - Admin public key. -/// @param {Option} writerPublicKey - Writer public key. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn update_store_metadata( - store: DataStore, - new_root_hash: Buffer, - new_label: Option, - new_description: Option, - new_bytes: Option, - owner_public_key: Option, - admin_public_key: Option, - writer_public_key: Option, -) -> napi::Result { - let inner_spend_info = match (owner_public_key, admin_public_key, writer_public_key) { - (Some(owner_public_key), None, None) => { - DataStoreInnerSpend::Owner(RustPublicKey::from_js(owner_public_key)?) - } - (None, Some(admin_public_key), None) => { - DataStoreInnerSpend::Admin(RustPublicKey::from_js(admin_public_key)?) - } - (None, None, Some(writer_public_key)) => { - DataStoreInnerSpend::Writer(RustPublicKey::from_js(writer_public_key)?) - } - _ => return Err(js::err( - "Exactly one of owner_public_key, admin_public_key, writer_public_key must be provided", - )), - }; - - let res = wallet::update_store_metadata( - RustDataStore::from_js(store)?, - RustBytes32::from_js(new_root_hash)?, - new_label, - new_description, - if let Some(bytes) = new_bytes { - Some(u64::from_js(bytes)?) - } else { - None - }, - inner_spend_info, - ) - .map_err(js::err)?; - - res.to_js() -} - -#[napi] -/// Updates the ownership of a store. Either the admin or owner public key must be provided. -/// -/// @param {DataStore} store - Store information. -/// @param {Option} newOwnerPuzzleHash - New owner puzzle hash. -/// @param {Vec} newDelegatedPuzzles - New delegated puzzles. -/// @param {Option} ownerPublicKey - Owner public key. -/// @param {Option} adminPublicKey - Admin public key. -/// @returns {SuccessResponse} The success response, which includes coin spends and information about the new datastore. -pub fn update_store_ownership( - store: DataStore, - new_owner_puzzle_hash: Option, - new_delegated_puzzles: Vec, - owner_public_key: Option, - admin_public_key: Option, -) -> napi::Result { - let store = RustDataStore::from_js(store)?; - let new_owner_puzzle_hash = new_owner_puzzle_hash - .map(RustBytes32::from_js) - .unwrap_or_else(|| Ok(store.info.owner_puzzle_hash))?; - - let inner_spend_info = match (owner_public_key, admin_public_key) { - (Some(owner_public_key), None) => { - DataStoreInnerSpend::Owner(RustPublicKey::from_js(owner_public_key)?) - } - (None, Some(admin_public_key)) => { - DataStoreInnerSpend::Admin(RustPublicKey::from_js(admin_public_key)?) - } - _ => { - return Err(js::err( - "Exactly one of owner_public_key, admin_public_key must be provided", - )) - } - }; - - let res = wallet::update_store_ownership( - store, - new_owner_puzzle_hash, - new_delegated_puzzles - .into_iter() - .map(RustDelegatedPuzzle::from_js) - .collect::>>()?, - inner_spend_info, - ) - .map_err(js::err)?; - - res.to_js() -} - -#[napi] -/// Melts a store. The 1 mojo change will be used as a fee. -/// -/// @param {DataStore} store - Store information. -/// @param {Buffer} ownerPublicKey - Owner's public key. -/// @returns {Vec} The coin spends that the owner can sign to melt the store. -pub fn melt_store(store: DataStore, owner_public_key: Buffer) -> napi::Result> { - let res = wallet::melt_store( - RustDataStore::from_js(store)?, - RustPublicKey::from_js(owner_public_key)?, - ) - .map_err(js::err)?; - - res.into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Signs a message using the provided private key. -/// -/// @param {Buffer} message - Message to sign, as bytes. "Chia Signed Message" will be prepended automatically, as per CHIP-2 - no need to add it before calling this function. -/// @param {Buffer} private_key - Private key to sign the message with. No derivation is done. -/// @returns {Buffer} The signature. -pub fn sign_message(message: Buffer, private_key: Buffer) -> napi::Result { - wallet::sign_message( - RustBytes::from_js(message)?, - RustSecretKey::from_js(private_key)?, - ) - .map_err(js::err)? - .to_js() -} - -#[napi] -/// Verifies a signed message using the provided public key. -/// -/// @param {Buffer} signature - Th signature to be verified. -/// @param {Buffer} public_key - Public key corresponding to the private key that was used to sign the message. -/// @param {Buffer} message - Message that was signed, as bytes. "Chia Signed Message" will be prepended automatically, as per CHIP-2 - no need to add it before calling this function. -/// @returns {Buffer} Boolean - true indicates that the signature is valid, while false indicates that it is not. -pub fn verify_signed_message( - signature: Buffer, - public_key: Buffer, - message: Buffer, -) -> napi::Result { - wallet::verify_signature( - RustBytes::from_js(message)?, - RustPublicKey::from_js(public_key)?, - RustSignature::from_js(signature)?, - ) - .map_err(js::err) -} - -#[napi] -/// Converts a synthetic key to its corresponding standard puzzle hash. -/// -/// @param {Buffer} syntheticKey - Synthetic key. -/// @returns {Buffer} The standard puzzle (puzzle) hash. -pub fn synthetic_key_to_puzzle_hash(synthetic_key: Buffer) -> napi::Result { - let puzzle_hash: RustBytes32 = - StandardArgs::curry_tree_hash(RustPublicKey::from_js(synthetic_key)?).into(); - - puzzle_hash.to_js() -} - -#[napi] -/// Calculates the total cost of a given array of coin spends/ -/// -/// @param {Vec} CoinSpend - Coin spends. -/// @returns {BigInt} The cost of the coin spends. -pub fn get_cost(coin_spends: Vec) -> napi::Result { - wallet::get_cost( - coin_spends - .into_iter() - .map(RustCoinSpend::from_js) - .collect::>>()?, - ) - .map_err(js::err)? - .to_js() -} - -#[napi] -/// Returns the mainnet genesis challenge. -/// -/// @returns {Buffer} The mainnet genesis challenge. -pub fn get_mainnet_genesis_challenge() -> napi::Result { - MAINNET_CONSTANTS.genesis_challenge.to_js() -} - -#[napi] -/// Returns the testnet11 genesis challenge. -/// -/// @returns {Buffer} The testnet11 genesis challenge. -pub fn get_testnet11_genesis_challenge() -> napi::Result { - TESTNET11_CONSTANTS.genesis_challenge.to_js() -} - -#[napi] -/// Mints a new NFT using a DID string. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Buffer} syntheticKey - The synthetic key of the wallet -/// @param {Vec} selectedCoins - Coins to spend for the transaction -/// @param {string} didString - The DID string (e.g., "did:chia:1s8j4pquxfu5mhlldzu357qfqkwa9r35mdx5a0p0ehn76dr4ut4tqs0n6kv") -/// @param {Buffer} recipientPuzzleHash - The puzzle hash to send the NFT to -/// @param {NftMetadata} metadata - The NFT metadata -/// @param {Buffer} royaltyPuzzleHash - Optional royalty puzzle hash (defaults to recipient if None) -/// @param {number} royaltyBasisPoints - Royalty percentage in basis points (e.g., 300 = 3%) -/// @param {BigInt} fee - Transaction fee -/// @param {boolean} forTestnet - Whether to use testnet or mainnet (defaults to mainnet) -/// @returns {Promise>} A vector of coin spends that mint the NFT -pub async fn mint_nft( - peer: &Peer, - synthetic_key: Buffer, - selected_coins: Vec, - did_string: String, - recipient_puzzle_hash: Buffer, - metadata: NftMetadata, - royalty_puzzle_hash: Option, - royalty_basis_points: u32, - fee: BigInt, - for_testnet: Option, -) -> napi::Result> { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - let selected_coins = selected_coins - .into_iter() - .map(RustCoin::from_js) - .collect::>>() - .map_err(js::err)?; - let recipient_puzzle_hash = RustBytes32::from_js(recipient_puzzle_hash)?; - let metadata = chia::puzzles::nft::NftMetadata::from_js(metadata)?; - let royalty_puzzle_hash = if let Some(hash) = royalty_puzzle_hash { - Some(RustBytes32::from_js(hash)?) - } else { - None - }; - let fee = u64::from_js(fee)?; - let network = if for_testnet.unwrap_or(false) { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let coin_spends = wallet::mint_nft( - &peer.inner, - synthetic_key, - selected_coins, - &did_string, - recipient_puzzle_hash, - metadata, - royalty_puzzle_hash, - royalty_basis_points as u16, - fee, - network, - ) - .await - .map_err(js::err)?; - - coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>() -} - -#[napi] -/// Generates a DID proof for a DID coin by analyzing its parent automatically. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Coin} didCoin - The DID coin to generate proof for -/// @param {boolean} forTestnet - Whether to use testnet or mainnet -/// @returns {Promise} An object containing the proof and the DID coin -pub async fn generate_did_proof( - peer: &Peer, - did_coin: Coin, - for_testnet: bool, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let network = if for_testnet { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let (proof, coin) = wallet::generate_did_proof(&peer.inner, did_coin, network) - .await - .map_err(js::err)?; - - Ok(js::DidProofResult { - proof: proof.to_js()?, - did_coin: coin.to_js()?, - }) -} - -#[napi] -/// Generates a DID proof manually when you have the parent information. -/// -/// @param {Coin} didCoin - The current DID coin -/// @param {Coin} parentCoin - The parent coin of the DID (null for eve proof) -/// @param {Buffer} parentInnerPuzzleHash - The parent's inner puzzle hash (for lineage proof) -/// @returns {Proof} A DID proof that can be used to spend the DID coin -pub fn generate_did_proof_manual( - did_coin: Coin, - parent_coin: Option, - parent_inner_puzzle_hash: Option, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let parent_coin = if let Some(coin) = parent_coin { - Some(rust::Coin::from_js(coin)?) - } else { - None - }; - let parent_inner_puzzle_hash = if let Some(hash) = parent_inner_puzzle_hash { - Some(RustBytes32::from_js(hash)?) - } else { - None - }; - - let proof = wallet::generate_did_proof_manual(did_coin, parent_coin, parent_inner_puzzle_hash) - .map_err(js::err)?; - - proof.to_js() -} - -#[napi] -/// Generates a DID proof from the blockchain by analyzing the parent spend. -/// -/// @param {Peer} peer - The peer to query blockchain data -/// @param {Coin} didCoin - The DID coin to generate proof for -/// @param {boolean} forTestnet - Whether to use testnet or mainnet -/// @returns {Promise} A DID proof that can be used to spend the DID coin -pub async fn generate_did_proof_from_chain( - peer: &Peer, - did_coin: Coin, - for_testnet: bool, -) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; - let network = if for_testnet { - wallet::TargetNetwork::Testnet11 - } else { - wallet::TargetNetwork::Mainnet - }; - - let proof = wallet::generate_did_proof_from_chain(&peer.inner, did_coin, network) - .await - .map_err(js::err)?; - - proof.to_js() -} - -#[napi] -/// Creates a simple DID from a private key and selected coins. -/// -/// @param {Buffer} syntheticKey - The synthetic key that will control the DID -/// @param {Vec} selectedCoins - Coins to spend for creating the DID -/// @param {BigInt} fee - Transaction fee -/// @returns {CreateDidResult} An object containing coinSpends and the created DID coin -pub fn create_simple_did( - synthetic_key: Buffer, - selected_coins: Vec, - fee: BigInt, -) -> napi::Result { - let synthetic_key = RustPublicKey::from_js(synthetic_key)?; - let selected_coins = selected_coins - .into_iter() - .map(rust::Coin::from_js) - .collect::>>() - .map_err(js::err)?; - let fee = u64::from_js(fee)?; - - let (coin_spends, did_coin) = - wallet::create_simple_did(synthetic_key, selected_coins, fee).map_err(js::err)?; - - let coin_spends_js = coin_spends - .into_iter() - .map(|cs| cs.to_js()) - .collect::>>()?; - - Ok(js::CreateDidResult { - coin_spends: coin_spends_js, - did_coin: did_coin.to_js()?, - }) -} - -#[napi] -/// Creates a new puzzle and its hash using the simulator. -/// -/// @param {BigInt} value - The value to use for the puzzle. -/// @returns {Promise} The puzzle hash and reveal. -pub async fn simulator_new_puzzle(value: BigInt) -> napi::Result { - let value = u64::from_js(value)?; - let (puzzle_hash, puzzle_reveal) = to_puzzle(value).map_err(js::err)?; - Ok(js::SimulatorPuzzle { - puzzle_hash: puzzle_hash.to_js()?, - puzzle_reveal: puzzle_reveal.to_js()?, - }) -} - -#[napi] -/// Creates a new BlsPair. -/// -/// @param {BigInt} value - The value to use to initialize the pair. -/// @returns {Promise} The BlsPair. -pub fn simulator_new_blspair(value: BigInt) -> napi::Result { - let value = u64::from_js(value)?; - let pair = chia_wallet_sdk::test::BlsPair::new(value); - Ok(js::BlsPair { - puzzle_hash: pair.puzzle_hash.to_js()?, - sk: pair.sk.to_js()?, - pk: pair.pk.to_js()?, - }) -} - -#[napi] -/// Creates a new simulator program. -/// -/// @returns {Promise} The program. -pub fn simulator_new_program(pk: Buffer) -> napi::Result { - let pk = RustPublicKey::from_js(pk)?; - let program = - to_program([AggSigMe::new(pk, b"Hello, world!".to_vec().into())]).map_err(js::err)?; - program.to_js() -} diff --git a/src/wallet.rs b/src/wallet.rs index d6789ab..843013f 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -26,8 +26,8 @@ use chia::puzzles::{ use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::{ClientError, Peer}; use chia_wallet_sdk::driver::{ - get_merkle_tree, Action, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, - Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, + get_merkle_tree, Action, Asset, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, + DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, StandardLayer, WriterLayer, }; @@ -45,7 +45,7 @@ use chia_wallet_sdk::utils::{self, CoinSelectionError}; use clvmr::{Allocator, NodePtr}; use hex_literal::hex; use thiserror::Error; - +use crate::morph_store_launcher_id; /* echo -n 'datastore' | sha256sum */ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " @@ -53,7 +53,7 @@ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " )); -pub const DIG_COIN_ASSET_ID: Bytes32 = Bytes32::new(hex!( +pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!( "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81" )); @@ -245,36 +245,25 @@ pub fn send_xch( } pub fn create_dig_collateral_coin( - collateral_dig_coins: Vec, - morphed_store_id: Bytes32, + dig_coins: Vec, + collateral_amount: u64, + store_id: Bytes32, synthetic_key: PublicKey, fee_coins: Vec, fee: u64, ) -> Result, WalletError> { - let mut collateral_amount = 0_u64; - for cat in &collateral_dig_coins { - if cat.info.asset_id != DIG_COIN_ASSET_ID { - return Err(WalletError::Driver(DriverError::InvalidAssetId)); - } - collateral_amount += cat.coin.amount; - } - - let p2_parent_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_COIN_ASSET_ID)); - let p2_parent_puzzle_hash = P2ParentCoin::puzzle_hash(Some(DIG_COIN_ASSET_ID)); + let p2_parent_inner_hash = P2ParentCoin::inner_puzzle_hash(Some(DIG_ASSET_ID)); let mut ctx = SpendContext::new(); - let p2_parent_layer = P2ParentLayer::cat(p2_parent_hash); - p2_parent_layer.construct_puzzle(&mut ctx)?; - - //todo add store id morph + let morphed_store_id = morph_store_launcher_id(store_id); let hint = ctx.hint(morphed_store_id)?; let actions = &[ Action::fee(fee), Action::send( - Id::Existing(DIG_COIN_ASSET_ID), - p2_parent_puzzle_hash.into(), + Id::Existing(DIG_ASSET_ID), + p2_parent_inner_hash.into(), collateral_amount, hint, ), @@ -285,8 +274,8 @@ pub fn create_dig_collateral_coin( let mut spends = Spends::new(p2_puzzle_hash); // add collateral coins to spends - for dig_cat in collateral_dig_coins { - spends.add(dig_cat); + for dig_coin in dig_coins { + spends.add(dig_coin); } // add fee coins to spends @@ -1290,10 +1279,11 @@ pub async fn look_up_possible_launchers( pub async fn prove_dig_cat_coin( peer: &Peer, - allocator: &mut Allocator, coin: &Coin, coin_created_height: u32, -) -> Result<(Cat, Puzzle, NodePtr), WalletError> { +) -> Result { + let mut ctx = SpendContext::new(); + // 1) Request parent coin state let parent_state_response = peer .request_coin_state( @@ -1315,29 +1305,30 @@ pub async fn prove_dig_cat_coin( parent_puzzle_and_solution_response.map_err(|_| WalletError::RejectPuzzleSolution)?; // 3) Convert puzzle to CLVM - let parent_puzzle_ptr = parent_puzzle_and_solution.puzzle.to_clvm(allocator)?; - let parent_puzzle = Puzzle::parse(allocator, parent_puzzle_ptr); + let parent_puzzle_ptr = ctx.alloc(&parent_puzzle_and_solution.puzzle)?; + let parent_puzzle = Puzzle::parse(&mut ctx, parent_puzzle_ptr); // 4) Convert solution to CLVM - let parent_solution = parent_puzzle_and_solution.solution.to_clvm(allocator)?; + let parent_solution = ctx.alloc(&parent_puzzle_and_solution.solution)?; // 5) Parse CAT - let (cat, puzzle, node_ptr) = Cat::parse( - allocator, + let parsed_children = Cat::parse_children( + &mut ctx, parent_state.coin_states[0].coin, parent_puzzle, parent_solution, )? .ok_or(WalletError::UnknownCoin)?; - // 6) Prove lineage - cat.lineage_proof.ok_or(WalletError::UnknownCoin)?; - - if cat.info.asset_id != DIG_COIN_ASSET_ID { - return Err(WalletError::UnknownCoin); - } - - Ok((cat, puzzle, node_ptr)) + let proved_cat = parsed_children + .into_iter() + .find(|parsed_child| { + parsed_child.coin_id() == coin.coin_id() + && parsed_child.lineage_proof.is_some() + && parsed_child.info.asset_id == DIG_ASSET_ID + }) + .ok_or_else(|| WalletError::UnknownCoin)?; + Ok(proved_cat) } pub async fn subscribe_to_coin_states( From 149426cb243cf6874baa75d0fdfb1362937857e2 Mon Sep 17 00:00:00 2001 From: William Wills Date: Mon, 20 Oct 2025 15:54:57 -0400 Subject: [PATCH 07/12] fix: coin spends to create p2 parent --- src/wallet.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet.rs b/src/wallet.rs index 843013f..ea870fb 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -245,7 +245,7 @@ pub fn send_xch( } pub fn create_dig_collateral_coin( - dig_coins: Vec, + dig_cats: Vec, collateral_amount: u64, store_id: Bytes32, synthetic_key: PublicKey, @@ -274,8 +274,8 @@ pub fn create_dig_collateral_coin( let mut spends = Spends::new(p2_puzzle_hash); // add collateral coins to spends - for dig_coin in dig_coins { - spends.add(dig_coin); + for cat in dig_cats { + spends.add(cat); } // add fee coins to spends From ce69f6e2d5c914eee7de3756903bf74037fbb30b Mon Sep 17 00:00:00 2001 From: William Wills Date: Thu, 23 Oct 2025 18:06:44 -0400 Subject: [PATCH 08/12] feat: add function for fetching coins by memo feat: add function for parsing $DIG collateral coin --- napi/src/conversions.rs | 62 +++---- napi/src/lib.rs | 8 +- napi/src/napi_lib.rs | 28 +-- src/error.rs | 60 +++++++ src/lib.rs | 43 +++-- src/{rust.rs => types.rs} | 43 +++-- src/wallet.rs | 187 +++++++++++++-------- src/{server_coin.rs => xch_server_coin.rs} | 10 +- 8 files changed, 295 insertions(+), 146 deletions(-) create mode 100644 src/error.rs rename src/{rust.rs => types.rs} (54%) rename src/{server_coin.rs => xch_server_coin.rs} (94%) diff --git a/napi/src/conversions.rs b/napi/src/conversions.rs index 1e00bc8..c3f89d6 100644 --- a/napi/src/conversions.rs +++ b/napi/src/conversions.rs @@ -6,7 +6,7 @@ use napi::bindgen_prelude::*; use napi::Result; use thiserror::Error; -use crate::{js, rust}; +use crate::{js, types}; #[derive(Error, Debug)] pub enum ConversionError { @@ -153,7 +153,7 @@ impl ToJs for u64 { } } -impl FromJs for rust::Coin { +impl FromJs for types::Coin { fn from_js(value: js::Coin) -> Result { Ok(Self { parent_coin_info: Bytes32::from_js(value.parent_coin_info)?, @@ -163,7 +163,7 @@ impl FromJs for rust::Coin { } } -impl ToJs for rust::Coin { +impl ToJs for types::Coin { fn to_js(&self) -> Result { Ok(js::Coin { parent_coin_info: self.parent_coin_info.to_js()?, @@ -173,7 +173,7 @@ impl ToJs for rust::Coin { } } -impl FromJs for rust::SimulatorPuzzle { +impl FromJs for types::SimulatorPuzzle { fn from_js(value: js::SimulatorPuzzle) -> Result { Ok(Self { puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, @@ -182,7 +182,7 @@ impl FromJs for rust::SimulatorPuzzle { } } -impl ToJs for rust::SimulatorPuzzle { +impl ToJs for types::SimulatorPuzzle { fn to_js(&self) -> Result { Ok(js::SimulatorPuzzle { puzzle_reveal: self.puzzle_reveal.to_js()?, @@ -191,7 +191,7 @@ impl ToJs for rust::SimulatorPuzzle { } } -impl FromJs for rust::BlsPair { +impl FromJs for types::BlsPair { fn from_js(value: js::BlsPair) -> Result { Ok(Self { puzzle_hash: Bytes32::from_js(value.puzzle_hash)?, @@ -201,7 +201,7 @@ impl FromJs for rust::BlsPair { } } -impl ToJs for rust::BlsPair { +impl ToJs for types::BlsPair { fn to_js(&self) -> Result { Ok(js::BlsPair { puzzle_hash: self.puzzle_hash.to_js()?, @@ -211,10 +211,10 @@ impl ToJs for rust::BlsPair { } } -impl FromJs for rust::CoinState { +impl FromJs for types::CoinState { fn from_js(value: js::CoinState) -> Result { Ok(Self { - coin: rust::Coin::from_js(value.coin)?, + coin: types::Coin::from_js(value.coin)?, spent_height: value .spent_height .map(|height| { @@ -235,7 +235,7 @@ impl FromJs for rust::CoinState { } } -impl ToJs for rust::CoinState { +impl ToJs for types::CoinState { fn to_js(&self) -> Result { Ok(js::CoinState { coin: self.coin.to_js()?, @@ -251,17 +251,17 @@ impl ToJs for rust::CoinState { } } -impl FromJs for rust::CoinSpend { +impl FromJs for types::CoinSpend { fn from_js(value: js::CoinSpend) -> Result { Ok(Self { - coin: rust::Coin::from_js(value.coin)?, + coin: types::Coin::from_js(value.coin)?, puzzle_reveal: Program::from_js(value.puzzle_reveal)?, solution: Program::from_js(value.solution)?, }) } } -impl ToJs for rust::CoinSpend { +impl ToJs for types::CoinSpend { fn to_js(&self) -> Result { Ok(js::CoinSpend { coin: self.coin.to_js()?, @@ -271,7 +271,7 @@ impl ToJs for rust::CoinSpend { } } -impl FromJs for rust::LineageProof { +impl FromJs for types::LineageProof { fn from_js(value: js::LineageProof) -> Result { Ok(Self { parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, @@ -281,7 +281,7 @@ impl FromJs for rust::LineageProof { } } -impl ToJs for rust::LineageProof { +impl ToJs for types::LineageProof { fn to_js(&self) -> Result { Ok(js::LineageProof { parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, @@ -291,16 +291,16 @@ impl ToJs for rust::LineageProof { } } -impl FromJs for rust::EveProof { +impl FromJs for types::EveProof { fn from_js(value: js::EveProof) -> Result { - Ok(rust::EveProof { + Ok(types::EveProof { parent_parent_coin_info: Bytes32::from_js(value.parent_parent_coin_info)?, parent_amount: u64::from_js(value.parent_amount)?, }) } } -impl ToJs for rust::EveProof { +impl ToJs for types::EveProof { fn to_js(&self) -> Result { Ok(js::EveProof { parent_parent_coin_info: self.parent_parent_coin_info.to_js()?, @@ -309,28 +309,28 @@ impl ToJs for rust::EveProof { } } -impl FromJs for rust::Proof { +impl FromJs for types::Proof { fn from_js(value: js::Proof) -> Result { if let Some(lineage_proof) = value.lineage_proof { - Ok(rust::Proof::Lineage(rust::LineageProof::from_js( + Ok(types::Proof::Lineage(types::LineageProof::from_js( lineage_proof, )?)) } else if let Some(eve_proof) = value.eve_proof { - Ok(rust::Proof::Eve(rust::EveProof::from_js(eve_proof)?)) + Ok(types::Proof::Eve(types::EveProof::from_js(eve_proof)?)) } else { Err(js::err(ConversionError::MissingProof)) } } } -impl ToJs for rust::Proof { +impl ToJs for types::Proof { fn to_js(&self) -> Result { Ok(match self { - rust::Proof::Lineage(lineage_proof) => js::Proof { + types::Proof::Lineage(lineage_proof) => js::Proof { lineage_proof: Some(lineage_proof.to_js()?), eve_proof: None, }, - rust::Proof::Eve(eve_proof) => js::Proof { + types::Proof::Eve(eve_proof) => js::Proof { lineage_proof: None, eve_proof: Some(eve_proof.to_js()?), }, @@ -338,17 +338,17 @@ impl ToJs for rust::Proof { } } -impl FromJs for rust::ServerCoin { +impl FromJs for types::XchServerCoin { fn from_js(value: js::ServerCoin) -> Result { Ok(Self { - coin: rust::Coin::from_js(value.coin)?, + coin: types::Coin::from_js(value.coin)?, p2_puzzle_hash: Bytes32::from_js(value.p2_puzzle_hash)?, memo_urls: value.memo_urls, }) } } -impl ToJs for rust::ServerCoin { +impl ToJs for types::XchServerCoin { fn to_js(&self) -> Result { Ok(js::ServerCoin { coin: self.coin.to_js()?, @@ -358,26 +358,26 @@ impl ToJs for rust::ServerCoin { } } -impl FromJs for rust::SpendBundle { +impl FromJs for types::SpendBundle { fn from_js(value: js::SpendBundle) -> Result { Ok(Self { coin_spends: value .coin_spends .into_iter() - .map(rust::CoinSpend::from_js) + .map(types::CoinSpend::from_js) .collect::>>()?, aggregated_signature: Signature::from_js(value.aggregated_signature)?, }) } } -impl ToJs for rust::SpendBundle { +impl ToJs for types::SpendBundle { fn to_js(&self) -> Result { Ok(js::SpendBundle { coin_spends: self .coin_spends .iter() - .map(rust::CoinSpend::to_js) + .map(types::CoinSpend::to_js) .collect::>>()?, aggregated_signature: self.aggregated_signature.to_js()?, }) diff --git a/napi/src/lib.rs b/napi/src/lib.rs index c214e35..dd1febf 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -14,10 +14,10 @@ pub use datalayer_driver::{ admin_delegated_puzzle_from_key, constants, get_coin_id, master_public_key_to_first_puzzle_hash, master_public_key_to_wallet_synthetic_key, master_secret_key_to_wallet_synthetic_secret_key, master_to_wallet_unhardened, - oracle_delegated_puzzle, rust, secret_key_to_public_key, server_coin, - synthetic_key_to_puzzle_hash, wallet, writer_delegated_puzzle_from_key, BlsPair, Bytes, - Bytes32, Coin, CoinSpend, CoinState, DataStoreInfo, EveProof, LineageProof, Program, Proof, - PublicKey, SecretKey, ServerCoin, Signature, SimulatorPuzzle, SpendBundle, + oracle_delegated_puzzle, secret_key_to_public_key, synthetic_key_to_puzzle_hash, types, wallet, + writer_delegated_puzzle_from_key, xch_server_coin, BlsPair, Bytes, Bytes32, Coin, CoinSpend, + CoinState, DataStoreInfo, EveProof, LineageProof, Program, Proof, PublicKey, SecretKey, + Signature, SimulatorPuzzle, SpendBundle, XchServerCoin, }; // Re-export the NAPI bindings diff --git a/napi/src/napi_lib.rs b/napi/src/napi_lib.rs index bb0ef8e..c9676fe 100644 --- a/napi/src/napi_lib.rs +++ b/napi/src/napi_lib.rs @@ -7,7 +7,7 @@ use crate::js::{ // Import from the main datalayer-driver crate use datalayer_driver::{ - master_to_wallet_unhardened, rust, server_coin, wallet, Bytes as RustBytes, + master_to_wallet_unhardened, types, wallet, xch_server_coin, Bytes as RustBytes, Bytes32 as RustBytes32, Coin as RustCoin, CoinSpend as RustCoinSpend, DataStore as RustDataStore, DataStoreInfo as RustDataStoreInfo, DataStoreMetadata as RustDataStoreMetadata, DelegatedPuzzle as RustDelegatedPuzzle, @@ -388,9 +388,9 @@ pub struct UnspentCoinsResponse { pub last_header_hash: Buffer, } -impl FromJs for rust::UnspentCoinsResponse { +impl FromJs for types::UnspentCoinsResponse { fn from_js(value: UnspentCoinsResponse) -> Result { - Ok(rust::UnspentCoinsResponse { + Ok(types::UnspentCoinsResponse { coins: value .coins .into_iter() @@ -402,7 +402,7 @@ impl FromJs for rust::UnspentCoinsResponse { } } -impl ToJs for rust::UnspentCoinsResponse { +impl ToJs for types::UnspentCoinsResponse { fn to_js(&self) -> Result { Ok(UnspentCoinsResponse { coins: self @@ -594,7 +594,7 @@ impl Peer { previous_height: Option, previous_header_hash: Buffer, ) -> napi::Result { - let resp: rust::UnspentCoinsResponse = get_unspent_coin_states( + let resp: types::UnspentCoinsResponse = get_unspent_coin_states( &self.inner.clone(), RustBytes32::from_js(puzzle_hash)?, previous_height, @@ -737,9 +737,9 @@ impl Peer { coin_state: CoinState, max_cost: BigInt, ) -> napi::Result { - let coin = wallet::fetch_server_coin( + let coin = wallet::fetch_xch_server_coin( &self.inner.clone(), - rust::CoinState::from_js(coin_state)?, + types::CoinState::from_js(coin_state)?, u64::from_js(max_cost)?, ) .await @@ -934,7 +934,7 @@ impl Peer { fee: BigInt, for_testnet: bool, ) -> napi::Result> { - let coin = wallet::spend_server_coins( + let coin = wallet::spend_xch_server_coins( &self.inner, RustPublicKey::from_js(synthetic_key)?, selected_coins @@ -1169,7 +1169,7 @@ pub fn send_xch( /// @param {BigInt} offset - The offset to add. #[napi] pub fn morph_launcher_id(launcher_id: Buffer, offset: BigInt) -> napi::Result { - server_coin::morph_launcher_id( + xch_server_coin::morph_launcher_id( RustBytes32::from_js(launcher_id)?, &u64::from_js(offset)?.into(), ) @@ -1803,7 +1803,7 @@ pub async fn generate_did_proof( did_coin: Coin, for_testnet: bool, ) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; + let did_coin = types::Coin::from_js(did_coin)?; let network = if for_testnet { wallet::TargetNetwork::Testnet11 } else { @@ -1832,9 +1832,9 @@ pub fn generate_did_proof_manual( parent_coin: Option, parent_inner_puzzle_hash: Option, ) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; + let did_coin = types::Coin::from_js(did_coin)?; let parent_coin = if let Some(coin) = parent_coin { - Some(rust::Coin::from_js(coin)?) + Some(types::Coin::from_js(coin)?) } else { None }; @@ -1862,7 +1862,7 @@ pub async fn generate_did_proof_from_chain( did_coin: Coin, for_testnet: bool, ) -> napi::Result { - let did_coin = rust::Coin::from_js(did_coin)?; + let did_coin = types::Coin::from_js(did_coin)?; let network = if for_testnet { wallet::TargetNetwork::Testnet11 } else { @@ -1891,7 +1891,7 @@ pub fn create_simple_did( let synthetic_key = RustPublicKey::from_js(synthetic_key)?; let selected_coins = selected_coins .into_iter() - .map(rust::Coin::from_js) + .map(types::Coin::from_js) .collect::>>() .map_err(crate::js::err)?; let fee = u64::from_js(fee)?; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1de55e8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,60 @@ +use chia::consensus::validation_error::ValidationErr; +use chia_wallet_sdk::client::ClientError; +use chia_wallet_sdk::driver::DriverError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum WalletError { + #[error("{0:?}")] + Client(#[from] ClientError), + + #[error("RejectPuzzleState")] + RejectPuzzleState, + + #[error("RejectCoinState")] + RejectCoinState, + + #[error("RejectPuzzleSolution")] + RejectPuzzleSolution, + + #[error("RejectHeaderRequest")] + RejectHeaderRequest, + + #[error("{0:?}")] + Driver(#[from] DriverError), + + #[error("ParseError")] + Parse, + + #[error("UnknownCoin")] + UnknownCoin, + + #[error("Clvm error")] + Clvm, + #[error("ToClvm error: {0}")] + ToClvm(#[from] chia::clvm_traits::ToClvmError), + + #[error("Permission error: puzzle can't perform this action")] + Permission, + + #[error("Io error: {0}")] + Io(std::io::Error), + + #[error("Validation error: {0}")] + Validation(#[from] ValidationErr), + + #[error("Fee estimation rejection: {0}")] + FeeEstimateRejection(String), + + #[error("Failed to retrieve coin(s): {0}")] + CoinRetrievalFailure(String), + + #[error("Puzzle hash mismatch: {0}")] + PuzzleHashMismatch(String), + + #[error("Insufficient coin amount")] + InsufficientCoinAmount, + + #[error("Coin is already spent")] + CoinIsAlreadySpent, +} diff --git a/src/lib.rs b/src/lib.rs index 46ac8a4..fb7b76e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,6 @@ // Re-export core types from dependencies pub use chia::bls::{master_to_wallet_unhardened, PublicKey, SecretKey, Signature}; -use chia::consensus::solution_generator::solution_generator; pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle}; pub use chia::puzzles::{EveProof, LineageProof, Proof}; pub use chia_wallet_sdk::client::Peer; @@ -28,22 +27,22 @@ pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkT pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge}; // Internal modules -pub mod rust; -pub mod server_coin; +mod error; +pub mod types; pub mod wallet; +pub mod xch_server_coin; // Re-export types from internal modules -pub use rust::{BlsPair, SimulatorPuzzle, UnspentCoinsResponse}; -pub use server_coin::{morph_launcher_id, ServerCoin}; +pub use types::{BlsPair, SimulatorPuzzle, UnspentCoinStates, UnspentCoinsResponse}; pub use wallet::{ create_simple_did, generate_did_proof, generate_did_proof_from_chain, generate_did_proof_manual, get_fee_estimate, get_header_hash, get_store_creation_height, get_unspent_coin_states, is_coin_spent, look_up_possible_launchers, mint_nft, - spend_server_coins, subscribe_to_coin_states, sync_store, sync_store_using_launcher_id, - unsubscribe_from_coin_states, verify_signature, DataStoreInnerSpend, NewServerCoin, - PossibleLaunchersResponse, SuccessResponse, SyncStoreResponse, TargetNetwork, - UnspentCoinStates, + spend_xch_server_coins, subscribe_to_coin_states, sync_store, sync_store_using_launcher_id, + unsubscribe_from_coin_states, verify_signature, DataStoreInnerSpend, PossibleLaunchersResponse, + SyncStoreResponse, TargetNetwork, }; +pub use xch_server_coin::{morph_launcher_id, XchServerCoin}; use hex_literal::hex; @@ -51,17 +50,24 @@ use hex_literal::hex; pub type Result = std::result::Result>; // Helper functions for common conversions +use crate::types::SuccessResponse; use chia::puzzles::{standard::StandardArgs, DeriveSynthetic}; use chia_wallet_sdk::prelude::ToTreeHash; +// Helper functions for common conversions +use xch_server_coin::NewXchServerCoin; pub const DIG_MIN_HEIGHT: u32 = 5777842; -pub const DIG_MIN_HEIGHT_HEADER_HASH: Bytes32 = Bytes32::new(hex!("b29a4daac2434fd17a36e15ba1aac5d65012d4a66f99bed0bf2b5342e92e562c")); +pub const DIG_MIN_HEIGHT_HEADER_HASH: Bytes32 = Bytes32::new(hex!( + "b29a4daac2434fd17a36e15ba1aac5d65012d4a66f99bed0bf2b5342e92e562c" +)); pub const DIG_STORE_LAUNCHER_ID_MORPH: &str = "DIG_STORE"; /// Morphs a DIG store launcher ID into the DIG namespace. Store launcher IDs should be morphed when hinted on coins pub fn morph_store_launcher_id(store_launcher_id: Bytes32) -> Bytes32 { - (store_launcher_id, DIG_STORE_LAUNCHER_ID_MORPH).tree_hash().into() + (store_launcher_id, DIG_STORE_LAUNCHER_ID_MORPH) + .tree_hash() + .into() } /// Converts a master public key to a wallet synthetic key. @@ -139,7 +145,7 @@ pub fn spend_bundle_to_hex(spend_bundle: &SpendBundle) -> Result { /// Adds an offset to a launcher id to make it deterministically unique from the original. pub fn morph_launcher_id_wrapper(launcher_id: Bytes32, offset: u64) -> Bytes32 { - server_coin::morph_launcher_id(launcher_id, &offset.into()) + xch_server_coin::morph_launcher_id(launcher_id, &offset.into()) } /// Output for send_xch function @@ -323,7 +329,7 @@ pub fn create_server_coin( uris: Vec, amount: u64, fee: u64, -) -> Result { +) -> Result { Ok(wallet::create_server_coin( synthetic_key, selected_coins, @@ -337,9 +343,7 @@ pub fn create_server_coin( /// Async functions for blockchain interaction (Rust API versions) pub mod async_api { use super::*; - use chia_wallet_sdk::driver::Puzzle; use chia_wallet_sdk::prelude::Cat; - use clvmr::{Allocator, NodePtr}; use futures_util::stream::{FuturesUnordered, StreamExt}; use rand::seq::SliceRandom; use std::net::SocketAddr; @@ -569,6 +573,15 @@ pub mod async_api { .await?) } + /// Gets all unspent coins hinted by one of the provided hints. + /// Uses the coinset.org API rather than a peer connection + pub async fn get_unspent_coins_by_hints( + network: NetworkType, + hints: Vec, + ) -> Result> { + Ok(wallet::get_unspent_coins_by_hints(network, hints).await?) + } + /// Gets all unspent coins for a puzzle hash (Rust API version). pub async fn get_all_unspent_coins( peer: &Peer, diff --git a/src/rust.rs b/src/types.rs similarity index 54% rename from src/rust.rs rename to src/types.rs index 0638770..576783f 100644 --- a/src/rust.rs +++ b/src/types.rs @@ -1,8 +1,26 @@ -pub use crate::server_coin::ServerCoin; -use crate::UnspentCoinStates; +pub use crate::xch_server_coin::XchServerCoin; +use crate::DataStore; use chia::bls::{PublicKey, SecretKey}; pub use chia::protocol::*; pub use chia::puzzles::{EveProof, LineageProof, Proof}; +use chia_wallet_sdk::coinset::CoinRecord; + +pub struct SimulatorPuzzle { + pub puzzle_hash: Bytes32, + pub puzzle_reveal: Program, +} + +pub struct BlsPair { + pub sk: SecretKey, + pub pk: PublicKey, + pub puzzle_hash: Bytes32, +} + +#[derive(Clone, Debug)] +pub struct SuccessResponse { + pub coin_spends: Vec, + pub new_datastore: DataStore, +} pub struct UnspentCoinsResponse { pub coins: Vec, @@ -24,13 +42,18 @@ impl From for UnspentCoinsResponse { } } -pub struct SimulatorPuzzle { - pub puzzle_hash: Bytes32, - pub puzzle_reveal: Program, +pub struct UnspentCoinStates { + pub coin_states: Vec, + pub last_height: u32, + pub last_header_hash: Bytes32, } - -pub struct BlsPair { - pub sk: SecretKey, - pub pk: PublicKey, - pub puzzle_hash: Bytes32, +pub fn coin_records_to_states(coin_records: Vec) -> Vec { + coin_records + .into_iter() + .map(|coin_record| CoinState { + coin: coin_record.coin, + spent_height: Some(coin_record.spent_block_index), + created_height: Some(coin_record.confirmed_block_index), + }) + .collect() } diff --git a/src/wallet.rs b/src/wallet.rs index ea870fb..a516bdf 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,6 +1,9 @@ #![allow(clippy::result_large_err)] + use indexmap::indexmap; +use std::alloc::alloc; use std::collections::HashMap; +use std::hash::Hash; use std::time::{SystemTime, UNIX_EPOCH}; use chia::bls::{sign, verify, PublicKey, SecretKey, Signature}; @@ -11,7 +14,6 @@ use chia::consensus::flags::{DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; use chia::consensus::owned_conditions::OwnedSpendBundleConditions; use chia::consensus::run_block_generator::run_block_generator; use chia::consensus::solution_generator::solution_generator; -use chia::consensus::validation_error::ValidationErr; use chia::protocol::{ Bytes, Bytes32, Coin, CoinSpend, CoinState, CoinStateFilters, RejectHeaderRequest, RequestBlockHeader, RequestFeeEstimates, RespondBlockHeader, RespondFeeEstimates, SpendBundle, @@ -24,17 +26,21 @@ use chia::puzzles::{ }; use chia_puzzles::SINGLETON_LAUNCHER_HASH; -use chia_wallet_sdk::client::{ClientError, Peer}; +use chia_wallet_sdk::client::Peer; +use chia_wallet_sdk::coinset::{ChiaRpcClient, CoinsetClient}; +use chia_wallet_sdk::driver::Puzzle::Curried; use chia_wallet_sdk::driver::{ - get_merkle_tree, Action, Asset, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, - DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, - OracleLayer, P2ParentCoin, P2ParentLayer, Puzzle, Relation, SpendContext, SpendWithConditions, + get_merkle_tree, Action, Asset, Cat, CurriedPuzzle, DataStore, DataStoreMetadata, + DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, + Layer, NftMint, OracleLayer, P2ParentCoin, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, StandardLayer, WriterLayer, }; // Import proof types from our own crate's rust module -use crate::rust::ServerCoin; -use crate::rust::{EveProof, LineageProof, Proof}; -use crate::server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution}; +use crate::error::WalletError; +pub use crate::types::{coin_records_to_states, SuccessResponse, XchServerCoin}; +use crate::types::{EveProof, LineageProof, Proof}; +use crate::xch_server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution, NewXchServerCoin}; +use crate::{morph_store_launcher_id, NetworkType, UnspentCoinStates, DIG_MIN_HEIGHT}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; use chia_wallet_sdk::types::{ announcement_id, @@ -42,10 +48,9 @@ use chia_wallet_sdk::types::{ Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS, }; use chia_wallet_sdk::utils::{self, CoinSelectionError}; -use clvmr::{Allocator, NodePtr}; +use clvmr::Allocator; +use futures_util::StreamExt; use hex_literal::hex; -use thiserror::Error; -use crate::morph_store_launcher_id; /* echo -n 'datastore' | sha256sum */ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " @@ -57,67 +62,107 @@ pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!( "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81" )); -#[derive(Clone, Debug)] -pub struct SuccessResponse { - pub coin_spends: Vec, - pub new_datastore: DataStore, -} - -/// The new server coin and coin spends to create it. -#[derive(Clone, Debug)] -pub struct NewServerCoin { - pub server_coin: ServerCoin, - pub coin_spends: Vec, -} - -#[derive(Debug, Error)] -pub enum WalletError { - #[error("{0:?}")] - Client(#[from] ClientError), - - #[error("RejectPuzzleState")] - RejectPuzzleState, - - #[error("RejectCoinState")] - RejectCoinState, +pub const MAX_CLVM_COST: u64 = 11_000_000_000; - #[error("RejectPuzzleSolution")] - RejectPuzzleSolution, +pub async fn get_unspent_coins_by_hints( + network_type: NetworkType, + hints: Vec, +) -> Result, WalletError> { + let coinset_client = match network_type { + NetworkType::Mainnet => CoinsetClient::mainnet(), + NetworkType::Testnet11 => CoinsetClient::testnet11(), + }; - #[error("RejectHeaderRequest")] - RejectHeaderRequest, + let get_coin_records_response = coinset_client + .get_coin_records_by_hints(hints, Some(DIG_MIN_HEIGHT), None, Some(false)) + .await + .map_err(|error| WalletError::CoinRetrievalFailure(error.to_string()))?; + + if get_coin_records_response.success + && get_coin_records_response.error.is_none() + && get_coin_records_response.coin_records.is_some() + { + Ok(coin_records_to_states( + get_coin_records_response.coin_records.unwrap_or(vec![]), + )) + } else { + let error_string = if let Some(error) = get_coin_records_response.error { + error + } else { + "An unknown error occurred".to_string() + }; + Err(WalletError::CoinRetrievalFailure(error_string)) + } +} - #[error("{0:?}")] - Driver(#[from] DriverError), +/// Instantiates a $DIG collateral coin +pub async fn fetch_dig_collateral_coin( + peer: &Peer, + coin_state: CoinState, + min_collateral_amount: u64, +) -> Result<(P2ParentCoin, Memos), WalletError> { + let coin = coin_state.coin; - #[error("ParseError")] - Parse, + // verify coin is unspent + if coin_state.spent_height.is_some() { + return Err(WalletError::CoinIsAlreadySpent); + } - #[error("UnknownCoin")] - UnknownCoin, + // verify the coin has the correct amount + if coin.amount < min_collateral_amount { + return Err(WalletError::InsufficientCoinAmount); + } - #[error("Clvm error")] - Clvm, - #[error("ToClvm error: {0}")] - ToClvm(#[from] chia::clvm_traits::ToClvmError), + // verify that the coin is $DIG p2 parent + let p2_parent_inner_hash = P2ParentCoin::puzzle_hash(Some(DIG_ASSET_ID)); + if coin.puzzle_hash != p2_parent_inner_hash.into() { + return Err(WalletError::PuzzleHashMismatch(format!( + "Coin {} is not locked by the $DIG collateral puzzle", + coin.coin_id() + ))); + } - #[error("Permission error: puzzle can't perform this action")] - Permission, + let Some(created_height) = coin_state.created_height else { + return Err(WalletError::UnknownCoin); + }; - #[error("Io error: {0}")] - Io(std::io::Error), + // 1) Request parent coin state + let parent_state = peer + .request_coin_state( + vec![coin.parent_coin_info], + Some(DIG_MIN_HEIGHT), + MAINNET_CONSTANTS.genesis_challenge, + false, + ) + .await? + .map_err(|_| WalletError::RejectCoinState)? + .coin_states + .first() + .copied() + .ok_or(WalletError::UnknownCoin)?; - #[error("Validation error: {0}")] - Validation(#[from] ValidationErr), + let parent_puzzle_and_solution_response = peer + .request_puzzle_and_solution(coin.parent_coin_info, created_height) + .await? + .map_err(|_| WalletError::RejectPuzzleSolution)?; - #[error("Fee estimation rejection: {0}")] - FeeEstimateRejection(String), -} + let mut allocator = Allocator::new(); + let parent_puzzle_ptr = parent_puzzle_and_solution_response + .puzzle + .to_clvm(&mut allocator)?; + let parent_solution_ptr = parent_puzzle_and_solution_response + .solution + .to_clvm(&mut allocator)?; + let parent_puzzle = Puzzle::parse(&mut allocator, parent_puzzle_ptr); -pub struct UnspentCoinStates { - pub coin_states: Vec, - pub last_height: u32, - pub last_header_hash: Bytes32, + Ok(P2ParentCoin::parse_child( + &mut allocator, + parent_state.coin, + parent_puzzle, + parent_solution_ptr, + ) + .unwrap() + .ok_or(WalletError::Parse)?) } pub async fn get_unspent_coin_states( @@ -259,7 +304,7 @@ pub fn create_dig_collateral_coin( let morphed_store_id = morph_store_launcher_id(store_id); let hint = ctx.hint(morphed_store_id)?; - let actions = &[ + let actions = [ Action::fee(fee), Action::send( Id::Existing(DIG_ASSET_ID), @@ -283,7 +328,7 @@ pub fn create_dig_collateral_coin( spends.add(fee_xch_coin); } - let deltas = spends.apply(&mut ctx, actions)?; + let deltas = spends.apply(&mut ctx, &actions)?; let index_map = indexmap! {p2_puzzle_hash => synthetic_key}; let _outputs = @@ -299,7 +344,7 @@ pub fn create_server_coin( uris: Vec, amount: u64, fee: u64, -) -> Result { +) -> Result { let puzzle_hash = StandardArgs::curry_tree_hash(synthetic_key).into(); let mut memos = Vec::with_capacity(uris.len() + 1); @@ -330,7 +375,7 @@ pub fn create_server_coin( puzzle_hash, )?; - let server_coin = ServerCoin { + let server_coin = XchServerCoin { coin: Coin::new( selected_coins[0].coin_id(), MirrorArgs::curry_tree_hash().into(), @@ -340,13 +385,13 @@ pub fn create_server_coin( memo_urls: uris, }; - Ok(NewServerCoin { + Ok(NewXchServerCoin { coin_spends: ctx.take(), server_coin, }) } -pub async fn spend_server_coins( +pub async fn spend_xch_server_coins( peer: &Peer, synthetic_key: PublicKey, selected_coins: Vec, @@ -437,11 +482,11 @@ pub async fn spend_server_coins( Ok(ctx.take()) } -pub async fn fetch_server_coin( +pub async fn fetch_xch_server_coin( peer: &Peer, coin_state: CoinState, max_cost: u64, -) -> Result { +) -> Result { let Some(created_height) = coin_state.created_height else { return Err(WalletError::UnknownCoin); }; @@ -473,7 +518,7 @@ pub async fn fetch_server_coin( .to_clvm(&mut allocator) .map_err(DriverError::ToClvm)?; - Ok(ServerCoin { + Ok(XchServerCoin { coin: coin_state.coin, p2_puzzle_hash: tree_hash(&allocator, puzzle).into(), memo_urls: urls, @@ -1288,7 +1333,7 @@ pub async fn prove_dig_cat_coin( let parent_state_response = peer .request_coin_state( vec![coin.parent_coin_info], - None, + Some(DIG_MIN_HEIGHT), MAINNET_CONSTANTS.genesis_challenge, false, ) diff --git a/src/server_coin.rs b/src/xch_server_coin.rs similarity index 94% rename from src/server_coin.rs rename to src/xch_server_coin.rs index bc10471..2c3d54d 100644 --- a/src/server_coin.rs +++ b/src/xch_server_coin.rs @@ -1,3 +1,4 @@ +use crate::CoinSpend; use chia_wallet_sdk::prelude::{ Allocator, Bytes, Bytes32, Coin, Condition, CreateCoin, CurriedProgram, FromClvm, Memos, Mod, ToClvm, ToTreeHash, TreeHash, @@ -6,8 +7,15 @@ use hex_literal::hex; use num_bigint::BigInt; use std::borrow::Cow; +/// The new server coin and coin spends to create it. +#[derive(Clone, Debug)] +pub struct NewXchServerCoin { + pub server_coin: XchServerCoin, + pub coin_spends: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ServerCoin { +pub struct XchServerCoin { pub coin: Coin, pub p2_puzzle_hash: Bytes32, pub memo_urls: Vec, From ecb641fcb883c3e0d75253e9a1750791ff313ba5 Mon Sep 17 00:00:00 2001 From: William Wills Date: Mon, 27 Oct 2025 20:16:26 -0400 Subject: [PATCH 09/12] feat: add WIP function to spend collateral --- src/lib.rs | 11 +++++--- src/wallet.rs | 70 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fb7b76e..f90c16e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,9 @@ pub use chia::bls::{master_to_wallet_unhardened, PublicKey, SecretKey, Signature pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle}; pub use chia::puzzles::{EveProof, LineageProof, Proof}; pub use chia_wallet_sdk::client::Peer; -pub use chia_wallet_sdk::driver::{DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle}; +pub use chia_wallet_sdk::driver::{ + DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin, +}; pub use chia_wallet_sdk::utils::Address; // Re-export async_api and constants modules at the top level for convenience @@ -33,7 +35,9 @@ pub mod wallet; pub mod xch_server_coin; // Re-export types from internal modules -pub use types::{BlsPair, SimulatorPuzzle, UnspentCoinStates, UnspentCoinsResponse}; +pub use types::{ + BlsPair, SimulatorPuzzle, SuccessResponse, UnspentCoinStates, UnspentCoinsResponse, +}; pub use wallet::{ create_simple_did, generate_did_proof, generate_did_proof_from_chain, generate_did_proof_manual, get_fee_estimate, get_header_hash, get_store_creation_height, @@ -50,7 +54,6 @@ use hex_literal::hex; pub type Result = std::result::Result>; // Helper functions for common conversions -use crate::types::SuccessResponse; use chia::puzzles::{standard::StandardArgs, DeriveSynthetic}; use chia_wallet_sdk::prelude::ToTreeHash; // Helper functions for common conversions @@ -579,7 +582,7 @@ pub mod async_api { network: NetworkType, hints: Vec, ) -> Result> { - Ok(wallet::get_unspent_coins_by_hints(network, hints).await?) + Ok(wallet::get_unspent_coin_states_by_hints(hints, network).await?) } /// Gets all unspent coins for a puzzle hash (Rust API version). diff --git a/src/wallet.rs b/src/wallet.rs index a516bdf..1623e12 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,9 +1,7 @@ #![allow(clippy::result_large_err)] use indexmap::indexmap; -use std::alloc::alloc; use std::collections::HashMap; -use std::hash::Hash; use std::time::{SystemTime, UNIX_EPOCH}; use chia::bls::{sign, verify, PublicKey, SecretKey, Signature}; @@ -24,16 +22,14 @@ use chia::puzzles::{ standard::{StandardArgs, StandardSolution}, DeriveSynthetic, }; - use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::Peer; use chia_wallet_sdk::coinset::{ChiaRpcClient, CoinsetClient}; -use chia_wallet_sdk::driver::Puzzle::Curried; use chia_wallet_sdk::driver::{ - get_merkle_tree, Action, Asset, Cat, CurriedPuzzle, DataStore, DataStoreMetadata, - DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, - Layer, NftMint, OracleLayer, P2ParentCoin, Puzzle, Relation, SpendContext, SpendWithConditions, - Spends, StandardLayer, WriterLayer, + get_merkle_tree, Action, Asset, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, + DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, + OracleLayer, P2ParentCoin, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, + StandardLayer, WriterLayer, }; // Import proof types from our own crate's rust module use crate::error::WalletError; @@ -49,8 +45,8 @@ use chia_wallet_sdk::types::{ }; use chia_wallet_sdk::utils::{self, CoinSelectionError}; use clvmr::Allocator; -use futures_util::StreamExt; use hex_literal::hex; + /* echo -n 'datastore' | sha256sum */ pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!( " @@ -64,9 +60,9 @@ pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!( pub const MAX_CLVM_COST: u64 = 11_000_000_000; -pub async fn get_unspent_coins_by_hints( - network_type: NetworkType, +pub async fn get_unspent_coin_states_by_hints( hints: Vec, + network_type: NetworkType, ) -> Result, WalletError> { let coinset_client = match network_type { NetworkType::Mainnet => CoinsetClient::mainnet(), @@ -96,10 +92,11 @@ pub async fn get_unspent_coins_by_hints( } /// Instantiates a $DIG collateral coin +/// Verifies that coin is unspent and locked by the $DIG P2Parent puzzle +/// Optionally validate coin ownership pub async fn fetch_dig_collateral_coin( peer: &Peer, coin_state: CoinState, - min_collateral_amount: u64, ) -> Result<(P2ParentCoin, Memos), WalletError> { let coin = coin_state.coin; @@ -108,14 +105,9 @@ pub async fn fetch_dig_collateral_coin( return Err(WalletError::CoinIsAlreadySpent); } - // verify the coin has the correct amount - if coin.amount < min_collateral_amount { - return Err(WalletError::InsufficientCoinAmount); - } - // verify that the coin is $DIG p2 parent - let p2_parent_inner_hash = P2ParentCoin::puzzle_hash(Some(DIG_ASSET_ID)); - if coin.puzzle_hash != p2_parent_inner_hash.into() { + let p2_parent_hash = P2ParentCoin::puzzle_hash(Some(DIG_ASSET_ID)); + if coin.puzzle_hash != p2_parent_hash.into() { return Err(WalletError::PuzzleHashMismatch(format!( "Coin {} is not locked by the $DIG collateral puzzle", coin.coin_id() @@ -153,16 +145,16 @@ pub async fn fetch_dig_collateral_coin( let parent_solution_ptr = parent_puzzle_and_solution_response .solution .to_clvm(&mut allocator)?; - let parent_puzzle = Puzzle::parse(&mut allocator, parent_puzzle_ptr); + let parent_puzzle = Puzzle::parse(&allocator, parent_puzzle_ptr); - Ok(P2ParentCoin::parse_child( + P2ParentCoin::parse_child( &mut allocator, parent_state.coin, parent_puzzle, parent_solution_ptr, ) .unwrap() - .ok_or(WalletError::Parse)?) + .ok_or(WalletError::Parse) } pub async fn get_unspent_coin_states( @@ -391,6 +383,38 @@ pub fn create_server_coin( }) } +pub fn spend_dig_collateral_coin( + synthetic_key: PublicKey, + fee_coins: Vec, + selected_collateral_coin: P2ParentCoin, + fee: u64, +) -> Result, WalletError> { + let mut ctx = SpendContext::new(); + let p2_layer = StandardLayer::new(synthetic_key); + let p2_puzzle_hash: Bytes32 = p2_layer.tree_hash().into(); + + // use actions and spends to attach fee to transaction and generate change + let actions = [Action::fee(fee)]; + let mut fee_spends = Spends::new(p2_puzzle_hash); + + // add fee coins to spends + for fee_xch_coin in fee_coins { + fee_spends.add(fee_xch_coin); + } + + // add the collateral p2 parent spend to the spend context + let p2_delegated_spend = p2_layer.spend_with_conditions(&mut ctx, Conditions::new())?; + selected_collateral_coin.spend(&mut ctx, p2_delegated_spend, ())?; + + let deltas = fee_spends.apply(&mut ctx, &actions)?; + let index_map = indexmap! {p2_puzzle_hash => synthetic_key}; + + let _outputs = + fee_spends.finish_with_keys(&mut ctx, &deltas, Relation::AssertConcurrent, &index_map)?; + + Ok(ctx.take()) +} + pub async fn spend_xch_server_coins( peer: &Peer, synthetic_key: PublicKey, @@ -1351,7 +1375,7 @@ pub async fn prove_dig_cat_coin( // 3) Convert puzzle to CLVM let parent_puzzle_ptr = ctx.alloc(&parent_puzzle_and_solution.puzzle)?; - let parent_puzzle = Puzzle::parse(&mut ctx, parent_puzzle_ptr); + let parent_puzzle = Puzzle::parse(&ctx, parent_puzzle_ptr); // 4) Convert solution to CLVM let parent_solution = ctx.alloc(&parent_puzzle_and_solution.solution)?; From fcc2249c4e1d7a655bfcd177dd16a1f712de3a89 Mon Sep 17 00:00:00 2001 From: William Wills Date: Thu, 30 Oct 2025 14:53:34 -0400 Subject: [PATCH 10/12] fix: coin spends to create p2 parent --- src/lib.rs | 12 +++++----- src/wallet.rs | 65 ++++++++++++++++++++++----------------------------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f90c16e..ec9e974 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, Sp pub use chia::puzzles::{EveProof, LineageProof, Proof}; pub use chia_wallet_sdk::client::Peer; pub use chia_wallet_sdk::driver::{ - DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin, + DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin }; pub use chia_wallet_sdk::utils::Address; @@ -576,13 +576,13 @@ pub mod async_api { .await?) } - /// Gets all unspent coins hinted by one of the provided hints. - /// Uses the coinset.org API rather than a peer connection + /// Gets all unspent coins hinted by the provided hint. pub async fn get_unspent_coins_by_hints( + peer: &Peer, + hint: Bytes32, network: NetworkType, - hints: Vec, - ) -> Result> { - Ok(wallet::get_unspent_coin_states_by_hints(hints, network).await?) + ) -> Result { + Ok(wallet::get_unspent_coin_states_by_hint(peer, hint, network).await?) } /// Gets all unspent coins for a puzzle hash (Rust API version). diff --git a/src/wallet.rs b/src/wallet.rs index 1623e12..3615053 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -31,6 +31,7 @@ use chia_wallet_sdk::driver::{ OracleLayer, P2ParentCoin, Puzzle, Relation, SpendContext, SpendWithConditions, Spends, StandardLayer, WriterLayer, }; +use chia_wallet_sdk::prelude::AssertConcurrentSpend; // Import proof types from our own crate's rust module use crate::error::WalletError; pub use crate::types::{coin_records_to_states, SuccessResponse, XchServerCoin}; @@ -60,40 +61,20 @@ pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!( pub const MAX_CLVM_COST: u64 = 11_000_000_000; -pub async fn get_unspent_coin_states_by_hints( - hints: Vec, +pub async fn get_unspent_coin_states_by_hint( + peer: &Peer, + hint: Bytes32, network_type: NetworkType, -) -> Result, WalletError> { - let coinset_client = match network_type { - NetworkType::Mainnet => CoinsetClient::mainnet(), - NetworkType::Testnet11 => CoinsetClient::testnet11(), +) -> Result { + let header_hash = match network_type { + NetworkType::Mainnet => MAINNET_CONSTANTS.genesis_challenge, + NetworkType::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge, }; - - let get_coin_records_response = coinset_client - .get_coin_records_by_hints(hints, Some(DIG_MIN_HEIGHT), None, Some(false)) - .await - .map_err(|error| WalletError::CoinRetrievalFailure(error.to_string()))?; - - if get_coin_records_response.success - && get_coin_records_response.error.is_none() - && get_coin_records_response.coin_records.is_some() - { - Ok(coin_records_to_states( - get_coin_records_response.coin_records.unwrap_or(vec![]), - )) - } else { - let error_string = if let Some(error) = get_coin_records_response.error { - error - } else { - "An unknown error occurred".to_string() - }; - Err(WalletError::CoinRetrievalFailure(error_string)) - } + Ok(get_unspent_coin_states(peer, hint, None, header_hash, true).await?) } /// Instantiates a $DIG collateral coin /// Verifies that coin is unspent and locked by the $DIG P2Parent puzzle -/// Optionally validate coin ownership pub async fn fetch_dig_collateral_coin( peer: &Peer, coin_state: CoinState, @@ -101,7 +82,7 @@ pub async fn fetch_dig_collateral_coin( let coin = coin_state.coin; // verify coin is unspent - if coin_state.spent_height.is_some() { + if matches!(coin_state.spent_height, Some(x) if x != 0) { return Err(WalletError::CoinIsAlreadySpent); } @@ -122,7 +103,7 @@ pub async fn fetch_dig_collateral_coin( let parent_state = peer .request_coin_state( vec![coin.parent_coin_info], - Some(DIG_MIN_HEIGHT), + None, MAINNET_CONSTANTS.genesis_challenge, false, ) @@ -152,8 +133,7 @@ pub async fn fetch_dig_collateral_coin( parent_state.coin, parent_puzzle, parent_solution_ptr, - ) - .unwrap() + )? .ok_or(WalletError::Parse) } @@ -393,19 +373,30 @@ pub fn spend_dig_collateral_coin( let p2_layer = StandardLayer::new(synthetic_key); let p2_puzzle_hash: Bytes32 = p2_layer.tree_hash().into(); + let collateral_spend_conditions = Conditions::new().create_coin( + p2_puzzle_hash, + selected_collateral_coin.coin.amount, + Memos::None, + ); + + // add the collateral p2 parent spend to the spend context + let p2_delegated_spend = + p2_layer.spend_with_conditions(&mut ctx, collateral_spend_conditions)?; + + selected_collateral_coin.spend(&mut ctx, p2_delegated_spend, ())?; + // use actions and spends to attach fee to transaction and generate change let actions = [Action::fee(fee)]; let mut fee_spends = Spends::new(p2_puzzle_hash); + fee_spends.conditions.required.push(AssertConcurrentSpend::new( + selected_collateral_coin.coin.coin_id(), + )); // add fee coins to spends for fee_xch_coin in fee_coins { fee_spends.add(fee_xch_coin); } - // add the collateral p2 parent spend to the spend context - let p2_delegated_spend = p2_layer.spend_with_conditions(&mut ctx, Conditions::new())?; - selected_collateral_coin.spend(&mut ctx, p2_delegated_spend, ())?; - let deltas = fee_spends.apply(&mut ctx, &actions)?; let index_map = indexmap! {p2_puzzle_hash => synthetic_key}; @@ -1357,7 +1348,7 @@ pub async fn prove_dig_cat_coin( let parent_state_response = peer .request_coin_state( vec![coin.parent_coin_info], - Some(DIG_MIN_HEIGHT), + None, MAINNET_CONSTANTS.genesis_challenge, false, ) From 74697e0030475ab1e4ad724a1f3c0170104fc3d4 Mon Sep 17 00:00:00 2001 From: William Wills Date: Fri, 31 Oct 2025 18:11:18 -0400 Subject: [PATCH 11/12] chore: fmt and clippy --- src/lib.rs | 2 +- src/wallet.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ec9e974..77d7fbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, Sp pub use chia::puzzles::{EveProof, LineageProof, Proof}; pub use chia_wallet_sdk::client::Peer; pub use chia_wallet_sdk::driver::{ - DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin + DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin, }; pub use chia_wallet_sdk::utils::Address; diff --git a/src/wallet.rs b/src/wallet.rs index 3615053..3c6e29d 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -24,7 +24,6 @@ use chia::puzzles::{ }; use chia_puzzles::SINGLETON_LAUNCHER_HASH; use chia_wallet_sdk::client::Peer; -use chia_wallet_sdk::coinset::{ChiaRpcClient, CoinsetClient}; use chia_wallet_sdk::driver::{ get_merkle_tree, Action, Asset, Cat, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError, HashedPtr, Id, IntermediateLauncher, Launcher, Layer, NftMint, @@ -37,7 +36,7 @@ use crate::error::WalletError; pub use crate::types::{coin_records_to_states, SuccessResponse, XchServerCoin}; use crate::types::{EveProof, LineageProof, Proof}; use crate::xch_server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution, NewXchServerCoin}; -use crate::{morph_store_launcher_id, NetworkType, UnspentCoinStates, DIG_MIN_HEIGHT}; +use crate::{morph_store_launcher_id, NetworkType, UnspentCoinStates}; use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError}; use chia_wallet_sdk::types::{ announcement_id, @@ -70,7 +69,7 @@ pub async fn get_unspent_coin_states_by_hint( NetworkType::Mainnet => MAINNET_CONSTANTS.genesis_challenge, NetworkType::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge, }; - Ok(get_unspent_coin_states(peer, hint, None, header_hash, true).await?) + get_unspent_coin_states(peer, hint, None, header_hash, true).await } /// Instantiates a $DIG collateral coin @@ -388,9 +387,12 @@ pub fn spend_dig_collateral_coin( // use actions and spends to attach fee to transaction and generate change let actions = [Action::fee(fee)]; let mut fee_spends = Spends::new(p2_puzzle_hash); - fee_spends.conditions.required.push(AssertConcurrentSpend::new( - selected_collateral_coin.coin.coin_id(), - )); + fee_spends + .conditions + .required + .push(AssertConcurrentSpend::new( + selected_collateral_coin.coin.coin_id(), + )); // add fee coins to spends for fee_xch_coin in fee_coins { From 057b28f920820b60bf7a791c739f06a7ed461ffa Mon Sep 17 00:00:00 2001 From: William Wills Date: Sun, 2 Nov 2025 10:56:54 -0500 Subject: [PATCH 12/12] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- napi/npm/darwin-arm64/package.json | 2 +- napi/npm/darwin-x64/package.json | 2 +- napi/npm/linux-arm64-gnu/package.json | 2 +- napi/npm/linux-x64-gnu/package.json | 2 +- napi/npm/win32-x64-msvc/package.json | 2 +- napi/package.json | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20a10a5..ce35956 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -879,7 +879,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "datalayer-driver" -version = "1.0.1" +version = "2.0.0" dependencies = [ "chia", "chia-puzzles", diff --git a/Cargo.toml b/Cargo.toml index 0e3959c..1f7408e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,9 @@ members = [".", "napi"] [package] edition = "2021" name = "datalayer-driver" -version = "1.0.1" +version = "2.0.0" license = "MIT" -authors = ["yakuhito "] +authors = ["yakuhito ", "William Wills "] homepage = "https://github.com/DIG-Network/DataLayer-Driver" repository = "https://github.com/DIG-Network/DataLayer-Driver" description = "Native Chia DataLayer Driver for storing and retrieving data in Chia blockchain" diff --git a/napi/npm/darwin-arm64/package.json b/napi/npm/darwin-arm64/package.json index 510e84b..bbab15f 100644 --- a/napi/npm/darwin-arm64/package.json +++ b/napi/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver-darwin-arm64", - "version": "1.0.1", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/DIG-Network/DataLayer-Driver" diff --git a/napi/npm/darwin-x64/package.json b/napi/npm/darwin-x64/package.json index 1750a0b..4873d86 100644 --- a/napi/npm/darwin-x64/package.json +++ b/napi/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver-darwin-x64", - "version": "1.0.1", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/DIG-Network/DataLayer-Driver" diff --git a/napi/npm/linux-arm64-gnu/package.json b/napi/npm/linux-arm64-gnu/package.json index a4b3b0c..1821088 100644 --- a/napi/npm/linux-arm64-gnu/package.json +++ b/napi/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver-linux-arm64-gnu", - "version": "1.0.1", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/DIG-Network/DataLayer-Driver" diff --git a/napi/npm/linux-x64-gnu/package.json b/napi/npm/linux-x64-gnu/package.json index c23488a..5b12b69 100644 --- a/napi/npm/linux-x64-gnu/package.json +++ b/napi/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver-linux-x64-gnu", - "version": "1.0.1", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/DIG-Network/DataLayer-Driver" diff --git a/napi/npm/win32-x64-msvc/package.json b/napi/npm/win32-x64-msvc/package.json index 0ea8b79..3b03df9 100644 --- a/napi/npm/win32-x64-msvc/package.json +++ b/napi/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver-win32-x64-msvc", - "version": "1.0.1", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/DIG-Network/DataLayer-Driver" diff --git a/napi/package.json b/napi/package.json index f3aac41..2e152f3 100644 --- a/napi/package.json +++ b/napi/package.json @@ -1,6 +1,6 @@ { "name": "@dignetwork/datalayer-driver", - "version": "1.0.1", + "version": "2.0.0", "main": "index.js", "types": "index.d.ts", "repository": {