icepick-cosmos: initial commit

This commit is contained in:
Ryan Heywood 2025-01-21 03:02:00 -05:00
parent 85adc05429
commit db5ca73d10
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
12 changed files with 1427 additions and 10 deletions

512
Cargo.lock generated
View File

@ -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"

View File

@ -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]

View File

@ -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"] }

View File

@ -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<Currency>,
pub fee_currencies: Vec<CurrencyWithGas>,
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<Blockchain> {
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
}

View File

@ -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<String>,
}
#[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<String>,
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<String>,
blockchain_config: coin_denoms::Blockchain,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Sign {
fee: remote_serde::Fee,
tx_messages: Vec<Any>,
account_number: String,
sequence_number: String,
blockchain_config: coin_denoms::Blockchain,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Broadcast {
transaction: Vec<u8>,
blockchain_config: coin_denoms::Blockchain,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Request {
derived_keys: Option<Vec<[u8; 32]>>,
#[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: std::future::Future>(f: F) -> F::Output {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap()
.block_on(f)
}
fn decode<T: cosmrs::proto::prost::Message + cosmrs::proto::prost::Name + std::default::Default>(
_type: &str,
value: &[u8],
) -> Result<T, String> {
// 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<cosmrs::tendermint::block::Height>,
prove: bool,
) -> Result<Response, Box<dyn std::error::Error>> {
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<icepick_module::help::Operation> {
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<serde_json::Value, Self::Error> {
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(),
}
})),
}
}
}
}
}

View File

@ -0,0 +1,6 @@
use icepick_module::Module;
use icepick_cosmos::Cosmos;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Cosmos::run_responder()
}

View File

@ -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<Coin>,
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::<Vec<_>>();
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::<Vec<_>>();
cosmrs::tx::Fee {
amount: amounts,
gas_limit: *gas_limit,
payer: None,
granter: None,
}
}
}

View File

@ -4,5 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bon = "3.3.2"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

View File

@ -16,6 +16,26 @@ pub mod help {
pub arguments: Vec<Argument>,
}
#[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.

View File

@ -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 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);
}
}
}
}

View File

@ -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}");
}
}

View File

@ -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"