diff --git a/Cargo.lock b/Cargo.lock index 4759fb8..76ae4d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,22 @@ dependencies = [ "serde", ] +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac 0.12.1", + "k256", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -442,6 +458,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bon" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7acc34ff59877422326db7d6f2d845a582b16396b6b08194942bf34c6528ab" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4159dd617a7fbc9be6a692fe69dc2954f8e6bb6bb5e4d7578467441390d77fd0" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.92", +] + [[package]] name = "borsh" version = "0.10.4" @@ -537,6 +578,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ + "sha2 0.10.8", "tinyvec", ] @@ -587,6 +629,9 @@ name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -754,6 +799,38 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cosmos-sdk-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462e1f6a8e005acc8835d32d60cbd7973ed65ea2a8d8473830e675f050956427" +dependencies = [ + "prost", + "tendermint-proto", +] + +[[package]] +name = "cosmrs" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210fbe6f98594963b46cc980f126a9ede5db9a3848ca65b71303bebdb01afcd9" +dependencies = [ + "bip32", + "cosmos-sdk-proto", + "ecdsa", + "eyre", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "signature 2.2.0", + "subtle-encoding", + "tendermint", + "tendermint-rpc", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "cpufeatures" version = "0.2.16" @@ -877,6 +954,19 @@ dependencies = [ "syn 2.0.92", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + [[package]] name = "darling" version = "0.20.10" @@ -922,6 +1012,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivation-path" version = "0.2.0" @@ -955,6 +1054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -983,7 +1083,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", + "digest 0.10.7", "elliptic-curve", + "rfc6979", "signature 2.2.0", "spki", ] @@ -1007,6 +1109,19 @@ dependencies = [ "signature 2.2.0", ] +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "1.0.1" @@ -1107,6 +1222,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "feature-probe" version = "0.1.1" @@ -1154,6 +1279,16 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1387,6 +1522,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.8.1" @@ -1527,6 +1668,19 @@ dependencies = [ "toml 0.8.19", ] +[[package]] +name = "icepick-cosmos" +version = "0.1.0" +dependencies = [ + "bon", + "cosmrs", + "icepick-module", + "serde", + "serde_json", + "thiserror 2.0.9", + "tokio", +] + [[package]] name = "icepick-internal" version = "0.1.0" @@ -1541,6 +1695,7 @@ dependencies = [ name = "icepick-module" version = "0.1.0" dependencies = [ + "bon", "serde", "serde_json", ] @@ -1713,6 +1868,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.7.0" @@ -1812,6 +1973,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", + "sha2 0.10.8", ] [[package]] @@ -2094,6 +2256,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -2214,6 +2382,12 @@ dependencies = [ "syn 2.0.92", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "openssl-sys" version = "0.9.104" @@ -2274,6 +2448,33 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "peg" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2289,6 +2490,26 @@ dependencies = [ "num", ] +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -2329,6 +2550,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2338,6 +2565,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.92", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2365,6 +2602,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.92", +] + [[package]] name = "qstring" version = "0.7.2" @@ -2518,6 +2778,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", + "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", @@ -2551,6 +2812,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2602,6 +2873,18 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2621,12 +2904,36 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2663,6 +2970,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.24" @@ -2710,6 +3040,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -2815,6 +3156,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4429,6 +4771,21 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "1.0.109" @@ -4498,6 +4855,98 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "tendermint" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9703e34d940c2a293804752555107f8dbe2b84ec4c6dd5203831235868105d2" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519 2.2.3", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature 2.2.0", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-config" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc3ea9a39b7ee34eefcff771cc067ecaa0c988c1c5ac08defd878471a06f76" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint", + "toml 0.8.19", + "url", +] + +[[package]] +name = "tendermint-proto" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9e1705aa0fa5ecb2c6aa7fb78c2313c4a31158ea5f02048bf318f849352eb" +dependencies = [ + "bytes", + "flex-error", + "prost", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-rpc" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a52aa504c63ec05519e31348d3f4ba2fe79493c588e2cad5323d5e81b161a" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.15", + "peg", + "pin-project", + "rand 0.8.5", + "reqwest", + "semver", + "serde", + "serde_bytes", + "serde_json", + "subtle", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", + "url", + "uuid", + "walkdir", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4538,6 +4987,36 @@ dependencies = [ "syn 2.0.92", ] +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4565,9 +5044,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -4583,9 +5062,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -4771,6 +5250,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4789,6 +5274,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -4909,6 +5404,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7039e12..bf623be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "crates/icepick", "crates/icepick-module", "crates/builtins/icepick-internal", - "crates/by-chain/icepick-solana", + "crates/by-chain/icepick-solana", "crates/by-chain/icepick-cosmos", ] [workspace.dependencies] diff --git a/crates/by-chain/icepick-cosmos/Cargo.toml b/crates/by-chain/icepick-cosmos/Cargo.toml new file mode 100644 index 0000000..2516262 --- /dev/null +++ b/crates/by-chain/icepick-cosmos/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "icepick-cosmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +bon = "3.3.2" +cosmrs = { version = "0.21.0", features = ["rpc", "tokio"] } +icepick-module = { version = "0.1.0", path = "../../icepick-module" } +serde.workspace = true +serde_json.workspace = true +thiserror = "2.0.9" +tokio = { version = "1.43.0", features = ["rt"] } + +[dev-dependencies] +cosmrs = { version = "0.21.0", features = ["dev"] } diff --git a/crates/by-chain/icepick-cosmos/src/coin_denoms.rs b/crates/by-chain/icepick-cosmos/src/coin_denoms.rs new file mode 100644 index 0000000..505ac66 --- /dev/null +++ b/crates/by-chain/icepick-cosmos/src/coin_denoms.rs @@ -0,0 +1,199 @@ +use bon::{bon, Builder}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, Builder)] +#[serde(rename_all = "camelCase")] +pub struct Bip44Config { + pub coin_type: u32, +} + +// NOTE: Are `public` variants used? + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Bech32Config { + #[serde(rename = "bech32PrefixAccAddress")] + pub account_address_prefix: String, + + #[serde(rename = "bech32PrefixAccPub")] + pub account_address_public_prefix: String, + + #[serde(rename = "bech32PrefixValOper")] + pub validator_operator_prefix: String, + + #[serde(rename = "bech32PrefixValPub")] + pub validator_operator_public_prefix: String, + + #[serde(rename = "bech32PrefixConsAddr")] + pub consensus_node_prefix: String, + + #[serde(rename = "bech32PrefixConsPub")] + pub consensus_node_public_prefix: String, +} + +#[bon] +impl Bech32Config { + #[builder] + fn new( + account_address_prefix: &'static str, + account_address_public_prefix: &'static str, + validator_operator_prefix: &'static str, + validator_operator_public_prefix: &'static str, + consensus_node_prefix: &'static str, + consensus_node_public_prefix: &'static str, + ) -> Self { + Self { + account_address_prefix: account_address_prefix.to_string(), + account_address_public_prefix: account_address_public_prefix.to_string(), + validator_operator_prefix: validator_operator_prefix.to_string(), + validator_operator_public_prefix: validator_operator_public_prefix.to_string(), + consensus_node_prefix: consensus_node_prefix.to_string(), + consensus_node_public_prefix: consensus_node_public_prefix.to_string(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Builder)] +pub struct GasPriceStep { + pub low: f64, + pub average: f64, + pub high: f64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Currency { + pub coin_denom: String, + pub coin_minimal_denom: String, + pub coin_decimals: u8, + pub coin_gecko_id: String, +} + +#[bon] +impl Currency { + #[builder] + fn new( + coin_denom: &'static str, + coin_minimal_denom: &'static str, + coin_decimals: u8, + coin_gecko_id: &'static str, + ) -> Self { + Self { + coin_denom: coin_denom.to_string(), + coin_minimal_denom: coin_minimal_denom.to_string(), + coin_decimals, + coin_gecko_id: coin_gecko_id.to_string(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Builder)] +#[serde(rename_all = "camelCase")] +pub struct CurrencyWithGas { + #[serde(flatten)] + pub currency: Currency, + + pub gas_price_step: GasPriceStep, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Blockchain { + pub chain_name: String, + pub chain_id: String, + + pub rpc_url: String, + pub rest_url: String, + + pub explorer_url_format: String, + + #[serde(rename = "bip44")] + pub bip44_config: Bip44Config, + + #[serde(rename = "bech32Config")] + pub bech32_config: Bech32Config, + + pub currencies: Vec, + pub fee_currencies: Vec, + + pub gas_price_step: GasPriceStep, + + pub stake_currency: Currency, +} + +#[bon] +impl Blockchain { + #[builder] + fn new( + chain_id: &'static str, + chain_name: &'static str, + rpc_url: &'static str, + rest_url: &'static str, + explorer_url_format: &'static str, + bip44_config: Bip44Config, + bech32_config: Bech32Config, + currencies: &[Currency], + fee_currencies: &[CurrencyWithGas], + gas_price_step: GasPriceStep, + stake_currency: Currency, + ) -> Self { + Self { + chain_id: chain_id.to_string(), + chain_name: chain_name.to_string(), + rpc_url: rpc_url.to_string(), + rest_url: rest_url.to_string(), + explorer_url_format: explorer_url_format.to_string(), + bip44_config, + bech32_config, + currencies: currencies.to_vec(), + fee_currencies: fee_currencies.to_vec(), + gas_price_step, + stake_currency, + } + } +} + +pub fn default_chains() -> Vec { + let mut chains = vec![]; + + let tkyve = Currency::builder() + .coin_denom("KYVE") + .coin_minimal_denom("tkyve") + .coin_decimals(6) + .coin_gecko_id("unknown") + .build(); + // NOTE: high is listed as 0.03 on devnet, but simulating tx actually cost 30915 tkyve. + let tkyve_gas = GasPriceStep::builder() + .low(0.01) + .average(0.05) + .high(0.1) + .build(); + + chains.push( + Blockchain::builder() + .chain_id("korellia-2") + .chain_name("korellia") + .rpc_url("https://rpc.korellia.kyve.network") + .rest_url("https://api.korellia.kyve.network") + .explorer_url_format("https://explorer.kyve.network/korellia/tx/%s") + .bip44_config(Bip44Config::builder().coin_type(118).build()) + .bech32_config( + Bech32Config::builder() + .account_address_prefix("kyve") + .account_address_public_prefix("kyvepub") + .validator_operator_prefix("kyvevaloper") + .validator_operator_public_prefix("kyvevaloperpub") + .consensus_node_prefix("kyvevalcons") + .consensus_node_public_prefix("kyvevalconspub") + .build(), + ) + .currencies(&[tkyve.clone()]) + .fee_currencies(&[CurrencyWithGas::builder() + .currency(tkyve.clone()) + .gas_price_step(tkyve_gas.clone()) + .build()]) + .gas_price_step(tkyve_gas.clone()) + .stake_currency(tkyve.clone()) + .build(), + ); + chains +} diff --git a/crates/by-chain/icepick-cosmos/src/lib.rs b/crates/by-chain/icepick-cosmos/src/lib.rs new file mode 100644 index 0000000..d80d251 --- /dev/null +++ b/crates/by-chain/icepick-cosmos/src/lib.rs @@ -0,0 +1,578 @@ +use cosmrs::{ + proto::prost::Message, + rpc::Client, + tx::{self, BodyBuilder, Fee, Msg, SignDoc, SignerInfo}, + AccountId, Any, +}; +use icepick_module::Module; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +use cosmrs::crypto::secp256k1; + +mod coin_denoms; +mod remote_serde; + +#[derive(thiserror::Error, Debug)] +pub enum Error {} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetChainInfo { + chain_name: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GenerateWallet { + account: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetWalletAddress { + address_prefix: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetAccountInfo { + account_id: String, + + blockchain_config: coin_denoms::Blockchain, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AwaitFunds { + address: String, + denom_name: String, + amount: String, + + blockchain_config: coin_denoms::Blockchain, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Transfer { + amount: String, + denom: String, + to_address: String, + from_account: Option, + from_address: String, + // TODO: find a way to simulate transaction and calculate gas necessary + // for now, 0.01KYVE seems to be a reasonable mainnet number? + // for testing purposes, i'm gonna go much lower. 0.0001. + gas: Option, + + blockchain_config: coin_denoms::Blockchain, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Sign { + fee: remote_serde::Fee, + tx_messages: Vec, + account_number: String, + sequence_number: String, + + blockchain_config: coin_denoms::Blockchain, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Broadcast { + transaction: Vec, + + blockchain_config: coin_denoms::Blockchain, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Request { + derived_keys: Option>, + + #[serde(flatten)] + operation: Operation, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "operation", content = "values", rename_all = "kebab-case")] +#[allow(clippy::large_enum_variant)] +pub enum Operation { + GetChainInfo(GetChainInfo), + GenerateWallet(GenerateWallet), + GetWalletAddress(GetWalletAddress), + GetAccountInfo(GetAccountInfo), + AwaitFunds(AwaitFunds), + Transfer(Transfer), + Sign(Sign), + Broadcast(Broadcast), +} + +pub fn run_async(f: F) -> F::Output { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build() + .unwrap() + .block_on(f) +} + +fn decode( + _type: &str, + value: &[u8], +) -> Result { + // move past the first `/`. + let _type = &_type[1..]; + let full_name = T::full_name(); + if _type != full_name { + return Err(format!( + "given type {_type} does not match expected type {full_name}" + )); + } + + T::decode(value).map_err(|e| e.to_string()) +} + +async fn abci_query< + Response: cosmrs::proto::prost::Message + cosmrs::proto::prost::Name + std::default::Default, +>( + client: &(impl Client + Sync), + path: &'static str, + request_data: Option<&impl cosmrs::proto::prost::Message>, + height: Option, + prove: bool, +) -> Result> { + let data = request_data.map(Message::encode_to_vec).unwrap_or_default(); + let response = client + .abci_query(Some(path.to_string()), data, height, prove) + .await?; + let parsed = Response::decode(&*response.value)?; + Ok(parsed) +} + +pub struct Cosmos; + +impl Module for Cosmos { + 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 address_prefix = Argument::builder() + .name("address_prefix") + .description("Prefix for the wallet (`cosmos`, `kyve`, etc.).") + .r#type(ArgumentType::Required) + .build(); + + let get_chain_info = Operation::builder() + .name("get-chain-info") + .description("Get information for a given chain") + .build() + .argument( + &Argument::builder() + .name("chain_name") + .description("Name of the blockchain") + .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(&address_prefix); + + let get_account_info = Operation::builder() + .name("get-account-info") + .description("Get the account number and sequence number for an account.") + .build() + .argument( + &Argument::builder() + .name("account_id") + .description("The account ID to get account information for.") + .r#type(ArgumentType::Required) + .build(), + ); + + let await_funds = icepick_module::help::Operation { + name: "await-funds".to_string(), + description: "Await a minimum amount of funds in an account".to_string(), + arguments: vec![ + Argument { + name: "address".to_string(), + description: "The address to monitor".to_string(), + r#type: ArgumentType::Required, + }, + Argument { + name: "denom_name".to_string(), + description: "The denomination of coin to monitor".to_string(), + r#type: ArgumentType::Required, + }, + Argument { + name: "amount".to_string(), + description: "The amount of the minimum denomination to await".to_string(), + r#type: ArgumentType::Required, + }, + ], + }; + + let transfer = Operation::builder() + .name("transfer") + .description("Transfer coins from one address to another.") + .build() + .argument( + &Argument::builder() + .name("from_address") + .description("The address to send coins from.") + .r#type(ArgumentType::Required) + .build(), + ) + .argument( + &Argument::builder() + .name("to_address") + .description("The address to send coins to.") + .r#type(ArgumentType::Required) + .build(), + ) + .argument( + &Argument::builder() + .name("amount") + .description("The amount of coins to send.") + .r#type(ArgumentType::Required) + .build(), + ) + .argument( + &Argument::builder() + .name("denom") + .description("The denomination of coin to send.") + .r#type(ArgumentType::Required) + .build(), + ) + .argument( + &Argument::builder() + .name("gas") + .description("The amount of gas to supply.") + .r#type(ArgumentType::Optional) + .build(), + ); + + let sign = Operation::builder() + .name("sign") + .description("Sign a previously-generated transaction.") + .build() + .argument( + &Argument::builder() + .name("account_number") + .description("The sequence number for the given public key") + .r#type(ArgumentType::Required) + .build(), + ) + .argument( + &Argument::builder() + .name("sequence_number") + .description("The account number for the given public key") + .r#type(ArgumentType::Required) + .build(), + ); + + let broadcast = Operation::builder() + .name("broadcast") + .description("Broadcast a transaction.") + .build(); + + vec![ + get_chain_info, + generate_wallet, + get_wallet_address, + get_account_info, + await_funds, + transfer, + sign, + broadcast, + ] + } + + fn handle_request(request: Self::Request) -> Result { + let Request { + operation, + derived_keys: _, + } = request; + + match operation { + Operation::GetChainInfo(GetChainInfo { chain_name }) => { + let chains = coin_denoms::default_chains(); + + let chain = chains + .iter() + .find(|chain| chain.chain_name == chain_name || chain.chain_id == chain_name); + + Ok(serde_json::json!({ + "blob": { + "blockchain_config": chain, + }, + })) + } + 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 { address_prefix }) => { + // NOTE: panics if doesn't exist + let key = request.derived_keys.unwrap()[0]; + let privkey = secp256k1::SigningKey::from_slice(&key).unwrap(); + let pubkey = privkey.public_key(); + let sender_account_id = pubkey.account_id(&address_prefix).unwrap(); + Ok(serde_json::json!({ + "blob": { + "pubkey": sender_account_id, + } + })) + } + Operation::GetAccountInfo(GetAccountInfo { + account_id, + blockchain_config, + }) => { + use cosmrs::proto::cosmos::auth::v1beta1::*; + + let response = run_async(async { + let client = + cosmrs::rpc::HttpClient::new(blockchain_config.rpc_url.as_str()).unwrap(); + let query = QueryAccountRequest { + address: account_id, + }; + let response: QueryAccountResponse = abci_query( + &client, + "/cosmos.auth.v1beta1.Query/Account", + Some(&query), + None, + false, + ) + .await + .unwrap(); + response + }); + let Any { type_url, value } = response.account.unwrap(); + let account: BaseAccount = decode(&type_url, &value).unwrap(); + Ok(serde_json::json!({ + "blob": { + "account_number": account.account_number, + "sequence_number": account.sequence, + } + })) + } + Operation::AwaitFunds(AwaitFunds { + address, + denom_name, + amount, + blockchain_config, + }) => { + use cosmrs::proto::cosmos::bank::v1beta1::*; + + // Check if given denom is min denom or normal and adjust accordingly + let Some(relevant_denom) = blockchain_config.currencies.iter().find(|c| { + [&c.coin_denom, &c.coin_minimal_denom] + .iter() + .any(|name| **name == denom_name) + }) else { + panic!("{denom_name} not in {blockchain_config:?}"); + }; + + let amount = f64::from_str(&amount).unwrap(); + let adjusted_amount = if relevant_denom.coin_denom == denom_name { + amount * 10f64.powi(i32::from(relevant_denom.coin_decimals)) + } else if relevant_denom.coin_minimal_denom == denom_name { + amount + } else { + unreachable!("broke invariant: check denom checker"); + } as u128; + + let coin = run_async(async { + let client = + cosmrs::rpc::HttpClient::new(blockchain_config.rpc_url.as_str()).unwrap(); + + loop { + let response: QueryBalanceResponse = abci_query( + &client, + "/cosmos.bank.v1beta1.Query/Balance", + Some(&QueryBalanceRequest { + address: address.clone(), + denom: relevant_denom.coin_minimal_denom.clone(), + }), + None, + false, + ) + .await + .unwrap(); + + if let Some(coin) = response.balance { + let amount = u128::from_str(&coin.amount).unwrap(); + if amount >= adjusted_amount { + break coin; + } + } + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + } + }); + let cosmrs::proto::cosmos::base::v1beta1::Coin { denom, amount } = coin; + Ok(serde_json::json!({ + "blob": { + "balance": { + "denom": denom, + "amount": u128::from_str(&amount).unwrap(), + }, + } + })) + } + Operation::Transfer(Transfer { + amount, + denom, + to_address, + from_account: _, + from_address, + gas: _, + blockchain_config, + }) => { + // Check if given denom is min denom or normal and adjust accordingly + let Some(relevant_denom) = blockchain_config.currencies.iter().find(|c| { + [&c.coin_denom, &c.coin_minimal_denom] + .iter() + .any(|name| **name == denom) + }) else { + panic!("{denom} not in {blockchain_config:?}"); + }; + + let amount = f64::from_str(&amount).unwrap(); + let adjusted_amount = if relevant_denom.coin_denom == denom { + amount * 10f64.powi(i32::from(relevant_denom.coin_decimals)) + } else if relevant_denom.coin_minimal_denom == denom { + amount + } else { + unreachable!("broke invariant: check denom checker"); + } as u128; + + let from_id = AccountId::from_str(&from_address).unwrap(); + let to_id = AccountId::from_str(&to_address).unwrap(); + + let coin = cosmrs::Coin { + denom: relevant_denom.coin_minimal_denom.parse().unwrap(), + amount: adjusted_amount, + }; + + let msg_send = cosmrs::bank::MsgSend { + from_address: from_id, + to_address: to_id, + amount: vec![coin.clone()], + } + .to_any() + .unwrap(); + + let fee = Fee::from_amount_and_gas( + coin, + (blockchain_config.gas_price_step.high + * 10f64.powi(relevant_denom.coin_decimals as i32)) + as u64, + ); + + #[allow(clippy::identity_op)] + Ok(serde_json::json!({ + "blob": { + "fee": remote_serde::Fee::from(&fee), + // TODO: Body does not implement Serialize and + // needs to be constructed in Sign + "tx_messages": [msg_send], + // re-export, but in general this should be copied over + // using workflows + "blockchain_config": blockchain_config, + }, + "derivation_accounts": [0u32 | 1 << 31], + })) + } + Operation::Sign(Sign { + fee, + tx_messages, + account_number, + sequence_number, + blockchain_config, + }) => { + let key = request.derived_keys.unwrap()[0]; + let privkey = secp256k1::SigningKey::from_slice(&key).unwrap(); + + let fee = cosmrs::tx::Fee::from(&fee); + let tx_body = BodyBuilder::new().msgs(tx_messages).finish(); + + // TODO: these should be gained by the `broadcast` workflow, similar to + // how nonce data is acquired in Solana + let account_number: u64 = account_number.parse().unwrap(); + let sequence_number: u64 = sequence_number.parse().unwrap(); + + let auth_info = + SignerInfo::single_direct(Some(privkey.public_key()), sequence_number) + .auth_info(fee); + let sign_doc = SignDoc::new( + &tx_body, + &auth_info, + &blockchain_config.chain_id.parse().unwrap(), + account_number, + ) + .unwrap(); + let signed_tx = sign_doc.sign(&privkey).unwrap(); + Ok(serde_json::json!({ + "blob": { + "transaction": signed_tx.to_bytes().unwrap(), + "blockchain_config": blockchain_config, + } + })) + } + Operation::Broadcast(Broadcast { + transaction, + blockchain_config, + }) => { + let tx = tx::Raw::from_bytes(&transaction).unwrap(); + + let response = run_async(async { + let client = + cosmrs::rpc::HttpClient::new(blockchain_config.rpc_url.as_str()).unwrap(); + + // broadcast_tx_sync awaits CheckTx, so we know that, at the bare minimum, the + // transaction is valid. + // + // TODO: make this expect() into a keyfork_bug!(). An error in this area of + // code may represent a bug in either our code or the blockchain's code. + // We _should_ get an Err code if the error was with the transaction data, such + // as us being out of gas, or the transaction being malformed. + client + .broadcast_tx_sync(tx.to_bytes().unwrap()) + .await + .expect("The server encountered a fatal error while processing the request") + }); + match response.code { + cosmrs::tendermint::abci::Code::Ok => { + // attempt to get transaction URL / blockchain explorer URL + Ok(serde_json::json!({ + "blob": { + "status": "send_and_confirm", + "success": response.hash.to_string(), + "url": blockchain_config.explorer_url_format.replace("%s", response.hash.to_string().as_str()), + } + })) + } + cosmrs::tendermint::abci::Code::Err(non_zero) => Ok(serde_json::json!({ + "blob": { + "status": "send_and_confirm", + "error": response.log, + "error_code": non_zero.get(), + } + })), + } + } + } + } +} diff --git a/crates/by-chain/icepick-cosmos/src/main.rs b/crates/by-chain/icepick-cosmos/src/main.rs new file mode 100644 index 0000000..cfbc376 --- /dev/null +++ b/crates/by-chain/icepick-cosmos/src/main.rs @@ -0,0 +1,6 @@ +use icepick_module::Module; +use icepick_cosmos::Cosmos; + +fn main() -> Result<(), Box> { + Cosmos::run_responder() +} diff --git a/crates/by-chain/icepick-cosmos/src/remote_serde.rs b/crates/by-chain/icepick-cosmos/src/remote_serde.rs new file mode 100644 index 0000000..9dfe01a --- /dev/null +++ b/crates/by-chain/icepick-cosmos/src/remote_serde.rs @@ -0,0 +1,68 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Coin { + amount: [u8; 16], + denom: cosmrs::Denom, +} + +impl From<&cosmrs::Coin> for Coin { + fn from(value: &cosmrs::Coin) -> Self { + let cosmrs::Coin { denom, amount } = value; + Coin { + denom: denom.clone(), + amount: amount.to_be_bytes(), + } + } +} + +impl From<&Coin> for cosmrs::Coin { + fn from(value: &Coin) -> Self { + let Coin { amount, denom } = value; + cosmrs::Coin { + denom: denom.clone(), + amount: u128::from_be_bytes(*amount), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Fee { + amount: Vec, + gas_limit: u64, +} + +impl From<&cosmrs::tx::Fee> for Fee { + fn from(value: &cosmrs::tx::Fee) -> Self { + let cosmrs::tx::Fee { + amount, + gas_limit, + payer, + granter, + } = value; + + assert!(payer.is_none(), "unimplemented: payer"); + assert!(granter.is_none(), "unimplemented: granter"); + + let amounts = amount.iter().map(Coin::from).collect::>(); + + Fee { + amount: amounts, + gas_limit: *gas_limit, + } + } +} + +impl From<&Fee> for cosmrs::tx::Fee { + fn from(value: &Fee) -> Self { + let Fee { amount, gas_limit } = value; + let amounts = amount.iter().map(cosmrs::Coin::from).collect::>(); + + cosmrs::tx::Fee { + amount: amounts, + gas_limit: *gas_limit, + payer: None, + granter: None, + } + } +} diff --git a/crates/icepick-module/Cargo.toml b/crates/icepick-module/Cargo.toml index 84c4169..214d436 100644 --- a/crates/icepick-module/Cargo.toml +++ b/crates/icepick-module/Cargo.toml @@ -4,5 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +bon = "3.3.2" serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/crates/icepick-module/src/lib.rs b/crates/icepick-module/src/lib.rs index e1db7ab..a89cceb 100644 --- a/crates/icepick-module/src/lib.rs +++ b/crates/icepick-module/src/lib.rs @@ -16,6 +16,26 @@ pub mod help { pub arguments: Vec, } + #[bon::bon] + impl Operation { + + #[builder] + pub fn new(name: &'static str, description: &'static str) -> Self { + Operation { + name: name.into(), + description: description.into(), + arguments: vec![], + } + } + } + + impl Operation { + pub fn argument(mut self, arg: &Argument) -> Self { + self.arguments.push(arg.clone()); + self + } + } + /* /// The context of whether a signature is signed, needs to be signed, or has been signed. #[derive(Serialize, Deserialize, Clone)] @@ -50,6 +70,19 @@ pub mod help { /// The type of argument - this may affect how it displays in the frontend. pub r#type: ArgumentType, } + + #[bon::bon] + impl Argument { + + #[builder] + pub fn new(name: &'static str, description: &'static str, r#type: ArgumentType) -> Self { + Argument { + name: name.into(), + description: description.into(), + r#type, + } + } + } } /// Implementation methods for Icepick Modules, performed over command I/O using JSON. diff --git a/crates/icepick/src/cli/mod.rs b/crates/icepick/src/cli/mod.rs index c312bf7..57f5330 100644 --- a/crates/icepick/src/cli/mod.rs +++ b/crates/icepick/src/cli/mod.rs @@ -308,10 +308,17 @@ pub fn do_cli_thing() { let mut input = child.stdin.take().unwrap(); serde_json::to_writer(&mut input, &json).unwrap(); input.write_all(b"\n{\"operation\": \"exit\"}\n").unwrap(); - let output = child.wait_with_output().unwrap().stdout; - let json: serde_json::Value = serde_json::from_slice(&output).expect("valid json"); - let json_as_str = serde_json::to_string(&json).unwrap(); - println!("{json_as_str}"); + let output = child.wait_with_output().unwrap(); + let stdout = &output.stdout; + if output.status.success() { + let json: serde_json::Value = + serde_json::from_slice(stdout).expect("valid json"); + let json_as_str = serde_json::to_string(&json).unwrap(); + println!("{json_as_str}"); + } else { + eprintln!("Error while invoking operation, check logs"); + std::process::exit(1); + } } } } diff --git a/crates/icepick/src/cli/workflow.rs b/crates/icepick/src/cli/workflow.rs index bd1e350..8a79e80 100644 --- a/crates/icepick/src/cli/workflow.rs +++ b/crates/icepick/src/cli/workflow.rs @@ -155,7 +155,7 @@ impl Workflow { // Check if we have the keys we want to pass into the module. for in_memory_name in step.inputs.values() { if !data.contains(in_memory_name) && !step.values.contains_key(in_memory_name) { - panic!("Failed simulation: step #{step_index} ({step_type}): missing value {in_memory_name}"); + eprintln!("Failed simulation: step #{step_index} ({step_type}): missing value {in_memory_name}"); } } diff --git a/icepick.toml b/icepick.toml index fc47140..22330b3 100644 --- a/icepick.toml +++ b/icepick.toml @@ -220,3 +220,8 @@ type = "internal-save-file" values = { filename = "transaction.json" } inputs = { transaction = "signed_transaction" } + +[[module]] +name = "cosmos" +derivation_prefix = "m/44'/118'/0'" +algorithm = "Secp256k1"