diff --git a/Cargo.lock b/Cargo.lock index f31f447..3cd1607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,7 +361,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -415,6 +415,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bincode" version = "1.3.3" @@ -433,13 +439,13 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -488,6 +494,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blahaj" version = "0.6.0" @@ -553,7 +571,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -599,7 +617,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -683,6 +701,12 @@ dependencies = [ "serde", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytemuck" version = "1.21.0" @@ -700,7 +724,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -800,7 +824,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -871,7 +895,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1099,7 +1123,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1136,7 +1160,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1147,7 +1171,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1247,7 +1271,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1408,7 +1432,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1553,6 +1577,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1609,7 +1639,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -1900,6 +1930,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1959,6 +2002,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "icepick-ed25519" +version = "0.1.0" +dependencies = [ + "ed25519-dalek 2.1.1", + "icepick-module", + "serde", + "serde_json", + "smex", + "thiserror 2.0.11", +] + [[package]] name = "icepick-internal" version = "0.1.0" @@ -2001,6 +2056,18 @@ dependencies = [ "thiserror 2.0.11", ] +[[package]] +name = "icepick-spacemesh" +version = "0.1.0" +dependencies = [ + "icepick-module", + "serde", + "serde_json", + "spacemesh", + "thiserror 2.0.11", + "tokio", +] + [[package]] name = "icepick-workflow" version = "0.1.0" @@ -2126,7 +2193,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -2156,6 +2223,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2164,12 +2242,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -2493,7 +2572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2584,9 +2663,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -2691,6 +2770,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nettle" version = "7.4.0" @@ -2810,7 +2906,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -2873,7 +2969,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -2897,6 +2993,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openapiv3" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc02deea53ffe807708244e5914f6b099ad7015a207ee24317c22112e17d9c5c" +dependencies = [ + "indexmap", + "serde", + "serde_json", +] + [[package]] name = "openpgp-card" version = "0.4.2" @@ -2930,9 +3037,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2951,7 +3058,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -2962,9 +3069,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -2972,6 +3079,32 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -3126,7 +3259,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -3214,12 +3347,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -3242,13 +3375,75 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] +[[package]] +name = "progenitor" +version = "0.7.0" +source = "git+https://github.com/geoffreygarrett/progenitor?rev=8726ea91eb19f92e1357f1ceeeab507477dcfeb6#8726ea91eb19f92e1357f1ceeeab507477dcfeb6" +dependencies = [ + "progenitor-client", + "progenitor-impl", + "progenitor-macro", +] + +[[package]] +name = "progenitor-client" +version = "0.7.0" +source = "git+https://github.com/geoffreygarrett/progenitor?rev=8726ea91eb19f92e1357f1ceeeab507477dcfeb6#8726ea91eb19f92e1357f1ceeeab507477dcfeb6" +dependencies = [ + "bytes", + "futures-core", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "progenitor-impl" +version = "0.7.0" +source = "git+https://github.com/geoffreygarrett/progenitor?rev=8726ea91eb19f92e1357f1ceeeab507477dcfeb6#8726ea91eb19f92e1357f1ceeeab507477dcfeb6" +dependencies = [ + "heck", + "http", + "indexmap", + "openapiv3", + "proc-macro2", + "quote", + "regex", + "schemars", + "serde", + "serde_json", + "syn 2.0.98", + "thiserror 1.0.69", + "typify", + "unicode-ident", +] + +[[package]] +name = "progenitor-macro" +version = "0.7.0" +source = "git+https://github.com/geoffreygarrett/progenitor?rev=8726ea91eb19f92e1357f1ceeeab507477dcfeb6#8726ea91eb19f92e1357f1ceeeab507477dcfeb6" +dependencies = [ + "openapiv3", + "proc-macro2", + "progenitor-impl", + "quote", + "schemars", + "serde", + "serde_json", + "serde_tokenstream", + "serde_yaml", + "syn 2.0.98", +] + [[package]] name = "prost" version = "0.13.4" @@ -3266,10 +3461,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -3290,6 +3485,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3410,6 +3611,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regress" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" +dependencies = [ + "hashbrown 0.15.2", + "memchr", +] + [[package]] name = "reqwest" version = "0.11.27" @@ -3427,11 +3638,13 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -3444,12 +3657,14 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg", @@ -3631,6 +3846,32 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "chrono", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.98", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3692,9 +3933,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +dependencies = [ + "serde", +] [[package]] name = "sequoia-openpgp" @@ -3703,7 +3947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e858e4e9e48ff079cede92e1b45c942a5466ce9a4e3cc0c2a7e66586a718ef59" dependencies = [ "anyhow", - "base64 0.21.7", + "base64 0.22.1", "buffered-reader", "bzip2", "chrono", @@ -3727,9 +3971,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -3745,20 +3989,31 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -3774,7 +4029,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -3786,6 +4041,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.98", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3818,7 +4085,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -4680,7 +4947,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5022,6 +5289,45 @@ dependencies = [ "winapi", ] +[[package]] +name = "spacemesh" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "bech32", + "smex", + "spacemesh-api-client", + "spacemesh-codec", + "tokio", +] + +[[package]] +name = "spacemesh-api-client" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "chrono", + "futures", + "prettyplease", + "progenitor", + "progenitor-client", + "reqwest", + "serde", + "serde_json", + "smex", + "syn 2.0.98", + "tokio", +] + +[[package]] +name = "spacemesh-codec" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "bech32", + "parity-scale-codec", +] + [[package]] name = "spin" version = "0.9.8" @@ -5121,7 +5427,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5133,7 +5439,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.92", + "syn 2.0.98", "thiserror 1.0.69", ] @@ -5242,7 +5548,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5598,9 +5904,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.92" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -5621,7 +5927,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5645,6 +5951,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "task-local-extensions" version = "0.1.4" @@ -5807,7 +6119,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5818,7 +6130,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -5911,7 +6223,17 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -6017,6 +6339,50 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "typify" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/typify#eed9bee494c3421b194e5d078cd9c9520ef8bb3c" +dependencies = [ + "typify-impl", + "typify-macro", +] + +[[package]] +name = "typify-impl" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/typify#eed9bee494c3421b194e5d078cd9c9520ef8bb3c" +dependencies = [ + "heck", + "log", + "proc-macro2", + "quote", + "regress", + "schemars", + "semver", + "serde", + "serde_json", + "syn 2.0.98", + "thiserror 2.0.11", + "unicode-ident", +] + +[[package]] +name = "typify-macro" +version = "0.3.0" +source = "git+https://github.com/oxidecomputer/typify#eed9bee494c3421b194e5d078cd9c9520ef8bb3c" +dependencies = [ + "proc-macro2", + "quote", + "schemars", + "semver", + "serde", + "serde_json", + "serde_tokenstream", + "syn 2.0.98", + "typify-impl", +] + [[package]] name = "unicase" version = "2.8.1" @@ -6025,9 +6391,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-xid" @@ -6181,7 +6547,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", "wasm-bindgen-shared", ] @@ -6216,7 +6582,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6227,6 +6593,19 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.76" @@ -6265,7 +6644,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6462,6 +6841,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -6500,7 +6888,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", "synstructure", ] @@ -6522,7 +6910,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -6542,7 +6930,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", "synstructure", ] @@ -6563,7 +6951,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] @@ -6585,7 +6973,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.92", + "syn 2.0.98", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 282739c..c1ee150 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,14 @@ members = [ "crates/icepick-workflow", "crates/icepick-module", "crates/builtins/icepick-internal", + "crates/builtins/icepick-ed25519", "crates/by-chain/icepick-solana", "crates/by-chain/icepick-cosmos", "crates/miniquorum", + "crates/spacemesh/api-client", + "crates/spacemesh/codec", + "crates/spacemesh/spacemesh", + "crates/by-chain/icepick-spacemesh", ] [workspace.dependencies] diff --git a/crates/builtins/icepick-ed25519/Cargo.toml b/crates/builtins/icepick-ed25519/Cargo.toml new file mode 100644 index 0000000..df9f409 --- /dev/null +++ b/crates/builtins/icepick-ed25519/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "icepick-ed25519" +version = "0.1.0" +edition = "2021" +publish = ["distrust"] + +[dependencies] +ed25519-dalek = "2.1.1" +icepick-module = { version = "0.1.0", path = "../../icepick-module" } +serde.workspace = true +serde_json.workspace = true +smex = { version = "0.1.0", registry = "distrust" } +thiserror = "2.0.9" diff --git a/crates/builtins/icepick-ed25519/src/lib.rs b/crates/builtins/icepick-ed25519/src/lib.rs new file mode 100644 index 0000000..26ade71 --- /dev/null +++ b/crates/builtins/icepick-ed25519/src/lib.rs @@ -0,0 +1,91 @@ +use ed25519_dalek::Signer; +use icepick_module::Module; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "operation", content = "values", rename_all = "kebab-case")] +pub enum Operation { + GetPubkey {}, + + Sign { message: Vec }, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Request { + derived_keys: Option>, + + #[serde(flatten)] + operation: Operation, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error {} + +pub struct Ed25519; + +impl Module for Ed25519 { + type Error = Error; + + type Request = Request; + + fn describe_operations() -> Vec { + use icepick_module::help::*; + + let message = Argument::builder() + .name("message") + .description("The message to sign, as an array of bytes.") + .r#type(ArgumentType::Required) + .build(); + + let get_pubkey = Operation::builder() + .name("get-pubkey") + .description("Get an Ed25519 public key from the provided private key.") + .build(); + + let sign = Operation::builder() + .name("sign") + .description("Sign a message using an Ed25519 private key.") + .build() + .argument(&message); + + vec![get_pubkey, sign] + } + + fn handle_request(request: Self::Request) -> Result { + let Request { + derived_keys, + operation, + } = request; + + match operation { + Operation::GetPubkey {} => { + let key = derived_keys + .iter() + .flatten() + .next() + .map(ed25519_dalek::SigningKey::from_bytes) + .unwrap(); + let key = key.verifying_key().to_bytes(); + Ok(serde_json::json!({ + "blob": { + "pubkey": key, + } + })) + } + Operation::Sign { message } => { + let key = derived_keys + .iter() + .flatten() + .next() + .map(ed25519_dalek::SigningKey::from_bytes) + .unwrap(); + let signature = key.sign(&message); + Ok(serde_json::json!({ + "blob": { + "signature": signature.to_vec(), + } + })) + } + } + } +} diff --git a/crates/builtins/icepick-ed25519/src/main.rs b/crates/builtins/icepick-ed25519/src/main.rs new file mode 100644 index 0000000..c547699 --- /dev/null +++ b/crates/builtins/icepick-ed25519/src/main.rs @@ -0,0 +1,6 @@ +use icepick_module::Module; +use icepick_ed25519::Ed25519; + +fn main() -> Result<(), Box> { + Ed25519::run_responder() +} diff --git a/crates/by-chain/icepick-spacemesh/Cargo.toml b/crates/by-chain/icepick-spacemesh/Cargo.toml new file mode 100644 index 0000000..5014774 --- /dev/null +++ b/crates/by-chain/icepick-spacemesh/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "icepick-spacemesh" +version = "0.1.0" +edition = "2021" +publish = ["distrust"] + +[dependencies] +icepick-module = { version = "0.1.0", path = "../../icepick-module" } +serde.workspace = true +serde_json.workspace = true +spacemesh = { version = "0.1.0", path = "../../spacemesh/spacemesh" } +thiserror = "2.0.11" +tokio = { version = "1.43.0", features = ["rt", "net"] } diff --git a/crates/by-chain/icepick-spacemesh/src/lib.rs b/crates/by-chain/icepick-spacemesh/src/lib.rs new file mode 100644 index 0000000..6500548 --- /dev/null +++ b/crates/by-chain/icepick-spacemesh/src/lib.rs @@ -0,0 +1,172 @@ +use icepick_module::Module; +use serde::{Deserialize, Serialize}; +use spacemesh::bech32::{self, Hrp}; +use std::str::FromStr; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)] +#[serde(rename_all = "kebab-case")] +pub enum Cluster { + Testnet, + #[default] + Mainnet, +} + +impl Cluster { + fn hrp(&self) -> bech32::Hrp { + match self { + Cluster::Testnet => Hrp::parse("stest").unwrap(), + Cluster::Mainnet => Hrp::parse("sm").unwrap(), + } + } +} + +impl std::str::FromStr for Cluster { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "testnet" => Ok(Self::Testnet), + "mainnet" => Ok(Self::Mainnet), + _ => Err("Invalid value"), + } + } +} + +impl std::fmt::Display for Cluster { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Cluster::Testnet => f.write_str("testnet"), + Cluster::Mainnet => f.write_str("mainnet"), + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error {} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GenerateWallet { + account: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetWalletAddress { + pubkey: [u8; 32], + cluster: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountData { + account: String, + cluster: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AwaitFunds { + address: String, + amount: String, + cluster: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "operation", content = "values", rename_all = "kebab-case")] +pub enum Operation { + GenerateWallet(GenerateWallet), + GetWalletAddress(GetWalletAddress), + AwaitFunds(AwaitFunds), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Request { + derived_keys: Option>, + + #[serde(flatten)] + operation: Operation, +} + +pub fn run_async(f: F) -> F::Output { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(f) +} + +pub struct Spacemesh; + +impl Module for Spacemesh { + type Error = Error; + type Request = Request; + + fn describe_operations() -> Vec { + use icepick_module::help::*; + + let account = Argument::builder() + .name("account") + .description("The derivation index for the account.") + .r#type(ArgumentType::Optional) + .build(); + + let cluster = Argument::builder() + .name("cluster") + .description("Spacemesh cluster to interact with (mainnet, testnet).") + .r#type(ArgumentType::Required) + .build(); + + let generate_wallet = Operation::builder() + .name("generate-wallet") + .description("Generate a wallet for the given account.") + .build() + .argument(&account); + + let get_wallet_address = Operation::builder() + .name("get-wallet-address") + .description("Get the address for a given wallet.") + .build() + .argument(&cluster) + .argument( + &Argument::builder() + .name("wallet_pubkey") + .description("Public key of the wallet.") + .r#type(ArgumentType::Required) + .build(), + ); + + vec![generate_wallet, get_wallet_address] + } + + fn handle_request(request: Self::Request) -> Result { + let Request { + operation, + derived_keys: _, + } = request; + + match operation { + Operation::GenerateWallet(GenerateWallet { account }) => { + let account = u32::from_str(account.as_deref().unwrap_or("0")).unwrap(); + + Ok(serde_json::json!({ + "blob": {}, + "derivation_accounts": [(account | 1 << 31)], + })) + } + Operation::GetWalletAddress(GetWalletAddress { pubkey, cluster }) => { + use spacemesh::wallet::AsAddress; + let account = pubkey.as_address(); + let hrp = cluster.unwrap_or_default().hrp(); + let address = bech32::encode(hrp, &account).unwrap(); + Ok(serde_json::json!({ + "blob": { + "address": address, + }, + "derivation_accounts": [], + })) + } + Operation::AwaitFunds(AwaitFunds { + address, + amount, + cluster, + }) => todo!(), + } + } +} diff --git a/crates/by-chain/icepick-spacemesh/src/main.rs b/crates/by-chain/icepick-spacemesh/src/main.rs new file mode 100644 index 0000000..56d2311 --- /dev/null +++ b/crates/by-chain/icepick-spacemesh/src/main.rs @@ -0,0 +1,6 @@ +use icepick_module::Module; +use icepick_spacemesh::Spacemesh; + +fn main() -> Result<(), Box> { + Spacemesh::run_responder() +} diff --git a/crates/icepick-workflow/src/lib.rs b/crates/icepick-workflow/src/lib.rs index 42df6f3..bb52e55 100644 --- a/crates/icepick-workflow/src/lib.rs +++ b/crates/icepick-workflow/src/lib.rs @@ -69,7 +69,8 @@ pub struct OperationResult { derivation_accounts: Vec, } -type DeriveKeys<'a> = &'a dyn Fn(&DerivationAlgorithm, &DerivationPath, &[DerivationIndex]) -> Vec>; +type DeriveKeys<'a> = + &'a dyn Fn(&DerivationAlgorithm, &DerivationPath, &[DerivationIndex]) -> Vec>; impl Workflow { pub fn simulate_workflow( @@ -128,15 +129,6 @@ impl Workflow { return Err(WorkflowError::InvocableOperationNotFound(step_type)); }; - // Add requested derivation keys and clear derivation account requests. - if !derivation_accounts.is_empty() { - let Some((algo, path_prefix)) = operation.derivation_configuration() else { - return Err(WorkflowError::DerivationConfigurationNotFound(step_type)); - }; - derived_keys.extend(derive_keys(algo, path_prefix, &derivation_accounts)); - } - derivation_accounts.clear(); - // Prepare all inputs for the operation invocation let inputs: StringMap = data .iter() @@ -167,13 +159,20 @@ impl Workflow { let (_given, stored) = step.outputs.iter().find(|(k1, _)| k == **k1)?; Some((stored.clone(), v)) })); + + // Add requested derivation keys and clear derivation account requests. + if !derivation_accounts.is_empty() { + let Some((algo, path_prefix)) = operation.derivation_configuration() else { + return Err(WorkflowError::DerivationConfigurationNotFound(step_type)); + }; + derived_keys.extend(derive_keys(algo, path_prefix, &derivation_accounts)); + } + derivation_accounts.clear(); } if let Some(last_step) = &self.steps.last() { let values = last_step.outputs.values().collect::>(); - data.retain(|stored_name, _| { - values.contains(stored_name) - }); + data.retain(|stored_name, _| values.contains(stored_name)); } Ok(data) diff --git a/crates/icepick/src/cli/mod.rs b/crates/icepick/src/cli/mod.rs index efe619b..f7344bc 100644 --- a/crates/icepick/src/cli/mod.rs +++ b/crates/icepick/src/cli/mod.rs @@ -139,6 +139,14 @@ pub fn do_cli_thing() { derivation_prefix: Default::default(), workflows: Default::default(), }); + config.modules.push(ModuleConfig { + name: "ed25519".to_string(), + command_name: Default::default(), + algorithm: Some(DerivationAlgorithm::Ed25519), + // TODO: impl Last + derivation_prefix: Default::default(), + workflows: Default::default(), + }); let workflows = default_workflows(); for module in &mut config.modules { diff --git a/crates/icepick/workflows/spacemesh/generate-wallet.yaml b/crates/icepick/workflows/spacemesh/generate-wallet.yaml new file mode 100644 index 0000000..4f9b647 --- /dev/null +++ b/crates/icepick/workflows/spacemesh/generate-wallet.yaml @@ -0,0 +1,17 @@ +name: generate-address +optional_inputs: +- account +- cluster +step: +- type: spacemesh-generate-wallet + inputs: + account: account +- type: ed25519-get-pubkey + outputs: + pubkey: pubkey +- type: spacemesh-get-wallet-address + inputs: + pubkey: pubkey + cluster: cluster + outputs: + address: address diff --git a/crates/spacemesh/api-client/Cargo.toml b/crates/spacemesh/api-client/Cargo.toml new file mode 100644 index 0000000..4220bad --- /dev/null +++ b/crates/spacemesh/api-client/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "spacemesh-api-client" +version = "0.1.0" +edition = "2021" +publish = ["distrust"] + +[dependencies] +futures = "0.3" +progenitor-client = { git = "https://github.com/geoffreygarrett/progenitor", rev = "8726ea91eb19f92e1357f1ceeeab507477dcfeb6" } +reqwest = { version = "0.11", features = ["json", "stream"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +chrono = { version = "0.4", features = ["serde"] } +base64 = "0.22.1" +smex = { version = "0.1.0", registry = "distrust" } + +[build-dependencies] +prettyplease = "0.2.22" +progenitor = { git = "https://github.com/geoffreygarrett/progenitor", rev = "8726ea91eb19f92e1357f1ceeeab507477dcfeb6" } +serde_json = "1.0" +syn = "2.0" + +[dev-dependencies] +base64 = "0.22.1" +smex = { version = "0.1.0", registry = "distrust" } +tokio = { version = "1.43.0", features = ["macros", "net", "rt", "test-util"] } diff --git a/crates/spacemesh/api-client/build.rs b/crates/spacemesh/api-client/build.rs new file mode 100644 index 0000000..c9463a0 --- /dev/null +++ b/crates/spacemesh/api-client/build.rs @@ -0,0 +1,22 @@ +/* +curl -X 'GET' \ + 'https://converter.swagger.io/api/convert?url=https%3A%2F%2Fmainnet-api-docs.spacemesh.network%2Fv1.7.12%2Fapi.swagger.json' \ + -H 'accept: application/json' +*/ + +fn main() { + let src = "openapi.json"; + println!("cargo:rerun-if-changed={}", src); + let file = std::fs::File::open(src).unwrap(); + let spec = serde_json::from_reader(file).unwrap(); + let mut generator = progenitor::Generator::default(); + + let tokens = generator.generate_tokens(&spec).unwrap(); + let ast = syn::parse2(tokens).unwrap(); + let content = prettyplease::unparse(&ast); + + let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf(); + out_file.push("codegen.rs"); + + std::fs::write(out_file, content).unwrap(); +} diff --git a/crates/spacemesh/api-client/openapi.json b/crates/spacemesh/api-client/openapi.json new file mode 100644 index 0000000..95f483b --- /dev/null +++ b/crates/spacemesh/api-client/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.0.1","info":{"title":"Spacemesh API","contact":{"name":"Spacemesh","url":"https://spacemesh.io/"},"license":{"name":"MIT License","url":"https://github.com/spacemeshos/go-spacemesh/blob/develop/LICENSE"},"version":"v2alpha1"},"servers":[{"url":"https://mainnet-api.spacemesh.network/"}],"tags":[{"name":"AccountService"},{"name":"ActivationService"},{"name":"TransactionService"},{"name":"LayerService"},{"name":"MalfeasanceService"},{"name":"NetworkService"},{"name":"NodeService"},{"name":"RewardService"}],"paths":{"/spacemesh.v2alpha1.AccountService/List":{"post":{"tags":["AccountService"],"summary":"List of accounts","description":"List is a method that takes an \"AccountRequest\" body and returns an \"AccountList\".\nThis method is used to retrieve a list of accounts based on the provided request parameters.","operationId":"AccountService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1AccountRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1AccountList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.ActivationService/ActivationsCount":{"post":{"tags":["ActivationService"],"summary":"Count of activations","description":"ActivationsCount is a method that takes an \"ActivationsCountRequest\" body and returns an \"ActivationsCountResponse\".\nThis method is used to retrieve the count of activations for a specified epoch.","operationId":"ActivationService_ActivationsCount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ActivationsCountRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ActivationsCountResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.ActivationService/Highest":{"post":{"tags":["ActivationService"],"summary":"Highest","description":"Highest is a method that takes an \"HighestRequest\" body and returns an \"HighestResponse\".\nThis method is used to retrieve the activation with the highest tick count.","operationId":"ActivationService_Highest","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1HighestRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1HighestResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.ActivationService/List":{"post":{"tags":["ActivationService"],"summary":"List of activations","description":"List is a method that takes an \"ActivationRequest\" body and returns an \"ActivationList\".\nThis method is used to retrieve a list of activations based on the provided request parameters.","operationId":"ActivationService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ActivationRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ActivationList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.LayerService/List":{"post":{"tags":["LayerService"],"summary":"List of layers","description":"List is a method that takes a \"LayerRequest\" body and returns an \"LayerList\".\nThis method is used to retrieve a list of layers based on the provided request parameters.","operationId":"LayerService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1LayerRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1LayerList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.MalfeasanceService/List":{"post":{"tags":["MalfeasanceService"],"summary":"List of malfeasance proofs","description":"List is a method that takes a \"MalfeasanceRequest\" body and returns an \"MalfeasanceList\".\nThis method is used to retrieve a list of malfeasance proofs based on the provided request parameters.","operationId":"MalfeasanceService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1MalfeasanceRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1MalfeasanceList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.NetworkService/Info":{"post":{"tags":["NetworkService"],"summary":"Network information","description":"Info is a method that returns an \"NetworkInfoResponse\".\nThis method is used to retrieve network information.","operationId":"NetworkService_Info","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1NetworkInfoRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1NetworkInfoResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.NodeService/Status":{"post":{"tags":["NodeService"],"summary":"Node status","description":"Status is a method that returns an \"NodeStatusResponse\".\nThis method is used to retrieve node status information.","operationId":"NodeService_Status","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1NodeStatusRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1NodeStatusResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.RewardService/List":{"post":{"tags":["RewardService"],"summary":"List of rewards","description":"List is a method that takes a \"RewardRequest\" body and returns an \"RewardList\".\nThis method is used to retrieve a list of rewards based on the provided request parameters.","operationId":"RewardService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1RewardRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1RewardList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.TransactionService/EstimateGas":{"post":{"tags":["TransactionService"],"summary":"Estimate gas for transaction","description":"EstimateGas is a method that takes a \"EstimateGasRequest\" body and returns an \"EstimateGasResponse\".\nThis method is used to estimate the recommended maximum gas for a signed binary transaction.","operationId":"TransactionService_EstimateGas","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1EstimateGasRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1EstimateGasResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.TransactionService/List":{"post":{"tags":["TransactionService"],"summary":"List of transactions","description":"List is a method that takes a \"TransactionRequest\" body and returns an \"TransactionList\".\nThis method is used to retrieve a list of transactions based on the provided request parameters.","operationId":"TransactionService_List","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionList"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.TransactionService/ParseTransaction":{"post":{"tags":["TransactionService"],"summary":"Parse transaction","description":"ParseTransaction is a method that takes a \"ParseTransactionRequest\" body and returns an \"ParseTransactionResponse\".\nThis method is used to parse a signed binary transaction and optionally verify its signature.","operationId":"TransactionService_ParseTransaction","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ParseTransactionRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1ParseTransactionResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}},"/spacemesh.v2alpha1.TransactionService/SubmitTransaction":{"post":{"tags":["TransactionService"],"summary":"Submit transaction","description":"SubmitTransaction is a method that takes a \"SubmitTransactionRequest\" body and returns an \"SubmitTransactionResponse\".\nThis method is used to submit a signed binary transaction to the network.","operationId":"TransactionService_SubmitTransaction","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1SubmitTransactionRequest"}}},"required":true},"responses":{"200":{"description":"A successful response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/spacemeshv2alpha1SubmitTransactionResponse"}}}},"default":{"description":"An unexpected error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/googlerpcStatus"}}}}},"x-codegen-request-body-name":"body"}}},"components":{"schemas":{"googlerpcStatus":{"type":"object","properties":{"code":{"type":"integer","description":"The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code].","format":"int32"},"details":{"type":"array","description":"A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use.","items":{"$ref":"#/components/schemas/protobufAny"}},"message":{"type":"string","description":"A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."}},"description":"The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."},"protobufAny":{"type":"object","properties":{"@type":{"type":"string","description":"A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."}},"additionalProperties":{"type":"object"},"description":"`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(&foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := &pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := &pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": ,\n \"lastName\": \n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"},"spacemeshv2alpha1Account":{"type":"object","properties":{"address":{"title":"account public address","type":"string"},"current":{"$ref":"#/components/schemas/spacemeshv2alpha1AccountState"},"projected":{"$ref":"#/components/schemas/spacemeshv2alpha1AccountState"},"template":{"title":"account template address","type":"string"}}},"spacemeshv2alpha1AccountList":{"type":"object","properties":{"accounts":{"title":"list of accounts","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1Account"}}}},"spacemeshv2alpha1AccountRequest":{"type":"object","properties":{"addresses":{"title":"list of account addresses","type":"array","items":{"type":"string"}},"limit":{"title":"specifies max number of items to fetch// bech32 format including HRP","type":"string","format":"uint64"},"offset":{"title":"adjusts the starting point for data","type":"string","format":"uint64"}}},"spacemeshv2alpha1AccountState":{"type":"object","properties":{"balance":{"title":"account balance in smidge","type":"string","format":"uint64"},"counter":{"title":"aka nonce","type":"string","format":"uint64"},"layer":{"title":"account balance as of layer X","type":"integer","format":"int64"}}},"spacemeshv2alpha1Activation":{"type":"object","properties":{"coinbase":{"type":"string"},"height":{"type":"string","format":"uint64"},"id":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"numUnits":{"title":"number of effective PoST data commitment units","type":"integer","format":"int64"},"publishEpoch":{"type":"integer","format":"int64"},"smesherId":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"weight":{"type":"string","format":"uint64"}}},"spacemeshv2alpha1ActivationList":{"type":"object","properties":{"activations":{"title":"list of activations","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1Activation"}}}},"spacemeshv2alpha1ActivationRequest":{"type":"object","properties":{"coinbase":{"type":"string","description":"`coinbase` filter is not supported by database index and will result in sequential scan."},"endEpoch":{"title":"ending epoch for the query","type":"integer","format":"int64"},"id":{"title":"list of activation IDs","type":"array","items":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}},"limit":{"title":"specifies max number of items to fetch","type":"string","format":"uint64"},"offset":{"title":"adjusts the starting point for data","type":"string","format":"uint64"},"smesherId":{"title":"list of smesher IDs","type":"array","items":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}},"startEpoch":{"title":"starting epoch for the query","type":"integer","format":"int64"}}},"spacemeshv2alpha1ActivationsCountRequest":{"type":"object","properties":{"epoch":{"title":"epoch number","type":"integer","format":"int64"}}},"spacemeshv2alpha1ActivationsCountResponse":{"type":"object","properties":{"count":{"title":"number of activations for the specified epoch","type":"integer","format":"int64"}}},"spacemeshv2alpha1Block":{"type":"object","properties":{"id":{"title":"block hash","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}},"spacemeshv2alpha1ContentsDrainVault":{"type":"object","properties":{"amount":{"type":"string","format":"uint64"},"destination":{"type":"string"},"vault":{"type":"string"}}},"spacemeshv2alpha1ContentsMultiSigSpawn":{"type":"object","properties":{"pubkey":{"type":"array","items":{"type":"string"}},"required":{"title":"number of required signatures","type":"integer","format":"int64"}}},"spacemeshv2alpha1ContentsSend":{"type":"object","properties":{"amount":{"title":"amount in smidge","type":"string","format":"uint64"},"destination":{"title":"recipient account","type":"string"}}},"spacemeshv2alpha1ContentsSingleSigSpawn":{"type":"object","properties":{"pubkey":{"type":"string"}}},"spacemeshv2alpha1ContentsVaultSpawn":{"type":"object","properties":{"initialUnlockAmount":{"type":"string","format":"uint64"},"owner":{"title":"owner account","type":"string"},"totalAmount":{"type":"string","format":"uint64"},"vestingEnd":{"title":"as layer number","type":"integer","format":"int64"},"vestingStart":{"title":"as layer number","type":"integer","format":"int64"}}},"spacemeshv2alpha1EstimateGasRequest":{"type":"object","properties":{"transaction":{"title":"signed binary transaction","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}},"spacemeshv2alpha1EstimateGasResponse":{"type":"object","properties":{"recommendedMaxGas":{"title":"recommended max gas","type":"string","format":"uint64"},"status":{"$ref":"#/components/schemas/googlerpcStatus"}}},"spacemeshv2alpha1HighestRequest":{"type":"object"},"spacemeshv2alpha1HighestResponse":{"type":"object","properties":{"activation":{"$ref":"#/components/schemas/spacemeshv2alpha1Activation"}}},"spacemeshv2alpha1Layer":{"type":"object","properties":{"block":{"$ref":"#/components/schemas/spacemeshv2alpha1Block"},"consensusHash":{"type":"string"},"cumulativeStateHash":{"title":"cumulative fingerprint that uniquely identifies state since genesis","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"number":{"title":"layer number - not hash - layer content may change","type":"integer","format":"int64"},"stateHash":{"title":"fingerprint of the computed state at the layer","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"status":{"$ref":"#/components/schemas/spacemeshv2alpha1LayerLayerStatus"}}},"spacemeshv2alpha1LayerLayerStatus":{"title":"- LAYER_STATUS_APPLIED: applied by hare\n - LAYER_STATUS_VERIFIED: verified by tortoise","type":"string","default":"LAYER_STATUS_UNSPECIFIED","enum":["LAYER_STATUS_UNSPECIFIED","LAYER_STATUS_APPLIED","LAYER_STATUS_VERIFIED"]},"spacemeshv2alpha1LayerList":{"type":"object","properties":{"layers":{"title":"list of layers","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1Layer"}}}},"spacemeshv2alpha1LayerRequest":{"type":"object","properties":{"endLayer":{"title":"ending layer for the query","type":"integer","format":"int64"},"limit":{"title":"specifies max number of items to fetch","type":"string","format":"uint64"},"offset":{"title":"adjusts the starting point for data","type":"string","format":"uint64"},"sortOrder":{"$ref":"#/components/schemas/spacemeshv2alpha1SortOrder"},"startLayer":{"title":"starting layer for the query","type":"integer","format":"int64"}}},"spacemeshv2alpha1MalfeasanceList":{"type":"object","properties":{"proofs":{"title":"list of malfeasance proofs","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1MalfeasanceProof"}}}},"spacemeshv2alpha1MalfeasanceProof":{"type":"object","properties":{"domain":{"$ref":"#/components/schemas/spacemeshv2alpha1MalfeasanceProofMalfeasanceDomain"},"properties":{"title":"Properties of the Malfeasance proof, different for every type of proof","type":"object","additionalProperties":{"type":"string"}},"smesher":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"type":{"title":"type of the malfeasance proof, depends on domain","type":"integer","description":"for legacy proofs the types are\n 1 - Double publish of ATX\n 2 - Multiple ballots for a layer by same smesher\n 3 - Hare Equivocation (currently unused)\n 4 - ATX with invalid PoST proof published\n 5 - ATX referencing an invalid previous ATX published","format":"int64"}}},"spacemeshv2alpha1MalfeasanceProofMalfeasanceDomain":{"title":"- DOMAIN_UNSPECIFIED: for legacy proofs\n - DOMAIN_ACTIVATION: ATX related proofs\n - DOMAIN_BALLOT: Ballot related proofs\n - DOMAIN_HARE: Hare related proofs","type":"string","default":"DOMAIN_UNSPECIFIED","enum":["DOMAIN_UNSPECIFIED","DOMAIN_ACTIVATION","DOMAIN_BALLOT","DOMAIN_HARE"]},"spacemeshv2alpha1MalfeasanceRequest":{"type":"object","properties":{"limit":{"title":"specifies max number of items to fetch","type":"string","format":"uint64"},"offset":{"title":"adjusts the starting point for data","type":"string","format":"uint64"},"smesherId":{"title":"list of smesher ids to fetch (can be empty for all)","type":"array","items":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}}},"spacemeshv2alpha1NetworkInfoRequest":{"type":"object"},"spacemeshv2alpha1NetworkInfoResponse":{"type":"object","properties":{"effectiveGenesisLayer":{"title":"effective genesis layer, i.e., first layer after genesis initialization period","type":"integer","format":"int64"},"genesisId":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"genesisTime":{"title":"genesis time of the network, represented as a timestamp","type":"string","format":"date-time"},"hrp":{"type":"string"},"labelsPerUnit":{"title":"number of labels per unit","type":"string","format":"uint64"},"layerDuration":{"title":"duration of each layer in the network, specified as a duration","type":"string"},"layersPerEpoch":{"title":"number of layers per epoch","type":"integer","format":"int64"}}},"spacemeshv2alpha1NodeStatusRequest":{"type":"object"},"spacemeshv2alpha1NodeStatusResponse":{"type":"object","properties":{"appliedLayer":{"title":"last layer node has applied to the state","type":"integer","format":"int64"},"connectedPeers":{"title":"number of connected neighbors","type":"string","format":"uint64"},"currentLayer":{"title":"current layer, based on clock time","type":"integer","format":"int64"},"latestLayer":{"title":"latest layer node has seen from blocks","type":"integer","format":"int64"},"processedLayer":{"title":"last layer whose votes have been processed","type":"integer","format":"int64"},"status":{"$ref":"#/components/schemas/spacemeshv2alpha1NodeStatusResponseSyncStatus"}}},"spacemeshv2alpha1NodeStatusResponseSyncStatus":{"type":"string","default":"SYNC_STATUS_UNSPECIFIED","enum":["SYNC_STATUS_UNSPECIFIED","SYNC_STATUS_OFFLINE","SYNC_STATUS_SYNCING","SYNC_STATUS_SYNCED"]},"spacemeshv2alpha1Nonce":{"type":"object","properties":{"counter":{"type":"string","format":"uint64"}}},"spacemeshv2alpha1ParseTransactionRequest":{"type":"object","properties":{"transaction":{"title":"signed binary transaction","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"verify":{"title":"if true signature verification will be executed","type":"boolean"}}},"spacemeshv2alpha1ParseTransactionResponse":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/googlerpcStatus"},"tx":{"$ref":"#/components/schemas/spacemeshv2alpha1Transaction"}}},"spacemeshv2alpha1Reward":{"type":"object","properties":{"coinbase":{"title":"account awarded this reward","type":"string"},"layer":{"title":"layer award was paid in","type":"integer","format":"int64"},"layerReward":{"title":"tx_fee = total - layer_reward","type":"string","format":"uint64"},"smesher":{"title":"id of smesher who earned this reward","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"total":{"title":"total reward paid in smidge (sum of tx fee and layer reward)","type":"string","format":"uint64"}}},"spacemeshv2alpha1RewardList":{"type":"object","properties":{"rewards":{"title":"list of rewards","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1Reward"}}}},"spacemeshv2alpha1RewardRequest":{"type":"object","properties":{"coinbase":{"title":"filter by coinbase","type":"string"},"endLayer":{"title":"ending layer for the query","type":"integer","format":"int64"},"limit":{"title":"specifies max number of items to fetch","type":"string","format":"uint64"},"offset":{"title":"adjusts the starting point for data","type":"string","format":"uint64"},"smesher":{"title":"filter by smesher","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"sortOrder":{"$ref":"#/components/schemas/spacemeshv2alpha1SortOrder"},"startLayer":{"title":"starting layer for the query","type":"integer","format":"int64"}}},"spacemeshv2alpha1SortOrder":{"type":"string","default":"ASC","enum":["ASC","DESC"]},"spacemeshv2alpha1SubmitTransactionRequest":{"type":"object","properties":{"transaction":{"title":"signed binary transaction","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}},"spacemeshv2alpha1SubmitTransactionResponse":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/googlerpcStatus"},"txId":{"title":"transaction ID","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}},"spacemeshv2alpha1Transaction":{"type":"object","properties":{"contents":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionContents"},"gasPrice":{"title":"fee per unit gas, in smidge","type":"string","format":"uint64"},"id":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"maxGas":{"title":"max gas units consumed by tx","type":"string","format":"uint64"},"maxSpend":{"type":"string","format":"uint64"},"method":{"type":"integer","description":"this is actually limited by uint8, but no type for that.","format":"int64"},"nonce":{"$ref":"#/components/schemas/spacemeshv2alpha1Nonce"},"principal":{"title":"principal account address","type":"string"},"raw":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"template":{"title":"template account address","type":"string"},"type":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionTransactionType"}},"description":"An immutable Spacemesh transaction.\ndo not include mutable data such as tx state or result."},"spacemeshv2alpha1TransactionContents":{"type":"object","properties":{"drainVault":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsDrainVault"},"multiSigSpawn":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsMultiSigSpawn"},"send":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsSend"},"singleSigSpawn":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsSingleSigSpawn"},"vaultSpawn":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsVaultSpawn"},"vestingSpawn":{"$ref":"#/components/schemas/spacemeshv2alpha1ContentsMultiSigSpawn"}}},"spacemeshv2alpha1TransactionList":{"type":"object","properties":{"transactions":{"title":"list of transactions","type":"array","items":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionResponse"}}}},"spacemeshv2alpha1TransactionRequest":{"type":"object","properties":{"address":{"title":"Filter by address (principal or recipient)","type":"string"},"endLayer":{"type":"integer","description":"Ending layer for transactions.","format":"int64"},"includeResult":{"type":"boolean","description":"Whether to include result of transactions in response."},"includeState":{"type":"boolean","description":"Whether to include transaction state in response."},"limit":{"type":"string","description":"Specifies maximum number of items to fetch.","format":"uint64"},"offset":{"type":"string","description":"Adjusts the starting point for data retrieval.","format":"uint64"},"sortOrder":{"$ref":"#/components/schemas/spacemeshv2alpha1SortOrder"},"startLayer":{"type":"integer","description":"Starting layer for transactions.","format":"int64"},"txid":{"type":"array","description":"Filter: specific transaction IDs to filter.","items":{"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"}}}},"spacemeshv2alpha1TransactionResponse":{"type":"object","properties":{"tx":{"$ref":"#/components/schemas/spacemeshv2alpha1Transaction"},"txResult":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionResult"},"txState":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionState"}}},"spacemeshv2alpha1TransactionResult":{"type":"object","properties":{"block":{"title":"block hash","pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$","type":"string","format":"byte"},"fee":{"title":"in smidge","type":"string","format":"uint64"},"gasConsumed":{"title":"in units of gas","type":"string","format":"uint64"},"layer":{"title":"layer number","type":"integer","format":"int64"},"message":{"type":"string"},"status":{"$ref":"#/components/schemas/spacemeshv2alpha1TransactionResultStatus"},"touchedAddresses":{"title":"addresses touched by the tx","type":"array","items":{"type":"string"}}}},"spacemeshv2alpha1TransactionResultStatus":{"type":"string","default":"TRANSACTION_STATUS_UNSPECIFIED","enum":["TRANSACTION_STATUS_UNSPECIFIED","TRANSACTION_STATUS_SUCCESS","TRANSACTION_STATUS_FAILURE","TRANSACTION_STATUS_INVALID"]},"spacemeshv2alpha1TransactionState":{"type":"string","description":"TransactionState is the \"journey\" of a tx from mempool to block inclusion to\nmesh to STF processing. To know whether or not the tx actually succeeded,\nand its side effects, check tx_state.\n\n - TRANSACTION_STATE_UNSPECIFIED: default state\n - TRANSACTION_STATE_REJECTED: rejected from mempool due to, e.g., invalid syntax\n - TRANSACTION_STATE_INSUFFICIENT_FUNDS: rejected from mempool by funds check\n - TRANSACTION_STATE_CONFLICTING: rejected from mempool due to conflicting counter\n - TRANSACTION_STATE_MEMPOOL: in mempool but not on the mesh yet\n - TRANSACTION_STATE_MESH: submitted to the mesh\n - TRANSACTION_STATE_PROCESSED: processed by STF; check Receipt for success or failure\n - TRANSACTION_STATE_INEFFECTUAL: removed from mempool and will be forgotten and never executed","default":"TRANSACTION_STATE_UNSPECIFIED","enum":["TRANSACTION_STATE_UNSPECIFIED","TRANSACTION_STATE_REJECTED","TRANSACTION_STATE_INSUFFICIENT_FUNDS","TRANSACTION_STATE_CONFLICTING","TRANSACTION_STATE_MEMPOOL","TRANSACTION_STATE_MESH","TRANSACTION_STATE_PROCESSED","TRANSACTION_STATE_INEFFECTUAL"]},"spacemeshv2alpha1TransactionTransactionType":{"type":"string","default":"TRANSACTION_TYPE_UNSPECIFIED","enum":["TRANSACTION_TYPE_UNSPECIFIED","TRANSACTION_TYPE_SINGLE_SIG_SEND","TRANSACTION_TYPE_SINGLE_SIG_SPAWN","TRANSACTION_TYPE_SINGLE_SIG_SELFSPAWN","TRANSACTION_TYPE_MULTI_SIG_SEND","TRANSACTION_TYPE_MULTI_SIG_SPAWN","TRANSACTION_TYPE_MULTI_SIG_SELFSPAWN","TRANSACTION_TYPE_VESTING_SPAWN","TRANSACTION_TYPE_VAULT_SPAWN","TRANSACTION_TYPE_DRAIN_VAULT"]}}},"x-original-swagger-version":"2.0"} \ No newline at end of file diff --git a/crates/spacemesh/api-client/src/lib.rs b/crates/spacemesh/api-client/src/lib.rs new file mode 100644 index 0000000..e1adbf5 --- /dev/null +++ b/crates/spacemesh/api-client/src/lib.rs @@ -0,0 +1,42 @@ +#![allow(warnings, unused)] + +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); + +// NOTE: The RPC API requires base64-encoded transaction IDs rather than hex-encoded. +// That was confusing, after all their branding is `0x` based. + +pub fn encode_transaction_id(txid: impl AsRef) -> Result { + use base64::prelude::*; + let tx = smex::decode(txid)?; + Ok(BASE64_STANDARD.encode(tx)) +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::prelude::*; + + #[tokio::test] + async fn it_works() { + let client = Client::new("https://mainnet-api.spacemesh.network"); + let txid = "638442a2033f20b5a7280b9a4f2bfc73022f6e7ec64b1497b85335444381d99d"; + let txid = smex::decode(txid).unwrap(); + let txid = BASE64_STANDARD.encode(txid); + let result = client + .transaction_service_list(&types::Spacemeshv2alpha1TransactionRequest { + txid: vec![txid], + limit: Some(100.to_string()), + ..Default::default() + }) + .await + .unwrap() + .into_inner(); + + let result = match result { + types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::GooglerpcStatus(googlerpc_status) => panic!("{:?}", googlerpc_status.message), + types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::Spacemeshv2alpha1TransactionList(transaction_list) => { + transaction_list + }, + }; + } +} diff --git a/crates/spacemesh/codec/Cargo.toml b/crates/spacemesh/codec/Cargo.toml new file mode 100644 index 0000000..d2fe15f --- /dev/null +++ b/crates/spacemesh/codec/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "spacemesh-codec" +version = "0.1.0" +edition = "2021" +publish = ["distrust"] + +[dependencies] +parity-scale-codec = { version = "3.6.12", features = ["derive"] } + +[dev-dependencies] +base64 = "0.22.1" +bech32 = "0.11.0" diff --git a/crates/spacemesh/codec/src/lib.rs b/crates/spacemesh/codec/src/lib.rs new file mode 100644 index 0000000..6842df8 --- /dev/null +++ b/crates/spacemesh/codec/src/lib.rs @@ -0,0 +1,530 @@ +//! Spacemesh transaction encoding and decoding. +//! Based loosely on: . +//! +//! # Encoding Transactions +//! +//! ```rust +//! let principal = [0u8; 24]; +//! let destination = [1u8; 24]; +//! +//! let single_sig_spend = Spend { +//! header: TxHeader { +//! principal, +//! }, +//! payload: SpendPayload { +//! nonce: Compact(2), +//! gas_price: Compact(1), +//! arguments: SpendArguments { +//! destination, +//! amount: Compact(100000), +//! }, +//! }, +//! // unsigned transaction +//! signature: [0; 64], +//! }; +//! ``` +//! +//! # Decoding Transactions +//! +//! Transactions can be decoded to bytes using the [`base64`][base64] crate. Using the Spacemesh +//! client, the transaction should also include `template` and `method` values. With those values, +//! [`tx_types::decode_by_address_and_method()`] can be used to attempt to parse the transaction. +//! +//! ```rust +//! use base64::prelude::*; +//! +//! let encoded_tx = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAIBAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAYIaBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; +//! let raw_tx = BASE64_STANDARD.decode(encoded_tx).unwrap(); +//! let spend = tx_types::single_signature::Spend::decode(&mut &raw_tx[..]).unwrap(); +//! ``` +//! +//! [base64]: https://docs.rs/base64/latest/base64/ + +pub use parity_scale_codec::{Compact, Decode, Encode}; + +pub mod constants { + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/constants.ts + + /// The length of an address. + pub const ADDRESS_BYTES_LENGTH: usize = 24; +} + +pub mod core { + use super::*; + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/core.ts + + // NOTE: Encoding an array doesn't encode length, matching the same functionality + // as Bytes in scale-ts. + pub type Address = [u8; constants::ADDRESS_BYTES_LENGTH]; + pub type PublicKey = [u8; 32]; + + pub type Nonce = Compact; + pub type GasPrice = Compact; +} + +pub mod signatures { + use super::*; + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/signatures.ts + + pub type SingleSig = [u8; 64]; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct MultiSigPart { + pub r#ref: Compact, + pub sig: SingleSig, + } + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct MultiSig { + pub parts: Vec, + } + + impl Encode for MultiSig { + fn size_hint(&self) -> usize { + self.parts.len() * std::mem::size_of::() + } + + fn encode(&self) -> Vec { + // NOTE: No inline length is included. + let mut r = Vec::with_capacity(self.size_hint()); + for sig in &self.parts { + sig.encode_to(&mut r); + } + r + } + } + + impl Decode for MultiSig { + fn decode( + input: &mut I, + ) -> Result { + let mut parts = vec![]; + // NOTE: We can't rely on the length of the input. It may not be available. + // Unfortunately, we also don't have enough context to know if the reason it can't + // decode is because we ran out of input, or because there was a format error. + while let Ok(part) = MultiSigPart::decode(input) { + parts.push(part); + } + Ok(Self { parts }) + } + } +} + +pub mod tx { + use super::*; + + pub trait TransactionMethod { + fn method_selector() -> u8; + } + + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/tx.ts + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct TxHeader { + // should always be 0 + // pub transaction_type: Compact, + pub principal: core::Address, + // covered by const M + // pub method_selector: Compact, + } + + impl Encode for TxHeader { + fn encode(&self) -> Vec { + let mut r = Vec::with_capacity(self.size_hint()); + let transaction_type = Compact(0u8); + transaction_type.encode_to(&mut r); + self.principal.encode_to(&mut r); + let method_selector = Compact(M); + method_selector.encode_to(&mut r); + r + } + } + + impl Decode for TxHeader { + fn decode( + input: &mut I, + ) -> Result { + let transaction_type = Compact::::decode(input)?; + if transaction_type.0 != 0 { + return Err("transaction_type != 0".into()); + } + let principal = core::Address::decode(input)?; + let method_selector = Compact::::decode(input)?; + if method_selector.0 != M { + return Err("method_selector != M".into()); + } + Ok(Self { + principal, + }) + } + } + + // NOTE: This is used in place of `withTemplateAddress()`. + // The original source implementation placed `template_address` as the last field, + // but I don't think that's correct based on the implementation of `withTemplateAddress()`. + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct SpawnTxHeader { + pub template_address: core::Address, + // should always be 0 + // pub transaction_type: Compact, + pub principal: core::Address, + // covered by const M + // pub method_selector: Compact, + } + + impl Encode for SpawnTxHeader { + fn encode(&self) -> Vec { + let mut r = Vec::with_capacity(self.size_hint()); + self.template_address.encode_to(&mut r); + let transaction_type = Compact(0u8); + transaction_type.encode_to(&mut r); + self.principal.encode_to(&mut r); + let method_selector = Compact(M); + method_selector.encode_to(&mut r); + r + } + } + + impl Decode for SpawnTxHeader { + fn decode( + input: &mut I, + ) -> Result { + let template_address = core::Address::decode(input)?; + let transaction_type = Compact::::decode(input)?; + if transaction_type.0 != 0 { + return Err("transaction_type != 0".into()); + } + let principal = core::Address::decode(input)?; + let method_selector = Compact::::decode(input)?; + if method_selector.0 != M { + return Err("method_selector != M".into()); + } + Ok(Self { + template_address, + principal, + }) + } + } + + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/transaction.ts + + mod sealed { + use super::signatures; + + pub trait Signature {} + impl Signature for signatures::SingleSig {} + impl Signature for signatures::MultiSig {} + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpawnTransaction { + pub header: SpawnTxHeader, + pub payload: Payload, + pub signature: Signature, + } + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct Transaction { + pub header: TxHeader, + pub payload: Payload, + pub signature: Signature, + } +} + +pub mod tx_types { + use super::*; + + pub type DecodeResult = Option>; + + pub mod common { + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/common.ts + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct TxPayload { + pub nonce: core::Nonce, + pub gas_price: core::GasPrice, + pub arguments: Arguments, + } + } + + pub mod vault { + use super::*; + use common::TxPayload; + use signatures::SingleSig; + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/vault.ts + + pub const VAULT_TEMPLATE_ADDRESS: core::Address = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, + ]; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpawnArguments { + pub owner: core::Address, + pub total_amount: Compact, + pub initial_unlock_amount: Compact, + pub vesting_start: Compact, + pub vesting_end: Compact, + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpendArguments { + pub destination: core::Address, + pub amount: Compact, + } + + pub type SpawnPayload = TxPayload; + pub type SpendPayload = TxPayload; + + pub type Spawn = tx::SpawnTransaction; + pub type Spend = tx::Transaction; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Method { + Spawn(Spawn), + Spend(Spend), + } + + pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult { + match method { + 0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)), + 16 => Some(Spend::decode(&mut &*input).map(Method::Spend)), + _ => None, + } + } + } + + pub mod vesting { + use super::*; + use common::TxPayload; + use signatures::MultiSig; + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/vesting.ts + + pub const VESTING_TEMPLATE_ADDRESS: core::Address = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + ]; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpawnArguments { + pub required: Compact, + pub public_keys: Vec, + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpendArguments { + pub destination: core::Address, + pub amount: Compact, + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct DrainVaultArguments { + pub vault: core::Address, + pub destination: core::Address, + pub amount: Compact, + } + + pub type SpawnPayload = TxPayload; + pub type SpendPayload = TxPayload; + pub type DrainVaultPayload = TxPayload; + + pub type Spawn = tx::SpawnTransaction; + pub type Spend = tx::Transaction; + pub type DrainVault = tx::Transaction; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Method { + Spawn(Spawn), + Spend(Spend), + DrainVault(DrainVault), + } + + pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult { + match method { + 0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)), + 16 => Some(Spend::decode(&mut &*input).map(Method::Spend)), + 17 => Some(DrainVault::decode(&mut &*input).map(Method::DrainVault)), + _ => None, + } + } + } + + pub mod single_signature { + use super::*; + use common::TxPayload; + use signatures::SingleSig; + + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/singlesig.ts + + pub const SINGLE_SIG_TEMPLATE_ADDRESS: core::Address = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpawnArguments { + pub public_key: core::PublicKey, + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpendArguments { + pub destination: core::Address, + pub amount: Compact, + } + + pub type SpawnPayload = TxPayload; + pub type SpendPayload = TxPayload; + + pub type Spawn = tx::SpawnTransaction; + pub type Spend = tx::Transaction; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Method { + Spawn(Spawn), + Spend(Spend), + } + + pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult { + match method { + 0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)), + 16 => Some(Spend::decode(&mut &*input).map(Method::Spend)), + _ => None, + } + } + } + + pub mod multi_signature { + use super::*; + use common::TxPayload; + use signatures::MultiSig; + + // ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/singlesig.ts + + pub const MULTI_SIG_TEMPLATE_ADDRESS: core::Address = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + ]; + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpawnArguments { + pub required: Compact, + pub public_key: Vec, + } + + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] + pub struct SpendArguments { + pub destination: core::Address, + pub amount: Compact, + } + + pub type SpawnPayload = TxPayload; + pub type SpendPayload = TxPayload; + + pub type Spawn = tx::SpawnTransaction; + pub type Spend = tx::Transaction; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Method { + Spawn(Spawn), + Spend(Spend), + } + + pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult { + match method { + 0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)), + 16 => Some(Spend::decode(&mut &*input).map(Method::Spend)), + _ => None, + } + } + } + + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum ModuleMethod { + Vault(vault::Method), + Vesting(vesting::Method), + SingleSig(single_signature::Method), + MultiSig(multi_signature::Method), + } + + #[rustfmt::skip] + pub fn decode_by_address_and_method( + address: core::Address, + method: u8, + input: &[u8], + ) -> DecodeResult { + match address { + vault::VAULT_TEMPLATE_ADDRESS => + vault::decode_by_method(method, input) + .map(|method| method.map(ModuleMethod::Vault)), + vesting::VESTING_TEMPLATE_ADDRESS => + vesting::decode_by_method(method, input) + .map(|method| method.map(ModuleMethod::Vesting)), + single_signature::SINGLE_SIG_TEMPLATE_ADDRESS => { + single_signature::decode_by_method(method, input) + .map(|method| method.map(ModuleMethod::SingleSig)) + } + multi_signature::MULTI_SIG_TEMPLATE_ADDRESS => { + multi_signature::decode_by_method(method, input) + .map(|method| method.map(ModuleMethod::MultiSig)) + } + _ => { + unimplemented!() + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + use base64::prelude::*; + use bech32::Bech32; + let (hrp, data) = + bech32::decode("sm1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg56ypy7").unwrap(); + assert_eq!(hrp.as_str(), "sm"); + assert_eq!( + &data, + &tx_types::single_signature::SINGLE_SIG_TEMPLATE_ADDRESS + ); + + let encoded_tx = "AAAAAAAvqmgSN6hBGS16FVNfNDURojTRU0AQBAAAAABJThXbKEnjnty59ht5e/5EkjDK8AeANolPDOAiIHlzj7CIG60FzFRpuR/fLVRQsmzRbApYBryfg4RKcnZgmmWPywafADHyuVjkLNGup0gpvhnXAHICeSXveAs="; + let raw_tx = BASE64_STANDARD.decode(encoded_tx).unwrap(); + let spend = tx_types::single_signature::Spend::decode(&mut &raw_tx[..]).unwrap(); + let equivalence = spend.encode(); + assert_eq!(raw_tx, equivalence); + + let recipient_address = + bech32::encode::(hrp, &spend.payload.arguments.destination).unwrap(); + assert_eq!( + recipient_address, + "sm1qqqqqqzffc2ak2zfuw0dew0krduhhljyjgcv4uqdt6nrd" + ); + } + + #[test] + fn recode() { + use tx::*; + use tx_types::single_signature::*; + + let principal = [0u8; 24]; + + let single_sig_spend = Spend { + header: TxHeader { + principal, + }, + payload: SpendPayload { + nonce: Compact(2), + gas_price: Compact(1), + arguments: SpendArguments { + destination: [1; 24], + amount: Compact(100000), + }, + }, + signature: [0; 64], + }; + + let encoded = single_sig_spend.encode(); + let recoded = Spend::decode(&mut &*encoded).unwrap(); + assert_eq!(single_sig_spend, recoded); + } +} diff --git a/crates/spacemesh/spacemesh/Cargo.toml b/crates/spacemesh/spacemesh/Cargo.toml new file mode 100644 index 0000000..37498ef --- /dev/null +++ b/crates/spacemesh/spacemesh/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "spacemesh" +version = "0.1.0" +edition = "2021" +publish = ["distrust"] + +[dependencies] +bech32 = "0.11.0" +spacemesh-api-client = { version = "0.1.0", path = "../api-client" } +spacemesh-codec = { version = "0.1.0", path = "../codec" } + +[dev-dependencies] +base64 = "0.22.1" +bech32 = "0.11.0" +smex = { version = "0.1.0", registry = "distrust" } +tokio = { version = "1.43.0", features = ["net", "rt", "macros"] } diff --git a/crates/spacemesh/spacemesh/src/lib.rs b/crates/spacemesh/spacemesh/src/lib.rs new file mode 100644 index 0000000..d19b80c --- /dev/null +++ b/crates/spacemesh/spacemesh/src/lib.rs @@ -0,0 +1,58 @@ +pub use spacemesh_api_client as client; +pub use spacemesh_api_client::Client; +pub use spacemesh_codec as codec; +pub use spacemesh_codec::tx_types as transaction; + + +pub mod wallet; + +pub mod bech32 { + pub use bech32::*; + + pub fn encode(hrp: Hrp, input: &[u8]) -> Result { + bech32::encode::(hrp, input) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::prelude::*; + use spacemesh_api_client::*; + + #[tokio::test] + async fn decodes_live_transaction() { + let client = Client::new("https://mainnet-api.spacemesh.network"); + let txid = "638442a2033f20b5a7280b9a4f2bfc73022f6e7ec64b1497b85335444381d99d"; + let txid = smex::decode(txid).unwrap(); + let txid = BASE64_STANDARD.encode(txid); + let result = client + .transaction_service_list(&types::Spacemeshv2alpha1TransactionRequest { + txid: vec![txid], + limit: Some(100.to_string()), + ..Default::default() + }) + .await + .unwrap() + .into_inner(); + + let mut result = match result { + types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::GooglerpcStatus(googlerpc_status) => panic!("{:?}", googlerpc_status.message), + types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::Spacemeshv2alpha1TransactionList(transaction_list) => { + transaction_list + }, + }; + + let tx = result.transactions.pop().unwrap().tx.unwrap(); + let (_hrp, address) = bech32::decode(&tx.template.unwrap()).unwrap(); + let tx_raw = BASE64_STANDARD.decode(tx.raw.unwrap()).unwrap(); + let decoded = transaction::decode_by_address_and_method( + address.try_into().unwrap(), + tx.method.unwrap() as u8, + &tx_raw, + ) + .unwrap() + .unwrap(); + drop(decoded); + } +} diff --git a/crates/spacemesh/spacemesh/src/wallet/mod.rs b/crates/spacemesh/spacemesh/src/wallet/mod.rs new file mode 100644 index 0000000..6c2eba7 --- /dev/null +++ b/crates/spacemesh/spacemesh/src/wallet/mod.rs @@ -0,0 +1,77 @@ +//! Spacemesh wallet management. + +pub use crate::codec::core::Address; +use crate::codec::tx::*; +use crate::codec::Compact; +use crate::transaction::single_signature; + +const ADDRESS_RESERVED: usize = 4; + +mod sealed { + pub trait Sealed {} +} + +pub trait AsAddress: sealed::Sealed { + fn as_address(&self) -> Address; +} + +impl sealed::Sealed for Address {} +impl AsAddress for Address { + #[inline(always)] + fn as_address(&self) -> Address { + *self + } +} + +impl sealed::Sealed for [u8; 32] {} +impl AsAddress for [u8; 32] { + #[inline(always)] + fn as_address(&self) -> Address { + let mut output = [0u8; std::mem::size_of::
()]; + const START: usize = 32 - std::mem::size_of::
() + ADDRESS_RESERVED; + output[ADDRESS_RESERVED..].copy_from_slice( + &self[START..], + ); + output + } +} + +pub fn spawn(principal: [u8; 32], nonce: u64, gas_price: u64) -> single_signature::Spawn { + single_signature::Spawn { + header: SpawnTxHeader { + principal: principal.as_address(), + template_address: single_signature::SINGLE_SIG_TEMPLATE_ADDRESS, + }, + payload: single_signature::SpawnPayload { + nonce: Compact(nonce), + gas_price: Compact(gas_price), + arguments: single_signature::SpawnArguments { + public_key: principal, + }, + }, + signature: [0u8; 64], + } +} + +pub fn transfer( + principal: impl AsAddress, + recipient: impl AsAddress, + amount: u64, + nonce: u64, + gas_price: u64, +) -> single_signature::Spend { + single_signature::Spend { + header: TxHeader { + principal: principal.as_address(), + }, + payload: single_signature::SpendPayload { + nonce: Compact(nonce), + gas_price: Compact(gas_price), + arguments: single_signature::SpendArguments { + destination: recipient.as_address(), + amount: Compact(amount), + }, + }, + signature: [0u8; 64], + } +} diff --git a/icepick.toml b/icepick.toml index 25a36de..e7103e1 100644 --- a/icepick.toml +++ b/icepick.toml @@ -221,3 +221,8 @@ outputs = { transaction = "transaction" } type = "cosmos-broadcast" inputs = { blockchain_config = "blockchain_config", transaction = "transaction" } outputs = { status = "status", url = "url", error = "error", error_code = "error_code" } + +[[module]] +name = "spacemesh" +derivation_prefix = "m/44'/540'/0'/0'" +algorithm = "Ed25519"