From ad67442c320ebec322f65f2aee2d8563489582e1 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 13:42:33 -0500 Subject: [PATCH 01/20] rk dep covpp commit 7eee1db --- Cargo.lock | 795 ++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 24 +- 2 files changed, 765 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7070ede7..0f2cefb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -96,6 +102,227 @@ dependencies = [ "password-hash", ] +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" +dependencies = [ + "ahash", + "ark-crypto-primitives-macros", + "ark-ec", + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "blake2", + "derivative", + "digest", + "fnv", + "merlin", + "rayon", + "sha2", +] + +[[package]] +name = "ark-crypto-primitives-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ark-groth16" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-relations", + "ark-serialize", + "ark-std", + "rayon", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "rayon", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff", + "ark-std", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", + "rayon", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ark-snark" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" +dependencies = [ + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", + "rayon", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -287,6 +514,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -310,9 +543,29 @@ checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.4", + "constant_time_eq 0.4.2", + "cpufeatures", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -383,6 +636,26 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -524,6 +797,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.17", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -546,12 +828,24 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.4.0" @@ -589,6 +883,17 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -923,6 +1228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -954,7 +1260,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags", + "bitflags 2.10.0", "block2", "libc", "objc2", @@ -995,12 +1301,42 @@ dependencies = [ "shared_child", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1016,6 +1352,26 @@ dependencies = [ "cfg-if 1.0.4", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1150,7 +1506,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -1159,6 +1536,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1366,6 +1749,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1412,6 +1804,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hexplay" version = "0.3.0" @@ -1845,7 +2243,7 @@ dependencies = [ [[package]] name = "kaspa-addresses" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "borsh", "js-sys", @@ -1860,7 +2258,7 @@ dependencies = [ [[package]] name = "kaspa-bip32" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "borsh", "bs58", @@ -1887,7 +2285,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "ahash", "cfg-if 1.0.4", @@ -1916,11 +2314,11 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "arc-swap", "async-trait", - "bitflags", + "bitflags 2.10.0", "borsh", "cfg-if 1.0.4", "faster-hex", @@ -1953,7 +2351,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-channel 2.5.0", "cfg-if 1.0.4", @@ -1973,7 +2371,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "cfg-if 1.0.4", "faster-hex", @@ -1998,7 +2396,7 @@ dependencies = [ [[package]] name = "kaspa-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "anyhow", "cfg-if 1.0.4", @@ -2018,9 +2416,10 @@ dependencies = [ [[package]] name = "kaspa-hashes" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "blake2b_simd", + "blake3", "borsh", "cc", "faster-hex", @@ -2037,7 +2436,7 @@ dependencies = [ [[package]] name = "kaspa-index-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2057,7 +2456,7 @@ dependencies = [ [[package]] name = "kaspa-math" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "borsh", "faster-hex", @@ -2077,7 +2476,7 @@ dependencies = [ [[package]] name = "kaspa-merkle" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "kaspa-hashes", ] @@ -2085,7 +2484,7 @@ dependencies = [ [[package]] name = "kaspa-metrics-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-trait", "borsh", @@ -2102,7 +2501,7 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "kaspa-consensus-core", "thiserror 1.0.69", @@ -2111,7 +2510,7 @@ dependencies = [ [[package]] name = "kaspa-muhash" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "kaspa-hashes", "kaspa-math", @@ -2122,7 +2521,7 @@ dependencies = [ [[package]] name = "kaspa-notify" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2192,7 +2591,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2235,7 +2634,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -2248,11 +2647,19 @@ dependencies = [ [[package]] name = "kaspa-txscript" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" -dependencies = [ +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-groth16", + "ark-relations", + "ark-serialize", + "ark-snark", "blake2b_simd", "borsh", + "bytemuck", "cfg-if 1.0.4", + "faster-hex", "hexplay", "indexmap", "itertools 0.13.0", @@ -2263,8 +2670,14 @@ dependencies = [ "kaspa-utils", "kaspa-wasm-core", "log", + "once_cell", "parking_lot", "rand 0.8.5", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-core", + "risc0-groth16", + "risc0-zkp", "secp256k1", "serde", "serde-wasm-bindgen", @@ -2279,8 +2692,10 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ + "borsh", + "kaspa-hashes", "secp256k1", "thiserror 1.0.69", ] @@ -2288,7 +2703,7 @@ dependencies = [ [[package]] name = "kaspa-utils" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "arc-swap", "async-channel 2.5.0", @@ -2318,7 +2733,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "aes", "ahash", @@ -2397,7 +2812,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-trait", "borsh", @@ -2430,7 +2845,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -2444,7 +2859,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "bincode", "derive_builder", @@ -2473,7 +2888,7 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "faster-hex", "hexplay", @@ -2485,7 +2900,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "async-std", "async-trait", @@ -2522,7 +2937,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=a311302#a3113026239282a1833e6afbf6623ef2628f6aa1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" dependencies = [ "ahash", "async-std", @@ -2579,6 +2994,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -2598,7 +3016,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "redox_syscall", ] @@ -2776,6 +3194,15 @@ dependencies = [ "malachite-base", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "manual_future" version = "0.1.3" @@ -2826,6 +3253,33 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + [[package]] name = "mime" version = "0.3.17" @@ -2906,7 +3360,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if 1.0.4", "cfg_aliases", "libc", @@ -2919,7 +3373,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if 1.0.4", "cfg_aliases", "libc", @@ -2988,6 +3442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3000,6 +3455,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -3025,6 +3501,15 @@ dependencies = [ "rustc-hash 2.1.1", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc2" version = "0.6.3" @@ -3058,9 +3543,9 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if 1.0.4", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -3328,6 +3813,18 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -3415,6 +3912,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +dependencies = [ + "bitflags 2.10.0", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + [[package]] name = "pyo3" version = "0.27.1" @@ -3667,6 +4178,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -3699,7 +4219,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -3806,6 +4326,110 @@ dependencies = [ "digest", ] +[[package]] +name = "risc0-binfmt" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1883f0c5d19b865f395209a137dcb29e56dc49951424967b8d0114c129f46e77" +dependencies = [ + "anyhow", + "borsh", + "bytemuck", + "derive_more 2.0.1", + "elf", + "lazy_static", + "postcard", + "risc0-zkp", + "risc0-zkvm-platform", + "ruint", + "semver", + "serde", + "tracing", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2347e909c6b2a65584b5898f3802eec5b8c1b4b45329edfdd8587b6a04dd3357" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "metal", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b956a976b8ce4713694dcc6c370b522a42ccef4ba45da5b6e57dbf26cdb7b1" +dependencies = [ + "bytemuck", + "rand_core 0.9.3", +] + +[[package]] +name = "risc0-groth16" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc57e76bb87193d154ac5ee6ee352fbd7edabddab36f02a81f40a048e5ca14f9" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-groth16", + "ark-serialize", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", +] + +[[package]] +name = "risc0-zkp" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f40d362a6c146ec6dc69208f539b92fd86e47b0dbc2083801423034a38155a2" +dependencies = [ + "anyhow", + "blake2", + "borsh", + "bytemuck", + "cfg-if 1.0.4", + "digest", + "hex", + "hex-literal", + "metal", + "paste", + "rand_core 0.9.3", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "stability", + "tracing", +] + +[[package]] +name = "risc0-zkvm-platform" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db893788c416287e2e1a87e6b8f5302511a04a45329e699d6a32a16874fd24f" +dependencies = [ + "cfg-if 1.0.4", + "num_enum", + "paste", + "stability", +] + [[package]] name = "rlimit" version = "0.10.2" @@ -3815,6 +4439,28 @@ dependencies = [ "libc", ] +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "borsh", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3842,7 +4488,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -4003,7 +4649,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -4299,6 +4945,22 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eec75fe132d95908f1c030f93630bc20b76f3ebaeca789a6180553b770ddcd39" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.110", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4385,7 +5047,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "core-foundation", "system-configuration-sys", ] @@ -4735,7 +5397,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ - "bitflags", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -4765,10 +5427,23 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "tracing-core" version = "0.1.35" @@ -4776,6 +5451,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", ] [[package]] @@ -4826,6 +5511,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5013,6 +5704,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.12.0" @@ -5983,6 +6680,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 90536e3b..5ecba00f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ ahash = "0.8.12" bincode = "1.3.3" faster-hex = "0.9.0" futures = "0.3.31" -kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302", features = ["wasm32-sdk"]} -kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } -kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "a311302" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db", features = ["wasm32-sdk"]} +kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } paste = "1.0" pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] } pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] } From b6f1e89434aeeea4328a89567111c49a5a3972c1 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 13:43:40 -0500 Subject: [PATCH 02/20] update opcodes --- src/crypto/txscript/opcodes.rs | 36 ++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/crypto/txscript/opcodes.rs b/src/crypto/txscript/opcodes.rs index 0c947289..db0c9223 100644 --- a/src/crypto/txscript/opcodes.rs +++ b/src/crypto/txscript/opcodes.rs @@ -9,6 +9,7 @@ crate::wrap_c_enum_for_py!( /// unlocking scripts for Kaspa transactions. PyOpcodes, "Opcodes", Opcodes, { OpFalse = 0x00, + OpData1 = 0x01, OpData2 = 0x02, OpData3 = 0x03, @@ -84,12 +85,17 @@ crate::wrap_c_enum_for_py!( OpData73 = 0x49, OpData74 = 0x4a, OpData75 = 0x4b, + OpPushData1 = 0x4c, OpPushData2 = 0x4d, OpPushData4 = 0x4e, + Op1Negate = 0x4f, + OpReserved = 0x50, + OpTrue = 0x51, + Op2 = 0x52, Op3 = 0x53, Op4 = 0x54, @@ -105,18 +111,21 @@ crate::wrap_c_enum_for_py!( Op14 = 0x5e, Op15 = 0x5f, Op16 = 0x60, + OpNop = 0x61, OpVer = 0x62, OpIf = 0x63, OpNotIf = 0x64, OpVerIf = 0x65, OpVerNotIf = 0x66, + OpElse = 0x67, OpEndIf = 0x68, OpVerify = 0x69, OpReturn = 0x6a, OpToAltStack = 0x6b, OpFromAltStack = 0x6c, + Op2Drop = 0x6d, Op2Dup = 0x6e, Op3Dup = 0x6f, @@ -130,23 +139,33 @@ crate::wrap_c_enum_for_py!( OpNip = 0x77, OpOver = 0x78, OpPick = 0x79, + OpRoll = 0x7a, OpRot = 0x7b, OpSwap = 0x7c, OpTuck = 0x7d, + + // Splice opcodes OpCat = 0x7e, - OpSubStr = 0x7f, + OpSubstr = 0x7f, OpLeft = 0x80, OpRight = 0x81, + OpSize = 0x82, + + // Bitwise logic opcodes OpInvert = 0x83, OpAnd = 0x84, OpOr = 0x85, OpXor = 0x86, + OpEqual = 0x87, OpEqualVerify = 0x88, + OpReserved1 = 0x89, OpReserved2 = 0x8a, + + // Numeric related opcodes Op1Add = 0x8b, Op1Sub = 0x8c, Op2Mul = 0x8d, @@ -155,6 +174,7 @@ crate::wrap_c_enum_for_py!( OpAbs = 0x90, OpNot = 0x91, Op0NotEqual = 0x92, + OpAdd = 0x93, OpSub = 0x94, OpMul = 0x95, @@ -162,11 +182,14 @@ crate::wrap_c_enum_for_py!( OpMod = 0x97, OpLShift = 0x98, OpRShift = 0x99, + OpBoolAnd = 0x9a, OpBoolOr = 0x9b, + OpNumEqual = 0x9c, OpNumEqualVerify = 0x9d, OpNumNotEqual = 0x9e, + OpLessThan = 0x9f, OpGreaterThan = 0xa0, OpLessThanOrEqual = 0xa1, @@ -174,10 +197,16 @@ crate::wrap_c_enum_for_py!( OpMin = 0xa3, OpMax = 0xa4, OpWithin = 0xa5, - OpUnknown166 = 0xa6, + + // Undefined opcodes + OpZkPrecompile = 0xa6, OpUnknown167 = 0xa7, + + // Crypto opcodes OpSHA256 = 0xa8, + OpCheckMultiSigECDSA = 0xa9, + OpBlake2b = 0xaa, OpCheckSigECDSA = 0xab, OpCheckSig = 0xac, @@ -186,6 +215,8 @@ crate::wrap_c_enum_for_py!( OpCheckMultiSigVerify = 0xaf, OpCheckLockTimeVerify = 0xb0, OpCheckSequenceVerify = 0xb1, + + // Undefined opcodes OpUnknown178 = 0xb2, OpUnknown179 = 0xb3, OpUnknown180 = 0xb4, @@ -258,6 +289,7 @@ crate::wrap_c_enum_for_py!( OpUnknown247 = 0xf7, OpUnknown248 = 0xf8, OpUnknown249 = 0xf9, + OpSmallInteger = 0xfa, OpPubKeys = 0xfb, OpUnknown252 = 0xfc, From 48664e786a1644f2b22b5f64bef8bc37836cf6ac Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 19:50:37 -0500 Subject: [PATCH 03/20] add covenantbinding --- src/consensus/core/tx.rs | 48 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 49 insertions(+) diff --git a/src/consensus/core/tx.rs b/src/consensus/core/tx.rs index 7f63ff53..36c518e2 100644 --- a/src/consensus/core/tx.rs +++ b/src/consensus/core/tx.rs @@ -1,3 +1,51 @@ use crate::crypto::hashes::PyHash; +use kaspa_consensus_core::tx::CovenantBinding; +use pyo3::{prelude::*, types::PyDict}; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; pub type TransactionId = PyHash; + +/// Binds a transaction output to the covenant and input authorizing its creation. +#[gen_stub_pyclass] +#[pyclass(name = "CovenantBinding")] +#[derive(Clone)] +pub struct PyCovenantBinding(CovenantBinding); + +#[gen_stub_pymethods] +#[pymethods] +impl PyCovenantBinding { + #[new] + pub fn new(authorizing_input: u16, covenant_id: PyHash) -> Self { + let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); + Self(inner) + } +} + +impl From for PyCovenantBinding { + fn from(value: CovenantBinding) -> Self { + Self(value) + } +} + +impl From for CovenantBinding { + fn from(value: PyCovenantBinding) -> Self { + value.0 + } +} + +impl TryFrom<&Bound<'_, PyDict>> for PyCovenantBinding { + type Error = PyErr; + + fn try_from(dict: &Bound<'_, PyDict>) -> Result { + let authorizing_input = dict + .as_any() + .get_item("authorizing_input")? + .extract::()?; + + let covenant_id = dict.as_any().get_item("covenant_id")?.extract::()?; + + let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); + + Ok(Self(inner)) + } +} diff --git a/src/lib.rs b/src/lib.rs index d4de83da..da536e90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ fn kaspa(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; From a38c690e837d69f72bd9ff281c876b1ce83ea6f4 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 19:51:29 -0500 Subject: [PATCH 04/20] utxo from dict covenant id --- src/consensus/client/utxo.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/consensus/client/utxo.rs b/src/consensus/client/utxo.rs index bb7d005d..8c4be000 100644 --- a/src/consensus/client/utxo.rs +++ b/src/consensus/client/utxo.rs @@ -1,7 +1,7 @@ use super::outpoint::PyTransactionOutpoint; use crate::{ - address::PyAddress, consensus::core::script_public_key::PyScriptPublicKey, traits::TryToPyDict, - types::PyBinary, + address::PyAddress, consensus::core::script_public_key::PyScriptPublicKey, + crypto::hashes::PyHash, traits::TryToPyDict, types::PyBinary, }; use kaspa_consensus_client::{UtxoEntry, UtxoEntryReference}; use kaspa_utils::hex::FromHex; @@ -166,6 +166,11 @@ impl TryFrom<&Bound<'_, PyDict>> for PyUtxoEntry { .ok_or_else(|| PyKeyError::new_err("Key `isCoinbase` not present"))? .extract()?; + let covenant_id: Option = dict + .get_item("covenantId")? + .ok_or_else(|| PyKeyError::new_err("Key `covenantId` not present"))? + .extract()?; + let utxo = UtxoEntry { address: address.map(|a| a.into()), outpoint: outpoint.into(), @@ -173,6 +178,7 @@ impl TryFrom<&Bound<'_, PyDict>> for PyUtxoEntry { script_public_key: script_public_key.into(), block_daa_score, is_coinbase, + covenant_id: covenant_id.map(PyHash::into), }; Ok(Self(utxo)) @@ -428,6 +434,11 @@ impl TryFrom<&Bound<'_, PyDict>> for PyUtxoEntryReference { .ok_or_else(|| PyKeyError::new_err("Key `isCoinbase` not present"))? .extract()?; + let covenant_id: Option = source_dict + .get_item("covenantId")? + .ok_or_else(|| PyKeyError::new_err("Key `covenantId` not present"))? + .extract()?; + let utxo = UtxoEntry { address: address.map(|a| a.into()), outpoint: outpoint.into(), @@ -435,6 +446,7 @@ impl TryFrom<&Bound<'_, PyDict>> for PyUtxoEntryReference { script_public_key: script_public_key.into(), block_daa_score, is_coinbase, + covenant_id: covenant_id.map(PyHash::into), }; let inner = UtxoEntryReference { From 2b492d20d5ba8524f038ce548c23eb41e9d88fed Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 19:54:51 -0500 Subject: [PATCH 05/20] output updates --- src/consensus/client/output.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/consensus/client/output.rs b/src/consensus/client/output.rs index c2743f72..354f8afa 100644 --- a/src/consensus/client/output.rs +++ b/src/consensus/client/output.rs @@ -7,7 +7,9 @@ use pyo3::{ use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use crate::{ - consensus::core::script_public_key::PyScriptPublicKey, traits::TryToPyDict, types::PyBinary, + consensus::core::{script_public_key::PyScriptPublicKey, tx::PyCovenantBinding}, + traits::TryToPyDict, + types::PyBinary, }; /// A transaction output defining a payment destination. @@ -27,12 +29,21 @@ impl PyTransactionOutput { /// Args: /// value: Amount in sompi (1 KAS = 100,000,000 sompi). /// script_public_key: The locking script. + /// covenant_id: The covenant ID. /// /// Returns: /// TransactionOutput: A new TransactionOutput instance. #[new] - pub fn ctor(value: u64, script_public_key: PyScriptPublicKey) -> Self { - let inner = TransactionOutput::new(value, script_public_key.into()); + pub fn ctor( + value: u64, + script_public_key: PyScriptPublicKey, + covenant_id: Option, + ) -> Self { + let inner = TransactionOutput::new( + value, + script_public_key.into(), + covenant_id.map(PyCovenantBinding::into), + ); Self(inner) } @@ -133,6 +144,11 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransactionOutput { )); }; - Ok(Self::ctor(value, spk)) + let covenant_id = dict + .as_any() + .get_item("covenantId")? + .extract::>()?; + + Ok(Self::ctor(value, spk, covenant_id)) } } From a2e6cf6a6fddaf85c6a9500fb5d66f2b5f76f0a1 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 19:55:09 -0500 Subject: [PATCH 06/20] lints --- src/crypto/txscript/opcodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/txscript/opcodes.rs b/src/crypto/txscript/opcodes.rs index db0c9223..b6ce3e59 100644 --- a/src/crypto/txscript/opcodes.rs +++ b/src/crypto/txscript/opcodes.rs @@ -197,7 +197,7 @@ crate::wrap_c_enum_for_py!( OpMin = 0xa3, OpMax = 0xa4, OpWithin = 0xa5, - + // Undefined opcodes OpZkPrecompile = 0xa6, OpUnknown167 = 0xa7, From 1393ccba29ed19e68c1aeef8d0b0ff2106609cf0 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Mon, 9 Feb 2026 20:05:36 -0500 Subject: [PATCH 07/20] stub file --- kaspa.pyi | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/kaspa.pyi b/kaspa.pyi index 83275ac7..3a158d16 100644 --- a/kaspa.pyi +++ b/kaspa.pyi @@ -203,6 +203,13 @@ class Binary: """ ... +@typing.final +class CovenantBinding: + r""" + Binds a transaction output to the covenant and input authorizing its creation. + """ + def __new__(cls, authorizing_input: builtins.int, covenant_id: Hash) -> CovenantBinding: ... + @typing.final class DerivationPath: r""" @@ -2235,13 +2242,14 @@ class TransactionOutput: Args: value: The script public key. """ - def __new__(cls, value: builtins.int, script_public_key: ScriptPublicKey) -> TransactionOutput: + def __new__(cls, value: builtins.int, script_public_key: ScriptPublicKey, covenant_id: typing.Optional[CovenantBinding]) -> TransactionOutput: r""" Create a new transaction output. Args: value: Amount in sompi (1 KAS = 100,000,000 sompi). script_public_key: The locking script. + covenant_id: The covenant ID. Returns: TransactionOutput: A new TransactionOutput instance. @@ -2331,6 +2339,21 @@ class UtxoContext: Return pending UTXO entries. """ +@typing.final +class UtxoEntries: + r""" + UTXO entries collection for flexible input handling. + + This type is not intended to be instantiated directly from Python. + It serves as a helper type that allows Rust functions to accept a list + of UTXO entries in multiple convenient forms. + + Accepts: + list[UtxoEntryReference]: A list of UtxoEntryReference objects. + list[dict]: A list of dicts with UtxoEntryReference-compatible keys. + """ + ... + @typing.final class UtxoEntries: r""" @@ -2372,21 +2395,6 @@ class UtxoEntries: """ def __eq__(self, other: UtxoEntries) -> builtins.bool: ... -@typing.final -class UtxoEntries: - r""" - UTXO entries collection for flexible input handling. - - This type is not intended to be instantiated directly from Python. - It serves as a helper type that allows Rust functions to accept a list - of UTXO entries in multiple convenient forms. - - Accepts: - list[UtxoEntryReference]: A list of UtxoEntryReference objects. - list[dict]: A list of dicts with UtxoEntryReference-compatible keys. - """ - ... - @typing.final class UtxoEntry: r""" @@ -3085,7 +3093,7 @@ class Opcodes(enum.Enum): OpSwap = ... OpTuck = ... OpCat = ... - OpSubStr = ... + OpSubstr = ... OpLeft = ... OpRight = ... OpSize = ... @@ -3124,7 +3132,7 @@ class Opcodes(enum.Enum): OpMin = ... OpMax = ... OpWithin = ... - OpUnknown166 = ... + OpZkPrecompile = ... OpUnknown167 = ... OpSHA256 = ... OpCheckMultiSigECDSA = ... From 9743bdee082aea07f7f7334c13fed18d4fdb577e Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 11:05:58 -0500 Subject: [PATCH 08/20] rusty-kaspa dep to commit 5e2fd7b in tn12 branch --- Cargo.lock | 55 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 24 ++++++++++++------------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f2cefb9..1d181486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2243,7 +2243,7 @@ dependencies = [ [[package]] name = "kaspa-addresses" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "borsh", "js-sys", @@ -2258,7 +2258,7 @@ dependencies = [ [[package]] name = "kaspa-bip32" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "borsh", "bs58", @@ -2285,7 +2285,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "ahash", "cfg-if 1.0.4", @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "arc-swap", "async-trait", @@ -2351,7 +2351,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-channel 2.5.0", "cfg-if 1.0.4", @@ -2371,7 +2371,7 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "cfg-if 1.0.4", "faster-hex", @@ -2396,7 +2396,7 @@ dependencies = [ [[package]] name = "kaspa-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "anyhow", "cfg-if 1.0.4", @@ -2416,7 +2416,7 @@ dependencies = [ [[package]] name = "kaspa-hashes" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "blake2b_simd", "blake3", @@ -2436,7 +2436,7 @@ dependencies = [ [[package]] name = "kaspa-index-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "kaspa-math" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "borsh", "faster-hex", @@ -2476,7 +2476,7 @@ dependencies = [ [[package]] name = "kaspa-merkle" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "kaspa-hashes", ] @@ -2484,7 +2484,7 @@ dependencies = [ [[package]] name = "kaspa-metrics-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-trait", "borsh", @@ -2501,7 +2501,7 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "kaspa-consensus-core", "thiserror 1.0.69", @@ -2510,7 +2510,7 @@ dependencies = [ [[package]] name = "kaspa-muhash" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "kaspa-hashes", "kaspa-math", @@ -2521,7 +2521,7 @@ dependencies = [ [[package]] name = "kaspa-notify" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2591,7 +2591,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2634,7 +2634,7 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "kaspa-txscript" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "ark-bn254", "ark-ec", @@ -2658,6 +2658,7 @@ dependencies = [ "blake2b_simd", "borsh", "bytemuck", + "cc", "cfg-if 1.0.4", "faster-hex", "hexplay", @@ -2692,7 +2693,7 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "borsh", "kaspa-hashes", @@ -2703,7 +2704,7 @@ dependencies = [ [[package]] name = "kaspa-utils" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "arc-swap", "async-channel 2.5.0", @@ -2733,7 +2734,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "aes", "ahash", @@ -2812,7 +2813,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-trait", "borsh", @@ -2845,7 +2846,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -2859,7 +2860,7 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "bincode", "derive_builder", @@ -2888,7 +2889,7 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "faster-hex", "hexplay", @@ -2900,7 +2901,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "async-std", "async-trait", @@ -2937,7 +2938,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=7eee1db#7eee1db43495cb4fc5e669a5723ee6659a2e5708" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" dependencies = [ "ahash", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 5ecba00f..7e214f27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ ahash = "0.8.12" bincode = "1.3.3" faster-hex = "0.9.0" futures = "0.3.31" -kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db", features = ["wasm32-sdk"]} -kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } -kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "7eee1db" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b", features = ["wasm32-sdk"]} +kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } paste = "1.0" pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] } pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] } From 366d16b600488a8502894061baf9797d2fd17619 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 11:09:06 -0500 Subject: [PATCH 09/20] add kip17 related opcodes --- src/crypto/txscript/opcodes.rs | 70 +++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/crypto/txscript/opcodes.rs b/src/crypto/txscript/opcodes.rs index b6ce3e59..238a5965 100644 --- a/src/crypto/txscript/opcodes.rs +++ b/src/crypto/txscript/opcodes.rs @@ -200,7 +200,7 @@ crate::wrap_c_enum_for_py!( // Undefined opcodes OpZkPrecompile = 0xa6, - OpUnknown167 = 0xa7, + OpBlake2bWithKey = 0xa7, // Crypto opcodes OpSHA256 = 0xa8, @@ -217,41 +217,41 @@ crate::wrap_c_enum_for_py!( OpCheckSequenceVerify = 0xb1, // Undefined opcodes - OpUnknown178 = 0xb2, - OpUnknown179 = 0xb3, - OpUnknown180 = 0xb4, - OpUnknown181 = 0xb5, - OpUnknown182 = 0xb6, - OpUnknown183 = 0xb7, - OpUnknown184 = 0xb8, - OpUnknown185 = 0xb9, - OpUnknown186 = 0xba, - OpUnknown187 = 0xbb, - OpUnknown188 = 0xbc, - OpUnknown189 = 0xbd, - OpUnknown190 = 0xbe, - OpUnknown191 = 0xbf, - OpUnknown192 = 0xc0, - OpUnknown193 = 0xc1, - OpUnknown194 = 0xc2, - OpUnknown195 = 0xc3, - OpUnknown196 = 0xc4, - OpUnknown197 = 0xc5, - OpUnknown198 = 0xc6, - OpUnknown199 = 0xc7, - OpUnknown200 = 0xc8, - OpUnknown201 = 0xc9, + OpTxVersion = 0xb2, + OpTxInputCount = 0xb3, + OpTxOutputCount = 0xb4, + OpTxLockTime = 0xb5, + OpTxSubnetId = 0xb6, + OpTxGas = 0xb7, + OpTxPayloadSubstr = 0xb8, + OpTxInputIndex = 0xb9, + OpOutpointTxId = 0xba, + OpOutpointIndex = 0xbb, + OpTxInputScriptSigSubstr = 0xbc, + OpTxInputSeq = 0xbd, + OpTxInputAmount = 0xbe, + OpTxInputSpk = 0xbf, + OpTxInputDaaScore = 0xc0, + OpTxInputIsCoinbase = 0xc1, + OpTxOutputAmount = 0xc2, + OpTxOutputSpk = 0xc3, + OpTxPayloadLen = 0xc4, + OpTxInputSpkLen = 0xc5, + OpTxInputSpkSubstr = 0xc6, + OpTxOutputSpkLen = 0xc7, + OpTxOutputSpkSubstr = 0xc8, + OpTxInputScriptSigLen = 0xc9, OpUnknown202 = 0xca, - OpUnknown203 = 0xcb, - OpUnknown204 = 0xcc, - OpUnknown205 = 0xcd, - OpUnknown206 = 0xce, - OpUnknown207 = 0xcf, - OpUnknown208 = 0xd0, - OpUnknown209 = 0xd1, - OpUnknown210 = 0xd2, - OpUnknown211 = 0xd3, - OpUnknown212 = 0xd4, + OpAuthOutputCount = 0xcb, + OpAuthOutputIdx = 0xcc, + OpNum2Bin = 0xcd, + OpBin2Num = 0xce, + OpInputCovenantId = 0xcf, + OpCovInputCount = 0xd0, + OpCovInputIdx = 0xd1, + OpCovOutCount = 0xd2, + OpCovOutputIdx = 0xd3, + OpChainblockSeqCommit = 0xd4, OpUnknown213 = 0xd5, OpUnknown214 = 0xd6, OpUnknown215 = 0xd7, From 01c5483e2115f62df6522746cd49c9453f4cfb70 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 12:14:29 -0500 Subject: [PATCH 10/20] covenant related to/from dict updates --- kaspa.pyi | 77 ++++++++++++++++-------------- src/consensus/client/output.rs | 4 +- src/consensus/client/utxo.rs | 4 +- src/consensus/convert.rs | 10 ++++ src/rpc/messages.rs | 2 +- tests/unit/test_dict_conversion.py | 6 +++ 6 files changed, 63 insertions(+), 40 deletions(-) diff --git a/kaspa.pyi b/kaspa.pyi index 3a158d16..91a7264b 100644 --- a/kaspa.pyi +++ b/kaspa.pyi @@ -2242,7 +2242,7 @@ class TransactionOutput: Args: value: The script public key. """ - def __new__(cls, value: builtins.int, script_public_key: ScriptPublicKey, covenant_id: typing.Optional[CovenantBinding]) -> TransactionOutput: + def __new__(cls, value: builtins.int, script_public_key: ScriptPublicKey, covenant_id: typing.Optional[CovenantBinding] = None) -> TransactionOutput: r""" Create a new transaction output. @@ -2271,6 +2271,7 @@ class TransactionOutput: dict: Dictionary containing transaction output fields with keys: - 'value' (int): The output value in sompi - 'scriptPublicKey' (dict): Dict with 'version' (int) and 'script' (str) keys + - 'covenant' (dict | None): The optional covenant binding. Returns: TransactionOutput: A new TransactionOutput instance. @@ -2454,6 +2455,7 @@ class UtxoEntry: - 'scriptPublicKey' (dict): Dict with 'version' (int) and 'script' (str) keys - 'blockDaaScore' (int): Block DAA score - 'isCoinbase' (bool): Whether from coinbase transaction + - 'covenantId' (str | None): The optional covenant ID of the UTXO. Returns: UtxoEntry: A new UtxoEntry instance. @@ -2529,11 +2531,12 @@ class UtxoEntryReference: - 'scriptPublicKey' (dict | str): Dict with 'version' and 'script', or hex string - 'blockDaaScore' (int): Block DAA score - 'isCoinbase' (bool): Whether from coinbase transaction + - 'covenantId' (str | None): The optional covenant ID of the UTXO. Nested format: - 'address' (str | None): The address string - 'outpoint' (dict): Transaction outpoint with 'transactionId' and 'index' - - 'utxoEntry' (dict): Nested dict containing amount, scriptPublicKey, blockDaaScore, isCoinbase + - 'utxoEntry' (dict): Nested dict containing amount, scriptPublicKey, blockDaaScore, isCoinbase, covenantId Returns: UtxoEntryReference: A new UtxoEntryReference instance. @@ -3133,7 +3136,7 @@ class Opcodes(enum.Enum): OpMax = ... OpWithin = ... OpZkPrecompile = ... - OpUnknown167 = ... + OpBlake2bWithKey = ... OpSHA256 = ... OpCheckMultiSigECDSA = ... OpBlake2b = ... @@ -3144,41 +3147,41 @@ class Opcodes(enum.Enum): OpCheckMultiSigVerify = ... OpCheckLockTimeVerify = ... OpCheckSequenceVerify = ... - OpUnknown178 = ... - OpUnknown179 = ... - OpUnknown180 = ... - OpUnknown181 = ... - OpUnknown182 = ... - OpUnknown183 = ... - OpUnknown184 = ... - OpUnknown185 = ... - OpUnknown186 = ... - OpUnknown187 = ... - OpUnknown188 = ... - OpUnknown189 = ... - OpUnknown190 = ... - OpUnknown191 = ... - OpUnknown192 = ... - OpUnknown193 = ... - OpUnknown194 = ... - OpUnknown195 = ... - OpUnknown196 = ... - OpUnknown197 = ... - OpUnknown198 = ... - OpUnknown199 = ... - OpUnknown200 = ... - OpUnknown201 = ... + OpTxVersion = ... + OpTxInputCount = ... + OpTxOutputCount = ... + OpTxLockTime = ... + OpTxSubnetId = ... + OpTxGas = ... + OpTxPayloadSubstr = ... + OpTxInputIndex = ... + OpOutpointTxId = ... + OpOutpointIndex = ... + OpTxInputScriptSigSubstr = ... + OpTxInputSeq = ... + OpTxInputAmount = ... + OpTxInputSpk = ... + OpTxInputDaaScore = ... + OpTxInputIsCoinbase = ... + OpTxOutputAmount = ... + OpTxOutputSpk = ... + OpTxPayloadLen = ... + OpTxInputSpkLen = ... + OpTxInputSpkSubstr = ... + OpTxOutputSpkLen = ... + OpTxOutputSpkSubstr = ... + OpTxInputScriptSigLen = ... OpUnknown202 = ... - OpUnknown203 = ... - OpUnknown204 = ... - OpUnknown205 = ... - OpUnknown206 = ... - OpUnknown207 = ... - OpUnknown208 = ... - OpUnknown209 = ... - OpUnknown210 = ... - OpUnknown211 = ... - OpUnknown212 = ... + OpAuthOutputCount = ... + OpAuthOutputIdx = ... + OpNum2Bin = ... + OpBin2Num = ... + OpInputCovenantId = ... + OpCovInputCount = ... + OpCovInputIdx = ... + OpCovOutCount = ... + OpCovOutputIdx = ... + OpChainblockSeqCommit = ... OpUnknown213 = ... OpUnknown214 = ... OpUnknown215 = ... diff --git a/src/consensus/client/output.rs b/src/consensus/client/output.rs index 354f8afa..6224fb2b 100644 --- a/src/consensus/client/output.rs +++ b/src/consensus/client/output.rs @@ -34,6 +34,7 @@ impl PyTransactionOutput { /// Returns: /// TransactionOutput: A new TransactionOutput instance. #[new] + #[pyo3(signature = (value, script_public_key, covenant_id=None))] pub fn ctor( value: u64, script_public_key: PyScriptPublicKey, @@ -92,6 +93,7 @@ impl PyTransactionOutput { /// dict: Dictionary containing transaction output fields with keys: /// - 'value' (int): The output value in sompi /// - 'scriptPublicKey' (dict): Dict with 'version' (int) and 'script' (str) keys + /// - 'covenant' (dict | None): The optional covenant binding. /// /// Returns: /// TransactionOutput: A new TransactionOutput instance. @@ -146,7 +148,7 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransactionOutput { let covenant_id = dict .as_any() - .get_item("covenantId")? + .get_item("covenant")? .extract::>()?; Ok(Self::ctor(value, spk, covenant_id)) diff --git a/src/consensus/client/utxo.rs b/src/consensus/client/utxo.rs index 8c4be000..6f053267 100644 --- a/src/consensus/client/utxo.rs +++ b/src/consensus/client/utxo.rs @@ -80,6 +80,7 @@ impl PyUtxoEntry { /// - 'scriptPublicKey' (dict): Dict with 'version' (int) and 'script' (str) keys /// - 'blockDaaScore' (int): Block DAA score /// - 'isCoinbase' (bool): Whether from coinbase transaction + /// - 'covenantId' (str | None): The optional covenant ID of the UTXO. /// /// Returns: /// UtxoEntry: A new UtxoEntry instance. @@ -333,11 +334,12 @@ impl PyUtxoEntryReference { /// - 'scriptPublicKey' (dict | str): Dict with 'version' and 'script', or hex string /// - 'blockDaaScore' (int): Block DAA score /// - 'isCoinbase' (bool): Whether from coinbase transaction + /// - 'covenantId' (str | None): The optional covenant ID of the UTXO. /// /// Nested format: /// - 'address' (str | None): The address string /// - 'outpoint' (dict): Transaction outpoint with 'transactionId' and 'index' - /// - 'utxoEntry' (dict): Nested dict containing amount, scriptPublicKey, blockDaaScore, isCoinbase + /// - 'utxoEntry' (dict): Nested dict containing amount, scriptPublicKey, blockDaaScore, isCoinbase, covenantId /// /// Returns: /// UtxoEntryReference: A new UtxoEntryReference instance. diff --git a/src/consensus/convert.rs b/src/consensus/convert.rs index 2c7582bf..8ec26b1b 100644 --- a/src/consensus/convert.rs +++ b/src/consensus/convert.rs @@ -1,3 +1,4 @@ +use crate::crypto::hashes::PyHash; use crate::traits::TryToPyDict; use kaspa_consensus_client::{ Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, @@ -50,6 +51,7 @@ impl TryToPyDict for UtxoEntryReference { )?; dict.set_item("blockDaaScore", self.block_daa_score())?; dict.set_item("isCoinbase", self.is_coinbase())?; + dict.set_item("covenantId", self.utxo.covenant_id.map(PyHash::from))?; Ok(dict) } @@ -84,6 +86,9 @@ impl TryToPyDict for UtxoEntry { // Set `isCoinbase` key dict.set_item("isCoinbase", self.is_coinbase())?; + // Set `covenantId` key + dict.set_item("covenantId", self.covenant_id.map(PyHash::from))?; + Ok(dict) } } @@ -130,6 +135,11 @@ impl TryToPyDict for TransactionOutput { inner.script_public_key.try_to_pydict(py)?, )?; + dict.set_item( + "covenant", + serde_pyobject::to_pyobject(py, &inner.covenant)?, + )?; + Ok(dict) } } diff --git a/src/rpc/messages.rs b/src/rpc/messages.rs index 4df4b695..85db7e54 100644 --- a/src/rpc/messages.rs +++ b/src/rpc/messages.rs @@ -236,7 +236,7 @@ try_from_args! ( dict : PySubmitTransactionRequest, { inputs, outputs, lock_time: inner.lock_time, - subnetwork_id: inner.subnetwork_id.clone(), + subnetwork_id: inner.subnetwork_id, gas: inner.gas, payload: inner.payload.clone(), mass: inner.mass, diff --git a/tests/unit/test_dict_conversion.py b/tests/unit/test_dict_conversion.py index 04a2eb49..5d6a1cdb 100644 --- a/tests/unit/test_dict_conversion.py +++ b/tests/unit/test_dict_conversion.py @@ -150,6 +150,7 @@ def test_utxo_entry_to_dict(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, } entry = UtxoEntry.from_dict(entry_dict) @@ -171,6 +172,7 @@ def test_utxo_entry_from_dict_roundtrip(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, } original = UtxoEntry.from_dict(entry_dict) @@ -192,6 +194,7 @@ def test_utxo_entry_reference_to_dict(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, } entry_ref = UtxoEntryReference.from_dict(entry_dict) @@ -216,6 +219,7 @@ def test_utxo_entry_reference_from_dict_flat_format(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, } entry_ref = UtxoEntryReference.from_dict(entry_dict) assert entry_ref.amount == 1000000 @@ -230,6 +234,7 @@ def test_utxo_entry_reference_from_dict_nested_format(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, }, } entry_ref = UtxoEntryReference.from_dict(entry_dict) @@ -244,6 +249,7 @@ def test_utxo_entry_reference_from_dict_roundtrip(self): "scriptPublicKey": {"version": 0, "script": "20852be1b87fca94453a35027c550a3ccdbebb5913106029f3a8bf18152bf93bffac"}, "blockDaaScore": 12345, "isCoinbase": False, + "covenantId": None, } original = UtxoEntryReference.from_dict(entry_dict) From 8af2e609d6bfc371d93989c6f5c07d5118f28c8b Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 17:17:21 -0500 Subject: [PATCH 11/20] covenant id hash function --- src/consensus/core/hashing.rs | 27 +++++++++++++++++++++++++++ src/lib.rs | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/src/consensus/core/hashing.rs b/src/consensus/core/hashing.rs index ee2e18a9..aa4bdd5e 100644 --- a/src/consensus/core/hashing.rs +++ b/src/consensus/core/hashing.rs @@ -1,8 +1,16 @@ +use kaspa_consensus_client as cctx; +use kaspa_consensus_core::hashing::covenant_id::covenant_id; use kaspa_consensus_core::hashing::wasm::SighashType; +use kaspa_consensus_core::tx as ctx; use pyo3::{exceptions::PyException, prelude::*}; use pyo3_stub_gen::derive::gen_stub_pyclass_enum; use std::str::FromStr; +use crate::{ + consensus::client::{outpoint::PyTransactionOutpoint, output::PyTransactionOutput}, + crypto::hashes::PyHash, +}; + crate::wrap_unit_enum_for_py!( /// Kaspa signature hash types for transaction signing. PySighashType, "SighashType", SighashType, { @@ -45,3 +53,22 @@ impl<'py> FromPyObject<'_, 'py> for PySighashType { } } } + +#[pyfunction] +#[pyo3(name = "covenant_id")] +pub fn py_covenant_id( + outpoint: PyTransactionOutpoint, + auth_outputs: Vec, +) -> PyHash { + let outpoint: cctx::TransactionOutpoint = outpoint.into(); + let auth_outputs = auth_outputs + .into_iter() + .map(|py_output| ctx::TransactionOutput::from(&cctx::TransactionOutput::from(py_output))) + .collect::>(); + let indexed: Vec<(u32, &ctx::TransactionOutput)> = auth_outputs + .iter() + .enumerate() + .map(|(i, o)| (i as u32, o)) + .collect(); + covenant_id(outpoint.into(), indexed.into_iter()).into() +} diff --git a/src/lib.rs b/src/lib.rs index da536e90..85171c8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,10 @@ fn kaspa(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { )?)?; m.add_class::()?; + m.add_function(wrap_pyfunction!( + consensus::core::hashing::py_covenant_id, + m + )?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; From a46a6c0449bbb89053e81a0a28ed20d3a33103d2 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 19:37:19 -0500 Subject: [PATCH 12/20] covenant examples wip --- examples/covenants/forced_recipient.py | 336 ++++++++++++++++ examples/covenants/native_token.py | 528 +++++++++++++++++++++++++ examples/covenants/vault.py | 487 +++++++++++++++++++++++ 3 files changed, 1351 insertions(+) create mode 100644 examples/covenants/forced_recipient.py create mode 100644 examples/covenants/native_token.py create mode 100644 examples/covenants/vault.py diff --git a/examples/covenants/forced_recipient.py b/examples/covenants/forced_recipient.py new file mode 100644 index 00000000..ca7f1922 --- /dev/null +++ b/examples/covenants/forced_recipient.py @@ -0,0 +1,336 @@ +""" +Forced-Recipient Covenant — Kaspa KIP-17 Example +================================================= +A UTXO whose redeem script enforces that it can *only* be spent to a single +predetermined address — no matter who has the private key to the input, the +destination is locked. This is the simplest useful covenant: output +introspection with zero on-chain state. +""" + +import asyncio +import os + +from kaspa import ( + Hash, + Keypair, + Opcodes, + PrivateKey, + RpcClient, + ScriptBuilder, + Transaction, + TransactionInput, + TransactionOutpoint, + TransactionOutput, + UtxoEntryReference, + address_from_script_public_key, + calculate_transaction_mass, + create_input_signature, + pay_to_address_script, + pay_to_script_hash_signature_script, + sign_transaction, +) + +# ============================================================================= +# Configuration +# ============================================================================= + +NETWORK_ID = "testnet-12" +NETWORK_TYPE = "testnet" +RPC_URL = os.environ.get("KASPA_RPC_URL") +SUBNETWORK_ID = bytes(20) + + +# ============================================================================= +# Covenant Script Construction +# ============================================================================= + +def build_covenant_redeem_script(recipient_spk, owner_xonly_pubkey_hex: str) -> ScriptBuilder: + """ + Build the P2SH redeem script that forces spending to a single recipient. + + Execution trace when spending (initial stack: []): + + OpTxOutputCount push number of outputs → [sig, N] + OpTrue push 1 → [sig, N, 1] + OpEqualVerify assert N == 1 → [sig] + OpFalse push 0 (output index) → [sig, 0] + OpTxOutputSpk push output[0].spk → [sig, spk0] + push hardcoded SPK → [sig, spk0, rec_spk] + OpEqualVerify assert spk0 == rec_spk → [sig] + push 32-byte x-only key → [sig, pubkey] + OpCheckSig verify Schnorr sig → [true] + + KIP-17 opcodes used: OpTxOutputCount, OpTxOutputSpk + """ + # OpTxOutputSpk pushes version (2 bytes, big-endian) + script bytes. + # bytes(spk) returns only the script; prepend the version to match. + spk_bytes = recipient_spk.version.to_bytes(2, 'big') + bytes(recipient_spk) + + return ( + ScriptBuilder() + .add_op(Opcodes.OpTxOutputCount) + .add_op(Opcodes.OpTrue) + .add_op(Opcodes.OpEqualVerify) + .add_op(Opcodes.OpFalse) + .add_op(Opcodes.OpTxOutputSpk) + .add_data(spk_bytes) + .add_op(Opcodes.OpEqualVerify) + .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + ) + + +# ============================================================================= +# Genesis — send funds into the covenant P2SH UTXO +# ============================================================================= + +async def genesis(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): + """ + Create and broadcast the genesis transaction. + + Moves funds from the owner's regular P2PK address into a covenant + P2SH UTXO whose redeem script enforces a single spending destination. + """ + keypair = Keypair.from_private_key(owner_key) + owner_pubkey_hex = keypair.xonly_public_key + + # In this demo the recipient is the same keypair (self-contained). + # In a real use-case this is likely an independent (pre-agreed) address. + recipient_address = keypair.to_address(NETWORK_TYPE) + recipient_spk = pay_to_address_script(recipient_address) + + redeem_script = build_covenant_redeem_script(recipient_spk, owner_pubkey_hex) + covenant_spk = redeem_script.create_pay_to_script_hash_script() + covenant_address = address_from_script_public_key(covenant_spk, NETWORK_TYPE) + print(f" Covenant P2SH address : {covenant_address.to_string()}") + + # Use the largest funding UTXO + funding = max(funding_utxos, key=lambda u: u["utxoEntry"]["amount"]) + funding_amount = funding["utxoEntry"]["amount"] + funding_utxo_ref = UtxoEntryReference.from_dict(funding) + funding_outpoint = TransactionOutpoint( + Hash(funding["outpoint"]["transactionId"]), + funding["outpoint"]["index"], + ) + + # ── Mass / fee calculation ────────────────────────────────────────────── + ph_input = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + ph_output = TransactionOutput(funding_amount, covenant_spk) + ph_tx = Transaction(0, [ph_input], [ph_output], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + covenant_amount = funding_amount - fee + + # ── Build & sign the genesis transaction ──────────────────────────────── + inp = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + out = TransactionOutput(covenant_amount, covenant_spk) + tx = Transaction(0, [inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) + + # sign_transaction handles standard P2PK inputs automatically + signed_tx = sign_transaction(tx, [owner_key], True) + + print(f" Fee: {fee} sompi") + print(f" Covenant amount: {covenant_amount} sompi") + + result = await client.submit_transaction({ + "transaction": signed_tx, + "allowOrphan": False, + }) + txid = result["transactionId"] + print(f" Genesis TXID: {txid}") + + outpoint = {"transactionId": txid, "index": 0} + return txid, outpoint, covenant_amount, redeem_script, covenant_spk + + +# ============================================================================= +# Spend — demonstrate that the covenant enforces the recipient +# ============================================================================= + +async def spend( + client: RpcClient, + owner_key: PrivateKey, + covenant_outpoint: dict, + covenant_amount: int, + redeem_script: ScriptBuilder, + covenant_spk, +) -> str: + """ + Spend the covenant UTXO. The script forces all funds to the recipient + encoded in the redeem script — any other destination fails script evaluation. + + Steps: + 1. Build the spending transaction with an empty signature script + 2. Sign with create_input_signature (P2SH Schnorr) + 3. Build the P2SH unlocking script: + 4. Submit + """ + keypair = Keypair.from_private_key(owner_key) + recipient_address = keypair.to_address(NETWORK_TYPE) + recipient_spk = pay_to_address_script(recipient_address) + covenant_address = address_from_script_public_key(covenant_spk, NETWORK_TYPE) + + # Build a UTXO reference for the covenant input + cov_utxo_ref = UtxoEntryReference.from_dict({ + "address": covenant_address.to_string(), + "outpoint": { + "transactionId": covenant_outpoint["transactionId"], + "index": covenant_outpoint["index"], + }, + "utxoEntry": { + "amount": covenant_amount, + "scriptPublicKey": {"version": 0, "script": covenant_spk.script}, + "blockDaaScore": 0, + "isCoinbase": False, + "covenantId": None, + }, + }) + cov_outpoint = TransactionOutpoint( + Hash(covenant_outpoint["transactionId"]), + covenant_outpoint["index"], + ) + + # ── Mass / fee calculation ────────────────────────────────────────────── + ph_input = TransactionInput(cov_outpoint, b"", 0, 1, cov_utxo_ref) + ph_output = TransactionOutput(covenant_amount, recipient_spk) + ph_tx = Transaction(0, [ph_input], [ph_output], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + spend_amount = covenant_amount - fee + + # ── Build the unsigned transaction ───────────────────────────────────── + # (empty sig script — the sighash does not commit to the sig script) + inp = TransactionInput(cov_outpoint, b"", 0, 1, cov_utxo_ref) + out = TransactionOutput(spend_amount, recipient_spk) + tx_unsigned = Transaction(0, [inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) + + # ── Create the P2SH signature ─────────────────────────────────────────── + # create_input_signature returns hex of [OP_DATA65, 64_sig_bytes, sighash_type] + # pass the full bytes directly to pay_to_script_hash_signature_script + sig_hex = create_input_signature(tx_unsigned, 0, owner_key) + sig_bytes = bytes.fromhex(sig_hex) # full 66 bytes including push opcode + redeem_bytes = bytes.fromhex(redeem_script.to_string()) + unlock_script_hex = pay_to_script_hash_signature_script(redeem_bytes, sig_bytes) + + # ── Build and submit the final signed transaction ─────────────────────── + signed_inp = TransactionInput( + cov_outpoint, bytes.fromhex(unlock_script_hex), 0, 1, cov_utxo_ref + ) + tx = Transaction(0, [signed_inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) + + print(f" Fee: {fee} sompi") + print(f" Spend amount: {spend_amount} sompi") + + result = await client.submit_transaction({ + "transaction": tx, + "allowOrphan": False, + }) + txid = result["transactionId"] + print(f" Spend TXID: {txid}") + return txid + + +# ============================================================================= +# Helpers — UTXO subscription and confirmation waiting +# ============================================================================= + +async def wait_for_utxos(client: RpcClient, address) -> list: + """Poll every 5 s until at least one UTXO exists at `address`.""" + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + print(f" Waiting for funds — send KAS to:\n {address.to_string()}\n") + while True: + await asyncio.sleep(5) + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + +async def wait_for_confirmation(client: RpcClient, txid: str): + """Poll the mempool every 1 s; once the tx leaves it has been accepted.""" + print(f" Waiting for confirmation of {txid}") + while True: + await asyncio.sleep(1) + try: + await client.get_mempool_entry({"transactionId": txid, "includeOrphanPool": True}) + except Exception: + print(" Confirmed!") + return + + +# ============================================================================= +# Main +# ============================================================================= + +async def main(): + keypair = Keypair.random() + owner_key = PrivateKey(keypair.private_key) + funding_address = keypair.to_address(NETWORK_TYPE) + + print("=" * 60) + print("Forced-Recipient Covenant — KIP-17 Demo") + print("=" * 60) + print(f"\nFund this testnet-12 address (owner key):") + print(f" {funding_address.to_string()}") + print("\nThe script detects incoming funds automatically.\n") + + client = RpcClient(url=RPC_URL, network_id=NETWORK_ID) + await client.connect() + print(f"Connected to {NETWORK_ID}\n") + + # ── Wait for funding ───────────────────────────────────────────────────── + utxos = await wait_for_utxos(client, funding_address) + total = sum(u["utxoEntry"]["amount"] for u in utxos) + print(f"Received {total} sompi across {len(utxos)} UTXO(s)\n") + + # ── Step 1: Genesis (lock funds into covenant P2SH) ────────────────────── + print("[Step 1/2] Broadcasting genesis transaction…") + txid, outpoint, covenant_amount, redeem_script, covenant_spk = await genesis( + client, owner_key, utxos + ) + await wait_for_confirmation(client, txid) + print() + + # ── Step 2: Spend to incorrect address ─────────────────────────────────── + # ── (recipient enforced by the covenant script, this will reject) ──────── + print("[Step 2/3] Broadcasting spend transaction to INCORRECT address…") + print(" Will reject, destination address does not match script enforced address.") + try: + spend_txid = await spend( + client, + PrivateKey(Keypair.random().private_key), + outpoint, + covenant_amount, + redeem_script, + covenant_spk + ) + except Exception as e: + print(f" Transaction properly rejected with error: {e}") + print() + + # ── Step 3: Spend to correct address ───────────────────────────────────── + # ── (recipient enforced by the covenant script, this will succeed) ─────── + print("[Step 3/3] Broadcasting spend transaction to CORRECT address…") + print(" Will succeed, destination address matches script enforced address.") + spend_txid = await spend( + client, owner_key, outpoint, covenant_amount, redeem_script, covenant_spk + ) + print() + + print("=" * 60) + print("Demo complete!") + print(f" Genesis TXID : {txid}") + print(f" Spend TXID : {spend_txid}") + print("=" * 60) + + await client.disconnect() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/covenants/native_token.py b/examples/covenants/native_token.py new file mode 100644 index 00000000..b351b407 --- /dev/null +++ b/examples/covenants/native_token.py @@ -0,0 +1,528 @@ +""" +Fungible Token Covenant — Kaspa KIP-17 + KIP-20 Example +======================================================== +A minimal fungible token protocol where: + • Token lineage is tracked by covenant_id (genesis = mint, continuation = transfer) + • The token script enforces that each spend produces exactly one authorized + continuation output (singleton transfer — no splitting/merging in this demo) + • KAS amounts ARE the token amounts (1 sompi = 1 token unit) + • The covenant_id is the stable "token type" identifier + +This example demonstrates the many-to-one delegation pattern from KIP-20: + - Leader validation: enforces the token conservation rule (input == output amount) + using KAS amounts as token amounts, verified via OpTxInputAmount / OpTxOutputAmount + - Delegator validation: each non-leader input verifies that input[0] is the leader + +Lifecycle: + 1. Fund a keypair on testnet-12 + 2. Mint tx (genesis): funding UTXO → two token UTXOs with computed covenant_id + 3. Transfer tx: spend both token UTXOs (leader + delegator), produce two new ones + 4. Print token balances at each step +""" + +import asyncio +import os + +from kaspa import ( + CovenantBinding, + Keypair, + Opcodes, + PrivateKey, + RpcClient, + ScriptBuilder, + Transaction, + TransactionInput, + TransactionOutpoint, + TransactionOutput, + UtxoEntryReference, + address_from_script_public_key, + calculate_transaction_mass, + covenant_id, + create_input_signature, + pay_to_address_script, + pay_to_script_hash_signature_script, + sign_transaction, +) + +# ============================================================================= +# Configuration +# ============================================================================= + +NETWORK_ID = "testnet-12" +NETWORK_TYPE = "testnet" +RPC_URL = os.environ.get("KASPA_RPC_URL") +SUBNETWORK_ID = bytes(20) + +# Token amounts expressed in sompi (1 sompi = 1 token unit for this demo) +TOKEN_A_AMOUNT = 10_000_000 # 0.1 KAS worth of tokens +TOKEN_B_AMOUNT = 5_000_000 # 0.05 KAS worth of tokens + + +# ============================================================================= +# Token Script Construction +# ============================================================================= + +def build_leader_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: + """ + Leader branch script — validates conservation and authorizes the spend. + + The leader is the first covenant input (covenant input index 0). + + For a 2-input-2-output transfer this script: + 1. Gets the token_id (covenant_id of current input) via OpInputCovenantId + 2. Verifies this input IS covenant input 0 (leader position) + 3. Saves token_id to the alt-stack for reuse across OpCovInputIdx/OpCovOutputIdx calls + 4. Sums amounts of covenant inputs 0 and 1 → total_in + 5. Sums amounts of covenant outputs 0 and 1 → total_out + 6. Asserts total_in == total_out (conservation) + 7. Verifies owner signature + + The script is parameterized for exactly 2 inputs and 2 outputs. + In production, use a more general design with unrolled loops. + + AltStack is used to preserve token_id across repeated OpCovInputIdx / OpCovOutputIdx + calls (each of which pops the covenant_id argument off the main stack). + + Stack trace (initial main stack: [sig], altstack: []): + + OpTxInputIndex [sig, curr_idx] + OpInputCovenantId [sig, token_id] + # Leader check: covenant input 0 must be us + OpDup [sig, token_id, token_id] + OpFalse [sig, token_id, token_id, 0] + OpCovInputIdx [sig, token_id, in0_global_idx] + OpTxInputIndex [sig, token_id, in0_idx, curr_idx] + OpEqualVerify [sig, token_id] + # Save token_id to altstack + OpToAltStack [sig] altstack=[token_id] + # Amount of covenant input 0 + OpFromAltStack [sig, token_id] altstack=[] + OpDup [sig, token_id, token_id] + OpToAltStack [sig, token_id] altstack=[token_id] + OpFalse [sig, token_id, 0] + OpCovInputIdx [sig, in0_idx] + OpTxInputAmount [sig, amt_in0] + # Amount of covenant input 1 + OpFromAltStack [sig, amt_in0, token_id] altstack=[] + OpDup [sig, amt_in0, token_id, token_id] + OpToAltStack [sig, amt_in0, token_id] altstack=[token_id] + OpTrue [sig, amt_in0, token_id, 1] + OpCovInputIdx [sig, amt_in0, in1_idx] + OpTxInputAmount [sig, amt_in0, amt_in1] + OpAdd [sig, total_in] + # Amount of covenant output 0 + OpFromAltStack [sig, total_in, token_id] altstack=[] + OpDup [sig, total_in, token_id, token_id] + OpToAltStack [sig, total_in, token_id] altstack=[token_id] + OpFalse [sig, total_in, token_id, 0] + OpCovOutputIdx [sig, total_in, out0_idx] + OpTxOutputAmount [sig, total_in, amt_out0] + # Amount of covenant output 1 + OpFromAltStack [sig, total_in, amt_out0, token_id] altstack=[] + OpTrue [sig, total_in, amt_out0, token_id, 1] + OpCovOutputIdx [sig, total_in, amt_out0, out1_idx] + OpTxOutputAmount [sig, total_in, amt_out0, amt_out1] + OpAdd [sig, total_in, total_out] + # Conservation + OpEqualVerify [sig] + # Signature check + [sig, owner_pubkey] + OpCheckSig [true] + + KIP-20 opcodes: OpInputCovenantId, OpCovInputIdx, OpCovOutputIdx + KIP-17 opcodes: OpTxInputAmount, OpTxOutputAmount, OpTxInputIndex + """ + return ( + ScriptBuilder() + + # ── Get token_id and verify we are the leader ────────────────────── + .add_op(Opcodes.OpTxInputIndex) # [sig, curr_idx] + .add_op(Opcodes.OpInputCovenantId) # [sig, token_id] + .add_op(Opcodes.OpDup) # [sig, token_id, token_id] + .add_op(Opcodes.OpFalse) # [sig, token_id, token_id, 0] + .add_op(Opcodes.OpCovInputIdx) # [sig, token_id, in0_global_idx] + .add_op(Opcodes.OpTxInputIndex) # [sig, token_id, in0_idx, curr_idx] + .add_op(Opcodes.OpEqualVerify) # [sig, token_id] + + # ── Save token_id to altstack ────────────────────────────────────── + .add_op(Opcodes.OpToAltStack) # [sig] altstack=[token_id] + + # ── Amount of covenant input 0 ───────────────────────────────────── + .add_op(Opcodes.OpFromAltStack) # [sig, token_id] altstack=[] + .add_op(Opcodes.OpDup) # [sig, token_id, token_id] + .add_op(Opcodes.OpToAltStack) # [sig, token_id] altstack=[token_id] + .add_op(Opcodes.OpFalse) # [sig, token_id, 0] + .add_op(Opcodes.OpCovInputIdx) # [sig, in0_idx] + .add_op(Opcodes.OpTxInputAmount) # [sig, amt_in0] + + # ── Amount of covenant input 1 ───────────────────────────────────── + .add_op(Opcodes.OpFromAltStack) # [sig, amt_in0, token_id] altstack=[] + .add_op(Opcodes.OpDup) # [sig, amt_in0, token_id, token_id] + .add_op(Opcodes.OpToAltStack) # [sig, amt_in0, token_id] altstack=[token_id] + .add_op(Opcodes.OpTrue) # [sig, amt_in0, token_id, 1] + .add_op(Opcodes.OpCovInputIdx) # [sig, amt_in0, in1_idx] + .add_op(Opcodes.OpTxInputAmount) # [sig, amt_in0, amt_in1] + .add_op(Opcodes.OpAdd) # [sig, total_in] + + # ── Amount of covenant output 0 ──────────────────────────────────── + .add_op(Opcodes.OpFromAltStack) # [sig, total_in, token_id] altstack=[] + .add_op(Opcodes.OpDup) # [sig, total_in, token_id, token_id] + .add_op(Opcodes.OpToAltStack) # [sig, total_in, token_id] altstack=[token_id] + .add_op(Opcodes.OpFalse) # [sig, total_in, token_id, 0] + .add_op(Opcodes.OpCovOutputIdx) # [sig, total_in, out0_idx] + .add_op(Opcodes.OpTxOutputAmount) # [sig, total_in, amt_out0] + + # ── Amount of covenant output 1 ──────────────────────────────────── + .add_op(Opcodes.OpFromAltStack) # [sig, total_in, amt_out0, token_id] altstack=[] + .add_op(Opcodes.OpTrue) # [sig, total_in, amt_out0, token_id, 1] + .add_op(Opcodes.OpCovOutputIdx) # [sig, total_in, amt_out0, out1_idx] + .add_op(Opcodes.OpTxOutputAmount) # [sig, total_in, amt_out0, amt_out1] + .add_op(Opcodes.OpAdd) # [sig, total_in, total_out] + + # ── Conservation check ───────────────────────────────────────────── + .add_op(Opcodes.OpEqualVerify) # [sig] + + # ── Verify owner signature ───────────────────────────────────────── + .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + ) + + +def build_delegator_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: + """ + Delegator branch script — just verifies that a leader exists and signs. + + A delegator checks that the first covenant input (the leader) has a + lower or equal index than itself, then signs. + + KIP-20 opcodes: OpInputCovenantId, OpCovInputIdx + KIP-17 opcodes: OpTxInputIndex + """ + return ( + ScriptBuilder() + + # Get this input's covenant_id (token_id) + .add_op(Opcodes.OpTxInputIndex) + .add_op(Opcodes.OpInputCovenantId) # [token_id] + + # Get the leader (covenant input 0) global index + .add_op(Opcodes.OpFalse) # [token_id, 0] + .add_op(Opcodes.OpCovInputIdx) # [leader_global_idx] + + # Verify leader_idx < our_idx (leader comes first) + .add_op(Opcodes.OpTxInputIndex) # [leader_idx, our_idx] + .add_op(Opcodes.OpLessThan) # [leader_idx < our_idx] → true if delegator + .add_op(Opcodes.OpVerify) # assert true + + # Verify signature + .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + ) + + +def build_token_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: + """ + Build a simplified single-input token script for the 1-in-1-out case. + + For the mint demo (minting two UTXOs, then single transfers), each + token UTXO uses a simpler script that: + 1. Verifies exactly 1 authorized continuation output exists + 2. Verifies the owner signature + + This is the singleton pattern — each token UTXO can only produce one + continuation token UTXO per spend. + + KIP-20 opcodes: OpAuthOutputCount + KIP-17 opcodes: OpTxInputIndex + """ + return ( + ScriptBuilder() + # Enforce exactly 1 authorized continuation output + .add_op(Opcodes.OpTxInputIndex) # push current input index + .add_op(Opcodes.OpAuthOutputCount) # count auth outputs for this input + .add_op(Opcodes.OpTrue) # push 1 + .add_op(Opcodes.OpEqualVerify) # must have exactly 1 continuation output + + # Verify owner signature + .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + ) + + +# ============================================================================= +# Mint — genesis transaction creating two token UTXOs +# ============================================================================= + +async def mint(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): + """ + Create and broadcast the mint (genesis) transaction. + + Creates two token UTXOs sharing the same covenant_id. + Both UTXOs use the same token script (same P2SH scriptPublicKey). + The covenant_id is what identifies them as the same token type. + + Returns: + (txid, [(outpoint, amount), ...], token_spk, cov_id) + """ + keypair = Keypair.from_private_key(owner_key) + owner_pubkey_hex = keypair.xonly_public_key + + # All token UTXOs share the same P2SH scriptPublicKey + token_script = build_token_script(owner_pubkey_hex) + token_spk = token_script.create_pay_to_script_hash_script() + token_address = address_from_script_public_key(token_spk, NETWORK_TYPE) + print(f" Token P2SH address : {token_address.to_string()}") + + # Select the largest funding UTXO + funding = max(funding_utxos, key=lambda u: u["utxoEntry"]["amount"]) + funding_amount = funding["utxoEntry"]["amount"] + funding_utxo_ref = UtxoEntryReference.from_dict(funding) + funding_outpoint = TransactionOutpoint( + funding["outpoint"]["transactionId"], + funding["outpoint"]["index"], + ) + + # ── Mass / fee calculation (version 1 required) ───────────────────────── + # Placeholder outputs for mass estimation (without covenant bindings) + ph_out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk) + ph_out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk) + ph_in = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + ph_tx = Transaction(1, [ph_in], [ph_out_a, ph_out_b], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + total_tokens = TOKEN_A_AMOUNT + TOKEN_B_AMOUNT + if funding_amount < total_tokens + fee: + raise ValueError( + f"Insufficient funds: have {funding_amount} sompi, " + f"need {total_tokens + fee} sompi" + ) + + # ── Compute genesis covenant_id ───────────────────────────────────────── + # auth_outputs are the outputs WITHOUT covenant bindings (in output order) + auth_out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk) + auth_out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk) + cov_id = covenant_id(funding_outpoint, [auth_out_a, auth_out_b]) + print(f" Token covenant_id : {cov_id}") + + # ── Create covenant-bound token outputs ───────────────────────────────── + binding_a = CovenantBinding(0, cov_id) # both outputs authorized by input 0 + binding_b = CovenantBinding(0, cov_id) + out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk, covenant_id=binding_a) + out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk, covenant_id=binding_b) + + inp = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + tx = Transaction(1, [inp], [out_a, out_b], 0, SUBNETWORK_ID, 0, b"", mass) + + # Standard P2PK signing for the funding input + signed_tx = sign_transaction(tx, [owner_key], True) + + print(f" Fee: {fee} sompi | Token A: {TOKEN_A_AMOUNT} | Token B: {TOKEN_B_AMOUNT}") + + result = await client.submit_transaction({ + "transaction": signed_tx, + "allowOrphan": False, + }) + txid = result["transactionId"] + print(f" Mint TXID: {txid}") + + token_outpoints = [ + ({"transactionId": txid, "index": 0}, TOKEN_A_AMOUNT), + ({"transactionId": txid, "index": 1}, TOKEN_B_AMOUNT), + ] + return txid, token_outpoints, token_spk, cov_id + + +# ============================================================================= +# Transfer — spend a single token UTXO, produce one continuation +# ============================================================================= + +async def transfer( + client: RpcClient, + owner_key: PrivateKey, + token_outpoint: dict, + token_amount: int, + token_script: ScriptBuilder, + token_spk, + cov_id, +) -> str: + """ + Transfer a token UTXO. The singleton token script enforces exactly one + authorized continuation output. + + scriptSig: + """ + keypair = Keypair.from_private_key(owner_key) + token_address = address_from_script_public_key(token_spk, NETWORK_TYPE) + + token_utxo_ref = UtxoEntryReference.from_dict({ + "address": token_address.to_string(), + "outpoint": { + "transactionId": token_outpoint["transactionId"], + "index": token_outpoint["index"], + }, + "utxoEntry": { + "amount": token_amount, + "scriptPublicKey": {"version": 0, "script": token_spk.script}, + "blockDaaScore": 0, + "isCoinbase": False, + "covenantId": str(cov_id), + }, + }) + tok_outpoint = TransactionOutpoint( + token_outpoint["transactionId"], + token_outpoint["index"], + ) + + # The continuation output carries the same covenant_id + # (authorizing_input = 0 — the index of this token input in the spending tx) + binding = CovenantBinding(0, cov_id) + cont_output = TransactionOutput(token_amount, token_spk, covenant_id=binding) + + # ── Mass / fee ────────────────────────────────────────────────────────── + ph_in = TransactionInput(tok_outpoint, b"", 0, 1, token_utxo_ref) + ph_tx = Transaction(1, [ph_in], [cont_output], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + + # Reduce the continuation amount by the fee + cont_amount = token_amount - fee + binding = CovenantBinding(0, cov_id) + cont_output = TransactionOutput(cont_amount, token_spk, covenant_id=binding) + + # ── Build unsigned transaction ────────────────────────────────────────── + inp = TransactionInput(tok_outpoint, b"", 0, 1, token_utxo_ref) + tx_unsigned = Transaction(1, [inp], [cont_output], 0, SUBNETWORK_ID, 0, b"", mass) + + # ── Build the P2SH unlocking script ──────────────────────────────────── + # create_input_signature returns 66-byte hex: [0x41=OP_DATA65, 64_sig, sighash] + # The bytes are already a valid script push — pass raw to the helper. + sig_hex = create_input_signature(tx_unsigned, 0, owner_key) + sig_bytes = bytes.fromhex(sig_hex) + token_script_bytes = bytes.fromhex(token_script.to_string()) + unlock_script_hex = pay_to_script_hash_signature_script(token_script_bytes, sig_bytes) + + # ── Build and submit the final transaction ────────────────────────────── + signed_inp = TransactionInput( + tok_outpoint, bytes.fromhex(unlock_script_hex), 0, 1, token_utxo_ref + ) + tx = Transaction(1, [signed_inp], [cont_output], 0, SUBNETWORK_ID, 0, b"", mass) + + print(f" Fee: {fee} sompi | Token amount after transfer: {cont_amount} sompi") + + result = await client.submit_transaction({ + "transaction": tx, + "allowOrphan": False, + }) + txid = result["transactionId"] + print(f" Transfer TXID: {txid}") + return txid, cont_amount + + +# ============================================================================= +# Helpers +# ============================================================================= + +async def wait_for_utxos(client: RpcClient, address) -> list: + """Poll every 5 s until at least one UTXO exists at `address`.""" + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + print(f" Waiting for funds — send KAS to:\n {address.to_string()}\n") + while True: + await asyncio.sleep(5) + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + +async def wait_for_confirmation(client: RpcClient, txid: str): + """Poll the mempool every 1 s; once the tx leaves it has been accepted.""" + print(f" Waiting for confirmation of {txid[:16]}…") + while True: + await asyncio.sleep(1) + try: + await client.get_mempool_entry({"transactionId": txid, "includeOrphanPool": True}) + except Exception: + print(" Confirmed!") + return + + +# ============================================================================= +# Main +# ============================================================================= + +async def main(): + keypair = Keypair.random() + owner_key = PrivateKey(keypair.private_key) + funding_address = keypair.to_address(NETWORK_TYPE) + + print("=" * 60) + print("Fungible Token Covenant — KIP-17 + KIP-20 Demo") + print("=" * 60) + print(f"\nFund this testnet-12 address (owner key):") + print(f" {funding_address.to_string()}") + print(f"\nNeeds at least {TOKEN_A_AMOUNT + TOKEN_B_AMOUNT} sompi + fees") + print("The script detects incoming funds automatically.\n") + + client = RpcClient(url=RPC_URL, network_id=NETWORK_ID) + await client.connect() + print(f"Connected to {NETWORK_ID}\n") + + # ── Wait for funding ───────────────────────────────────────────────────── + utxos = await wait_for_utxos(client, funding_address) + total = sum(u["utxoEntry"]["amount"] for u in utxos) + print(f"Received {total} sompi across {len(utxos)} UTXO(s)\n") + + # ── Step 1: Mint (genesis) ─────────────────────────────────────────────── + print("[Step 1/3] Broadcasting mint (genesis) transaction…") + mint_txid, token_outpoints, token_spk, cov_id = await mint( + client, owner_key, utxos + ) + await wait_for_confirmation(client, mint_txid) + print() + + # Rebuild the token script for signing (same parameters) + keypair2 = Keypair.from_private_key(owner_key) + token_script = build_token_script(keypair2.xonly_public_key) + + # ── Step 2: Transfer token A ───────────────────────────────────────────── + print("[Step 2/3] Transferring token A…") + outpoint_a, amount_a = token_outpoints[0] + print(f" Token A balance before: {amount_a} sompi") + transfer_txid_a, new_amount_a = await transfer( + client, owner_key, outpoint_a, amount_a, token_script, token_spk, cov_id + ) + await wait_for_confirmation(client, transfer_txid_a) + print(f" Token A balance after : {new_amount_a} sompi") + print() + + # ── Step 3: Transfer token B ───────────────────────────────────────────── + print("[Step 3/3] Transferring token B…") + outpoint_b, amount_b = token_outpoints[1] + print(f" Token B balance before: {amount_b} sompi") + transfer_txid_b, new_amount_b = await transfer( + client, owner_key, outpoint_b, amount_b, token_script, token_spk, cov_id + ) + print(f" Token B balance after : {new_amount_b} sompi") + print() + + print("=" * 60) + print("Demo complete!") + print(f" Token covenant_id : {cov_id}") + print(f" Mint TXID : {mint_txid}") + print(f" Transfer A TXID : {transfer_txid_a}") + print(f" Transfer B TXID : {transfer_txid_b}") + print(f" Token A balance : {new_amount_a} sompi") + print(f" Token B balance : {new_amount_b} sompi") + print("=" * 60) + + await client.disconnect() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/covenants/vault.py b/examples/covenants/vault.py new file mode 100644 index 00000000..cb7bf76a --- /dev/null +++ b/examples/covenants/vault.py @@ -0,0 +1,487 @@ +""" +Smart Vault Covenant — Kaspa KIP-17 + KIP-20 Example +===================================================== +A vault UTXO with two spending paths: + • Emergency sweep (any time): send all funds to a pre-committed recovery address. + • Normal withdrawal (after locktime): owner may spend freely, but any remainder + must continue the covenant (singleton pattern — exactly one vault UTXO at all times). + +Uses KIP-20 covenant_id for stable vault lineage. The singleton pattern means +the UTXO set always contains at most one UTXO with this covenant_id. +""" + +import asyncio +import os + +from kaspa import ( + CovenantBinding, + Hash, + Keypair, + Opcodes, + PrivateKey, + RpcClient, + ScriptBuilder, + Transaction, + TransactionInput, + TransactionOutpoint, + TransactionOutput, + UtxoEntryReference, + address_from_script_public_key, + calculate_transaction_mass, + covenant_id, + create_input_signature, + pay_to_address_script, + sign_transaction, +) + +# ============================================================================= +# Configuration +# ============================================================================= + +NETWORK_ID = "testnet-12" +NETWORK_TYPE = "testnet" +RPC_URL = os.environ.get("KASPA_RPC_URL") +SUBNETWORK_ID = bytes(20) + +# Locktime for the normal withdrawal path (DAA score). +# testnet-12 produces ~10 block/sec so 100 blocks = approx 10 seconds +LOCKTIME_BLOCKS = 100 + + +# ============================================================================= +# Covenant Script Construction +# ============================================================================= + +def build_vault_redeem_script( + recovery_spk, + recovery_xonly_pubkey_hex: str, + owner_xonly_pubkey_hex: str, + locktime: int, +) -> ScriptBuilder: + """ + Build the vault P2SH redeem script. + + Branch selection: the integer on top of the stack when the redeem script + begins execution selects the spending path: + 1 (OP_TRUE) → Emergency sweep (OpIf body) + 0 (OP_FALSE) → Normal withdrawal (OpElse body) + + ── Emergency branch ──────────────────────────────────────────────────────── + Initial stack coming into redeem script execution: [, 1] + + OpIf pop 1, enter emergency branch → [recovery_sig] + OpFalse OpTxOutputSpk push output[0].spk → [sig, spk0] + push hardcoded recovery SPK → [sig, spk0, rec_spk] + OpEqualVerify assert spk0 == rec_spk → [sig] + OpTxOutputCount OpTrue push N, push 1 → [sig, N, 1] + OpEqualVerify assert N == 1 → [sig] + OpCheckSig verify recovery sig → [true] + OpElse ... OpEndIf + + ── Normal withdrawal branch ──────────────────────────────────────────────── + Initial stack: [, 0] + + OpIf ... OpElse + OpCheckLockTimeVerify OpDrop enforce locktime → [sig] + OpTxInputIndex push current input index → [sig, idx] + OpAuthOutputCount count authorized outputs → [sig, count] + OpTrue OpEqualVerify assert count == 1 → [sig] + OpCheckSig verify owner sig → [true] + OpEndIf + + KIP-17: OpTxOutputSpk, OpTxOutputCount, OpCheckLockTimeVerify + KIP-20: OpAuthOutputCount + """ + # OpTxOutputSpk pushes version (2 bytes, big-endian) + script bytes. + # bytes(spk) returns only the script; prepend the version to match. + recovery_spk_bytes = recovery_spk.version.to_bytes(2, 'big') + bytes(recovery_spk) + + return ( + ScriptBuilder() + .add_op(Opcodes.OpIf) + + # ── Emergency path ─────────────────────────────────────────────── + .add_op(Opcodes.OpFalse) # push 0 (output index 0) + .add_op(Opcodes.OpTxOutputSpk) # push output[0].spk + .add_data(recovery_spk_bytes) # push hardcoded recovery SPK + .add_op(Opcodes.OpEqualVerify) # assert: destination == recovery SPK + .add_op(Opcodes.OpTxOutputCount) # push total number of outputs + .add_op(Opcodes.OpTrue) # push 1 + .add_op(Opcodes.OpEqualVerify) # assert: exactly 1 output + .add_data(bytes.fromhex(recovery_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + + .add_op(Opcodes.OpElse) + + # ── Normal withdrawal path ──────────────────────────────────────── + .add_lock_time(locktime) + .add_op(Opcodes.OpCheckLockTimeVerify) + .add_op(Opcodes.OpDrop) + .add_op(Opcodes.OpTxInputIndex) # push current input index + .add_op(Opcodes.OpAuthOutputCount) # count authorized continuation outputs + .add_op(Opcodes.OpTrue) # push 1 + .add_op(Opcodes.OpEqualVerify) # assert: exactly 1 authorized output + .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) + .add_op(Opcodes.OpCheckSig) + + .add_op(Opcodes.OpEndIf) + ) + + +def _make_p2sh_unlock(sig_hex: str, branch_opcode: int, redeem_bytes: bytes) -> bytes: + """ + Build a P2SH unlock script for a branch-selecting covenant. + + The unlock script format is: + [sig_push][branch_opcode][redeem_script_push] + + sig_hex is the 66-byte hex from create_input_signature: + [OP_DATA65=0x41][64_schnorr_sig][sighash_type] + + Because the first byte (0x41 = OP_DATA65) is already a valid script push + opcode, the 66 bytes are appended as-is. When the script engine executes + them, OP_DATA65 pushes the following 65 bytes (sig64 + sighash) onto the + stack — which is exactly the 65-byte value OpCheckSig expects. + + branch_opcode: 0x51 (OP_TRUE=1) → emergency, 0x00 (OP_FALSE=0) → normal + """ + sig_bytes = bytes.fromhex(sig_hex) # pre-encoded: [0x41, sig64, sighash] + branch_byte = bytes([branch_opcode]) + redeem_push = bytes.fromhex( + ScriptBuilder().add_data(redeem_bytes).to_string() + ) + return sig_bytes + branch_byte + redeem_push + + +# ============================================================================= +# Genesis — create the vault UTXO with a KIP-20 covenant_id +# ============================================================================= + +async def genesis(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): + """ + Create and broadcast the genesis (vault creation) transaction. + + Computes the vault's covenant_id from the funding outpoint and the + planned vault output, then creates the covenant-bound output. + """ + keypair = Keypair.from_private_key(owner_key) + owner_pubkey_hex = keypair.xonly_public_key + + # Recovery and owner are the same keypair in this demo. + recovery_address = keypair.to_address(NETWORK_TYPE) + recovery_spk = pay_to_address_script(recovery_address) + recovery_pubkey_hex = owner_pubkey_hex + + redeem_script = build_vault_redeem_script( + recovery_spk, recovery_pubkey_hex, owner_pubkey_hex, LOCKTIME_BLOCKS + ) + vault_spk = redeem_script.create_pay_to_script_hash_script() + vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) + print(f" Vault P2SH address : {vault_address.to_string()}") + + funding = max(funding_utxos, key=lambda u: u["utxoEntry"]["amount"]) + funding_amount = funding["utxoEntry"]["amount"] + funding_utxo_ref = UtxoEntryReference.from_dict(funding) + funding_outpoint = TransactionOutpoint( + Hash(funding["outpoint"]["transactionId"]), + funding["outpoint"]["index"], + ) + + # ── Mass / fee (version 1 required for covenant outputs) ──────────────── + ph_out = TransactionOutput(funding_amount, vault_spk) + ph_in = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + ph_tx = Transaction(1, [ph_in], [ph_out], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + vault_amount = funding_amount - fee + + # ── Compute genesis covenant_id ───────────────────────────────────────── + # covenant_id is hashed from (authorizing outpoint, auth_outputs) + # auth_outputs do NOT include the covenant binding (to avoid self-reference) + auth_output = TransactionOutput(vault_amount, vault_spk) + cov_id = covenant_id(funding_outpoint, [auth_output]) + print(f" Vault covenant_id : {cov_id}") + + # ── Create the covenant-bound vault output ────────────────────────────── + binding = CovenantBinding(0, cov_id) + vault_output = TransactionOutput(vault_amount, vault_spk, covenant_id=binding) + + inp = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) + tx = Transaction(1, [inp], [vault_output], 0, SUBNETWORK_ID, 0, b"", mass) + signed_tx = sign_transaction(tx, [owner_key], True) + + print(f" Fee: {fee} sompi | Vault: {vault_amount} sompi") + + result = await client.submit_transaction({"transaction": signed_tx, "allowOrphan": False}) + txid = result["transactionId"] + print(f" Genesis TXID: {txid}") + + return txid, {"transactionId": txid, "index": 0}, vault_amount, redeem_script, vault_spk, cov_id + + +# ============================================================================= +# Emergency Sweep — spend via the fast exit path +# ============================================================================= + +async def emergency_sweep( + client: RpcClient, + owner_key: PrivateKey, + vault_outpoint: dict, + vault_amount: int, + redeem_script: ScriptBuilder, + vault_spk, + cov_id, +) -> str: + """ + Sweep the vault to the recovery address using the emergency branch. + + Unlock script: [sig_push] [OP_TRUE=1] [redeem_script_push] + Stack at redeem script start: [sig, 1] + OpIf sees 1 → emergency branch. + """ + keypair = Keypair.from_private_key(owner_key) + recovery_address = keypair.to_address(NETWORK_TYPE) + recovery_spk = pay_to_address_script(recovery_address) + vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) + + vault_utxo_ref = UtxoEntryReference.from_dict({ + "address": vault_address.to_string(), + "outpoint": { + "transactionId": vault_outpoint["transactionId"], + "index": vault_outpoint["index"], + }, + "utxoEntry": { + "amount": vault_amount, + "scriptPublicKey": {"version": 0, "script": vault_spk.script}, + "blockDaaScore": 0, + "isCoinbase": False, + "covenantId": str(cov_id), + }, + }) + cov_outpoint = TransactionOutpoint( + Hash(vault_outpoint["transactionId"]), vault_outpoint["index"] + ) + + # ── Mass / fee ────────────────────────────────────────────────────────── + ph_in = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) + ph_out = TransactionOutput(vault_amount, recovery_spk) + # Emergency sweep has no covenant outputs → version 0 acceptable + ph_tx = Transaction(0, [ph_in], [ph_out], 0, SUBNETWORK_ID, 0, b"", 0) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + sweep_amount = vault_amount - fee + + # ── Build unsigned transaction & sign ─────────────────────────────────── + inp = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) + out = TransactionOutput(sweep_amount, recovery_spk) + tx_unsigned = Transaction(0, [inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) + + sig_hex = create_input_signature(tx_unsigned, 0, owner_key) + redeem_bytes = bytes.fromhex(redeem_script.to_string()) + + # Build unlock script manually: + # [sig_push=pre-encoded 66 bytes] [0x51=OP_TRUE] [redeem_script_push] + # 0x51 is OP_TRUE → pushes 1 → OpIf enters the emergency branch + unlock_bytes = _make_p2sh_unlock(sig_hex, 0x51, redeem_bytes) + + signed_inp = TransactionInput(cov_outpoint, unlock_bytes, 0, 1, vault_utxo_ref) + tx = Transaction(0, [signed_inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) + + print(f" Fee: {fee} sompi | Sweep: {sweep_amount} sompi") + + result = await client.submit_transaction({"transaction": tx, "allowOrphan": False}) + txid = result["transactionId"] + print(f" Emergency sweep TXID: {txid}") + return txid + + +# ============================================================================= +# Normal Withdrawal — continue the vault covenant after locktime +# ============================================================================= + +async def normal_withdrawal( + client: RpcClient, + owner_key: PrivateKey, + vault_outpoint: dict, + vault_amount: int, + redeem_script: ScriptBuilder, + vault_spk, + cov_id, + withdraw_amount: int, + current_daa_score: int, +) -> str: + """ + Withdraw `withdraw_amount` from the vault after the locktime passes. + + The remainder stays in a continuation vault UTXO (same covenant_id, + same vault P2SH script). The lock_time field on the transaction must + be >= LOCKTIME_BLOCKS for OpCheckLockTimeVerify to pass. + + Unlock script: [sig_push] [OP_FALSE=0] [redeem_script_push] + Stack at redeem script start: [sig, 0] + OpIf sees 0 → else branch (normal withdrawal). + """ + keypair = Keypair.from_private_key(owner_key) + dest_address = keypair.to_address(NETWORK_TYPE) + dest_spk = pay_to_address_script(dest_address) + vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) + + vault_utxo_ref = UtxoEntryReference.from_dict({ + "address": vault_address.to_string(), + "outpoint": { + "transactionId": vault_outpoint["transactionId"], + "index": vault_outpoint["index"], + }, + "utxoEntry": { + "amount": vault_amount, + "scriptPublicKey": {"version": 0, "script": vault_spk.script}, + "blockDaaScore": 0, + "isCoinbase": False, + "covenantId": str(cov_id), + }, + }) + cov_outpoint = TransactionOutpoint( + Hash(vault_outpoint["transactionId"]), vault_outpoint["index"] + ) + + remainder = vault_amount - withdraw_amount + # Continuation output: same vault script, same covenant_id + binding = CovenantBinding(0, cov_id) + cont_output = TransactionOutput(remainder, vault_spk, covenant_id=binding) + dest_output = TransactionOutput(withdraw_amount, dest_spk) + + # ── Mass / fee ────────────────────────────────────────────────────────── + ph_in = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) + # lock_time must satisfy CLTV: set it to current_daa_score (>= LOCKTIME_BLOCKS) + ph_tx = Transaction( + 1, [ph_in], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", 0 + ) + mass = calculate_transaction_mass(NETWORK_ID, ph_tx) + fee_rates = await client.get_fee_estimate() + fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) + final_withdraw = withdraw_amount - fee + dest_output = TransactionOutput(final_withdraw, dest_spk) + + # ── Build unsigned transaction & sign ─────────────────────────────────── + inp = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) + tx_unsigned = Transaction( + 1, [inp], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", mass + ) + + sig_hex = create_input_signature(tx_unsigned, 0, owner_key) + redeem_bytes = bytes.fromhex(redeem_script.to_string()) + + # Build unlock script: + # [sig_push] [0x00=OP_FALSE] [redeem_script_push] + # 0x00 is OP_FALSE → pushes empty bytes (0) → OpIf goes to else branch + unlock_bytes = _make_p2sh_unlock(sig_hex, 0x00, redeem_bytes) + + signed_inp = TransactionInput(cov_outpoint, unlock_bytes, 0, 1, vault_utxo_ref) + tx = Transaction( + 1, [signed_inp], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", mass + ) + + print(f" Fee: {fee} sompi | Withdrawal: {final_withdraw} | Remainder: {remainder}") + + result = await client.submit_transaction({"transaction": tx, "allowOrphan": False}) + txid = result["transactionId"] + print(f" Normal withdrawal TXID: {txid}") + return txid + + +# ============================================================================= +# Helpers +# ============================================================================= + +async def wait_for_utxos(client: RpcClient, address) -> list: + """Poll every 5 s until at least one UTXO exists at `address`.""" + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + print(f" Waiting for funds — send KAS to:\n {address.to_string()}\n") + while True: + await asyncio.sleep(5) + result = await client.get_utxos_by_addresses({"addresses": [address]}) + entries = result.get("entries", []) + if entries: + return entries + + +async def wait_for_confirmation(client: RpcClient, txid: str): + """Poll the mempool every 1 s; once the tx leaves it has been accepted.""" + print(f" Waiting for confirmation of {txid[:16]}…") + while True: + await asyncio.sleep(1) + try: + await client.get_mempool_entry({"transactionId": txid, "includeOrphanPool": True}) + except Exception: + print(" Confirmed!") + return + + +# ============================================================================= +# Main +# ============================================================================= + +async def main(): + keypair = Keypair.random() + owner_key = PrivateKey(keypair.private_key) + funding_address = keypair.to_address(NETWORK_TYPE) + + print("=" * 60) + print("Smart Vault Covenant — KIP-17 + KIP-20 Demo") + print("=" * 60) + print(f"\nFund this testnet-12 address (owner key):") + print(f" {funding_address.to_string()}") + print("\nThe script detects incoming funds automatically.\n") + + client = RpcClient(url=RPC_URL, network_id=NETWORK_ID) + await client.connect() + print(f"Connected to {NETWORK_ID}\n") + + utxos = await wait_for_utxos(client, funding_address) + total = sum(u["utxoEntry"]["amount"] for u in utxos) + print(f"Received {total} sompi across {len(utxos)} UTXO(s)\n") + + print("[Step 1/2] Broadcasting genesis (vault creation) transaction…") + txid, outpoint, vault_amount, redeem_script, vault_spk, cov_id = await genesis( + client, owner_key, utxos + ) + await wait_for_confirmation(client, txid) + print() + + print("[Step 2/2] Broadcasting emergency sweep transaction…") + sweep_txid = await emergency_sweep( + client, owner_key, outpoint, vault_amount, redeem_script, vault_spk, cov_id + ) + print() + + # ── Commented out: Normal withdrawal demo ───────────────────────────────── + # To test the normal path, re-fund and uncomment: + # + # from kaspa import RpcClient as _RpcClient + # info = await client.get_block_dag_info() + # daa = info["virtualDaaScore"] + # print("[Step 3/3] Normal withdrawal (after locktime)…") + # withdrawal_txid = await normal_withdrawal( + # client, owner_key, outpoint, vault_amount, + # redeem_script, vault_spk, cov_id, + # withdraw_amount=vault_amount // 2, + # current_daa_score=daa, + # ) + + print("=" * 60) + print("Demo complete!") + print(f" Genesis TXID : {txid}") + print(f" Emergency sweep TXID: {sweep_txid}") + print("=" * 60) + + await client.disconnect() + + +if __name__ == "__main__": + asyncio.run(main()) From 1b788b685b281f015ba2b9e3b9fd7a22eae5d99e Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 19:51:18 -0500 Subject: [PATCH 13/20] covenant example updates --- examples/covenants/forced_recipient.py | 27 +++++++++++++------------- examples/covenants/native_token.py | 5 +++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/covenants/forced_recipient.py b/examples/covenants/forced_recipient.py index ca7f1922..54d1aa5a 100644 --- a/examples/covenants/forced_recipient.py +++ b/examples/covenants/forced_recipient.py @@ -2,9 +2,10 @@ Forced-Recipient Covenant — Kaspa KIP-17 Example ================================================= A UTXO whose redeem script enforces that it can *only* be spent to a single -predetermined address — no matter who has the private key to the input, the -destination is locked. This is the simplest useful covenant: output -introspection with zero on-chain state. +predetermined address. + +AI disclaimer - Credit to Claude for large parts of this example :) +We just provided the building blocks. """ import asyncio @@ -50,17 +51,15 @@ def build_covenant_redeem_script(recipient_spk, owner_xonly_pubkey_hex: str) -> Execution trace when spending (initial stack: []): - OpTxOutputCount push number of outputs → [sig, N] - OpTrue push 1 → [sig, N, 1] - OpEqualVerify assert N == 1 → [sig] - OpFalse push 0 (output index) → [sig, 0] - OpTxOutputSpk push output[0].spk → [sig, spk0] - push hardcoded SPK → [sig, spk0, rec_spk] - OpEqualVerify assert spk0 == rec_spk → [sig] - push 32-byte x-only key → [sig, pubkey] - OpCheckSig verify Schnorr sig → [true] - - KIP-17 opcodes used: OpTxOutputCount, OpTxOutputSpk + OpTxOutputCount push number of outputs → [sig, N] + OpTrue push 1 → [sig, N, 1] + OpEqualVerify assert N == 1 → [sig] + OpFalse push 0 (output index) → [sig, 0] + OpTxOutputSpk push output[0].spk → [sig, spk0] + push hardcoded SPK → [sig, spk0, rec_spk] + OpEqualVerify assert spk0 == rec_spk → [sig] + push 32-byte x-only key → [sig, pubkey] + OpCheckSig verify Schnorr sig → [true] """ # OpTxOutputSpk pushes version (2 bytes, big-endian) + script bytes. # bytes(spk) returns only the script; prepend the version to match. diff --git a/examples/covenants/native_token.py b/examples/covenants/native_token.py index b351b407..d073ff62 100644 --- a/examples/covenants/native_token.py +++ b/examples/covenants/native_token.py @@ -25,6 +25,7 @@ from kaspa import ( CovenantBinding, + Hash, Keypair, Opcodes, PrivateKey, @@ -278,7 +279,7 @@ async def mint(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): funding_amount = funding["utxoEntry"]["amount"] funding_utxo_ref = UtxoEntryReference.from_dict(funding) funding_outpoint = TransactionOutpoint( - funding["outpoint"]["transactionId"], + Hash(funding["outpoint"]["transactionId"]), funding["outpoint"]["index"], ) @@ -370,7 +371,7 @@ async def transfer( }, }) tok_outpoint = TransactionOutpoint( - token_outpoint["transactionId"], + Hash(token_outpoint["transactionId"]), token_outpoint["index"], ) From 216e06aa195fa65f60df8bd9ba49129d0e9f2609 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 19 Feb 2026 19:55:31 -0500 Subject: [PATCH 14/20] remove incomplete examples --- examples/covenants/native_token.py | 529 ----------------------------- examples/covenants/vault.py | 487 -------------------------- 2 files changed, 1016 deletions(-) delete mode 100644 examples/covenants/native_token.py delete mode 100644 examples/covenants/vault.py diff --git a/examples/covenants/native_token.py b/examples/covenants/native_token.py deleted file mode 100644 index d073ff62..00000000 --- a/examples/covenants/native_token.py +++ /dev/null @@ -1,529 +0,0 @@ -""" -Fungible Token Covenant — Kaspa KIP-17 + KIP-20 Example -======================================================== -A minimal fungible token protocol where: - • Token lineage is tracked by covenant_id (genesis = mint, continuation = transfer) - • The token script enforces that each spend produces exactly one authorized - continuation output (singleton transfer — no splitting/merging in this demo) - • KAS amounts ARE the token amounts (1 sompi = 1 token unit) - • The covenant_id is the stable "token type" identifier - -This example demonstrates the many-to-one delegation pattern from KIP-20: - - Leader validation: enforces the token conservation rule (input == output amount) - using KAS amounts as token amounts, verified via OpTxInputAmount / OpTxOutputAmount - - Delegator validation: each non-leader input verifies that input[0] is the leader - -Lifecycle: - 1. Fund a keypair on testnet-12 - 2. Mint tx (genesis): funding UTXO → two token UTXOs with computed covenant_id - 3. Transfer tx: spend both token UTXOs (leader + delegator), produce two new ones - 4. Print token balances at each step -""" - -import asyncio -import os - -from kaspa import ( - CovenantBinding, - Hash, - Keypair, - Opcodes, - PrivateKey, - RpcClient, - ScriptBuilder, - Transaction, - TransactionInput, - TransactionOutpoint, - TransactionOutput, - UtxoEntryReference, - address_from_script_public_key, - calculate_transaction_mass, - covenant_id, - create_input_signature, - pay_to_address_script, - pay_to_script_hash_signature_script, - sign_transaction, -) - -# ============================================================================= -# Configuration -# ============================================================================= - -NETWORK_ID = "testnet-12" -NETWORK_TYPE = "testnet" -RPC_URL = os.environ.get("KASPA_RPC_URL") -SUBNETWORK_ID = bytes(20) - -# Token amounts expressed in sompi (1 sompi = 1 token unit for this demo) -TOKEN_A_AMOUNT = 10_000_000 # 0.1 KAS worth of tokens -TOKEN_B_AMOUNT = 5_000_000 # 0.05 KAS worth of tokens - - -# ============================================================================= -# Token Script Construction -# ============================================================================= - -def build_leader_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: - """ - Leader branch script — validates conservation and authorizes the spend. - - The leader is the first covenant input (covenant input index 0). - - For a 2-input-2-output transfer this script: - 1. Gets the token_id (covenant_id of current input) via OpInputCovenantId - 2. Verifies this input IS covenant input 0 (leader position) - 3. Saves token_id to the alt-stack for reuse across OpCovInputIdx/OpCovOutputIdx calls - 4. Sums amounts of covenant inputs 0 and 1 → total_in - 5. Sums amounts of covenant outputs 0 and 1 → total_out - 6. Asserts total_in == total_out (conservation) - 7. Verifies owner signature - - The script is parameterized for exactly 2 inputs and 2 outputs. - In production, use a more general design with unrolled loops. - - AltStack is used to preserve token_id across repeated OpCovInputIdx / OpCovOutputIdx - calls (each of which pops the covenant_id argument off the main stack). - - Stack trace (initial main stack: [sig], altstack: []): - - OpTxInputIndex [sig, curr_idx] - OpInputCovenantId [sig, token_id] - # Leader check: covenant input 0 must be us - OpDup [sig, token_id, token_id] - OpFalse [sig, token_id, token_id, 0] - OpCovInputIdx [sig, token_id, in0_global_idx] - OpTxInputIndex [sig, token_id, in0_idx, curr_idx] - OpEqualVerify [sig, token_id] - # Save token_id to altstack - OpToAltStack [sig] altstack=[token_id] - # Amount of covenant input 0 - OpFromAltStack [sig, token_id] altstack=[] - OpDup [sig, token_id, token_id] - OpToAltStack [sig, token_id] altstack=[token_id] - OpFalse [sig, token_id, 0] - OpCovInputIdx [sig, in0_idx] - OpTxInputAmount [sig, amt_in0] - # Amount of covenant input 1 - OpFromAltStack [sig, amt_in0, token_id] altstack=[] - OpDup [sig, amt_in0, token_id, token_id] - OpToAltStack [sig, amt_in0, token_id] altstack=[token_id] - OpTrue [sig, amt_in0, token_id, 1] - OpCovInputIdx [sig, amt_in0, in1_idx] - OpTxInputAmount [sig, amt_in0, amt_in1] - OpAdd [sig, total_in] - # Amount of covenant output 0 - OpFromAltStack [sig, total_in, token_id] altstack=[] - OpDup [sig, total_in, token_id, token_id] - OpToAltStack [sig, total_in, token_id] altstack=[token_id] - OpFalse [sig, total_in, token_id, 0] - OpCovOutputIdx [sig, total_in, out0_idx] - OpTxOutputAmount [sig, total_in, amt_out0] - # Amount of covenant output 1 - OpFromAltStack [sig, total_in, amt_out0, token_id] altstack=[] - OpTrue [sig, total_in, amt_out0, token_id, 1] - OpCovOutputIdx [sig, total_in, amt_out0, out1_idx] - OpTxOutputAmount [sig, total_in, amt_out0, amt_out1] - OpAdd [sig, total_in, total_out] - # Conservation - OpEqualVerify [sig] - # Signature check - [sig, owner_pubkey] - OpCheckSig [true] - - KIP-20 opcodes: OpInputCovenantId, OpCovInputIdx, OpCovOutputIdx - KIP-17 opcodes: OpTxInputAmount, OpTxOutputAmount, OpTxInputIndex - """ - return ( - ScriptBuilder() - - # ── Get token_id and verify we are the leader ────────────────────── - .add_op(Opcodes.OpTxInputIndex) # [sig, curr_idx] - .add_op(Opcodes.OpInputCovenantId) # [sig, token_id] - .add_op(Opcodes.OpDup) # [sig, token_id, token_id] - .add_op(Opcodes.OpFalse) # [sig, token_id, token_id, 0] - .add_op(Opcodes.OpCovInputIdx) # [sig, token_id, in0_global_idx] - .add_op(Opcodes.OpTxInputIndex) # [sig, token_id, in0_idx, curr_idx] - .add_op(Opcodes.OpEqualVerify) # [sig, token_id] - - # ── Save token_id to altstack ────────────────────────────────────── - .add_op(Opcodes.OpToAltStack) # [sig] altstack=[token_id] - - # ── Amount of covenant input 0 ───────────────────────────────────── - .add_op(Opcodes.OpFromAltStack) # [sig, token_id] altstack=[] - .add_op(Opcodes.OpDup) # [sig, token_id, token_id] - .add_op(Opcodes.OpToAltStack) # [sig, token_id] altstack=[token_id] - .add_op(Opcodes.OpFalse) # [sig, token_id, 0] - .add_op(Opcodes.OpCovInputIdx) # [sig, in0_idx] - .add_op(Opcodes.OpTxInputAmount) # [sig, amt_in0] - - # ── Amount of covenant input 1 ───────────────────────────────────── - .add_op(Opcodes.OpFromAltStack) # [sig, amt_in0, token_id] altstack=[] - .add_op(Opcodes.OpDup) # [sig, amt_in0, token_id, token_id] - .add_op(Opcodes.OpToAltStack) # [sig, amt_in0, token_id] altstack=[token_id] - .add_op(Opcodes.OpTrue) # [sig, amt_in0, token_id, 1] - .add_op(Opcodes.OpCovInputIdx) # [sig, amt_in0, in1_idx] - .add_op(Opcodes.OpTxInputAmount) # [sig, amt_in0, amt_in1] - .add_op(Opcodes.OpAdd) # [sig, total_in] - - # ── Amount of covenant output 0 ──────────────────────────────────── - .add_op(Opcodes.OpFromAltStack) # [sig, total_in, token_id] altstack=[] - .add_op(Opcodes.OpDup) # [sig, total_in, token_id, token_id] - .add_op(Opcodes.OpToAltStack) # [sig, total_in, token_id] altstack=[token_id] - .add_op(Opcodes.OpFalse) # [sig, total_in, token_id, 0] - .add_op(Opcodes.OpCovOutputIdx) # [sig, total_in, out0_idx] - .add_op(Opcodes.OpTxOutputAmount) # [sig, total_in, amt_out0] - - # ── Amount of covenant output 1 ──────────────────────────────────── - .add_op(Opcodes.OpFromAltStack) # [sig, total_in, amt_out0, token_id] altstack=[] - .add_op(Opcodes.OpTrue) # [sig, total_in, amt_out0, token_id, 1] - .add_op(Opcodes.OpCovOutputIdx) # [sig, total_in, amt_out0, out1_idx] - .add_op(Opcodes.OpTxOutputAmount) # [sig, total_in, amt_out0, amt_out1] - .add_op(Opcodes.OpAdd) # [sig, total_in, total_out] - - # ── Conservation check ───────────────────────────────────────────── - .add_op(Opcodes.OpEqualVerify) # [sig] - - # ── Verify owner signature ───────────────────────────────────────── - .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) - .add_op(Opcodes.OpCheckSig) - ) - - -def build_delegator_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: - """ - Delegator branch script — just verifies that a leader exists and signs. - - A delegator checks that the first covenant input (the leader) has a - lower or equal index than itself, then signs. - - KIP-20 opcodes: OpInputCovenantId, OpCovInputIdx - KIP-17 opcodes: OpTxInputIndex - """ - return ( - ScriptBuilder() - - # Get this input's covenant_id (token_id) - .add_op(Opcodes.OpTxInputIndex) - .add_op(Opcodes.OpInputCovenantId) # [token_id] - - # Get the leader (covenant input 0) global index - .add_op(Opcodes.OpFalse) # [token_id, 0] - .add_op(Opcodes.OpCovInputIdx) # [leader_global_idx] - - # Verify leader_idx < our_idx (leader comes first) - .add_op(Opcodes.OpTxInputIndex) # [leader_idx, our_idx] - .add_op(Opcodes.OpLessThan) # [leader_idx < our_idx] → true if delegator - .add_op(Opcodes.OpVerify) # assert true - - # Verify signature - .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) - .add_op(Opcodes.OpCheckSig) - ) - - -def build_token_script(owner_xonly_pubkey_hex: str) -> ScriptBuilder: - """ - Build a simplified single-input token script for the 1-in-1-out case. - - For the mint demo (minting two UTXOs, then single transfers), each - token UTXO uses a simpler script that: - 1. Verifies exactly 1 authorized continuation output exists - 2. Verifies the owner signature - - This is the singleton pattern — each token UTXO can only produce one - continuation token UTXO per spend. - - KIP-20 opcodes: OpAuthOutputCount - KIP-17 opcodes: OpTxInputIndex - """ - return ( - ScriptBuilder() - # Enforce exactly 1 authorized continuation output - .add_op(Opcodes.OpTxInputIndex) # push current input index - .add_op(Opcodes.OpAuthOutputCount) # count auth outputs for this input - .add_op(Opcodes.OpTrue) # push 1 - .add_op(Opcodes.OpEqualVerify) # must have exactly 1 continuation output - - # Verify owner signature - .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) - .add_op(Opcodes.OpCheckSig) - ) - - -# ============================================================================= -# Mint — genesis transaction creating two token UTXOs -# ============================================================================= - -async def mint(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): - """ - Create and broadcast the mint (genesis) transaction. - - Creates two token UTXOs sharing the same covenant_id. - Both UTXOs use the same token script (same P2SH scriptPublicKey). - The covenant_id is what identifies them as the same token type. - - Returns: - (txid, [(outpoint, amount), ...], token_spk, cov_id) - """ - keypair = Keypair.from_private_key(owner_key) - owner_pubkey_hex = keypair.xonly_public_key - - # All token UTXOs share the same P2SH scriptPublicKey - token_script = build_token_script(owner_pubkey_hex) - token_spk = token_script.create_pay_to_script_hash_script() - token_address = address_from_script_public_key(token_spk, NETWORK_TYPE) - print(f" Token P2SH address : {token_address.to_string()}") - - # Select the largest funding UTXO - funding = max(funding_utxos, key=lambda u: u["utxoEntry"]["amount"]) - funding_amount = funding["utxoEntry"]["amount"] - funding_utxo_ref = UtxoEntryReference.from_dict(funding) - funding_outpoint = TransactionOutpoint( - Hash(funding["outpoint"]["transactionId"]), - funding["outpoint"]["index"], - ) - - # ── Mass / fee calculation (version 1 required) ───────────────────────── - # Placeholder outputs for mass estimation (without covenant bindings) - ph_out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk) - ph_out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk) - ph_in = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) - ph_tx = Transaction(1, [ph_in], [ph_out_a, ph_out_b], 0, SUBNETWORK_ID, 0, b"", 0) - mass = calculate_transaction_mass(NETWORK_ID, ph_tx) - fee_rates = await client.get_fee_estimate() - fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) - total_tokens = TOKEN_A_AMOUNT + TOKEN_B_AMOUNT - if funding_amount < total_tokens + fee: - raise ValueError( - f"Insufficient funds: have {funding_amount} sompi, " - f"need {total_tokens + fee} sompi" - ) - - # ── Compute genesis covenant_id ───────────────────────────────────────── - # auth_outputs are the outputs WITHOUT covenant bindings (in output order) - auth_out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk) - auth_out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk) - cov_id = covenant_id(funding_outpoint, [auth_out_a, auth_out_b]) - print(f" Token covenant_id : {cov_id}") - - # ── Create covenant-bound token outputs ───────────────────────────────── - binding_a = CovenantBinding(0, cov_id) # both outputs authorized by input 0 - binding_b = CovenantBinding(0, cov_id) - out_a = TransactionOutput(TOKEN_A_AMOUNT, token_spk, covenant_id=binding_a) - out_b = TransactionOutput(TOKEN_B_AMOUNT, token_spk, covenant_id=binding_b) - - inp = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) - tx = Transaction(1, [inp], [out_a, out_b], 0, SUBNETWORK_ID, 0, b"", mass) - - # Standard P2PK signing for the funding input - signed_tx = sign_transaction(tx, [owner_key], True) - - print(f" Fee: {fee} sompi | Token A: {TOKEN_A_AMOUNT} | Token B: {TOKEN_B_AMOUNT}") - - result = await client.submit_transaction({ - "transaction": signed_tx, - "allowOrphan": False, - }) - txid = result["transactionId"] - print(f" Mint TXID: {txid}") - - token_outpoints = [ - ({"transactionId": txid, "index": 0}, TOKEN_A_AMOUNT), - ({"transactionId": txid, "index": 1}, TOKEN_B_AMOUNT), - ] - return txid, token_outpoints, token_spk, cov_id - - -# ============================================================================= -# Transfer — spend a single token UTXO, produce one continuation -# ============================================================================= - -async def transfer( - client: RpcClient, - owner_key: PrivateKey, - token_outpoint: dict, - token_amount: int, - token_script: ScriptBuilder, - token_spk, - cov_id, -) -> str: - """ - Transfer a token UTXO. The singleton token script enforces exactly one - authorized continuation output. - - scriptSig: - """ - keypair = Keypair.from_private_key(owner_key) - token_address = address_from_script_public_key(token_spk, NETWORK_TYPE) - - token_utxo_ref = UtxoEntryReference.from_dict({ - "address": token_address.to_string(), - "outpoint": { - "transactionId": token_outpoint["transactionId"], - "index": token_outpoint["index"], - }, - "utxoEntry": { - "amount": token_amount, - "scriptPublicKey": {"version": 0, "script": token_spk.script}, - "blockDaaScore": 0, - "isCoinbase": False, - "covenantId": str(cov_id), - }, - }) - tok_outpoint = TransactionOutpoint( - Hash(token_outpoint["transactionId"]), - token_outpoint["index"], - ) - - # The continuation output carries the same covenant_id - # (authorizing_input = 0 — the index of this token input in the spending tx) - binding = CovenantBinding(0, cov_id) - cont_output = TransactionOutput(token_amount, token_spk, covenant_id=binding) - - # ── Mass / fee ────────────────────────────────────────────────────────── - ph_in = TransactionInput(tok_outpoint, b"", 0, 1, token_utxo_ref) - ph_tx = Transaction(1, [ph_in], [cont_output], 0, SUBNETWORK_ID, 0, b"", 0) - mass = calculate_transaction_mass(NETWORK_ID, ph_tx) - fee_rates = await client.get_fee_estimate() - fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) - - # Reduce the continuation amount by the fee - cont_amount = token_amount - fee - binding = CovenantBinding(0, cov_id) - cont_output = TransactionOutput(cont_amount, token_spk, covenant_id=binding) - - # ── Build unsigned transaction ────────────────────────────────────────── - inp = TransactionInput(tok_outpoint, b"", 0, 1, token_utxo_ref) - tx_unsigned = Transaction(1, [inp], [cont_output], 0, SUBNETWORK_ID, 0, b"", mass) - - # ── Build the P2SH unlocking script ──────────────────────────────────── - # create_input_signature returns 66-byte hex: [0x41=OP_DATA65, 64_sig, sighash] - # The bytes are already a valid script push — pass raw to the helper. - sig_hex = create_input_signature(tx_unsigned, 0, owner_key) - sig_bytes = bytes.fromhex(sig_hex) - token_script_bytes = bytes.fromhex(token_script.to_string()) - unlock_script_hex = pay_to_script_hash_signature_script(token_script_bytes, sig_bytes) - - # ── Build and submit the final transaction ────────────────────────────── - signed_inp = TransactionInput( - tok_outpoint, bytes.fromhex(unlock_script_hex), 0, 1, token_utxo_ref - ) - tx = Transaction(1, [signed_inp], [cont_output], 0, SUBNETWORK_ID, 0, b"", mass) - - print(f" Fee: {fee} sompi | Token amount after transfer: {cont_amount} sompi") - - result = await client.submit_transaction({ - "transaction": tx, - "allowOrphan": False, - }) - txid = result["transactionId"] - print(f" Transfer TXID: {txid}") - return txid, cont_amount - - -# ============================================================================= -# Helpers -# ============================================================================= - -async def wait_for_utxos(client: RpcClient, address) -> list: - """Poll every 5 s until at least one UTXO exists at `address`.""" - result = await client.get_utxos_by_addresses({"addresses": [address]}) - entries = result.get("entries", []) - if entries: - return entries - - print(f" Waiting for funds — send KAS to:\n {address.to_string()}\n") - while True: - await asyncio.sleep(5) - result = await client.get_utxos_by_addresses({"addresses": [address]}) - entries = result.get("entries", []) - if entries: - return entries - - -async def wait_for_confirmation(client: RpcClient, txid: str): - """Poll the mempool every 1 s; once the tx leaves it has been accepted.""" - print(f" Waiting for confirmation of {txid[:16]}…") - while True: - await asyncio.sleep(1) - try: - await client.get_mempool_entry({"transactionId": txid, "includeOrphanPool": True}) - except Exception: - print(" Confirmed!") - return - - -# ============================================================================= -# Main -# ============================================================================= - -async def main(): - keypair = Keypair.random() - owner_key = PrivateKey(keypair.private_key) - funding_address = keypair.to_address(NETWORK_TYPE) - - print("=" * 60) - print("Fungible Token Covenant — KIP-17 + KIP-20 Demo") - print("=" * 60) - print(f"\nFund this testnet-12 address (owner key):") - print(f" {funding_address.to_string()}") - print(f"\nNeeds at least {TOKEN_A_AMOUNT + TOKEN_B_AMOUNT} sompi + fees") - print("The script detects incoming funds automatically.\n") - - client = RpcClient(url=RPC_URL, network_id=NETWORK_ID) - await client.connect() - print(f"Connected to {NETWORK_ID}\n") - - # ── Wait for funding ───────────────────────────────────────────────────── - utxos = await wait_for_utxos(client, funding_address) - total = sum(u["utxoEntry"]["amount"] for u in utxos) - print(f"Received {total} sompi across {len(utxos)} UTXO(s)\n") - - # ── Step 1: Mint (genesis) ─────────────────────────────────────────────── - print("[Step 1/3] Broadcasting mint (genesis) transaction…") - mint_txid, token_outpoints, token_spk, cov_id = await mint( - client, owner_key, utxos - ) - await wait_for_confirmation(client, mint_txid) - print() - - # Rebuild the token script for signing (same parameters) - keypair2 = Keypair.from_private_key(owner_key) - token_script = build_token_script(keypair2.xonly_public_key) - - # ── Step 2: Transfer token A ───────────────────────────────────────────── - print("[Step 2/3] Transferring token A…") - outpoint_a, amount_a = token_outpoints[0] - print(f" Token A balance before: {amount_a} sompi") - transfer_txid_a, new_amount_a = await transfer( - client, owner_key, outpoint_a, amount_a, token_script, token_spk, cov_id - ) - await wait_for_confirmation(client, transfer_txid_a) - print(f" Token A balance after : {new_amount_a} sompi") - print() - - # ── Step 3: Transfer token B ───────────────────────────────────────────── - print("[Step 3/3] Transferring token B…") - outpoint_b, amount_b = token_outpoints[1] - print(f" Token B balance before: {amount_b} sompi") - transfer_txid_b, new_amount_b = await transfer( - client, owner_key, outpoint_b, amount_b, token_script, token_spk, cov_id - ) - print(f" Token B balance after : {new_amount_b} sompi") - print() - - print("=" * 60) - print("Demo complete!") - print(f" Token covenant_id : {cov_id}") - print(f" Mint TXID : {mint_txid}") - print(f" Transfer A TXID : {transfer_txid_a}") - print(f" Transfer B TXID : {transfer_txid_b}") - print(f" Token A balance : {new_amount_a} sompi") - print(f" Token B balance : {new_amount_b} sompi") - print("=" * 60) - - await client.disconnect() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/covenants/vault.py b/examples/covenants/vault.py deleted file mode 100644 index cb7bf76a..00000000 --- a/examples/covenants/vault.py +++ /dev/null @@ -1,487 +0,0 @@ -""" -Smart Vault Covenant — Kaspa KIP-17 + KIP-20 Example -===================================================== -A vault UTXO with two spending paths: - • Emergency sweep (any time): send all funds to a pre-committed recovery address. - • Normal withdrawal (after locktime): owner may spend freely, but any remainder - must continue the covenant (singleton pattern — exactly one vault UTXO at all times). - -Uses KIP-20 covenant_id for stable vault lineage. The singleton pattern means -the UTXO set always contains at most one UTXO with this covenant_id. -""" - -import asyncio -import os - -from kaspa import ( - CovenantBinding, - Hash, - Keypair, - Opcodes, - PrivateKey, - RpcClient, - ScriptBuilder, - Transaction, - TransactionInput, - TransactionOutpoint, - TransactionOutput, - UtxoEntryReference, - address_from_script_public_key, - calculate_transaction_mass, - covenant_id, - create_input_signature, - pay_to_address_script, - sign_transaction, -) - -# ============================================================================= -# Configuration -# ============================================================================= - -NETWORK_ID = "testnet-12" -NETWORK_TYPE = "testnet" -RPC_URL = os.environ.get("KASPA_RPC_URL") -SUBNETWORK_ID = bytes(20) - -# Locktime for the normal withdrawal path (DAA score). -# testnet-12 produces ~10 block/sec so 100 blocks = approx 10 seconds -LOCKTIME_BLOCKS = 100 - - -# ============================================================================= -# Covenant Script Construction -# ============================================================================= - -def build_vault_redeem_script( - recovery_spk, - recovery_xonly_pubkey_hex: str, - owner_xonly_pubkey_hex: str, - locktime: int, -) -> ScriptBuilder: - """ - Build the vault P2SH redeem script. - - Branch selection: the integer on top of the stack when the redeem script - begins execution selects the spending path: - 1 (OP_TRUE) → Emergency sweep (OpIf body) - 0 (OP_FALSE) → Normal withdrawal (OpElse body) - - ── Emergency branch ──────────────────────────────────────────────────────── - Initial stack coming into redeem script execution: [, 1] - - OpIf pop 1, enter emergency branch → [recovery_sig] - OpFalse OpTxOutputSpk push output[0].spk → [sig, spk0] - push hardcoded recovery SPK → [sig, spk0, rec_spk] - OpEqualVerify assert spk0 == rec_spk → [sig] - OpTxOutputCount OpTrue push N, push 1 → [sig, N, 1] - OpEqualVerify assert N == 1 → [sig] - OpCheckSig verify recovery sig → [true] - OpElse ... OpEndIf - - ── Normal withdrawal branch ──────────────────────────────────────────────── - Initial stack: [, 0] - - OpIf ... OpElse - OpCheckLockTimeVerify OpDrop enforce locktime → [sig] - OpTxInputIndex push current input index → [sig, idx] - OpAuthOutputCount count authorized outputs → [sig, count] - OpTrue OpEqualVerify assert count == 1 → [sig] - OpCheckSig verify owner sig → [true] - OpEndIf - - KIP-17: OpTxOutputSpk, OpTxOutputCount, OpCheckLockTimeVerify - KIP-20: OpAuthOutputCount - """ - # OpTxOutputSpk pushes version (2 bytes, big-endian) + script bytes. - # bytes(spk) returns only the script; prepend the version to match. - recovery_spk_bytes = recovery_spk.version.to_bytes(2, 'big') + bytes(recovery_spk) - - return ( - ScriptBuilder() - .add_op(Opcodes.OpIf) - - # ── Emergency path ─────────────────────────────────────────────── - .add_op(Opcodes.OpFalse) # push 0 (output index 0) - .add_op(Opcodes.OpTxOutputSpk) # push output[0].spk - .add_data(recovery_spk_bytes) # push hardcoded recovery SPK - .add_op(Opcodes.OpEqualVerify) # assert: destination == recovery SPK - .add_op(Opcodes.OpTxOutputCount) # push total number of outputs - .add_op(Opcodes.OpTrue) # push 1 - .add_op(Opcodes.OpEqualVerify) # assert: exactly 1 output - .add_data(bytes.fromhex(recovery_xonly_pubkey_hex)) - .add_op(Opcodes.OpCheckSig) - - .add_op(Opcodes.OpElse) - - # ── Normal withdrawal path ──────────────────────────────────────── - .add_lock_time(locktime) - .add_op(Opcodes.OpCheckLockTimeVerify) - .add_op(Opcodes.OpDrop) - .add_op(Opcodes.OpTxInputIndex) # push current input index - .add_op(Opcodes.OpAuthOutputCount) # count authorized continuation outputs - .add_op(Opcodes.OpTrue) # push 1 - .add_op(Opcodes.OpEqualVerify) # assert: exactly 1 authorized output - .add_data(bytes.fromhex(owner_xonly_pubkey_hex)) - .add_op(Opcodes.OpCheckSig) - - .add_op(Opcodes.OpEndIf) - ) - - -def _make_p2sh_unlock(sig_hex: str, branch_opcode: int, redeem_bytes: bytes) -> bytes: - """ - Build a P2SH unlock script for a branch-selecting covenant. - - The unlock script format is: - [sig_push][branch_opcode][redeem_script_push] - - sig_hex is the 66-byte hex from create_input_signature: - [OP_DATA65=0x41][64_schnorr_sig][sighash_type] - - Because the first byte (0x41 = OP_DATA65) is already a valid script push - opcode, the 66 bytes are appended as-is. When the script engine executes - them, OP_DATA65 pushes the following 65 bytes (sig64 + sighash) onto the - stack — which is exactly the 65-byte value OpCheckSig expects. - - branch_opcode: 0x51 (OP_TRUE=1) → emergency, 0x00 (OP_FALSE=0) → normal - """ - sig_bytes = bytes.fromhex(sig_hex) # pre-encoded: [0x41, sig64, sighash] - branch_byte = bytes([branch_opcode]) - redeem_push = bytes.fromhex( - ScriptBuilder().add_data(redeem_bytes).to_string() - ) - return sig_bytes + branch_byte + redeem_push - - -# ============================================================================= -# Genesis — create the vault UTXO with a KIP-20 covenant_id -# ============================================================================= - -async def genesis(client: RpcClient, owner_key: PrivateKey, funding_utxos: list): - """ - Create and broadcast the genesis (vault creation) transaction. - - Computes the vault's covenant_id from the funding outpoint and the - planned vault output, then creates the covenant-bound output. - """ - keypair = Keypair.from_private_key(owner_key) - owner_pubkey_hex = keypair.xonly_public_key - - # Recovery and owner are the same keypair in this demo. - recovery_address = keypair.to_address(NETWORK_TYPE) - recovery_spk = pay_to_address_script(recovery_address) - recovery_pubkey_hex = owner_pubkey_hex - - redeem_script = build_vault_redeem_script( - recovery_spk, recovery_pubkey_hex, owner_pubkey_hex, LOCKTIME_BLOCKS - ) - vault_spk = redeem_script.create_pay_to_script_hash_script() - vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) - print(f" Vault P2SH address : {vault_address.to_string()}") - - funding = max(funding_utxos, key=lambda u: u["utxoEntry"]["amount"]) - funding_amount = funding["utxoEntry"]["amount"] - funding_utxo_ref = UtxoEntryReference.from_dict(funding) - funding_outpoint = TransactionOutpoint( - Hash(funding["outpoint"]["transactionId"]), - funding["outpoint"]["index"], - ) - - # ── Mass / fee (version 1 required for covenant outputs) ──────────────── - ph_out = TransactionOutput(funding_amount, vault_spk) - ph_in = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) - ph_tx = Transaction(1, [ph_in], [ph_out], 0, SUBNETWORK_ID, 0, b"", 0) - mass = calculate_transaction_mass(NETWORK_ID, ph_tx) - fee_rates = await client.get_fee_estimate() - fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) - vault_amount = funding_amount - fee - - # ── Compute genesis covenant_id ───────────────────────────────────────── - # covenant_id is hashed from (authorizing outpoint, auth_outputs) - # auth_outputs do NOT include the covenant binding (to avoid self-reference) - auth_output = TransactionOutput(vault_amount, vault_spk) - cov_id = covenant_id(funding_outpoint, [auth_output]) - print(f" Vault covenant_id : {cov_id}") - - # ── Create the covenant-bound vault output ────────────────────────────── - binding = CovenantBinding(0, cov_id) - vault_output = TransactionOutput(vault_amount, vault_spk, covenant_id=binding) - - inp = TransactionInput(funding_outpoint, b"", 0, 1, funding_utxo_ref) - tx = Transaction(1, [inp], [vault_output], 0, SUBNETWORK_ID, 0, b"", mass) - signed_tx = sign_transaction(tx, [owner_key], True) - - print(f" Fee: {fee} sompi | Vault: {vault_amount} sompi") - - result = await client.submit_transaction({"transaction": signed_tx, "allowOrphan": False}) - txid = result["transactionId"] - print(f" Genesis TXID: {txid}") - - return txid, {"transactionId": txid, "index": 0}, vault_amount, redeem_script, vault_spk, cov_id - - -# ============================================================================= -# Emergency Sweep — spend via the fast exit path -# ============================================================================= - -async def emergency_sweep( - client: RpcClient, - owner_key: PrivateKey, - vault_outpoint: dict, - vault_amount: int, - redeem_script: ScriptBuilder, - vault_spk, - cov_id, -) -> str: - """ - Sweep the vault to the recovery address using the emergency branch. - - Unlock script: [sig_push] [OP_TRUE=1] [redeem_script_push] - Stack at redeem script start: [sig, 1] - OpIf sees 1 → emergency branch. - """ - keypair = Keypair.from_private_key(owner_key) - recovery_address = keypair.to_address(NETWORK_TYPE) - recovery_spk = pay_to_address_script(recovery_address) - vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) - - vault_utxo_ref = UtxoEntryReference.from_dict({ - "address": vault_address.to_string(), - "outpoint": { - "transactionId": vault_outpoint["transactionId"], - "index": vault_outpoint["index"], - }, - "utxoEntry": { - "amount": vault_amount, - "scriptPublicKey": {"version": 0, "script": vault_spk.script}, - "blockDaaScore": 0, - "isCoinbase": False, - "covenantId": str(cov_id), - }, - }) - cov_outpoint = TransactionOutpoint( - Hash(vault_outpoint["transactionId"]), vault_outpoint["index"] - ) - - # ── Mass / fee ────────────────────────────────────────────────────────── - ph_in = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) - ph_out = TransactionOutput(vault_amount, recovery_spk) - # Emergency sweep has no covenant outputs → version 0 acceptable - ph_tx = Transaction(0, [ph_in], [ph_out], 0, SUBNETWORK_ID, 0, b"", 0) - mass = calculate_transaction_mass(NETWORK_ID, ph_tx) - fee_rates = await client.get_fee_estimate() - fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) - sweep_amount = vault_amount - fee - - # ── Build unsigned transaction & sign ─────────────────────────────────── - inp = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) - out = TransactionOutput(sweep_amount, recovery_spk) - tx_unsigned = Transaction(0, [inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) - - sig_hex = create_input_signature(tx_unsigned, 0, owner_key) - redeem_bytes = bytes.fromhex(redeem_script.to_string()) - - # Build unlock script manually: - # [sig_push=pre-encoded 66 bytes] [0x51=OP_TRUE] [redeem_script_push] - # 0x51 is OP_TRUE → pushes 1 → OpIf enters the emergency branch - unlock_bytes = _make_p2sh_unlock(sig_hex, 0x51, redeem_bytes) - - signed_inp = TransactionInput(cov_outpoint, unlock_bytes, 0, 1, vault_utxo_ref) - tx = Transaction(0, [signed_inp], [out], 0, SUBNETWORK_ID, 0, b"", mass) - - print(f" Fee: {fee} sompi | Sweep: {sweep_amount} sompi") - - result = await client.submit_transaction({"transaction": tx, "allowOrphan": False}) - txid = result["transactionId"] - print(f" Emergency sweep TXID: {txid}") - return txid - - -# ============================================================================= -# Normal Withdrawal — continue the vault covenant after locktime -# ============================================================================= - -async def normal_withdrawal( - client: RpcClient, - owner_key: PrivateKey, - vault_outpoint: dict, - vault_amount: int, - redeem_script: ScriptBuilder, - vault_spk, - cov_id, - withdraw_amount: int, - current_daa_score: int, -) -> str: - """ - Withdraw `withdraw_amount` from the vault after the locktime passes. - - The remainder stays in a continuation vault UTXO (same covenant_id, - same vault P2SH script). The lock_time field on the transaction must - be >= LOCKTIME_BLOCKS for OpCheckLockTimeVerify to pass. - - Unlock script: [sig_push] [OP_FALSE=0] [redeem_script_push] - Stack at redeem script start: [sig, 0] - OpIf sees 0 → else branch (normal withdrawal). - """ - keypair = Keypair.from_private_key(owner_key) - dest_address = keypair.to_address(NETWORK_TYPE) - dest_spk = pay_to_address_script(dest_address) - vault_address = address_from_script_public_key(vault_spk, NETWORK_TYPE) - - vault_utxo_ref = UtxoEntryReference.from_dict({ - "address": vault_address.to_string(), - "outpoint": { - "transactionId": vault_outpoint["transactionId"], - "index": vault_outpoint["index"], - }, - "utxoEntry": { - "amount": vault_amount, - "scriptPublicKey": {"version": 0, "script": vault_spk.script}, - "blockDaaScore": 0, - "isCoinbase": False, - "covenantId": str(cov_id), - }, - }) - cov_outpoint = TransactionOutpoint( - Hash(vault_outpoint["transactionId"]), vault_outpoint["index"] - ) - - remainder = vault_amount - withdraw_amount - # Continuation output: same vault script, same covenant_id - binding = CovenantBinding(0, cov_id) - cont_output = TransactionOutput(remainder, vault_spk, covenant_id=binding) - dest_output = TransactionOutput(withdraw_amount, dest_spk) - - # ── Mass / fee ────────────────────────────────────────────────────────── - ph_in = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) - # lock_time must satisfy CLTV: set it to current_daa_score (>= LOCKTIME_BLOCKS) - ph_tx = Transaction( - 1, [ph_in], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", 0 - ) - mass = calculate_transaction_mass(NETWORK_ID, ph_tx) - fee_rates = await client.get_fee_estimate() - fee = mass * int(fee_rates["estimate"]["priorityBucket"]["feerate"]) - final_withdraw = withdraw_amount - fee - dest_output = TransactionOutput(final_withdraw, dest_spk) - - # ── Build unsigned transaction & sign ─────────────────────────────────── - inp = TransactionInput(cov_outpoint, b"", 0, 1, vault_utxo_ref) - tx_unsigned = Transaction( - 1, [inp], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", mass - ) - - sig_hex = create_input_signature(tx_unsigned, 0, owner_key) - redeem_bytes = bytes.fromhex(redeem_script.to_string()) - - # Build unlock script: - # [sig_push] [0x00=OP_FALSE] [redeem_script_push] - # 0x00 is OP_FALSE → pushes empty bytes (0) → OpIf goes to else branch - unlock_bytes = _make_p2sh_unlock(sig_hex, 0x00, redeem_bytes) - - signed_inp = TransactionInput(cov_outpoint, unlock_bytes, 0, 1, vault_utxo_ref) - tx = Transaction( - 1, [signed_inp], [cont_output, dest_output], current_daa_score, SUBNETWORK_ID, 0, b"", mass - ) - - print(f" Fee: {fee} sompi | Withdrawal: {final_withdraw} | Remainder: {remainder}") - - result = await client.submit_transaction({"transaction": tx, "allowOrphan": False}) - txid = result["transactionId"] - print(f" Normal withdrawal TXID: {txid}") - return txid - - -# ============================================================================= -# Helpers -# ============================================================================= - -async def wait_for_utxos(client: RpcClient, address) -> list: - """Poll every 5 s until at least one UTXO exists at `address`.""" - result = await client.get_utxos_by_addresses({"addresses": [address]}) - entries = result.get("entries", []) - if entries: - return entries - - print(f" Waiting for funds — send KAS to:\n {address.to_string()}\n") - while True: - await asyncio.sleep(5) - result = await client.get_utxos_by_addresses({"addresses": [address]}) - entries = result.get("entries", []) - if entries: - return entries - - -async def wait_for_confirmation(client: RpcClient, txid: str): - """Poll the mempool every 1 s; once the tx leaves it has been accepted.""" - print(f" Waiting for confirmation of {txid[:16]}…") - while True: - await asyncio.sleep(1) - try: - await client.get_mempool_entry({"transactionId": txid, "includeOrphanPool": True}) - except Exception: - print(" Confirmed!") - return - - -# ============================================================================= -# Main -# ============================================================================= - -async def main(): - keypair = Keypair.random() - owner_key = PrivateKey(keypair.private_key) - funding_address = keypair.to_address(NETWORK_TYPE) - - print("=" * 60) - print("Smart Vault Covenant — KIP-17 + KIP-20 Demo") - print("=" * 60) - print(f"\nFund this testnet-12 address (owner key):") - print(f" {funding_address.to_string()}") - print("\nThe script detects incoming funds automatically.\n") - - client = RpcClient(url=RPC_URL, network_id=NETWORK_ID) - await client.connect() - print(f"Connected to {NETWORK_ID}\n") - - utxos = await wait_for_utxos(client, funding_address) - total = sum(u["utxoEntry"]["amount"] for u in utxos) - print(f"Received {total} sompi across {len(utxos)} UTXO(s)\n") - - print("[Step 1/2] Broadcasting genesis (vault creation) transaction…") - txid, outpoint, vault_amount, redeem_script, vault_spk, cov_id = await genesis( - client, owner_key, utxos - ) - await wait_for_confirmation(client, txid) - print() - - print("[Step 2/2] Broadcasting emergency sweep transaction…") - sweep_txid = await emergency_sweep( - client, owner_key, outpoint, vault_amount, redeem_script, vault_spk, cov_id - ) - print() - - # ── Commented out: Normal withdrawal demo ───────────────────────────────── - # To test the normal path, re-fund and uncomment: - # - # from kaspa import RpcClient as _RpcClient - # info = await client.get_block_dag_info() - # daa = info["virtualDaaScore"] - # print("[Step 3/3] Normal withdrawal (after locktime)…") - # withdrawal_txid = await normal_withdrawal( - # client, owner_key, outpoint, vault_amount, - # redeem_script, vault_spk, cov_id, - # withdraw_amount=vault_amount // 2, - # current_daa_score=daa, - # ) - - print("=" * 60) - print("Demo complete!") - print(f" Genesis TXID : {txid}") - print(f" Emergency sweep TXID: {sweep_txid}") - print("=" * 60) - - await client.disconnect() - - -if __name__ == "__main__": - asyncio.run(main()) From f500fc9dc042c37bd30e69e0974731f996aee3c6 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Sat, 28 Mar 2026 09:36:52 -0400 Subject: [PATCH 15/20] Bump to latest rk tn12 commit & all required changes (#34) * bump rk dep to commit c11f7a6 * rename opcode OpCovOutCount to OpCovOutputCount * bump to latest rk tn12 base & all according changes --- Cargo.lock | 174 ++++++++++-------- Cargo.toml | 26 +-- .../covenants/populate_genesis_outputs.py | 106 +++++++++++ kaspa.pyi | 49 +++-- src/consensus/client/covenant.rs | 143 ++++++++++++++ src/consensus/client/mod.rs | 1 + src/consensus/client/output.rs | 5 +- src/consensus/client/transaction.rs | 20 +- src/consensus/core/tx.rs | 48 ----- src/crypto/txscript/opcodes.rs | 2 +- src/lib.rs | 3 +- 11 files changed, 414 insertions(+), 163 deletions(-) create mode 100644 examples/covenants/populate_genesis_outputs.py create mode 100644 src/consensus/client/covenant.rs diff --git a/Cargo.lock b/Cargo.lock index 1d181486..2e7c9c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -803,7 +803,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2232,9 +2232,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -2242,14 +2242,14 @@ dependencies = [ [[package]] name = "kaspa-addresses" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "borsh", "js-sys", "serde", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-log", "workflow-wasm", @@ -2257,8 +2257,8 @@ dependencies = [ [[package]] name = "kaspa-bip32" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "borsh", "bs58", @@ -2284,10 +2284,11 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "ahash", + "borsh", "cfg-if 1.0.4", "faster-hex", "hex", @@ -2313,8 +2314,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "arc-swap", "async-trait", @@ -2350,8 +2351,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-channel 2.5.0", "cfg-if 1.0.4", @@ -2370,8 +2371,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "cfg-if 1.0.4", "faster-hex", @@ -2395,8 +2396,8 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "anyhow", "cfg-if 1.0.4", @@ -2415,8 +2416,8 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "blake2b_simd", "blake3", @@ -2435,8 +2436,8 @@ dependencies = [ [[package]] name = "kaspa-index-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2455,8 +2456,8 @@ dependencies = [ [[package]] name = "kaspa-math" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "borsh", "faster-hex", @@ -2475,16 +2476,16 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-trait", "borsh", @@ -2500,8 +2501,8 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "kaspa-consensus-core", "thiserror 1.0.69", @@ -2509,8 +2510,8 @@ dependencies = [ [[package]] name = "kaspa-muhash" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "kaspa-hashes", "kaspa-math", @@ -2520,8 +2521,8 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2581,7 +2582,7 @@ dependencies = [ "serde", "serde-pyobject", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "workflow-core", "workflow-log", "workflow-rpc", @@ -2590,8 +2591,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2633,8 +2634,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -2646,8 +2647,8 @@ dependencies = [ [[package]] name = "kaspa-txscript" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "ark-bn254", "ark-ec", @@ -2692,8 +2693,8 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "borsh", "kaspa-hashes", @@ -2703,8 +2704,8 @@ dependencies = [ [[package]] name = "kaspa-utils" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "arc-swap", "async-channel 2.5.0", @@ -2733,8 +2734,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "aes", "ahash", @@ -2812,8 +2813,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-trait", "borsh", @@ -2845,8 +2846,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -2859,8 +2860,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "bincode", "derive_builder", @@ -2888,8 +2889,8 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "faster-hex", "hexplay", @@ -2900,8 +2901,8 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "async-std", "async-trait", @@ -2937,8 +2938,8 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "1.1.0-rc.3" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=5e2fd7b#5e2fd7bd14bb9783e9ead229e89eddf9df35834c" +version = "1.1.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" dependencies = [ "ahash", "async-std", @@ -3102,7 +3103,7 @@ dependencies = [ "serde-value", "serde_json", "serde_yaml", - "thiserror 2.0.17", + "thiserror 2.0.18", "thread-id", "typemap-ors", "unicode-segmentation", @@ -4064,7 +4065,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -4085,7 +4086,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -5102,11 +5103,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5122,9 +5123,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -5770,9 +5771,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if 1.0.4", "once_cell", @@ -5780,14 +5781,27 @@ dependencies = [ "serde", "serde_json", "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.110", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if 1.0.4", "js-sys", @@ -5798,9 +5812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5808,31 +5822,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "bumpalo", "proc-macro2", "quote", "syn 2.0.110", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 7e214f27..32952384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ ahash = "0.8.12" bincode = "1.3.3" faster-hex = "0.9.0" futures = "0.3.31" -kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b", features = ["wasm32-sdk"]} -kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } -kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "5e2fd7b" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8", features = ["wasm32-sdk"]} +kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } paste = "1.0" pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] } pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] } @@ -43,7 +43,7 @@ secp256k1 = { version = "0.29.0", features = [ serde = { version = "1.0.228", features = ["derive"] } serde-pyobject = "0.8.0" serde_json = "1.0.149" -thiserror = "2.0.17" +thiserror = "2.0.18" workflow-core = "0.18.0" workflow-log = "0.18.0" workflow-rpc = "0.18.0" diff --git a/examples/covenants/populate_genesis_outputs.py b/examples/covenants/populate_genesis_outputs.py new file mode 100644 index 00000000..d52b8902 --- /dev/null +++ b/examples/covenants/populate_genesis_outputs.py @@ -0,0 +1,106 @@ +""" +Populate Genesis Covenant Outputs — Kaspa KIP-17 Example +======================================================== +Demonstrates populating genesis covenant bindings on transaction outputs, +converting to dict and back, and verifying the covenant data is preserved. +""" + +from kaspa import ( + GenesisCovenantGroup, + Keypair, + Opcodes, + ScriptBuilder, + Transaction, + TransactionInput, + TransactionOutpoint, + TransactionOutput, + Hash, + pay_to_address_script, +) + +NETWORK_TYPE = "testnet" +SUBNETWORK_ID = bytes(20) + + +def main(): + # Generate two keypairs: one for the funding input, one as a covenant recipient + funder = Keypair.random() + recipient = Keypair.random() + + funder_address = funder.to_address(NETWORK_TYPE) + recipient_address = recipient.to_address(NETWORK_TYPE) + + recipient_spk = pay_to_address_script(recipient_address) + + # Build a simple covenant redeem script (just owner pubkey + checksig for demo) + redeem_script = ( + ScriptBuilder() + .add_data(bytes.fromhex(funder.xonly_public_key)) + .add_op(Opcodes.OpCheckSig) + ) + covenant_spk = redeem_script.create_pay_to_script_hash_script() + + # --- Build a transaction with multiple outputs --- + dummy_txid = Hash("0" * 64) + outpoint = TransactionOutpoint(dummy_txid, 0) + inp = TransactionInput(outpoint, b"", 0, 1) + + # Three outputs: two covenant outputs, one regular change output + out0 = TransactionOutput(1_000, covenant_spk) + out1 = TransactionOutput(2_000, covenant_spk) + out2 = TransactionOutput(500, recipient_spk) # change / non-covenant + + tx = Transaction(0, [inp], [out0, out1, out2], 0, SUBNETWORK_ID, 0, b"", 0) + + # --- Verify outputs start without covenant bindings --- + outputs_before = tx.outputs + for i, out in enumerate(outputs_before): + d = out.to_dict() + assert d["covenant"] is None, f"Output {i} should have no covenant before populate" + print("Before populate: all outputs have covenant = None") + + # --- Populate genesis covenants --- + # Group: input 0 authorizes covenant outputs at indices 0 and 1 + group = GenesisCovenantGroup(authorizing_input=0, outputs=[0, 1]) + tx.populate_genesis_covenants([group]) + print("Called populate_genesis_covenants with group(input=0, outputs=[0, 1])") + + # --- Verify covenant bindings are now set --- + outputs_after = tx.outputs + for i, out in enumerate(outputs_after): + d = out.to_dict() + if i < 2: + assert d["covenant"] is not None, f"Output {i} should have a covenant binding" + print(f" Output {i}: covenant = {d['covenant']}") + else: + assert d["covenant"] is None, f"Output {i} (change) should remain None" + print(f" Output {i}: covenant = None (change output, as expected)") + + # --- Round-trip: Transaction -> dict -> Transaction --- + tx_dict = tx.to_dict() + print(f"\nTransaction dict keys: {list(tx_dict.keys())}") + + tx_restored = Transaction.from_dict(tx_dict) + + # Verify outputs survived the round-trip + restored_outputs = tx_restored.outputs + assert len(restored_outputs) == 3, "Should have 3 outputs after round-trip" + + for i, out in enumerate(restored_outputs): + d = out.to_dict() + if i < 2: + assert d["covenant"] is not None, f"Restored output {i} lost its covenant binding" + print(f" Restored output {i}: covenant = {d['covenant']}") + else: + assert d["covenant"] is None, f"Restored output {i} should remain None" + print(f" Restored output {i}: covenant = None (as expected)") + + # Verify equality between original and restored transaction + assert tx == tx_restored, "Restored transaction should equal the original" + print("\nRound-trip passed: original == restored") + + print("\nAll assertions passed!") + + +if __name__ == "__main__": + main() diff --git a/kaspa.pyi b/kaspa.pyi index 91a7264b..33676b38 100644 --- a/kaspa.pyi +++ b/kaspa.pyi @@ -208,6 +208,14 @@ class CovenantBinding: r""" Binds a transaction output to the covenant and input authorizing its creation. """ + @property + def authorizing_input(self) -> builtins.int: ... + @authorizing_input.setter + def authorizing_input(self, value: builtins.int) -> None: ... + @property + def covenant_id(self) -> Hash: ... + @covenant_id.setter + def covenant_id(self, value: Hash) -> None: ... def __new__(cls, authorizing_input: builtins.int, covenant_id: Hash) -> CovenantBinding: ... @typing.final @@ -399,6 +407,14 @@ class GeneratorSummary: """ def __eq__(self, other: GeneratorSummary) -> builtins.bool: ... +@typing.final +class GenesisCovenantGroup: + @property + def authorizing_input(self) -> builtins.int: ... + @authorizing_input.setter + def authorizing_input(self, value: builtins.int) -> None: ... + def __new__(cls, authorizing_input: builtins.int, outputs: typing.Sequence[builtins.int]) -> GenesisCovenantGroup: ... + @typing.final class Hash: r""" @@ -2004,6 +2020,7 @@ class Transaction: Returns: list[Address]: List of unique addresses referenced by inputs. """ + def populate_genesis_covenants(self, groups: typing.Sequence[GenesisCovenantGroup]) -> None: ... def to_dict(self) -> dict: r""" Get a dictionary representation of the Transaction. @@ -2340,21 +2357,6 @@ class UtxoContext: Return pending UTXO entries. """ -@typing.final -class UtxoEntries: - r""" - UTXO entries collection for flexible input handling. - - This type is not intended to be instantiated directly from Python. - It serves as a helper type that allows Rust functions to accept a list - of UTXO entries in multiple convenient forms. - - Accepts: - list[UtxoEntryReference]: A list of UtxoEntryReference objects. - list[dict]: A list of dicts with UtxoEntryReference-compatible keys. - """ - ... - @typing.final class UtxoEntries: r""" @@ -2396,6 +2398,21 @@ class UtxoEntries: """ def __eq__(self, other: UtxoEntries) -> builtins.bool: ... +@typing.final +class UtxoEntries: + r""" + UTXO entries collection for flexible input handling. + + This type is not intended to be instantiated directly from Python. + It serves as a helper type that allows Rust functions to accept a list + of UTXO entries in multiple convenient forms. + + Accepts: + list[UtxoEntryReference]: A list of UtxoEntryReference objects. + list[dict]: A list of dicts with UtxoEntryReference-compatible keys. + """ + ... + @typing.final class UtxoEntry: r""" @@ -3179,7 +3196,7 @@ class Opcodes(enum.Enum): OpInputCovenantId = ... OpCovInputCount = ... OpCovInputIdx = ... - OpCovOutCount = ... + OpCovOutputCount = ... OpCovOutputIdx = ... OpChainblockSeqCommit = ... OpUnknown213 = ... diff --git a/src/consensus/client/covenant.rs b/src/consensus/client/covenant.rs new file mode 100644 index 00000000..6e75b1c8 --- /dev/null +++ b/src/consensus/client/covenant.rs @@ -0,0 +1,143 @@ +use crate::crypto::hashes::PyHash; +use kaspa_consensus_client::{CovenantBinding, GenesisCovenantGroup}; +use kaspa_consensus_core::tx::GenesisCovenantGroup as CoreGenesisCovenantGroup; +use pyo3::{ + prelude::*, + types::{PyAny, PyDict}, +}; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; + +/// Binds a transaction output to the covenant and input authorizing its creation. +#[gen_stub_pyclass] +#[pyclass(name = "CovenantBinding", skip_from_py_object)] +#[derive(Clone)] +pub struct PyCovenantBinding(CovenantBinding); + +#[gen_stub_pymethods] +#[pymethods] +impl PyCovenantBinding { + #[new] + pub fn new(authorizing_input: u16, covenant_id: PyHash) -> Self { + let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); + Self(inner) + } + + #[getter] + pub fn get_authorizing_input(&self) -> u16 { + self.0.get_authorizing_input() + } + + #[setter] + pub fn set_authorizing_input(&mut self, value: u16) { + self.0.set_authorizing_input(value); + } + + #[getter] + pub fn get_covenant_id(&self) -> PyHash { + self.0.get_covenant_id().into() + } + + #[setter] + pub fn set_covenant_id(&mut self, value: PyHash) { + self.0.set_covenant_id(value.into()); + } +} + +impl From for PyCovenantBinding { + fn from(value: CovenantBinding) -> Self { + Self(value) + } +} + +impl From for CovenantBinding { + fn from(value: PyCovenantBinding) -> Self { + value.0 + } +} + +impl TryFrom<&Bound<'_, PyDict>> for PyCovenantBinding { + type Error = PyErr; + + fn try_from(dict: &Bound<'_, PyDict>) -> Result { + let inner = serde_pyobject::from_pyobject(dict.clone())?; + + Ok(Self(inner)) + } +} + +impl<'a, 'py> FromPyObject<'a, 'py> for PyCovenantBinding { + type Error = PyErr; + + fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult { + // Try native CovenantBinding instance first, then fall back to dict + if let Ok(cb) = ob.cast::() { + return Ok(cb.to_owned().borrow().clone()); + } + let dict = ob.cast::()?.to_owned(); + Self::try_from(&dict) + } +} + +#[gen_stub_pyclass] +#[pyclass(name = "GenesisCovenantGroup")] +#[derive(Clone)] +pub struct PyGenesisCovenantGroup(GenesisCovenantGroup); + +#[gen_stub_pymethods] +#[pymethods] +impl PyGenesisCovenantGroup { + #[new] + pub fn constructor(authorizing_input: u16, outputs: Vec) -> Self { + // TODO this tmp construction process is temporary + // until client::GenesisCovenantGroup exposes new fn that takes rust native types + let tmp = CoreGenesisCovenantGroup::new(authorizing_input, outputs); + let inner = GenesisCovenantGroup::from(tmp); + Self(inner) + } + + #[getter] + pub fn get_authorizing_input(&self) -> u16 { + self.0.authorizing_input() + } + + #[setter] + pub fn set_authorizing_input(&mut self, value: u16) { + self.0.set_authorizing_input(value); + } + + // TODO blocked until GenesisCovenantGroup exposes non-WASM `outputs` fns + // `GenesisCovenantGroup::outputs -> NumberArray` instead of Vec return type + // NumberArray is WASM type, we should not convert from that here. + // Better solution is that native GenesisCovenantGroup exposes native Rust getter/setter + // #[getter] + // pub fn get_outputs(&self) -> Vec { + // self.0.outputs.clone() + // } + + // #[setter] + // pub fn set_outputs(&mut self, value: Vec) { + // self.0.outputs = value + // } +} + +impl From for PyGenesisCovenantGroup { + fn from(value: GenesisCovenantGroup) -> Self { + Self(value) + } +} + +impl From for GenesisCovenantGroup { + fn from(value: PyGenesisCovenantGroup) -> Self { + value.0.clone() + } +} + +impl TryFrom<&Bound<'_, PyDict>> for PyGenesisCovenantGroup { + type Error = PyErr; + + fn try_from(dict: &Bound<'_, PyDict>) -> Result { + let inner: GenesisCovenantGroup = serde_pyobject::from_pyobject(dict.clone())?; + + Ok(Self(inner)) + } +} diff --git a/src/consensus/client/mod.rs b/src/consensus/client/mod.rs index eecfc11d..160199c2 100644 --- a/src/consensus/client/mod.rs +++ b/src/consensus/client/mod.rs @@ -1,3 +1,4 @@ +pub mod covenant; pub mod input; pub mod outpoint; pub mod output; diff --git a/src/consensus/client/output.rs b/src/consensus/client/output.rs index 6224fb2b..a0b216be 100644 --- a/src/consensus/client/output.rs +++ b/src/consensus/client/output.rs @@ -7,9 +7,8 @@ use pyo3::{ use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use crate::{ - consensus::core::{script_public_key::PyScriptPublicKey, tx::PyCovenantBinding}, - traits::TryToPyDict, - types::PyBinary, + consensus::client::covenant::PyCovenantBinding, + consensus::core::script_public_key::PyScriptPublicKey, traits::TryToPyDict, types::PyBinary, }; /// A transaction output defining a payment destination. diff --git a/src/consensus/client/transaction.rs b/src/consensus/client/transaction.rs index a1f8e0e5..40256fbd 100644 --- a/src/consensus/client/transaction.rs +++ b/src/consensus/client/transaction.rs @@ -1,11 +1,15 @@ use crate::address::PyAddress; +use crate::consensus::client::covenant::PyGenesisCovenantGroup; use crate::consensus::client::input::PyTransactionInput; use crate::consensus::client::output::PyTransactionOutput; use crate::consensus::core::network::PyNetworkType; use crate::crypto::hashes::PyHash; use crate::traits::TryToPyDict; use crate::types::PyBinary; -use kaspa_consensus_client::{Transaction, TransactionInput, TransactionOutput}; +use kaspa_consensus_client::{ + GenesisCovenantGroup as ClientGenesisCovenantGroup, Transaction, TransactionInput, + TransactionOutput, +}; use kaspa_consensus_core::network::NetworkType; use kaspa_consensus_core::subnets; use kaspa_consensus_core::subnets::SubnetworkId; @@ -285,6 +289,20 @@ impl PyTransaction { self.0.inner().mass = value; } + pub fn populate_genesis_covenants(&self, groups: Vec) -> PyResult<()> { + let groups = groups + .into_iter() + .map(|g| { + let client_group: ClientGenesisCovenantGroup = g.into(); + cctx::GenesisCovenantGroup::from(client_group) + }) + .collect::>(); + self.0 + .populate_genesis_covenants(groups.as_slice()) + .map_err(|e| PyException::new_err(format!("{}", e)))?; + Ok(()) + } + /// Get a dictionary representation of the Transaction. /// Note that this creates a second separate object on the Python heap. /// diff --git a/src/consensus/core/tx.rs b/src/consensus/core/tx.rs index 36c518e2..7f63ff53 100644 --- a/src/consensus/core/tx.rs +++ b/src/consensus/core/tx.rs @@ -1,51 +1,3 @@ use crate::crypto::hashes::PyHash; -use kaspa_consensus_core::tx::CovenantBinding; -use pyo3::{prelude::*, types::PyDict}; -use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; pub type TransactionId = PyHash; - -/// Binds a transaction output to the covenant and input authorizing its creation. -#[gen_stub_pyclass] -#[pyclass(name = "CovenantBinding")] -#[derive(Clone)] -pub struct PyCovenantBinding(CovenantBinding); - -#[gen_stub_pymethods] -#[pymethods] -impl PyCovenantBinding { - #[new] - pub fn new(authorizing_input: u16, covenant_id: PyHash) -> Self { - let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); - Self(inner) - } -} - -impl From for PyCovenantBinding { - fn from(value: CovenantBinding) -> Self { - Self(value) - } -} - -impl From for CovenantBinding { - fn from(value: PyCovenantBinding) -> Self { - value.0 - } -} - -impl TryFrom<&Bound<'_, PyDict>> for PyCovenantBinding { - type Error = PyErr; - - fn try_from(dict: &Bound<'_, PyDict>) -> Result { - let authorizing_input = dict - .as_any() - .get_item("authorizing_input")? - .extract::()?; - - let covenant_id = dict.as_any().get_item("covenant_id")?.extract::()?; - - let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); - - Ok(Self(inner)) - } -} diff --git a/src/crypto/txscript/opcodes.rs b/src/crypto/txscript/opcodes.rs index 238a5965..29680fb5 100644 --- a/src/crypto/txscript/opcodes.rs +++ b/src/crypto/txscript/opcodes.rs @@ -249,7 +249,7 @@ crate::wrap_c_enum_for_py!( OpInputCovenantId = 0xcf, OpCovInputCount = 0xd0, OpCovInputIdx = 0xd1, - OpCovOutCount = 0xd2, + OpCovOutputCount = 0xd2, OpCovOutputIdx = 0xd3, OpChainblockSeqCommit = 0xd4, OpUnknown213 = 0xd5, diff --git a/src/lib.rs b/src/lib.rs index 85171c8e..7b03ea53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ fn kaspa(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -71,7 +73,6 @@ fn kaspa(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_class::()?; m.add_class::()?; m.add_class::()?; From 042c4f2da172ab404c39febeb9a9f0d6db2116f8 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Sun, 3 May 2026 18:49:43 -0400 Subject: [PATCH 16/20] Bump rk tn12 dep commit (#45) --- Cargo.lock | 665 +++++++++++++++++---------------- Cargo.toml | 24 +- docs/CHANGELOG.md | 2 +- kaspa.pyi | 31 +- src/consensus/client/input.rs | 28 +- src/crypto/txscript/opcodes.rs | 10 +- src/wallet/core/tx/utils.rs | 1 + 7 files changed, 422 insertions(+), 339 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e7c9c5e..c071fa93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,35 +104,37 @@ dependencies = [ [[package]] name = "ark-bn254" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +checksum = "6bc66f96ebe2a17a499475b4f94791d379817592ef494171586967ffdc6f95db" dependencies = [ "ark-ec", "ark-ff", - "ark-r1cs-std", "ark-std", ] [[package]] name = "ark-crypto-primitives" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" +checksum = "31b3409b1846fe459d19c95df039481575ac6d5842ae63858ad75cc31219bfc1" dependencies = [ "ahash", "ark-crypto-primitives-macros", "ark-ec", "ark-ff", + "ark-r1cs-std", "ark-relations", "ark-serialize", "ark-snark", "ark-std", "blake2", + "blake3", "derivative", "digest", "fnv", "merlin", + "num-bigint", "rayon", "sha2", ] @@ -150,9 +152,9 @@ dependencies = [ [[package]] name = "ark-ec" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +checksum = "8352a2b2aedf6ba2cc38f7520fc51191d518dde96175c729af19f2d059f191c4" dependencies = [ "ahash", "ark-ff", @@ -161,8 +163,8 @@ dependencies = [ "ark-std", "educe", "fnv", - "hashbrown 0.15.5", - "itertools 0.13.0", + "hashbrown 0.17.0", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", @@ -172,30 +174,27 @@ dependencies = [ [[package]] name = "ark-ff" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" dependencies = [ "ark-ff-asm", "ark-ff-macros", "ark-serialize", "ark-std", - "arrayvec", "digest", "educe", - "itertools 0.13.0", "num-bigint", "num-traits", - "paste", "rayon", "zeroize", ] [[package]] name = "ark-ff-asm" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" dependencies = [ "quote", "syn 2.0.110", @@ -203,9 +202,9 @@ dependencies = [ [[package]] name = "ark-ff-macros" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" dependencies = [ "num-bigint", "num-traits", @@ -216,9 +215,9 @@ dependencies = [ [[package]] name = "ark-groth16" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" +checksum = "a293328aa422e65527e285614ce5d1dceb0bd7b8b18d18b1b63191ee1f74cb41" dependencies = [ "ark-crypto-primitives", "ark-ec", @@ -226,15 +225,16 @@ dependencies = [ "ark-poly", "ark-relations", "ark-serialize", + "ark-snark", "ark-std", "rayon", ] [[package]] name = "ark-poly" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +checksum = "75f55af10b672002b8d953e230282c51206842e20e5791a94432219b4201de5c" dependencies = [ "ahash", "ark-ff", @@ -242,21 +242,22 @@ dependencies = [ "ark-std", "educe", "fnv", - "hashbrown 0.15.5", + "hashbrown 0.17.0", "rayon", ] [[package]] name = "ark-r1cs-std" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +checksum = "291f1c6628bfcac79b0dc2adbe401aa9100e2e96daa971645e0b18fc94de9a98" dependencies = [ "ark-ec", "ark-ff", "ark-relations", "ark-std", "educe", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", @@ -265,35 +266,40 @@ dependencies = [ [[package]] name = "ark-relations" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +checksum = "fe4c11c797a64b8a23e22bf4e77bf582ac27bb21395e3183a9a506ba2561e9f9" dependencies = [ "ark-ff", + "ark-poly", + "ark-serialize", "ark-std", + "foldhash 0.1.5", + "indexmap 2.12.1", + "rayon", "tracing", "tracing-subscriber", ] [[package]] name = "ark-serialize" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" dependencies = [ "ark-serialize-derive", "ark-std", - "arrayvec", "digest", "num-bigint", "rayon", + "serde_with", ] [[package]] name = "ark-serialize-derive" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" dependencies = [ "proc-macro2", "quote", @@ -302,9 +308,9 @@ dependencies = [ [[package]] name = "ark-snark" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" +checksum = "5bdb461d2be9b2bd6f303c79fffc89f5858790a7b4d33257bca3178e2c071fb9" dependencies = [ "ark-ff", "ark-relations", @@ -314,9 +320,9 @@ dependencies = [ [[package]] name = "ark-std" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" dependencies = [ "num-traits", "rand 0.8.5", @@ -417,9 +423,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -782,6 +788,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -1028,18 +1035,8 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core", + "darling_macro", ] [[package]] @@ -1056,38 +1053,13 @@ dependencies = [ "syn 2.0.110", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.110", -] - [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", + "darling_core", "quote", "syn 2.0.110", ] @@ -1131,6 +1103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -1159,7 +1132,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.20.11", + "darling", "proc-macro2", "quote", "syn 2.0.110", @@ -1301,6 +1274,12 @@ dependencies = [ "shared_child", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "educe" version = "0.6.0" @@ -1500,6 +1479,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1724,7 +1715,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -1740,29 +1731,35 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "allocator-api2", + "foldhash 0.2.0", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "allocator-api2", +] [[package]] name = "heapless" @@ -2091,12 +2088,23 @@ dependencies = [ "delegate-display", "fancy_constructor", "js-sys", - "uuid 1.18.1", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -2105,6 +2113,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -2137,29 +2147,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "intertrait" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00fc6ef7d878dfcf59d9e556ef1b368d7f55b9da5813ed481a3573eef485a01" -dependencies = [ - "intertrait-macros", - "linkme", - "once_cell", -] - -[[package]] -name = "intertrait-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d56984da2d4c9d6d7de8463892e65a9354f4238f641c246fe99176150e97bb8" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "uuid 0.8.2", -] - [[package]] name = "inventory" version = "0.3.21" @@ -2242,8 +2229,8 @@ dependencies = [ [[package]] name = "kaspa-addresses" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "borsh", "js-sys", @@ -2257,8 +2244,8 @@ dependencies = [ [[package]] name = "kaspa-bip32" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "borsh", "bs58", @@ -2276,7 +2263,7 @@ dependencies = [ "serde", "sha2", "subtle", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-wasm", "zeroize", @@ -2284,8 +2271,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "ahash", "borsh", @@ -2306,7 +2293,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-log", "workflow-wasm", @@ -2314,8 +2301,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "arc-swap", "async-trait", @@ -2333,15 +2320,17 @@ dependencies = [ "kaspa-math", "kaspa-merkle", "kaspa-muhash", + "kaspa-smt", "kaspa-txscript-errors", "kaspa-utils", "rand 0.8.5", "secp256k1", "serde", + "serde-value", "serde-wasm-bindgen", "serde_json", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-core", "workflow-log", @@ -2351,8 +2340,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-channel 2.5.0", "cfg-if 1.0.4", @@ -2365,14 +2354,14 @@ dependencies = [ "kaspa-utils", "log", "paste", - "thiserror 1.0.69", + "thiserror 2.0.18", "triggered", ] [[package]] name = "kaspa-consensus-wasm" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "cfg-if 1.0.4", "faster-hex", @@ -2388,7 +2377,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-log", "workflow-wasm", @@ -2396,18 +2385,18 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "anyhow", "cfg-if 1.0.4", "ctrlc", + "downcast", "futures-util", - "intertrait", "log", "log4rs", "num_cpus", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "triggered", "wasm-bindgen", @@ -2416,8 +2405,8 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "blake2b_simd", "blake3", @@ -2427,17 +2416,18 @@ dependencies = [ "js-sys", "kaspa-utils", "keccak", - "once_cell", "serde", "sha2", + "sha2-const-stable", "wasm-bindgen", "workflow-wasm", + "zerocopy", ] [[package]] name = "kaspa-index-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2450,14 +2440,14 @@ dependencies = [ "log", "paste", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "triggered", ] [[package]] name = "kaspa-math" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "borsh", "faster-hex", @@ -2467,7 +2457,7 @@ dependencies = [ "malachite-nz", "serde", "serde-wasm-bindgen", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-core", "workflow-log", @@ -2476,16 +2466,16 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-trait", "borsh", @@ -2494,24 +2484,24 @@ dependencies = [ "kaspa-rpc-core", "separator", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "workflow-core", "workflow-log", ] [[package]] name = "kaspa-mining-errors" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "kaspa-consensus-core", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "kaspa-muhash" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "kaspa-hashes", "kaspa-math", @@ -2521,8 +2511,8 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2530,7 +2520,7 @@ dependencies = [ "derive_more 0.99.20", "futures", "futures-util", - "indexmap", + "indexmap 2.12.1", "itertools 0.13.0", "kaspa-addresses", "kaspa-consensus-core", @@ -2544,7 +2534,7 @@ dependencies = [ "paste", "rand 0.8.5", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "triggered", "workflow-core", "workflow-log", @@ -2591,8 +2581,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2622,10 +2612,9 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen", - "serde_nested_with", "smallvec", - "thiserror 1.0.69", - "uuid 1.18.1", + "thiserror 2.0.18", + "uuid", "wasm-bindgen", "workflow-core", "workflow-serializer", @@ -2634,8 +2623,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -2645,10 +2634,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "kaspa-smt" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +dependencies = [ + "blake3", + "kaspa-hashes", + "thiserror 2.0.18", + "zerocopy", +] + [[package]] name = "kaspa-txscript" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "ark-bn254", "ark-ec", @@ -2657,13 +2657,13 @@ dependencies = [ "ark-serialize", "ark-snark", "blake2b_simd", + "blake3", "borsh", - "bytemuck", "cc", "cfg-if 1.0.4", "faster-hex", "hexplay", - "indexmap", + "indexmap 2.12.1", "itertools 0.13.0", "kaspa-addresses", "kaspa-consensus-core", @@ -2672,13 +2672,11 @@ dependencies = [ "kaspa-utils", "kaspa-wasm-core", "log", - "once_cell", "parking_lot", "rand 0.8.5", "risc0-binfmt", "risc0-circuit-recursion", "risc0-core", - "risc0-groth16", "risc0-zkp", "secp256k1", "serde", @@ -2686,28 +2684,27 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-wasm", ] [[package]] name = "kaspa-txscript-errors" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "borsh", "kaspa-hashes", "secp256k1", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "kaspa-utils" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ - "arc-swap", "async-channel 2.5.0", "borsh", "cfg-if 1.0.4", @@ -2726,22 +2723,22 @@ dependencies = [ "sha2", "smallvec", "sysinfo", - "thiserror 1.0.69", + "thiserror 2.0.18", "triggered", - "uuid 1.18.1", + "uuid", "wasm-bindgen", ] [[package]] name = "kaspa-wallet-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "aes", "ahash", "argon2", "async-channel 2.5.0", - "async-std", + "async-lock", "async-trait", "base64", "borsh", @@ -2751,7 +2748,6 @@ dependencies = [ "convert_case 0.6.0", "crypto_box", "dashmap", - "derivative", "downcast", "evpkdf", "faster-hex", @@ -2797,7 +2793,7 @@ dependencies = [ "sha2", "slugify-rs", "sorted-insert", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2813,8 +2809,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "async-trait", "borsh", @@ -2836,7 +2832,7 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "sha2", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "workflow-core", @@ -2846,8 +2842,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -2860,8 +2856,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "bincode", "derive_builder", @@ -2882,15 +2878,15 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "serde_repr", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasm-bindgen", "workflow-wasm", ] [[package]] name = "kaspa-wasm-core" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "faster-hex", "hexplay", @@ -2901,10 +2897,10 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ - "async-std", + "async-lock", "async-trait", "borsh", "cfg-if 1.0.4", @@ -2923,7 +2919,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "toml 0.8.23", "wasm-bindgen", "wasm-bindgen-futures", @@ -2938,11 +2934,11 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "1.1.0" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=9b185f8#9b185f80cd2a5f6fbb3ca6c7ae5c3bb6385352ef" +version = "1.1.1-toc.1" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" dependencies = [ "ahash", - "async-std", + "async-lock", "cfg-if 1.0.4", "futures", "js-sys", @@ -3008,9 +3004,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -3023,26 +3019,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "linkme" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd4ad156b9934dc21cad96fd17278a7cb6f30a5657a9d976cd7b71d6d49c02c" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fd9dc7072de7168cbdaba9125e8f742cd3a965aa12bde994b4611a174488d8" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -3175,25 +3151,26 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.4.22" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +checksum = "a8b6f86fdbb1eb9955946be91775239dfcb0acdb1a51bb07d5fc9b8c854f5ccd" dependencies = [ - "hashbrown 0.14.5", - "itertools 0.11.0", + "hashbrown 0.16.1", + "itertools 0.14.0", "libm", "ryu", ] [[package]] name = "malachite-nz" -version = "0.4.22" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" +checksum = "0197a2f5cfee19d59178e282985c6ca79a9233e26a2adcf40acb693896aa09f6" dependencies = [ - "itertools 0.11.0", + "itertools 0.14.0", "libm", "malachite-base", + "wide", ] [[package]] @@ -3403,6 +3380,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3424,9 +3410,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -3883,28 +3869,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.110", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -4023,7 +3987,7 @@ dependencies = [ "anyhow", "chrono", "either", - "indexmap", + "indexmap 2.12.1", "inventory", "itertools 0.14.0", "log", @@ -4044,7 +4008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2426ba759d848787239d80f9fdb1f223786976f87fb6c3da8188ca7c17744b28" dependencies = [ "heck", - "indexmap", + "indexmap 2.12.1", "proc-macro2", "quote", "rustpython-parser", @@ -4235,6 +4199,26 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "regex" version = "1.12.2" @@ -4374,27 +4358,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "risc0-groth16" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc57e76bb87193d154ac5ee6ee352fbd7edabddab36f02a81f40a048e5ca14f9" -dependencies = [ - "anyhow", - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-groth16", - "ark-serialize", - "bytemuck", - "hex", - "num-bigint", - "num-traits", - "risc0-binfmt", - "risc0-zkp", - "serde", -] - [[package]] name = "risc0-zkp" version = "3.0.4" @@ -4597,9 +4560,18 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed" +dependencies = [ + "bytemuck", +] [[package]] name = "salsa20" @@ -4619,6 +4591,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4759,18 +4755,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_nested_with" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc84538493ef215370434907a7dca8117778d16ac1acd0482ce88a0f5cf19707" -dependencies = [ - "darling 0.21.3", - "proc-macro-error2", - "quote", - "syn 2.0.110", -] - [[package]] name = "serde_repr" version = "0.1.20" @@ -4812,13 +4796,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "time", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.12.1", "itoa", "ryu", "serde", @@ -4847,6 +4849,21 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shared_child" version = "1.1.1" @@ -5142,11 +5159,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if 1.0.4", +] + [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -5154,22 +5180,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -5304,7 +5330,7 @@ version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ - "indexmap", + "indexmap 2.12.1", "serde_core", "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", @@ -5337,7 +5363,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.12.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -5351,7 +5377,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap", + "indexmap 2.12.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", @@ -5456,13 +5482,29 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" -version = "0.2.25" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", "tracing-core", + "tracing-log", ] [[package]] @@ -5684,15 +5726,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.16", -] - [[package]] name = "uuid" version = "1.18.1" @@ -5778,8 +5811,6 @@ dependencies = [ "cfg-if 1.0.4", "once_cell", "rustversion", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -5880,6 +5911,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wide" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9479f84a757f819cfab37295955906479181395de83add28f74975fde083141" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 32952384..8446eec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ ahash = "0.8.12" bincode = "1.3.3" faster-hex = "0.9.0" futures = "0.3.31" -kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8", features = ["wasm32-sdk"]} -kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } -kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "9b185f8" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179", features = ["wasm32-sdk"]} +kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } paste = "1.0" pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] } pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4ffbcf62..f79fac16 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -20,7 +20,7 @@ - Added `version` getter for `ScriptPublicKey`. ### Changed -- Bumped rusty-kaspa dependency version to commit a311302. +- Bumped rusty-kaspa dependency version to commit d290179. - Moved Kaspa Python SDK out of Rusty-Kaspa (as a workspace member crate) to its own dedicated repository. The internals of this project have changed significantly as a result. However, all APIs exposed to Python remain unchanged. - All Python-exposed structs and enums are prefixed with `Py` (e.g. `PyAddress`) internally. The corresponding Python class name has not changed (prefix is dropped in Python). - All Python-exposed functions are prefixed with `py_` (e.g. `py_sign_message`) internally. The corresponding Python function name has not changed (prefix is dropped in Python). diff --git a/kaspa.pyi b/kaspa.pyi index 33676b38..fc177f62 100644 --- a/kaspa.pyi +++ b/kaspa.pyi @@ -2102,11 +2102,24 @@ class TransactionInput: def sig_op_count(self, value: builtins.int) -> None: r""" Set the signature operation count. - + Args: value: The number of signature operations. """ @property + def compute_budget(self) -> builtins.int: + r""" + The compute budget for this input. + """ + @compute_budget.setter + def compute_budget(self, value: builtins.int) -> None: + r""" + Set the compute budget for this input. + + Args: + value: The compute budget. + """ + @property def utxo(self) -> typing.Optional[UtxoEntryReference]: r""" The UTXO entry reference for transaction signing, or None if not set. @@ -2119,15 +2132,16 @@ class TransactionInput: Args: value: The signature script as bytes or hex string. """ - def __new__(cls, previous_outpoint: TransactionOutpoint, signature_script: Binary, sequence: builtins.int, sig_op_count: builtins.int, utxo: typing.Optional[UtxoEntryReference] = None) -> TransactionInput: + def __new__(cls, previous_outpoint: TransactionOutpoint, signature_script: Binary, sequence: builtins.int, sig_op_count: builtins.int, compute_budget: builtins.int = 0, utxo: typing.Optional[UtxoEntryReference] = None) -> TransactionInput: r""" Create a new transaction input. - + Args: previous_outpoint: Reference to the UTXO being spent. signature_script: The unlocking script (signature). sequence: Sequence number for relative time locks. sig_op_count: Number of signature operations. + compute_budget: Compute budget for this input (default: 0). utxo: Optional UTXO entry reference for signing. Returns: @@ -2152,6 +2166,7 @@ class TransactionInput: - 'signatureScript' (str | None): The signature script as hex string - 'sequence' (int): Sequence number - 'sigOpCount' (int): Signature operation count + - 'computeBudget' (int, optional): Compute budget for this input (default: 0) - 'utxo' (dict | None): Optional UTXO entry reference dict Returns: @@ -3199,12 +3214,12 @@ class Opcodes(enum.Enum): OpCovOutputCount = ... OpCovOutputIdx = ... OpChainblockSeqCommit = ... - OpUnknown213 = ... + OpOutputCovenantId = ... OpUnknown214 = ... - OpUnknown215 = ... - OpUnknown216 = ... - OpUnknown217 = ... - OpUnknown218 = ... + OpCheckSigFromStack = ... + OpCheckSigFromStackECDSA = ... + OpBlake3 = ... + OpBlake3WithKey = ... OpUnknown219 = ... OpUnknown220 = ... OpUnknown221 = ... diff --git a/src/consensus/client/input.rs b/src/consensus/client/input.rs index 022d6fbf..55ffe508 100644 --- a/src/consensus/client/input.rs +++ b/src/consensus/client/input.rs @@ -30,17 +30,19 @@ impl PyTransactionInput { /// signature_script: The unlocking script (signature). /// sequence: Sequence number for relative time locks. /// sig_op_count: Number of signature operations. + /// compute_budget: Compute budget for this input (default: 0). /// utxo: Optional UTXO entry reference for signing. /// /// Returns: /// TransactionInput: A new TransactionInput instance. #[new] - #[pyo3(signature = (previous_outpoint, signature_script, sequence, sig_op_count, utxo=None))] + #[pyo3(signature = (previous_outpoint, signature_script, sequence, sig_op_count, compute_budget=0, utxo=None))] pub fn constructor( previous_outpoint: PyTransactionOutpoint, signature_script: PyBinary, sequence: u64, sig_op_count: u8, + compute_budget: u16, utxo: Option, ) -> PyResult { let inner = TransactionInput::new( @@ -48,6 +50,7 @@ impl PyTransactionInput { Some(signature_script.into()), sequence, sig_op_count, + compute_budget, utxo.map(UtxoEntryReference::from), ); Ok(Self(inner)) @@ -119,6 +122,21 @@ impl PyTransactionInput { self.0.inner().sig_op_count = value; } + /// The compute budget for this input. + #[getter] + pub fn get_compute_budget(&self) -> u16 { + self.0.inner().compute_budget + } + + /// Set the compute budget for this input. + /// + /// Args: + /// value: The compute budget. + #[setter] + pub fn set_compute_budget(&mut self, value: u16) { + self.0.inner().compute_budget = value; + } + /// The UTXO entry reference for transaction signing, or None if not set. #[getter] pub fn get_utxo(&self) -> Option { @@ -142,6 +160,7 @@ impl PyTransactionInput { /// - 'signatureScript' (str | None): The signature script as hex string /// - 'sequence' (int): Sequence number /// - 'sigOpCount' (int): Signature operation count + /// - 'computeBudget' (int, optional): Compute budget for this input (default: 0) /// - 'utxo' (dict | None): Optional UTXO entry reference dict /// /// Returns: @@ -218,6 +237,12 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransactionInput { .ok_or_else(|| PyKeyError::new_err("Key `sigOpCount` not present"))? .extract()?; + // Parse computeBudget (optional, defaults to 0) + let compute_budget: u16 = match dict.get_item("computeBudget")? { + Some(item) if !item.is_none() => item.extract()?, + _ => 0, + }; + // Parse utxo (optional) let utxo: Option = if let Some(utxo_item) = dict.get_item("utxo")? { if utxo_item.is_none() { @@ -235,6 +260,7 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransactionInput { signature_script, sequence, sig_op_count, + compute_budget, utxo, ); Ok(Self(input)) diff --git a/src/crypto/txscript/opcodes.rs b/src/crypto/txscript/opcodes.rs index 29680fb5..1a7c6b89 100644 --- a/src/crypto/txscript/opcodes.rs +++ b/src/crypto/txscript/opcodes.rs @@ -252,12 +252,12 @@ crate::wrap_c_enum_for_py!( OpCovOutputCount = 0xd2, OpCovOutputIdx = 0xd3, OpChainblockSeqCommit = 0xd4, - OpUnknown213 = 0xd5, + OpOutputCovenantId = 0xd5, OpUnknown214 = 0xd6, - OpUnknown215 = 0xd7, - OpUnknown216 = 0xd8, - OpUnknown217 = 0xd9, - OpUnknown218 = 0xda, + OpCheckSigFromStack = 0xd7, + OpCheckSigFromStackECDSA = 0xd8, + OpBlake3 = 0xd9, + OpBlake3WithKey = 0xda, OpUnknown219 = 0xdb, OpUnknown220 = 0xdc, OpUnknown221 = 0xdd, diff --git a/src/wallet/core/tx/utils.rs b/src/wallet/core/tx/utils.rs index f0badca9..7faaf34b 100644 --- a/src/wallet/core/tx/utils.rs +++ b/src/wallet/core/tx/utils.rs @@ -55,6 +55,7 @@ pub fn py_create_transaction( None, sequence as u64, sig_op_count, + 0, Some(reference), ) }) From 421f38660339aaa16ab5dc7238f5af17c3224647 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Sun, 3 May 2026 19:37:07 -0400 Subject: [PATCH 17/20] Add covenant binding field to PaymentOutput (#46) --- python/kaspa/__init__.pyi | 3 +++ src/consensus/client/covenant.rs | 8 ++++++++ src/wallet/core/tx/payment.rs | 31 +++++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/python/kaspa/__init__.pyi b/python/kaspa/__init__.pyi index 5d62ad84..da3ccd7d 100644 --- a/python/kaspa/__init__.pyi +++ b/python/kaspa/__init__.pyi @@ -376,6 +376,7 @@ class CovenantBinding: @covenant_id.setter def covenant_id(self, value: Hash) -> None: ... def __new__(cls, authorizing_input: builtins.int, covenant_id: Hash) -> CovenantBinding: ... + def __repr__(self) -> builtins.str: ... @typing.final class DerivationPath: @@ -1015,6 +1016,8 @@ class PaymentOutput: address: The address to send this output to. amount: The amount, in sompi, to send on this output. """ + @staticmethod + def with_covenant(address: Address, amount: builtins.int, covenant: CovenantBinding) -> PaymentOutput: ... def __eq__(self, other: PaymentOutput) -> builtins.bool: r""" Equality comparison. diff --git a/src/consensus/client/covenant.rs b/src/consensus/client/covenant.rs index 6e75b1c8..8cf41f0e 100644 --- a/src/consensus/client/covenant.rs +++ b/src/consensus/client/covenant.rs @@ -41,6 +41,14 @@ impl PyCovenantBinding { pub fn set_covenant_id(&mut self, value: PyHash) { self.0.set_covenant_id(value.into()); } + + pub fn __repr__(&self) -> String { + format!( + "CovenantBinding(authorizing_input={}, covenant_id={})", + self.0.get_authorizing_input(), + self.get_covenant_id().__repr__(), + ) + } } impl From for PyCovenantBinding { diff --git a/src/wallet/core/tx/payment.rs b/src/wallet/core/tx/payment.rs index 6bdc05f0..4f2dbc36 100644 --- a/src/wallet/core/tx/payment.rs +++ b/src/wallet/core/tx/payment.rs @@ -1,3 +1,4 @@ +use kaspa_consensus_client::CovenantBinding; use kaspa_wallet_core::tx::payment::PaymentOutput; use pyo3::{ exceptions::{PyException, PyKeyError}, @@ -6,7 +7,7 @@ use pyo3::{ }; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; -use crate::address::PyAddress; +use crate::{address::PyAddress, consensus::client::covenant::PyCovenantBinding}; /// A payment destination with address and amount. /// @@ -30,6 +31,15 @@ impl PyPaymentOutput { Self(PaymentOutput::new(address.into(), amount)) } + #[staticmethod] + fn with_covenant(address: PyAddress, amount: u64, covenant: PyCovenantBinding) -> Self { + Self(PaymentOutput::with_covenant( + address.into(), + amount, + covenant.into(), + )) + } + /// Equality comparison. /// /// Args: @@ -51,9 +61,13 @@ impl PyPaymentOutput { /// str: The PaymentOutput as a repr string. fn __repr__(&self) -> String { format!( - "PaymentOutput(address='{}', amount={})", + "PaymentOutput(address='{}', amount={}, covenant={})", self.0.address.address_to_string(), - self.0.amount + self.0.amount, + match &self.0.covenant { + Some(covenant) => PyCovenantBinding::from(*covenant).__repr__(), + None => "None".to_string(), + } ) } } @@ -86,7 +100,16 @@ impl TryFrom<&Bound<'_, PyDict>> for PyPaymentOutput { .ok_or_else(|| PyKeyError::new_err("Key `amount` not present"))? .extract()?; - let inner = PaymentOutput::new(address.into(), amount); + let covenant_id = value + .as_any() + .get_item("covenant")? + .extract::>()?; + + let inner = PaymentOutput { + address: address.into(), + amount, + covenant: covenant_id.map(CovenantBinding::from), + }; Ok(Self(inner)) } From 9c187c002480cba76795992fdc19be66c5afa802 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Thu, 7 May 2026 17:00:58 -0400 Subject: [PATCH 18/20] Add covenant changes to RPC stubs(#49) --- kaspa_rpc.pyi | 9 +++++++++ python/kaspa/__init__.pyi | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/kaspa_rpc.pyi b/kaspa_rpc.pyi index 7819c0ea..e09e9577 100644 --- a/kaspa_rpc.pyi +++ b/kaspa_rpc.pyi @@ -26,12 +26,19 @@ class RpcScriptPublicKey(TypedDict): script: str +class RpcCovenantBinding(TypedDict): + """Binds a transaction output to the covenant and input authorizing its creation.""" + authorizingInput: int + covenantId: str + + class RpcUtxoEntry(TypedDict): """A UTXO entry.""" amount: int scriptPublicKey: RpcScriptPublicKey blockDaaScore: int isCoinbase: bool + covenantId: str | None class RpcUtxosByAddressesEntry(TypedDict): @@ -71,6 +78,7 @@ class RpcTransactionInput(TypedDict): signatureScript: str sequence: int sigOpCount: int + computeBudget: int verboseData: RpcVerboseData | None @@ -85,6 +93,7 @@ class RpcTransactionOutput(TypedDict): value: int scriptPublicKey: str verboseData: RpcTransactionOutputVerboseData | None + covenant: RpcCovenantBinding | None class RpcTransaction(TypedDict): diff --git a/python/kaspa/__init__.pyi b/python/kaspa/__init__.pyi index da3ccd7d..8cc3548b 100644 --- a/python/kaspa/__init__.pyi +++ b/python/kaspa/__init__.pyi @@ -5007,12 +5007,19 @@ class RpcScriptPublicKey(TypedDict): script: str +class RpcCovenantBinding(TypedDict): + """Binds a transaction output to the covenant and input authorizing its creation.""" + authorizingInput: int + covenantId: str + + class RpcUtxoEntry(TypedDict): """A UTXO entry.""" amount: int scriptPublicKey: RpcScriptPublicKey blockDaaScore: int isCoinbase: bool + covenantId: str | None class RpcUtxosByAddressesEntry(TypedDict): @@ -5052,6 +5059,7 @@ class RpcTransactionInput(TypedDict): signatureScript: str sequence: int sigOpCount: int + computeBudget: int verboseData: RpcVerboseData | None @@ -5066,6 +5074,7 @@ class RpcTransactionOutput(TypedDict): value: int scriptPublicKey: str verboseData: RpcTransactionOutputVerboseData | None + covenant: RpcCovenantBinding | None class RpcTransaction(TypedDict): From eeba4fc52bb9b6d03dde836d6f78f5f15e059739 Mon Sep 17 00:00:00 2001 From: smartgoo Date: Sat, 6 Jun 2026 15:59:59 +0200 Subject: [PATCH 19/20] Bump rusty-kaspa dependency to commit 90dbf07 (v2.0.0) (#53) --- Cargo.lock | 293 +++++------------------ Cargo.toml | 24 +- docs/CHANGELOG.md | 5 + docs/learn/rpc/calls.md | 2 +- docs/learn/transactions/mass-and-fees.md | 5 + docs/learn/wallet-sdk/tx-generator.md | 2 +- kaspa_rpc.pyi | 14 ++ python/kaspa/__init__.pyi | 105 ++++++-- src/consensus/client/transaction.rs | 39 ++- src/consensus/convert.rs | 5 +- src/crypto/txscript/builder.rs | 75 +++++- src/rpc/messages.rs | 3 +- src/rpc/model.rs | 1 + src/rpc/wrpc/client.rs | 1 + tests/unit/test_dict_conversion.py | 19 ++ 15 files changed, 308 insertions(+), 285 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4b8cd08..9519f590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1027,7 +1027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.2", + "nix", "windows-sys 0.61.2", ] @@ -1291,18 +1291,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "duct" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" -dependencies = [ - "libc", - "once_cell", - "os_pipe", - "shared_child", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -2004,7 +1992,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -2283,8 +2271,8 @@ dependencies = [ [[package]] name = "kaspa-addresses" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "borsh", "js-sys", @@ -2298,8 +2286,8 @@ dependencies = [ [[package]] name = "kaspa-bip32" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "borsh", "bs58", @@ -2325,8 +2313,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-client" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "ahash", "borsh", @@ -2355,8 +2343,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "arc-swap", "async-trait", @@ -2394,8 +2382,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-notify" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-channel 2.5.0", "cfg-if 1.0.4", @@ -2414,8 +2402,8 @@ dependencies = [ [[package]] name = "kaspa-consensus-wasm" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "cfg-if 1.0.4", "faster-hex", @@ -2439,8 +2427,8 @@ dependencies = [ [[package]] name = "kaspa-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "anyhow", "cfg-if 1.0.4", @@ -2459,8 +2447,8 @@ dependencies = [ [[package]] name = "kaspa-hashes" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "blake2b_simd", "blake3", @@ -2480,8 +2468,8 @@ dependencies = [ [[package]] name = "kaspa-index-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2500,8 +2488,8 @@ dependencies = [ [[package]] name = "kaspa-math" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "borsh", "faster-hex", @@ -2520,16 +2508,16 @@ dependencies = [ [[package]] name = "kaspa-merkle" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "kaspa-hashes", ] [[package]] name = "kaspa-metrics-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-trait", "borsh", @@ -2545,8 +2533,8 @@ dependencies = [ [[package]] name = "kaspa-mining-errors" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "kaspa-consensus-core", "thiserror 2.0.18", @@ -2554,8 +2542,8 @@ dependencies = [ [[package]] name = "kaspa-muhash" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "kaspa-hashes", "kaspa-math", @@ -2565,8 +2553,8 @@ dependencies = [ [[package]] name = "kaspa-notify" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2635,8 +2623,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-channel 2.5.0", "async-trait", @@ -2677,8 +2665,8 @@ dependencies = [ [[package]] name = "kaspa-rpc-macros" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "convert_case 0.6.0", "proc-macro-error", @@ -2690,8 +2678,8 @@ dependencies = [ [[package]] name = "kaspa-smt" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "blake3", "kaspa-hashes", @@ -2701,8 +2689,8 @@ dependencies = [ [[package]] name = "kaspa-txscript" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "ark-bn254", "ark-ec", @@ -2719,6 +2707,7 @@ dependencies = [ "hexplay", "indexmap 2.14.0", "itertools 0.13.0", + "js-sys", "kaspa-addresses", "kaspa-consensus-core", "kaspa-hashes", @@ -2745,8 +2734,8 @@ dependencies = [ [[package]] name = "kaspa-txscript-errors" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "borsh", "kaspa-hashes", @@ -2756,27 +2745,22 @@ dependencies = [ [[package]] name = "kaspa-utils" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-channel 2.5.0", "borsh", "cfg-if 1.0.4", - "duct", "event-listener 2.5.3", "faster-hex", "ipnet", "itertools 0.13.0", "log", - "mac_address", - "num_cpus", "once_cell", "parking_lot", - "rlimit", "serde", "sha2", "smallvec", - "sysinfo", "thiserror 2.0.18", "triggered", "uuid", @@ -2785,8 +2769,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "aes", "ahash", @@ -2863,8 +2847,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-keys" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-trait", "borsh", @@ -2896,8 +2880,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-macros" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "convert_case 0.5.0", "proc-macro-error", @@ -2910,8 +2894,8 @@ dependencies = [ [[package]] name = "kaspa-wallet-pskt" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "bincode", "derive_builder", @@ -2939,8 +2923,8 @@ dependencies = [ [[package]] name = "kaspa-wasm-core" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "faster-hex", "hexplay", @@ -2951,8 +2935,8 @@ dependencies = [ [[package]] name = "kaspa-wrpc-client" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "async-lock", "async-trait", @@ -2988,8 +2972,8 @@ dependencies = [ [[package]] name = "kaspa-wrpc-wasm" -version = "1.1.1-toc.1" -source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=d290179#d290179438511ac3ee145339f4f23d0542c2e657" +version = "2.0.0" +source = "git+https://github.com/kaspanet/rusty-kaspa.git?rev=90dbf07#90dbf074275d60c1fe74a3491883196f110970c0" dependencies = [ "ahash", "async-lock", @@ -3153,16 +3137,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "mac_address" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" -dependencies = [ - "nix 0.29.0", - "winapi", -] - [[package]] name = "macroific" version = "1.3.1" @@ -3394,19 +3368,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.1", - "cfg-if 1.0.4", - "cfg_aliases", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.31.2" @@ -3432,15 +3393,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -3654,16 +3606,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "pad" version = "0.1.6" @@ -4973,54 +4915,12 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared_child" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" -dependencies = [ - "libc", - "sigchld", - "windows-sys 0.60.2", -] - [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "sigchld" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" -dependencies = [ - "libc", - "os_pipe", - "signal-hook", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - [[package]] name = "simd-adler32" version = "0.3.9" @@ -5156,20 +5056,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "sysinfo" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -6114,52 +6000,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.4.1", + "windows-result", "windows-strings", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -6171,17 +6024,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -6206,19 +6048,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ "windows-link", - "windows-result 0.4.1", + "windows-result", "windows-strings", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index bb839b5f..26cd3f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ ahash = "0.8.12" bincode = "1.3.3" faster-hex = "0.9.0" futures = "0.3.31" -kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179", features = ["wasm32-sdk"]} -kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } -kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07", features = ["wasm32-sdk"]} +kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" } paste = "1.0" pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] } pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 94ec41de..91151a3d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -26,8 +26,13 @@ search: - Examples under `examples/wallet/` demonstrating wallet usage - Pytest options `--network-id` and `--rpc-url` for targeting integration tests at a specific network / node. - TypedDicts for `RpcClient` subscription event payloads — `BlockAddedEvent`, `VirtualChainChangedEvent`, `FinalityConflictEvent`, `FinalityConflictResolvedEvent`, `UtxosChangedEvent`, `SinkBlueScoreChangedEvent`, `VirtualDaaScoreChangedEvent`, `PruningPointUtxoSetOverrideEvent`, `NewBlockTemplateEvent`, `ConnectEvent`, `DisconnectEvent` — and their notification body TypedDicts (`RpcBlockAddedNotification`, etc.) for typing event-listener callbacks. +- `RpcClient.get_block_reward_info(request)` — new RPC returning a block's reward info (header, block color, confirmation count, merging chain block hash, reward amount). +- `ScriptBuilder` covenant/engine flags: optional `covenants_enabled` and `sigop_script_units` arguments on `ScriptBuilder(...)` and `ScriptBuilder.from_script(...)`, plus read-only `covenants_enabled` / `sigop_script_units` properties (mirrors the WASM SDK's `ScriptBuilderOptions`). +- `Transaction.storage_mass` property and a `storageMass` key in the `Transaction` dict output (alongside the existing `mass`), mirroring the WASM SDK. ### Changed +- Bumped the pinned `rusty-kaspa` dependency from `d290179` to `90dbf07` (Toccata hardfork). The transaction mass field is now tracked internally as `storage_mass`; the Python-facing `mass` property, constructor argument, and dict key are retained as aliases. +- The network minimum relay fee was raised upstream to 100 sompi/gram (Toccata). Fees produced by the wallet and `Generator` reflect the new floor; explicit `fee_rate` values must meet it. - `py_error_map!` macro extended to register wallet exception variants into the `kaspa.exceptions` submodule. - Integration tests now default to `mainnet` (overridable via `--network-id` / `--rpc-url`). - `build-dev` script builds with `--strip` for smaller artifacts. diff --git a/docs/learn/rpc/calls.md b/docs/learn/rpc/calls.md index 02c33ee2..ef27fced 100644 --- a/docs/learn/rpc/calls.md +++ b/docs/learn/rpc/calls.md @@ -180,7 +180,7 @@ flow and `allowOrphan` semantics. fee = await client.get_fee_estimate() # { # "estimate": { -# "priorityBucket": {"feerate": 1.0, "estimatedSeconds": 1.0}, +# "priorityBucket": {"feerate": 100.0, "estimatedSeconds": 1.0}, # "normalBuckets": [...], # "lowBuckets": [...], # } diff --git a/docs/learn/transactions/mass-and-fees.md b/docs/learn/transactions/mass-and-fees.md index e805e1b3..d2ab6fbe 100644 --- a/docs/learn/transactions/mass-and-fees.md +++ b/docs/learn/transactions/mass-and-fees.md @@ -92,6 +92,11 @@ network's current rate, or `None` if the transaction's mass exceeds [`maximum_standard_transaction_mass()`](../../reference/Functions/maximum_standard_transaction_mass.md). Split the inputs across multiple transactions in that case. +Since the Toccata hardfork, the network minimum relay fee is **100 sompi per gram +of mass**. `calculate_transaction_fee` and the fee-estimate buckets are floored at +this rate, and any explicit `fee_rate` you supply must meet it or the node rejects +the transaction. + ## Querying the fee rate The network exposes a fee estimator over RPC — see diff --git a/docs/learn/wallet-sdk/tx-generator.md b/docs/learn/wallet-sdk/tx-generator.md index e1e9513a..8db00121 100644 --- a/docs/learn/wallet-sdk/tx-generator.md +++ b/docs/learn/wallet-sdk/tx-generator.md @@ -93,7 +93,7 @@ gen = Generator( priority_entries=priority, # UTXOs to consume first sig_op_count=1, # signature ops per input minimum_signatures=1, # for multisig mass estimation - fee_rate=2.0, # explicit sompi/gram override + fee_rate=120.0, # explicit sompi/gram override (>= network minimum) ) ``` diff --git a/kaspa_rpc.pyi b/kaspa_rpc.pyi index e09e9577..4533fa76 100644 --- a/kaspa_rpc.pyi +++ b/kaspa_rpc.pyi @@ -610,6 +610,11 @@ class GetBlockTemplateRequest(TypedDict): extraData: str +class GetBlockRewardInfoRequest(TypedDict): + """Request for get_block_reward_info.""" + hash: str + + class GetCurrentBlockColorRequest(TypedDict): """Request for get_current_block_color.""" hash: str @@ -886,6 +891,15 @@ class GetBlockTemplateResponse(TypedDict): isSynced: bool +class GetBlockRewardInfoResponse(TypedDict): + """Response from get_block_reward_info.""" + header: RpcBlockHeader + blockColor: Literal["unknown", "blue", "red"] + confirmationCount: int | None + mergingChainBlockHash: str | None + rewardAmount: int | None + + class GetCurrentBlockColorResponse(TypedDict): """Response from get_current_block_color.""" blue: bool diff --git a/python/kaspa/__init__.pyi b/python/kaspa/__init__.pyi index aa378984..480e65ad 100644 --- a/python/kaspa/__init__.pyi +++ b/python/kaspa/__init__.pyi @@ -2025,6 +2025,7 @@ class RpcClient: def get_block(self, request: GetBlockRequest) -> GetBlockResponse: ... def get_blocks(self, request: GetBlocksRequest) -> GetBlocksResponse: ... def get_block_template(self, request: GetBlockTemplateRequest) -> GetBlockTemplateResponse: ... + def get_block_reward_info(self, request: GetBlockRewardInfoRequest) -> GetBlockRewardInfoResponse: ... def get_current_block_color(self, request: GetCurrentBlockColorRequest) -> GetCurrentBlockColorResponse: ... def get_daa_score_timestamp_estimate(self, request: GetDaaScoreTimestampEstimateRequest) -> GetDaaScoreTimestampEstimateResponse: ... def get_fee_estimate_experimental(self, request: GetFeeEstimateExperimentalRequest) -> GetFeeEstimateExperimentalResponse: ... @@ -2052,20 +2053,46 @@ class ScriptBuilder: Used for creating complex spending conditions like multi-signature or time-locked transactions. """ - def __new__(cls) -> ScriptBuilder: + @property + def covenants_enabled(self) -> builtins.bool: + r""" + Whether covenant opcodes and post-Toccata script limits are enabled. + + Returns: + bool: True if covenants are enabled for this builder. + """ + @property + def sigop_script_units(self) -> builtins.int: + r""" + Script units charged for each signature operation. + + Returns: + int: The configured sigop script units. + """ + def __new__(cls, covenants_enabled: builtins.bool = False, sigop_script_units: typing.Optional[builtins.int] = None) -> ScriptBuilder: r""" Create a new empty script builder. + Args: + covenants_enabled: Enable covenant opcodes and post-Toccata script + limits (default: False). + sigop_script_units: Script units charged per signature operation. + Defaults to the native engine default when omitted. + Returns: ScriptBuilder: A new empty ScriptBuilder instance. """ @staticmethod - def from_script(script: Binary) -> ScriptBuilder: + def from_script(script: Binary, covenants_enabled: builtins.bool = False, sigop_script_units: typing.Optional[builtins.int] = None) -> ScriptBuilder: r""" Create a script builder from an existing script. Args: script: Existing script bytes as hex, bytes, or list. + covenants_enabled: Enable covenant opcodes and post-Toccata script + limits (default: False). + sigop_script_units: Script units charged per signature operation. + Defaults to the native engine default when omitted. Returns: ScriptBuilder: A new ScriptBuilder initialized with the script. @@ -2363,15 +2390,33 @@ class Transaction: def mass(self) -> builtins.int: r""" The transaction mass used for fee calculation. + + Alias of `storage_mass`, retained for compatibility with the WASM SDK + and earlier releases of this package. """ @mass.setter def mass(self, value: builtins.int) -> None: r""" Set the transaction mass. + Alias of `storage_mass`. + Args: value: The transaction mass value. """ + @property + def storage_mass(self) -> builtins.int: + r""" + The transaction storage mass used for fee calculation. + """ + @storage_mass.setter + def storage_mass(self, value: builtins.int) -> None: + r""" + Set the transaction storage mass. + + Args: + value: The transaction storage mass value. + """ @subnetwork_id.setter def subnetwork_id(self, value: builtins.str) -> None: r""" @@ -2832,27 +2877,6 @@ class UtxoContext: str: The UtxoContext as a repr string. """ -@typing.final -class UtxoEntries: - r""" - UTXO entries collection for flexible input handling. - - This type is not intended to be instantiated directly from Python. - It serves as a helper type that allows Rust functions to accept a list - of UTXO entries in multiple convenient forms. - - Accepts: - list[UtxoEntryReference]: A list of UtxoEntryReference objects. - list[dict]: A list of dicts with UtxoEntryReference-compatible keys. - """ - def __repr__(self) -> builtins.str: - r""" - The detailed string representation. - - Returns: - str: The UtxoEntries as a repr string. - """ - @typing.final class UtxoEntries: r""" @@ -2910,6 +2934,27 @@ class UtxoEntries: str: The UtxoEntries as a repr string. """ +@typing.final +class UtxoEntries: + r""" + UTXO entries collection for flexible input handling. + + This type is not intended to be instantiated directly from Python. + It serves as a helper type that allows Rust functions to accept a list + of UTXO entries in multiple convenient forms. + + Accepts: + list[UtxoEntryReference]: A list of UtxoEntryReference objects. + list[dict]: A list of dicts with UtxoEntryReference-compatible keys. + """ + def __repr__(self) -> builtins.str: + r""" + The detailed string representation. + + Returns: + str: The UtxoEntries as a repr string. + """ + @typing.final class UtxoEntry: r""" @@ -5591,6 +5636,11 @@ class GetBlockTemplateRequest(TypedDict): extraData: str +class GetBlockRewardInfoRequest(TypedDict): + """Request for get_block_reward_info.""" + hash: str + + class GetCurrentBlockColorRequest(TypedDict): """Request for get_current_block_color.""" hash: str @@ -5867,6 +5917,15 @@ class GetBlockTemplateResponse(TypedDict): isSynced: bool +class GetBlockRewardInfoResponse(TypedDict): + """Response from get_block_reward_info.""" + header: RpcBlockHeader + blockColor: Literal["unknown", "blue", "red"] + confirmationCount: int | None + mergingChainBlockHash: str | None + rewardAmount: int | None + + class GetCurrentBlockColorResponse(TypedDict): """Response from get_current_block_color.""" blue: bool diff --git a/src/consensus/client/transaction.rs b/src/consensus/client/transaction.rs index dbb825a9..e117630e 100644 --- a/src/consensus/client/transaction.rs +++ b/src/consensus/client/transaction.rs @@ -275,18 +275,38 @@ impl PyTransaction { } /// The transaction mass used for fee calculation. + /// + /// Alias of `storage_mass`, retained for compatibility with the WASM SDK + /// and earlier releases of this package. #[getter] pub fn get_mass(&self) -> u64 { - self.0.inner().mass + self.0.inner().storage_mass } /// Set the transaction mass. /// + /// Alias of `storage_mass`. + /// /// Args: /// value: The transaction mass value. #[setter] pub fn set_mass(&mut self, value: u64) { - self.0.inner().mass = value; + self.0.inner().storage_mass = value; + } + + /// The transaction storage mass used for fee calculation. + #[getter] + pub fn get_storage_mass(&self) -> u64 { + self.0.inner().storage_mass + } + + /// Set the transaction storage mass. + /// + /// Args: + /// value: The transaction storage mass value. + #[setter] + pub fn set_storage_mass(&mut self, value: u64) { + self.0.inner().storage_mass = value; } pub fn populate_genesis_covenants(&self, groups: Vec) -> PyResult<()> { @@ -433,11 +453,16 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransaction { Vec::from_hex(&payload_str).map_err(|err| PyException::new_err(err.to_string()))? }; - // Parse mass - let mass: u64 = dict - .get_item("mass")? - .ok_or_else(|| PyKeyError::new_err("Key `mass` not present"))? - .extract()?; + // Parse mass. Accept `storageMass` (WASM SDK) or the legacy/alias `mass`, + // preferring `storageMass` when both are present. + let mass: u64 = match (dict.get_item("storageMass")?, dict.get_item("mass")?) { + (Some(value), _) | (None, Some(value)) => value.extract()?, + (None, None) => { + return Err(PyKeyError::new_err( + "Key `mass` or `storageMass` not present", + )); + } + }; // Parse inputs let inputs_list = dict diff --git a/src/consensus/convert.rs b/src/consensus/convert.rs index 8ec26b1b..15ecf729 100644 --- a/src/consensus/convert.rs +++ b/src/consensus/convert.rs @@ -189,8 +189,9 @@ impl TryToPyDict for Transaction { // Set `payload` key dict.set_item("payload", inner.payload.to_hex())?; - // Set `mass` - dict.set_item("mass", inner.mass)?; + // Set `mass` (alias) and `storageMass` (WASM SDK parity) + dict.set_item("mass", inner.storage_mass)?; + dict.set_item("storageMass", inner.storage_mass)?; Ok(dict) } diff --git a/src/crypto/txscript/builder.rs b/src/crypto/txscript/builder.rs index d4b2a15b..1ab49d43 100644 --- a/src/crypto/txscript/builder.rs +++ b/src/crypto/txscript/builder.rs @@ -2,7 +2,8 @@ use crate::{ consensus::core::script_public_key::PyScriptPublicKey, crypto::txscript::opcodes::PyOpcodes, types::PyBinary, }; -use kaspa_txscript::{script_builder as native, standard}; +use kaspa_consensus_core::mass::ScriptUnits; +use kaspa_txscript::{EngineFlags, script_builder as native, standard}; use pyo3::{exceptions::PyException, prelude::*}; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use std::sync::{Arc, Mutex, MutexGuard}; @@ -36,29 +37,69 @@ impl Default for PyScriptBuilder { impl PyScriptBuilder { /// Create a new empty script builder. /// + /// Args: + /// covenants_enabled: Enable covenant opcodes and post-Toccata script + /// limits (default: False). + /// sigop_script_units: Script units charged per signature operation. + /// Defaults to the native engine default when omitted. + /// /// Returns: /// ScriptBuilder: A new empty ScriptBuilder instance. #[new] - pub fn new() -> Self { - Self::default() + #[pyo3(signature = (covenants_enabled=false, sigop_script_units=None))] + pub fn new(covenants_enabled: bool, sigop_script_units: Option) -> Self { + let flags = build_engine_flags(covenants_enabled, sigop_script_units); + Self(Arc::new(Mutex::new(native::ScriptBuilder::with_flags( + flags, + )))) } /// Create a script builder from an existing script. /// /// Args: /// script: Existing script bytes as hex, bytes, or list. + /// covenants_enabled: Enable covenant opcodes and post-Toccata script + /// limits (default: False). + /// sigop_script_units: Script units charged per signature operation. + /// Defaults to the native engine default when omitted. /// /// Returns: /// ScriptBuilder: A new ScriptBuilder initialized with the script. #[staticmethod] - pub fn from_script(script: PyBinary) -> PyResult { - let builder = PyScriptBuilder::default(); + #[pyo3(signature = (script, covenants_enabled=false, sigop_script_units=None))] + pub fn from_script( + script: PyBinary, + covenants_enabled: bool, + sigop_script_units: Option, + ) -> PyResult { + let flags = build_engine_flags(covenants_enabled, sigop_script_units); + let builder = Self(Arc::new(Mutex::new(native::ScriptBuilder::with_flags( + flags, + )))); let script: Vec = script.into(); builder.inner().script_mut().extend(&script); Ok(builder) } + /// Whether covenant opcodes and post-Toccata script limits are enabled. + /// + /// Returns: + /// bool: True if covenants are enabled for this builder. + #[getter] + pub fn get_covenants_enabled(&self) -> bool { + self.inner().flags().covenants_enabled + } + + /// Script units charged for each signature operation. + /// + /// Returns: + /// int: The configured sigop script units. + #[getter] + pub fn get_sigop_script_units(&self) -> u64 { + self.inner().flags().sigop_script_units.0 + } + /// Add a single opcode to the script. /// /// Args: @@ -248,9 +289,13 @@ impl PyScriptBuilder { pub fn pay_to_script_hash_signature_script(&self, signature: PyBinary) -> PyResult { let inner = self.inner(); let script = inner.script(); - let generated_script = - standard::pay_to_script_hash_signature_script(script.into(), signature.into()) - .map_err(|err| PyException::new_err(format!("{}", err)))?; + let flags = inner.flags(); + let generated_script = standard::pay_to_script_hash_signature_script_with_flags( + script.into(), + signature.into(), + flags, + ) + .map_err(|err| PyException::new_err(format!("{}", err)))?; Ok(generated_script.to_hex()) } @@ -283,6 +328,20 @@ impl PyScriptBuilder { } } +// Builds script engine flags from the Python-facing kwargs, mirroring the WASM +// SDK's `ScriptBuilderOptions { flags: { covenantsEnabled, sigopScriptUnits } }`. +// `sigop_script_units` falls back to the native engine default when omitted. +fn build_engine_flags(covenants_enabled: bool, sigop_script_units: Option) -> EngineFlags { + let mut flags = EngineFlags { + covenants_enabled, + ..Default::default() + }; + if let Some(units) = sigop_script_units { + flags.sigop_script_units = ScriptUnits(units); + } + flags +} + // TODO change to PyOpcode struct and handle similar to PyBinary? // Extracts multiple opcodes from a Python list[int | Opcodes] fn extract_ops(input: &Bound) -> PyResult> { diff --git a/src/rpc/messages.rs b/src/rpc/messages.rs index 85db7e54..4a688f80 100644 --- a/src/rpc/messages.rs +++ b/src/rpc/messages.rs @@ -59,6 +59,7 @@ impl_try_from_pydict!([ GetBlock, GetBlocks, GetBlockTemplate, + GetBlockRewardInfo, GetCurrentBlockColor, GetDaaScoreTimestampEstimate, GetFeeEstimateExperimental, @@ -239,7 +240,7 @@ try_from_args! ( dict : PySubmitTransactionRequest, { subnetwork_id: inner.subnetwork_id, gas: inner.gas, payload: inner.payload.clone(), - mass: inner.mass, + storage_mass: inner.storage_mass, verbose_data: None, }; diff --git a/src/rpc/model.rs b/src/rpc/model.rs index 2d60497e..f2280515 100644 --- a/src/rpc/model.rs +++ b/src/rpc/model.rs @@ -48,6 +48,7 @@ define_py_request_types!([ GetBlock, GetBlocks, GetBlockTemplate, + GetBlockRewardInfo, GetCurrentBlockColor, GetDaaScoreTimestampEstimate, GetFeeEstimateExperimental, diff --git a/src/rpc/wrpc/client.rs b/src/rpc/wrpc/client.rs index 8e98a9e1..be1f1408 100644 --- a/src/rpc/wrpc/client.rs +++ b/src/rpc/wrpc/client.rs @@ -1067,6 +1067,7 @@ build_wrpc_python_interface_with_args!([ GetBlock, GetBlocks, GetBlockTemplate, + GetBlockRewardInfo, GetCurrentBlockColor, GetDaaScoreTimestampEstimate, GetFeeEstimateExperimental, diff --git a/tests/unit/test_dict_conversion.py b/tests/unit/test_dict_conversion.py index 5d6a1cdb..77c84c9f 100644 --- a/tests/unit/test_dict_conversion.py +++ b/tests/unit/test_dict_conversion.py @@ -120,6 +120,25 @@ def test_transaction_to_dict(self): assert "gas" in d assert "payload" in d assert "mass" in d + assert "storageMass" in d + assert d["mass"] == d["storageMass"] + + def test_transaction_storage_mass_alias(self): + """`storage_mass` mirrors `mass` (kept as an alias for WASM & back-compat).""" + tx_hash = Hash("0" * 64) + outpoint = TransactionOutpoint(tx_hash, 0) + input = TransactionInput(outpoint, "", 0, 1) + spk = ScriptPublicKey(0, "51") + output = TransactionOutput(1000000, spk) + + tx = Transaction(0, [input], [output], 100, "0" * 40, 0, "", 1234) + assert tx.mass == 1234 + assert tx.storage_mass == 1234 + + tx.storage_mass = 5678 + assert tx.mass == 5678 + tx.mass = 99 + assert tx.storage_mass == 99 def test_transaction_from_dict_roundtrip(self): """Test Transaction to_dict/from_dict round-trip.""" From 6e22e603e1de643f73801046face2bc2670ba45a Mon Sep 17 00:00:00 2001 From: smartgoo Date: Sat, 6 Jun 2026 17:14:36 +0200 Subject: [PATCH 20/20] Add doc comments where missing (#54) --- python/kaspa/__init__.pyi | 139 ++++++++++++++++++++++++-- src/consensus/client/covenant.rs | 40 ++++++++ src/consensus/client/transaction.rs | 15 +++ src/wallet/core/account/descriptor.rs | 5 + src/wallet/core/storage/interface.rs | 4 + src/wallet/core/storage/keydata.rs | 5 + src/wallet/core/tx/payment.rs | 9 ++ src/wallet/core/wallet.rs | 7 ++ 8 files changed, 213 insertions(+), 11 deletions(-) diff --git a/python/kaspa/__init__.pyi b/python/kaspa/__init__.pyi index 480e65ad..da8087e4 100644 --- a/python/kaspa/__init__.pyi +++ b/python/kaspa/__init__.pyi @@ -8,6 +8,13 @@ from . import exceptions @typing.final class AccountDescriptor: + r""" + A read-only snapshot describing a wallet account. + + Exposes the account's kind, id, name, balance, addresses, and derivation + metadata. Returned by `Wallet` account enumeration, creation, and + activation methods. + """ @property def kind(self) -> AccountKind: r""" @@ -368,15 +375,49 @@ class CovenantBinding: Binds a transaction output to the covenant and input authorizing its creation. """ @property - def authorizing_input(self) -> builtins.int: ... + def authorizing_input(self) -> builtins.int: + r""" + The index of the transaction input authorizing the covenant. + """ @authorizing_input.setter - def authorizing_input(self, value: builtins.int) -> None: ... + def authorizing_input(self, value: builtins.int) -> None: + r""" + Set the authorizing input index. + + Args: + value: The index of the transaction input authorizing the covenant. + """ @property - def covenant_id(self) -> Hash: ... + def covenant_id(self) -> Hash: + r""" + The covenant id the output is bound to. + """ @covenant_id.setter - def covenant_id(self, value: Hash) -> None: ... - def __new__(cls, authorizing_input: builtins.int, covenant_id: Hash) -> CovenantBinding: ... - def __repr__(self) -> builtins.str: ... + def covenant_id(self, value: Hash) -> None: + r""" + Set the covenant id. + + Args: + value: The covenant id the output is bound to. + """ + def __new__(cls, authorizing_input: builtins.int, covenant_id: Hash) -> CovenantBinding: + r""" + Create a new CovenantBinding. + + Args: + authorizing_input: The index of the transaction input authorizing the covenant. + covenant_id: The covenant id the output is bound to. + + Returns: + CovenantBinding: A new CovenantBinding instance. + """ + def __repr__(self) -> builtins.str: + r""" + The detailed string representation. + + Returns: + str: The CovenantBinding as a repr string. + """ @typing.final class DerivationPath: @@ -627,11 +668,37 @@ class GeneratorSummary: @typing.final class GenesisCovenantGroup: + r""" + A genesis covenant group for bulk covenant binding population. + + All listed outputs are bound to the same covenant id, derived from the + authorizing input outpoint and the exact ordered output list. Used with + `Transaction.populate_genesis_covenants`. + """ @property - def authorizing_input(self) -> builtins.int: ... + def authorizing_input(self) -> builtins.int: + r""" + The index of the transaction input authorizing the covenant. + """ @authorizing_input.setter - def authorizing_input(self, value: builtins.int) -> None: ... - def __new__(cls, authorizing_input: builtins.int, outputs: typing.Sequence[builtins.int]) -> GenesisCovenantGroup: ... + def authorizing_input(self, value: builtins.int) -> None: + r""" + Set the authorizing input index. + + Args: + value: The index of the transaction input authorizing the covenant. + """ + def __new__(cls, authorizing_input: builtins.int, outputs: typing.Sequence[builtins.int]) -> GenesisCovenantGroup: + r""" + Create a new GenesisCovenantGroup. + + Args: + authorizing_input: The index of the transaction input authorizing the covenant. + outputs: The indices of the transaction outputs to bind to the covenant. + + Returns: + GenesisCovenantGroup: A new GenesisCovenantGroup instance. + """ @typing.final class Hash: @@ -1017,7 +1084,18 @@ class PaymentOutput: amount: The amount, in sompi, to send on this output. """ @staticmethod - def with_covenant(address: Address, amount: builtins.int, covenant: CovenantBinding) -> PaymentOutput: ... + def with_covenant(address: Address, amount: builtins.int, covenant: CovenantBinding) -> PaymentOutput: + r""" + Create a new Payment Output bound to a covenant. + + Args: + address: The address to send this output to. + amount: The amount, in sompi, to send on this output. + covenant: The covenant binding to attach to this output. + + Returns: + PaymentOutput: A new PaymentOutput instance. + """ def __eq__(self, other: PaymentOutput) -> builtins.bool: r""" Equality comparison. @@ -1348,6 +1426,13 @@ class PrvKeyDataId: @typing.final class PrvKeyDataInfo: + r""" + Metadata describing a private key data entry stored in a wallet file. + + Exposes the entry's id, optional user-assigned name, and whether the + underlying key material requires a payment secret (BIP39 passphrase). + Returned by `Wallet.prv_key_data_enumerate`. + """ @property def id(self) -> PrvKeyDataId: r""" @@ -2472,7 +2557,24 @@ class Transaction: Returns: list[Address]: List of unique addresses referenced by inputs. """ - def populate_genesis_covenants(self, groups: typing.Sequence[GenesisCovenantGroup]) -> None: ... + def populate_genesis_covenants(self, groups: typing.Sequence[GenesisCovenantGroup]) -> None: + r""" + Populate genesis covenant bindings for multiple output groups. + + For each group, computes the covenant id from the authorizing input + outpoint and the group's output list, then sets that binding on all + listed outputs. All groups are validated before the transaction is + mutated. + + Args: + groups: The genesis covenant groups to populate. + + Raises: + Exception: If a group references a non-existent input or output, + output indices are not strictly increasing, outputs overlap + across groups, or a targeted output already has a covenant + binding. + """ def to_dict(self) -> dict: r""" Get a dictionary representation of the Transaction. @@ -3231,6 +3333,15 @@ class UtxoProcessor: @typing.final class Wallet: + r""" + A managed Kaspa wallet with persistent encrypted on-disk storage. + + The SDK's high-level wallet. Provides encrypted storage for keys and + account metadata, multi-account management (BIP32 and keypair accounts), + address derivation and discovery, built-in send, transfer, and sweep + flows, transaction history tracking, and an event bus for chain + notifications (balance, maturity, reorg). + """ @property def rpc(self) -> RpcClient: r""" @@ -3834,6 +3945,12 @@ class Wallet: @typing.final class WalletDescriptor: + r""" + Describes a wallet file in the wallet store. + + Pairs the on-disk wallet filename with its optional user-assigned title. + Returned by `Wallet.wallet_enumerate`. + """ @property def title(self) -> typing.Optional[builtins.str]: r""" diff --git a/src/consensus/client/covenant.rs b/src/consensus/client/covenant.rs index 8cf41f0e..b70d90d2 100644 --- a/src/consensus/client/covenant.rs +++ b/src/consensus/client/covenant.rs @@ -16,32 +16,54 @@ pub struct PyCovenantBinding(CovenantBinding); #[gen_stub_pymethods] #[pymethods] impl PyCovenantBinding { + /// Create a new CovenantBinding. + /// + /// Args: + /// authorizing_input: The index of the transaction input authorizing the covenant. + /// covenant_id: The covenant id the output is bound to. + /// + /// Returns: + /// CovenantBinding: A new CovenantBinding instance. #[new] pub fn new(authorizing_input: u16, covenant_id: PyHash) -> Self { let inner = CovenantBinding::new(authorizing_input, covenant_id.into()); Self(inner) } + /// The index of the transaction input authorizing the covenant. #[getter] pub fn get_authorizing_input(&self) -> u16 { self.0.get_authorizing_input() } + /// Set the authorizing input index. + /// + /// Args: + /// value: The index of the transaction input authorizing the covenant. #[setter] pub fn set_authorizing_input(&mut self, value: u16) { self.0.set_authorizing_input(value); } + /// The covenant id the output is bound to. #[getter] pub fn get_covenant_id(&self) -> PyHash { self.0.get_covenant_id().into() } + /// Set the covenant id. + /// + /// Args: + /// value: The covenant id the output is bound to. #[setter] pub fn set_covenant_id(&mut self, value: PyHash) { self.0.set_covenant_id(value.into()); } + /// The detailed string representation. + /// + /// Returns: + /// str: The CovenantBinding as a repr string. pub fn __repr__(&self) -> String { format!( "CovenantBinding(authorizing_input={}, covenant_id={})", @@ -86,6 +108,11 @@ impl<'a, 'py> FromPyObject<'a, 'py> for PyCovenantBinding { } } +/// A genesis covenant group for bulk covenant binding population. +/// +/// All listed outputs are bound to the same covenant id, derived from the +/// authorizing input outpoint and the exact ordered output list. Used with +/// `Transaction.populate_genesis_covenants`. #[gen_stub_pyclass] #[pyclass(name = "GenesisCovenantGroup")] #[derive(Clone)] @@ -94,6 +121,14 @@ pub struct PyGenesisCovenantGroup(GenesisCovenantGroup); #[gen_stub_pymethods] #[pymethods] impl PyGenesisCovenantGroup { + /// Create a new GenesisCovenantGroup. + /// + /// Args: + /// authorizing_input: The index of the transaction input authorizing the covenant. + /// outputs: The indices of the transaction outputs to bind to the covenant. + /// + /// Returns: + /// GenesisCovenantGroup: A new GenesisCovenantGroup instance. #[new] pub fn constructor(authorizing_input: u16, outputs: Vec) -> Self { // TODO this tmp construction process is temporary @@ -103,11 +138,16 @@ impl PyGenesisCovenantGroup { Self(inner) } + /// The index of the transaction input authorizing the covenant. #[getter] pub fn get_authorizing_input(&self) -> u16 { self.0.authorizing_input() } + /// Set the authorizing input index. + /// + /// Args: + /// value: The index of the transaction input authorizing the covenant. #[setter] pub fn set_authorizing_input(&mut self, value: u16) { self.0.set_authorizing_input(value); diff --git a/src/consensus/client/transaction.rs b/src/consensus/client/transaction.rs index e117630e..7e7e0e81 100644 --- a/src/consensus/client/transaction.rs +++ b/src/consensus/client/transaction.rs @@ -309,6 +309,21 @@ impl PyTransaction { self.0.inner().storage_mass = value; } + /// Populate genesis covenant bindings for multiple output groups. + /// + /// For each group, computes the covenant id from the authorizing input + /// outpoint and the group's output list, then sets that binding on all + /// listed outputs. All groups are validated before the transaction is + /// mutated. + /// + /// Args: + /// groups: The genesis covenant groups to populate. + /// + /// Raises: + /// Exception: If a group references a non-existent input or output, + /// output indices are not strictly increasing, outputs overlap + /// across groups, or a targeted output already has a covenant + /// binding. pub fn populate_genesis_covenants(&self, groups: Vec) -> PyResult<()> { let groups = groups .into_iter() diff --git a/src/wallet/core/account/descriptor.rs b/src/wallet/core/account/descriptor.rs index 2896733b..23c0d486 100644 --- a/src/wallet/core/account/descriptor.rs +++ b/src/wallet/core/account/descriptor.rs @@ -13,6 +13,11 @@ use kaspa_wallet_core::{ use pyo3::prelude::*; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; +/// A read-only snapshot describing a wallet account. +/// +/// Exposes the account's kind, id, name, balance, addresses, and derivation +/// metadata. Returned by `Wallet` account enumeration, creation, and +/// activation methods. #[gen_stub_pyclass] #[pyclass(name = "AccountDescriptor")] #[derive(Clone)] diff --git a/src/wallet/core/storage/interface.rs b/src/wallet/core/storage/interface.rs index cc204c49..c7c4f040 100644 --- a/src/wallet/core/storage/interface.rs +++ b/src/wallet/core/storage/interface.rs @@ -2,6 +2,10 @@ use kaspa_wallet_core::storage::WalletDescriptor; use pyo3::prelude::*; use pyo3_stub_gen::derive::*; +/// Describes a wallet file in the wallet store. +/// +/// Pairs the on-disk wallet filename with its optional user-assigned title. +/// Returned by `Wallet.wallet_enumerate`. #[gen_stub_pyclass] #[pyclass(name = "WalletDescriptor")] pub struct PyWalletDescriptor(WalletDescriptor); diff --git a/src/wallet/core/storage/keydata.rs b/src/wallet/core/storage/keydata.rs index 52e85dae..81aa35ff 100644 --- a/src/wallet/core/storage/keydata.rs +++ b/src/wallet/core/storage/keydata.rs @@ -141,6 +141,11 @@ impl<'py> FromPyObject<'_, 'py> for PyPrvKeyDataId { } } +/// Metadata describing a private key data entry stored in a wallet file. +/// +/// Exposes the entry's id, optional user-assigned name, and whether the +/// underlying key material requires a payment secret (BIP39 passphrase). +/// Returned by `Wallet.prv_key_data_enumerate`. #[gen_stub_pyclass] #[pyclass(name = "PrvKeyDataInfo")] pub struct PyPrvKeyDataInfo(PrvKeyDataInfo); diff --git a/src/wallet/core/tx/payment.rs b/src/wallet/core/tx/payment.rs index 4f2dbc36..7843c567 100644 --- a/src/wallet/core/tx/payment.rs +++ b/src/wallet/core/tx/payment.rs @@ -31,6 +31,15 @@ impl PyPaymentOutput { Self(PaymentOutput::new(address.into(), amount)) } + /// Create a new Payment Output bound to a covenant. + /// + /// Args: + /// address: The address to send this output to. + /// amount: The amount, in sompi, to send on this output. + /// covenant: The covenant binding to attach to this output. + /// + /// Returns: + /// PaymentOutput: A new PaymentOutput instance. #[staticmethod] fn with_covenant(address: PyAddress, amount: u64, covenant: PyCovenantBinding) -> Self { Self(PaymentOutput::with_covenant( diff --git a/src/wallet/core/wallet.rs b/src/wallet/core/wallet.rs index 8fe3d692..5aade35f 100644 --- a/src/wallet/core/wallet.rs +++ b/src/wallet/core/wallet.rs @@ -81,6 +81,13 @@ impl Inner { } } +/// A managed Kaspa wallet with persistent encrypted on-disk storage. +/// +/// The SDK's high-level wallet. Provides encrypted storage for keys and +/// account metadata, multi-account management (BIP32 and keypair accounts), +/// address derivation and discovery, built-in send, transfer, and sweep +/// flows, transaction history tracking, and an event bus for chain +/// notifications (balance, maturity, reorg). #[gen_stub_pyclass] #[pyclass(name = "Wallet")] #[derive(Clone)]