diff --git a/Cargo.lock b/Cargo.lock index 619f6ae..140daec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -79,29 +79,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arrayvec" @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -332,9 +332,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "block-buffer" @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.31" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] @@ -395,9 +395,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -422,9 +422,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -432,9 +432,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -444,9 +444,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", @@ -563,7 +563,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "crossterm_winapi", "futures-core", "mio 1.0.4", @@ -621,7 +621,7 @@ dependencies = [ "ed25519-bip32", "futures", "hex", - "indexmap 2.10.0", + "indexmap 2.11.0", "inquire", "jsonrpsee", "num-format", @@ -790,9 +790,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -947,7 +947,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", "wasm-bindgen", ] @@ -1005,9 +1005,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1015,7 +1015,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.11.0", "slab", "tokio", "tokio-util", @@ -1040,9 +1040,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1118,13 +1118,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -1132,6 +1133,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1310,9 +1312,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1342,12 +1344,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "serde", ] @@ -1363,7 +1365,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "crossterm 0.25.0", "dyn-clone", "fuzzy-matcher", @@ -1398,11 +1400,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -1493,9 +1495,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -1507,9 +1509,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a320a3f1464e4094f780c4d48413acd786ce5627aaaecfac9e9c7431d13ae1" +checksum = "cf36eb27f8e13fa93dcb50ccb44c417e25b818cfa1a481b5470cd07b19c60b98" dependencies = [ "base64 0.22.1", "futures-channel", @@ -1522,7 +1524,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "soketto", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-rustls", "tokio-util", @@ -1532,9 +1534,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" +checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480" dependencies = [ "async-trait", "bytes", @@ -1548,7 +1550,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-stream", "tower 0.5.2", @@ -1558,9 +1560,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791" +checksum = "790bedefcec85321e007ff3af84b4e417540d5c87b3c9779b9e247d1bcc3dab8" dependencies = [ "base64 0.22.1", "http-body", @@ -1573,7 +1575,7 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tower 0.5.2", "url", @@ -1581,21 +1583,21 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ "http", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] name = "jsonrpsee-wasm-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b67695cbcf4653f39f8f8738925547e0e23fd9fe315bccf951097b9f6a38781" +checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -1605,9 +1607,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da2694c9ff271a9d3ebfe520f6b36820e85133a51be77a3cb549fd615095261" +checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" dependencies = [ "http", "jsonrpsee-client-transport", @@ -1625,9 +1627,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" @@ -1669,7 +1671,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -2164,9 +2166,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" @@ -2178,7 +2180,7 @@ dependencies = [ "miette", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "ucd-trie", ] @@ -2222,7 +2224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.11.0", ] [[package]] @@ -2283,9 +2285,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -2293,9 +2295,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -2354,9 +2356,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -2365,8 +2367,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", - "thiserror 2.0.12", + "socket2 0.6.0", + "thiserror 2.0.16", "tokio", "tracing", "web-time", @@ -2374,9 +2376,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -2387,7 +2389,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.16", "tinyvec", "tracing", "web-time", @@ -2395,16 +2397,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2487,7 +2489,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cassowary", "compact_str", "crossterm 0.28.1", @@ -2508,7 +2510,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -2533,9 +2535,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -2545,9 +2547,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -2556,15 +2558,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -2630,7 +2632,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2643,7 +2645,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2736,9 +2738,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2796,11 +2798,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation", "core-foundation-sys", "libc", @@ -2845,9 +2847,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -2886,7 +2888,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.11.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -2978,9 +2980,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slug" @@ -3123,9 +3125,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -3160,25 +3162,25 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3202,11 +3204,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -3222,9 +3224,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -3283,9 +3285,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -3389,7 +3391,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "serde", "serde_spanned", "toml_datetime", @@ -3477,7 +3479,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytes", "futures-util", "http", @@ -3587,14 +3589,14 @@ dependencies = [ [[package]] name = "tx3-cardano" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c7d4d7792ae33be71cf41512de7b40e5337cdef92b0b647aa611b6721dda974" +checksum = "ca5f14e3e1b68b1bff81d58c891604a3087f34be70c2a2ad58a8605b6f15ed82" dependencies = [ "hex", "pallas", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "trait-variant", "tx3-lang", "utxorpc 0.10.0", @@ -3602,9 +3604,9 @@ dependencies = [ [[package]] name = "tx3-lang" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faaad8f112e459a8e06c0ccd07756cf4b71a5d5545d1b95078c304dc74934fe" +checksum = "7607218726810f53079b50a63ff4b5e25a8ccf53a82a80d7b6cc1815feec7a65" dependencies = [ "bincode", "hex", @@ -3612,7 +3614,7 @@ dependencies = [ "pest", "pest_derive", "serde", - "thiserror 2.0.12", + "thiserror 2.0.16", "trait-variant", ] @@ -3628,7 +3630,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.16", "tx3-lang", "uuid", ] @@ -3709,9 +3711,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -3747,13 +3749,13 @@ dependencies = [ [[package]] name = "utxorpc" version = "0.11.0" -source = "git+https://github.com/utxorpc/rust-sdk#b5f08b439e11331ccbef1d98657cd2ba3727b9fa" +source = "git+https://github.com/utxorpc/rust-sdk#8760682ea8c37bbbd098dba638ccdd0d6902b949" dependencies = [ "bytes", "thiserror 1.0.69", "tokio", "tonic", - "utxorpc-spec 0.15.0", + "utxorpc-spec 0.17.0", ] [[package]] @@ -3788,11 +3790,27 @@ dependencies = [ "tonic", ] +[[package]] +name = "utxorpc-spec" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d984ee351b308377e118135e638a5d544fdb0855f12a3b088d9dcaf0432052" +dependencies = [ + "bytes", + "futures-core", + "pbjson", + "pbjson-types", + "prost", + "prost-types", + "serde", + "tonic", +] + [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -3844,11 +3862,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -3987,11 +4005,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4349,21 +4367,18 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "writeable" @@ -4455,9 +4470,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/docs/explorer.mdx b/docs/explorer.mdx new file mode 100644 index 0000000..d3a4393 --- /dev/null +++ b/docs/explorer.mdx @@ -0,0 +1,35 @@ +--- +title: Explorer +sidebar: + order: 4 +--- + +The explorer is a terminal-based UI where you can interact with your wallets and blockchain data in real-time. + +## Usage + +```bash +cshell explorer +``` + +## Features + +The explorer provides the following features: + +### Accounts Tab + +Displays a list of your wallets and their balances. + +### Blocks Tab + +Shows a real-time stream of new blocks as they are added to the blockchain. + +### Transactions Tab + +Displays a list of recent transactions. And it is possible to fetch older transactions typing the tx hash. + +## Keybindings + +- `q`: Quit the explorer +- `Tab`: Switch between tabs +- `?`: Show the help popup diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..35c7aff --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,60 @@ +--- +title: Quickstart +sidebar: + order: 0 +--- + +import { Steps, Tabs, TabItem, Cards, Card } from "@astrojs/starlight/components" + +In this guide we're going to learn how to go from zero to a running Cshell instance with the least amount of effort. + + +1. ### Installation + + + + You can use Homebrew to install the latest version of Cshell in Mac (both x86 / ARM64) + + ```sh + brew install txpipe/tap/cshell + ``` + + + You can install Cshell on linux system using the following command: + + ```sh + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/txpipe/cshell/releases/latest/download/cshell-installer.sh | sh + ``` + + + You can use Npm to install Cshell on your system: + + ```sh + npm install @txpipe/cshell + ``` + + + +2. ### Configuration + + _Cshell_ requires a UtxoRPC and TRP provider, the UtxoRPC is to fetch on-chain data, and the TRP is to resolve and submit transactions. You can get one free instance on [demeter](https://demeter.run/). So when you have the UtxoRPC and TRP url, run the following command that will guide you through a set of questions to define a provider: + + ```sh + cshell provider create + ``` + + With the provider configured, it is possible to create a wallet or import one. The following example shows how to create one. + + ```sh + cshell wallet create + ``` + +3. ### Running + + Explore the commands in the docs or run `cshell --help`. The following command opens a GUI explorer using the default provider. + + ```sh + cshell explorer + ``` + + diff --git a/docs/provider.mdx b/docs/provider.mdx new file mode 100644 index 0000000..e5bf411 --- /dev/null +++ b/docs/provider.mdx @@ -0,0 +1,42 @@ +--- +title: Provider +sidebar: + order: 1 +--- + +import { Aside } from '@astrojs/starlight/components'; + +Cshell uses [**UtxoRPC**](https://utxorpc.org) as its on-chain data provider. It connects to a UtxoRPC server to collect blockchain data and follow the tip. Any service that implements the UtxoRPC interface can act as a data provider for Cshell, for example, [**Dolos**](https://github.com/txpipe/dolos). + +UtxoRPC is used only for supplying on-chain data. For transaction resolution and submission, Cshell uses a TRP (Transaction Resolution Protocol) server. TRP, used with [**Tx3**](https://github.com/tx3-lang/tx3), exposes a JSON-RPC interface to resolve and submit transactions. Any application that implements that JSON-RPC interface can serve as Cshell's TRP provider, for example [**Hydra TRP**](https://github.com/tx3-lang/tx3-hydra). + + + +### Provider storage +All cshell provider configurations are stored in a single file on your local machine: + +- File: **cshell.toml** +- Location: In your user home directory (examples: **~/cshell.toml**) + +This file contains the definitions for every provider you create. + +## Usage +When providers are available, create and configure a provider in Cshell using the **interactive** command below: + +```bash +cshell provider create +``` + +You can manage providers using Cshell's provider commands. To see which provider commands are available, run the following: + +```bash +cshell provider --help +``` + +And the `--help` flag can be used anywhere in Cshell. For example, after choosing the create command, you can check the options available for that command as well: + +```bash +cshell provider create --help +``` diff --git a/docs/usage.mdx b/docs/usage.mdx new file mode 100644 index 0000000..a1c6194 --- /dev/null +++ b/docs/usage.mdx @@ -0,0 +1,52 @@ +--- +title: Usage +sidebar: + order: 3 +--- + +import { Aside } from '@astrojs/starlight/components'; + +With Cshell fully configured ([provider](/cshell/provider) and [wallet](/cshell/wallet)), it's time to start using it to manage transactions or fetch on-chain data. + +You can execute any transaction in Cshell using [**tx3**](https://docs.txpipe.io/tx3). Cshell will call the TRP to resolve and submit the transaction. + + + + + +## Commands +The commands below are used to handle transactions and fetch on-chain data. + +### Transactions +You can manage transactions by using Cshell's `tx` or `transactions` commands. To see which tx commands are available, run the following: + +```bash +cshell tx --help +``` + +Use the `--help` flag with any command to view its available options. + +```bash +cshell tx invoke --help +``` + +### Search +You can search by block or transactions using the commands search. To see which search commands are available, run the following + + + +```bash +cshell search --help +``` + +Use the `--help` flag with any command to view its available options. + +```bash +cshell search block --help +``` diff --git a/docs/wallet.mdx b/docs/wallet.mdx new file mode 100644 index 0000000..81876b6 --- /dev/null +++ b/docs/wallet.mdx @@ -0,0 +1,63 @@ +--- +title: Wallet +sidebar: + order: 2 +--- + +import { Aside } from '@astrojs/starlight/components'; + +How cshell handles wallet creation, storage, and security. Understanding these concepts is crucial for managing your funds safely. + +### Wallet storage +All cshell wallet configurations are stored in a single file on your local machine: + +- File: **cshell.toml** +- Location: In your user home directory (examples: **~/cshell.toml**) + +This file contains the definitions for every wallet you create, import, or restore. + +### Security and encryption +Cshell protects private keys using a password-based encryption model. + +- When you create a wallet you set a spending password. +- The password is run through Argon2 to derive an encryption key, and that key is used with the `ChaCha20-Poly1305` cipher to encrypt the wallet’s private key. +- **Your password is never stored on disk**, it exists only in memory when needed to sign a transaction. + + + +### Unsafe wallets (plain-text keys) +Cshell supports an `--unsafe` option at creation time (wallet create --unsafe). + +- What it does: disables password protection and stores the private key in plain text inside cshell.toml. +- Intended use: temporary, disposable wallets for development or testing only. + + + +### Importing vs restoring wallets +Cshell supports both wallet import and wallet restore, these are different operations with different functional implications. + +- Import: creating read-only (watch-only) wallets. +- Restore: recovering fully functional wallets using the mnemonic seed phrase. + +## Usage +Create a wallet using the **interactive** command below: + +```bash +cshell wallet create +``` + +You can manage wallets using Cshell's wallet commands. To see which wallet commands are available, run the following: + +```bash +cshell wallet --help +``` + +And the `--help` flag can be used anywhere in Cshell. For example, after choosing the create command, you can check the options available for that command as well: + +```bash +cshell wallet create --help +``` diff --git a/src/explorer/event.rs b/src/explorer/event.rs index 5d425a2..4f0395b 100644 --- a/src/explorer/event.rs +++ b/src/explorer/event.rs @@ -240,7 +240,7 @@ impl EventTask { self.check_balances().await?; } TipEvent::Reset(point) => { - self.send(Event::App(AppEvent::Reset(point.index)))?; + self.send(Event::App(AppEvent::Reset(point.slot)))?; self.check_balances().await?; } } diff --git a/src/explorer/mod.rs b/src/explorer/mod.rs index 584f52f..a93b060 100644 --- a/src/explorer/mod.rs +++ b/src/explorer/mod.rs @@ -102,7 +102,7 @@ impl App { events: EventHandler::new(context.clone()), accounts_tab_state: AccountsTabState::default(), blocks_tab_state: BlocksTabState::default(), - transactions_tab_state: TransactionsTabState::default(), + transactions_tab_state: TransactionsTabState::new(Arc::clone(&context)), } } @@ -173,7 +173,7 @@ impl App { _ => self.accounts_tab_state.handle_key(&key), }, SelectedTab::Blocks(_) => self.blocks_tab_state.handle_key(&key), - SelectedTab::Transactions(_) => self.transactions_tab_state.handle_key(&key), + SelectedTab::Transactions(_) => self.transactions_tab_state.handle_key(&key).await, } } } @@ -197,13 +197,10 @@ impl App { .update_scroll_state(self.chain.blocks.borrow().len()); self.transactions_tab_state - .update_scroll_state(self.chain.blocks.borrow().iter().map(|b| b.tx_count).sum()); + .update_blocks(Rc::clone(&self.chain.blocks)); self.selected_tab = match &self.selected_tab { SelectedTab::Blocks(_) => SelectedTab::Blocks(BlocksTab::from(&*self)), - SelectedTab::Transactions(_) => { - SelectedTab::Transactions(TransactionsTab::from(&*self)) - } x => x.clone(), } } @@ -223,7 +220,7 @@ impl App { .update_scroll_state(self.chain.blocks.borrow().len()); self.transactions_tab_state - .update_scroll_state(self.chain.blocks.borrow().iter().map(|b| b.tx_count).sum()); + .update_blocks(Rc::clone(&self.chain.blocks)); self.selected_tab = match &self.selected_tab { SelectedTab::Blocks(_) => SelectedTab::Blocks(BlocksTab::from(&*self)), @@ -233,7 +230,7 @@ impl App { fn select_previous_tab(&mut self) { self.selected_tab = match &self.selected_tab { - SelectedTab::Accounts(_) => SelectedTab::Transactions(TransactionsTab::from(&*self)), + SelectedTab::Accounts(_) => SelectedTab::Transactions(TransactionsTab {}), SelectedTab::Blocks(_) => SelectedTab::Accounts(AccountsTab::new(self.context.clone())), SelectedTab::Transactions(_) => SelectedTab::Blocks(BlocksTab::from(&*self)), } @@ -242,7 +239,7 @@ impl App { fn select_next_tab(&mut self) { self.selected_tab = match &self.selected_tab { SelectedTab::Accounts(_) => SelectedTab::Blocks(BlocksTab::from(&*self)), - SelectedTab::Blocks(_) => SelectedTab::Transactions(TransactionsTab::from(&*self)), + SelectedTab::Blocks(_) => SelectedTab::Transactions(TransactionsTab {}), SelectedTab::Transactions(_) => { SelectedTab::Accounts(AccountsTab::new(self.context.clone())) } diff --git a/src/explorer/widgets/tabs/transactions.rs b/src/explorer/widgets/tabs/transactions.rs index ae3a6f8..45692c6 100644 --- a/src/explorer/widgets/tabs/transactions.rs +++ b/src/explorer/widgets/tabs/transactions.rs @@ -1,7 +1,7 @@ -use std::{cell::RefCell, collections::VecDeque, rc::Rc}; +use std::{cell::RefCell, collections::VecDeque, rc::Rc, sync::Arc}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use pallas::ledger::addresses::Address; +use pallas::{interop::utxorpc::TxHash, ledger::addresses::Address}; use ratatui::{ buffer::Buffer, layout::{Constraint, Layout, Margin, Rect}, @@ -14,33 +14,53 @@ use ratatui::{ }; use regex::Regex; use tui_tree_widget::{Tree, TreeItem, TreeState}; -use utxorpc::spec::cardano::{ - self, big_int, - certificate::Certificate, - d_rep, metadatum, native_script, plutus_data, - script::{self}, - stake_credential, AuxData, Datum, Metadatum, NativeScript, PlutusData, Redeemer, - RedeemerPurpose, Script, Tx, TxInput, TxOutput, TxValidity, VKeyWitness, Withdrawal, - WitnessSet, +use utxorpc::spec::{ + cardano::{ + self, big_int, + certificate::Certificate, + d_rep, metadatum, native_script, plutus_data, + script::{self}, + stake_credential, AuxData, Datum, Metadatum, NativeScript, PlutusData, Redeemer, + RedeemerPurpose, Script, Tx, TxInput, TxOutput, TxValidity, VKeyWitness, Withdrawal, + WitnessSet, + }, + query, }; use crate::{ - explorer::{App, ChainBlock}, + explorer::{ChainBlock, ExplorerContext}, utils::AdaFormat, }; -#[derive(Default)] pub struct TransactionsTabState { + context: Arc, + blocks: Rc>>, scroll_state: ScrollbarState, table_state: TableState, search_input: String, input_mode: InputMode, view_mode: ViewMode, + txs: Vec, tx_selected: Option, detail_state: TransactionsDetailState, } impl TransactionsTabState { - pub fn handle_key(&mut self, key: &KeyEvent) { + pub fn new(context: Arc) -> Self { + Self { + context, + blocks: Rc::default(), + scroll_state: Default::default(), + table_state: Default::default(), + search_input: Default::default(), + input_mode: Default::default(), + view_mode: Default::default(), + txs: Default::default(), + tx_selected: Default::default(), + detail_state: Default::default(), + } + } + + pub async fn handle_key(&mut self, key: &KeyEvent) { match self.view_mode { ViewMode::Normal => match self.input_mode { InputMode::Normal => match (key.code, key.modifiers) { @@ -53,7 +73,13 @@ impl TransactionsTabState { } (KeyCode::Esc, _) => { if !self.search_input.is_empty() { - self.search_input.clear() + self.search_input.clear(); + self.txs = self + .blocks + .borrow() + .iter() + .flat_map(TxView::from_chain_block) + .collect(); } } (KeyCode::Enter, _) => { @@ -73,6 +99,46 @@ impl TransactionsTabState { } KeyCode::Esc => self.input_mode = InputMode::Normal, KeyCode::Enter => { + let mut txs: Vec = self + .blocks + .borrow() + .iter() + .flat_map(TxView::from_chain_block) + .collect(); + + if !self.search_input.is_empty() { + let input_regex = Regex::new(&self.search_input).unwrap(); + + txs.retain(|tx| { + input_regex.is_match(&tx.hash) + || input_regex.is_match(&tx.block_slot.to_string()) + }); + + if txs.is_empty() { + if let Ok(v) = hex::decode(&self.search_input) { + if let Ok(tx_hash) = v.try_into() { + let tx_hash = TxHash::new(tx_hash); + if let Ok(Some(any_chain_tx)) = + self.context.provider.fetch_tx(tx_hash.to_vec()).await + { + if let Some(tx_view) = + TxView::from_any_chain_tx(&any_chain_tx) + { + txs.push(tx_view); + } + } + } + } + } + + if !self.txs.is_empty() { + self.table_state.select_first(); + self.input_mode = InputMode::Normal + } + } + + self.txs = txs; + self.table_state.select_first(); self.input_mode = InputMode::Normal } @@ -87,12 +153,23 @@ impl TransactionsTabState { } } - pub fn update_scroll_state(&mut self, len: usize) { + pub fn update_blocks(&mut self, blocks: Rc>>) { + self.blocks = blocks; + let len: usize = self.blocks.borrow().iter().map(|b| b.tx_count).sum(); self.scroll_state = self.scroll_state.content_length( len.checked_mul(3) .and_then(|v| v.checked_sub(2)) .unwrap_or(0), - ) + ); + + if self.search_input.is_empty() { + self.txs = self + .blocks + .borrow() + .iter() + .flat_map(TxView::from_chain_block) + .collect(); + } } fn next_row(&mut self) { @@ -124,15 +201,17 @@ impl TransactionsTabState { #[derive(Clone)] pub struct TransactionsTab { - blocks: Rc>>, -} -impl From<&App> for TransactionsTab { - fn from(value: &App) -> Self { - Self { - blocks: Rc::clone(&value.chain.blocks), - } - } + // blocks: Rc>>, + // context: Arc, } +// impl From<&App> for TransactionsTab { +// fn from(value: &App) -> Self { +// Self { +// blocks: Rc::clone(&value.chain.blocks), +// context: value.context.clone(), +// } +// } +// } #[derive(Clone, Default, PartialEq)] enum InputMode { @@ -188,18 +267,8 @@ impl StatefulWidget for TransactionsTab { .collect::() .style(Style::default().fg(Color::Green).bold()) .height(1); - let mut txs: Vec = - self.blocks.borrow().iter().flat_map(TxView::new).collect(); - if !state.search_input.is_empty() { - let input_regex = Regex::new(&state.search_input).unwrap(); - - txs.retain(|tx| { - input_regex.is_match(&tx.hash) - || input_regex.is_match(&tx.block_slot.to_string()) - }); - } - let rows = txs.iter().enumerate().map(|(i, tx)| { + let rows = state.txs.iter().enumerate().map(|(i, tx)| { let color = match i % 2 { 0 => Color::Black, _ => Color::Reset, @@ -246,15 +315,7 @@ impl StatefulWidget for TransactionsTab { ViewMode::Detail => { if state.tx_selected.is_none() { let index = state.table_state.selected().unwrap(); - - let txs: Vec = self - .blocks - .borrow() - .iter() - .flat_map(TxView::new_with_tx) - .collect(); - - state.tx_selected = Some(txs[index].clone()); + state.tx_selected = Some(state.txs[index].clone()); } TransactionsDetail::new(state.tx_selected.clone().unwrap()).render( @@ -530,51 +591,68 @@ pub struct TxView { block_hash: String, } impl TxView { - pub fn new(chain_block: &ChainBlock) -> Vec { - match &chain_block.body { - Some(body) => body - .tx - .iter() - .map(|tx| Self { - hash: hex::encode(&tx.hash), - certs: tx.certificates.len(), - assets: tx.outputs.iter().map(|o| o.assets.len()).sum(), - amount_ada: tx.outputs.iter().map(|o| o.coin).sum(), - datum: tx.outputs.iter().any(|o| match &o.datum { - Some(datum) => !datum.hash.is_empty(), - None => false, - }), - tx: None, + pub fn from_chain_block(chain_block: &ChainBlock) -> Vec { + let body = match &chain_block.body { + Some(b) => b, + None => return Vec::new(), + }; + + body.tx + .iter() + .map(|tx| { + let hash = hex::encode(&tx.hash); + let certs = tx.certificates.len(); + let assets = tx.outputs.iter().map(|o| o.assets.len()).sum(); + let amount_ada = tx.outputs.iter().map(|o| o.coin).sum(); + let datum = tx + .outputs + .iter() + .any(|o| o.datum.as_ref().is_some_and(|d| !d.hash.is_empty())); + + TxView { + hash, + certs, + assets, + amount_ada, + datum, + // TODO: improve to not clone the whole tx + tx: Some(tx.clone()), block_slot: chain_block.slot, block_height: chain_block.number, block_hash: hex::encode(&chain_block.hash), - }) - .collect(), - None => Default::default(), - } + } + }) + .collect() } - pub fn new_with_tx(chain_block: &ChainBlock) -> Vec { - match &chain_block.body { - Some(body) => body - .tx - .iter() - .map(|tx| Self { - hash: hex::encode(&tx.hash), - certs: tx.certificates.len(), - assets: tx.outputs.iter().map(|o| o.assets.len()).sum(), - amount_ada: tx.outputs.iter().map(|o| o.coin).sum(), - datum: tx.outputs.iter().any(|o| match &o.datum { - Some(datum) => !datum.hash.is_empty(), - None => false, - }), + pub fn from_any_chain_tx(any_chain_tx: &query::AnyChainTx) -> Option { + let chain = any_chain_tx.chain.as_ref()?; + let block_ref = any_chain_tx.block_ref.as_ref()?; + + match chain { + query::any_chain_tx::Chain::Cardano(tx) => { + let hash = hex::encode(&tx.hash); + let certs = tx.certificates.len(); + let assets = tx.outputs.iter().map(|o| o.assets.len()).sum(); + let amount_ada = tx.outputs.iter().map(|o| o.coin).sum(); + let datum = tx + .outputs + .iter() + .any(|o| o.datum.as_ref().is_some_and(|d| !d.hash.is_empty())); + + Some(TxView { + hash, + certs, + assets, + amount_ada, + datum, + // TODO: improve to not clone the whole tx tx: Some(tx.clone()), - block_slot: chain_block.slot, - block_height: chain_block.number, - block_hash: hex::encode(&chain_block.hash), + block_slot: block_ref.slot, + block_height: block_ref.height, + block_hash: hex::encode(&block_ref.hash), }) - .collect(), - None => Default::default(), + } } } } diff --git a/src/main.rs b/src/main.rs index 61c2789..b7c83cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod explorer; mod output; mod provider; mod reports; +mod search; mod store; mod tx; mod types; @@ -65,6 +66,9 @@ enum Commands { /// Explore the blockchain #[command()] Explorer(explorer::Args), + + /// Search on chain data + Search(search::Args), } #[derive(Clone, ValueEnum)] @@ -131,6 +135,7 @@ async fn run_command() -> anyhow::Result<()> { Commands::Tx(args) => tx::run(args, &ctx).await, Commands::Wallet(args) => wallet::run(args, &mut ctx).await, Commands::Explorer(args) => explorer::run(args, &ctx).await, + Commands::Search(args) => search::run(args, &mut ctx).await, }; ctx.store.write()?; diff --git a/src/provider/types.rs b/src/provider/types.rs index e81cc05..0981e65 100644 --- a/src/provider/types.rs +++ b/src/provider/types.rs @@ -6,8 +6,11 @@ use pallas::ledger::addresses::Address; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use utxorpc::{ - spec::query::any_utxo_pattern::UtxoPattern, CardanoQueryClient, CardanoSubmitClient, - CardanoSyncClient, ClientBuilder, InnerService, + spec::{ + query::{any_utxo_pattern::UtxoPattern, AnyChainTx, ReadTxRequest}, + sync::{AnyChainBlock, BlockRef, FetchBlockRequest}, + }, + CardanoQueryClient, CardanoSubmitClient, CardanoSyncClient, ClientBuilder, InnerService, }; use crate::{ @@ -70,7 +73,7 @@ impl Provider { Some(blockref) => { println!( "Successfull request, block tip at slot {} and hash {}.", - blockref.index, + blockref.slot, hex::encode(blockref.hash) ) } @@ -286,6 +289,48 @@ impl Provider { Ok(client.submit(tx, vec![]).await?) } + + pub async fn fetch_block(&self, refs: Vec<(Vec, u64)>) -> Result> { + let mut client: utxorpc::CardanoSyncClient = self.client().await?; + + let refs = refs + .iter() + .map(|(hash, index)| BlockRef { + hash: hash.clone().into(), + slot: *index, + ..Default::default() + }) + .collect(); + + let request = FetchBlockRequest { + r#ref: refs, + ..Default::default() + }; + let response = client + .fetch_block(request) + .await + .map_err(|err| anyhow::Error::msg(format!("Fetch block. {}", err.code())))? + .into_inner(); + + Ok(response.block) + } + + pub async fn fetch_tx(&self, hash: Vec) -> Result> { + let mut client: utxorpc::CardanoQueryClient = self.client().await?; + + let request = ReadTxRequest { + hash: hash.into(), + ..Default::default() + }; + + let response = client + .read_tx(request) + .await + .map_err(|err| anyhow::Error::msg(format!("Fetch transaction. {}", err.code())))? + .into_inner(); + + Ok(response.tx) + } } impl OutputFormatter for Provider { diff --git a/src/reports.rs b/src/reports.rs index 9d837d4..a09d5d9 100644 --- a/src/reports.rs +++ b/src/reports.rs @@ -72,13 +72,6 @@ impl ErrorReport { let _ = writeln!(stderr); } - - /// Print the error report to stdout with JSON formatting - pub fn print_json(&self) { - let json = serde_json::to_string_pretty(self) - .unwrap_or_else(|_| format!("{{\"error\": \"Failed to serialize error report\"}}")); - println!("{}", json); - } } impl std::fmt::Display for ErrorReport { diff --git a/src/search/block.rs b/src/search/block.rs new file mode 100644 index 0000000..39ebe0c --- /dev/null +++ b/src/search/block.rs @@ -0,0 +1,53 @@ +use anyhow::{bail, Result}; +use clap::Parser; +use regex::Regex; +use tracing::instrument; + +use crate::output::OutputFormatter; + +#[derive(Parser)] +pub struct Args { + /// List of hash,index + #[arg(required = true, help = "List of hash,index to fetch block")] + refs: Vec, + + /// Name of the provider to use. If undefined, will use default + #[arg(long, help = "Name of the provider to use")] + provider: Option, +} + +#[instrument(skip_all, name = "block")] +pub async fn run(args: Args, ctx: &mut crate::Context) -> Result<()> { + let provider = match args.provider { + Some(name) => ctx.store.find_provider(&name), + None => ctx.store.default_provider(), + }; + + let Some(provider) = provider else { + bail!("Provider not found") + }; + + let ref_regex = Regex::new(r"(.+),(\d+)")?; + + let refs = args + .refs + .iter() + .map(|r| { + let captures = ref_regex + .captures(r) + .ok_or_else(|| anyhow::Error::msg(format!("Invalid reference format: {r}")))?; + + let hash_str = captures.get(1).unwrap().as_str(); + let index = captures.get(2).unwrap().as_str().parse::()?; + + let decoded_hash = hex::decode(hash_str)?; + + Ok((decoded_hash, index)) + }) + .collect::, u64)>>>()?; + + let blocks = provider.fetch_block(refs).await?; + blocks.output(&ctx.output_format); + + Ok(()) +} diff --git a/src/search/mod.rs b/src/search/mod.rs new file mode 100644 index 0000000..4b9a2a5 --- /dev/null +++ b/src/search/mod.rs @@ -0,0 +1,187 @@ +use anyhow::Result; +use clap::{command, Parser, Subcommand}; +use comfy_table::Table; +use tracing::instrument; +use utxorpc::spec::{cardano::Tx, query, sync}; + +use crate::output::OutputFormatter; + +mod block; +mod transaction; + +#[derive(Parser)] +pub struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// fetch block + Block(block::Args), + + /// fetch transaction + Transaction(transaction::Args), +} + +#[instrument("search", skip_all)] +pub async fn run(args: Args, ctx: &mut crate::Context) -> Result<()> { + match args.command { + Commands::Block(args) => block::run(args, ctx).await, + Commands::Transaction(args) => transaction::run(args, ctx).await, + } +} + +fn cardano_tx_table(block_hash: Vec, tx: &[Tx]) -> Table { + let mut table = Table::new(); + table.set_header(vec![ + "Block", + "", + "Hash", + "Inputs", + "Outputs", + "Certificates", + "Ref Inputs", + "Datum", + ]); + + let block_hash = hex::encode(block_hash); + let block_hash_trucated = format!( + "{}...{}", + &block_hash[..4], + &block_hash[block_hash.len() - 4..] + ); + + for (i, tx) in tx.iter().enumerate() { + let hash = hex::encode(&tx.hash); + let inputs = tx.inputs.len(); + let outputs = tx.outputs.len(); + let certificates = tx.certificates.len(); + let reference_inputs = tx.reference_inputs.len(); + + let contains_datum = if tx.outputs.iter().any(|o| { + o.datum + .as_ref() + .map(|d| !d.hash.is_empty()) + .unwrap_or_default() + }) { + "contain" + } else { + "empty" + }; + + table.add_row(vec![ + &block_hash_trucated, + &i.to_string(), + &hash, + &inputs.to_string(), + &outputs.to_string(), + &certificates.to_string(), + &reference_inputs.to_string(), + contains_datum, + ]); + } + + table +} + +impl OutputFormatter for Vec { + fn to_table(&self) { + for block in self { + if let Some(chain) = &block.chain { + match chain { + sync::any_chain_block::Chain::Cardano(block) => { + if block.header.is_none() { + return; + } + let header = block.header.as_ref().unwrap(); + if let Some(body) = &block.body { + let table = cardano_tx_table(header.hash.clone().into(), &body.tx); + println!("{table}"); + } + } + } + } + } + } + + fn to_json(&self) { + let result = serde_json::to_value(self); + if let Err(err) = result { + eprintln!("{err}"); + return; + } + + println!( + "{}", + serde_json::to_string_pretty(&result.unwrap()).unwrap() + ); + } +} + +impl OutputFormatter for Vec { + fn to_table(&self) { + for block in self { + if let Some(chain) = &block.chain { + match chain { + query::any_chain_block::Chain::Cardano(block) => { + if block.header.is_none() { + return; + } + let header = block.header.as_ref().unwrap(); + if let Some(body) = &block.body { + let table = cardano_tx_table(header.hash.clone().into(), &body.tx); + println!("{table}"); + } + } + } + } + } + } + + fn to_json(&self) { + let result = serde_json::to_value(self); + if let Err(err) = result { + eprintln!("{err}"); + return; + } + + println!( + "{}", + serde_json::to_string_pretty(&result.unwrap()).unwrap() + ); + } +} + +impl OutputFormatter for query::AnyChainTx { + fn to_table(&self) { + if let Some(chain) = &self.chain { + match chain { + query::any_chain_tx::Chain::Cardano(tx) => { + let block_hash = self.block_ref.as_ref().unwrap().hash.clone(); + let table = cardano_tx_table(block_hash.into(), std::slice::from_ref(tx)); + println!("{table}"); + } + } + } + } + + fn to_json(&self) { + if let Some(chain) = &self.chain { + match chain { + query::any_chain_tx::Chain::Cardano(tx) => { + let result = serde_json::to_value(tx); + if let Err(err) = result { + eprintln!("{err}"); + return; + } + + println!( + "{}", + serde_json::to_string_pretty(&result.unwrap()).unwrap() + ); + } + } + } + } +} diff --git a/src/search/transaction.rs b/src/search/transaction.rs new file mode 100644 index 0000000..537d62d --- /dev/null +++ b/src/search/transaction.rs @@ -0,0 +1,39 @@ +use anyhow::{bail, Context, Result}; +use clap::Parser; +use tracing::instrument; + +use crate::output::OutputFormatter; + +#[derive(Parser)] +pub struct Args { + /// Transaction hash + #[arg(required = true, help = "Transaction hash")] + hash: String, + + /// Name of the provider to use. If undefined, will use default + #[arg(long, help = "Name of the provider to use")] + provider: Option, +} + +#[instrument(skip_all, name = "block")] +pub async fn run(args: Args, ctx: &mut crate::Context) -> Result<()> { + let provider = match args.provider { + Some(name) => ctx.store.find_provider(&name), + None => ctx.store.default_provider(), + }; + + let Some(provider) = provider else { + bail!("Provider not found") + }; + + let hash = hex::decode(args.hash).context("invalid transaction hash")?; + + match provider.fetch_tx(hash).await? { + Some(v) => { + v.output(&ctx.output_format); + } + None => bail!("transaction hash not found"), + } + + Ok(()) +} diff --git a/src/tx/common.rs b/src/tx/common.rs index 379528b..d6f73c4 100644 --- a/src/tx/common.rs +++ b/src/tx/common.rs @@ -191,7 +191,7 @@ pub fn define_args( let mut remaining_params = params.clone(); let mut loaded_args = - super::common::load_args(inline_args, file_args.as_deref(), &remaining_params)?; + super::common::load_args(inline_args, file_args, &remaining_params)?; // remove from the remaining params the args we already managed to load from the // file or json diff --git a/src/tx/invoke.rs b/src/tx/invoke.rs index eeec441..1c0ec3d 100644 --- a/src/tx/invoke.rs +++ b/src/tx/invoke.rs @@ -60,10 +60,10 @@ pub async fn run(args: Args, ctx: &crate::Context) -> Result<()> { args.tx3_args_json.as_deref(), args.tx3_args_file.as_deref(), ctx, - &provider, + provider, )?; - let TxEnvelope { tx, hash } = super::common::resolve_tx(&prototx, tx_args, &provider).await?; + let TxEnvelope { tx, hash } = super::common::resolve_tx(&prototx, tx_args, provider).await?; let cbor = hex::decode(tx).unwrap(); diff --git a/src/tx/resolve.rs b/src/tx/resolve.rs index 3c26884..1c5242a 100644 --- a/src/tx/resolve.rs +++ b/src/tx/resolve.rs @@ -49,10 +49,10 @@ pub async fn run(args: Args, ctx: &crate::Context) -> Result<()> { args.tx3_args_json.as_deref(), args.tx3_args_file.as_deref(), ctx, - &provider, + provider, )?; - let TxEnvelope { tx, hash } = super::common::resolve_tx(&prototx, tx_args, &provider).await?; + let TxEnvelope { tx, hash } = super::common::resolve_tx(&prototx, tx_args, provider).await?; let cbor = hex::decode(tx).unwrap();