diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index b5879b79b..c303d2595 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -12,6 +12,7 @@ defaults: env: CARGO_TERM_COLOR: always + ENVIO_API_TOKEN: ${{ secrets.ENVIO_API_TOKEN }} jobs: build_and_test: diff --git a/codegenerator/Cargo.lock b/codegenerator/Cargo.lock index 65117179e..52b7c5804 100644 --- a/codegenerator/Cargo.lock +++ b/codegenerator/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -142,6 +142,129 @@ dependencies = [ "term", ] +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.2.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.2", + "slab", + "windows-sys 0.61.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.41", + "windows-sys 0.48.0", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -153,6 +276,30 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io 2.6.0", + "async-lock 3.4.1", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.2", + "signal-hook-registry", + "slab", + "windows-sys 0.61.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.83" @@ -184,6 +331,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "auto_impl" version = "1.2.0" @@ -297,6 +450,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + [[package]] name = "bs58" version = "0.5.1" @@ -387,6 +562,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.1" @@ -553,6 +737,15 @@ dependencies = [ "unreachable", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-hex" version = "1.13.1" @@ -758,6 +951,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -950,6 +1154,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "envio" version = "0.0.1-dev" @@ -968,6 +1193,7 @@ dependencies = [ "include_dir", "inquire", "itertools 0.11.0", + "keyring", "open", "openssl", "paste", @@ -998,12 +1224,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1341,6 +1567,38 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1351,6 +1609,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.2.0" @@ -1534,6 +1801,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.2.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-locks" version = "0.7.1" @@ -1760,6 +2055,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1844,7 +2145,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2096,6 +2397,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -2136,6 +2438,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -2246,6 +2559,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keyring" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363387f0019d714aa60cc30ab4fe501a747f4c08fc58f069dd14be971bd495a0" +dependencies = [ + "byteorder", + "lazy_static", + "linux-keyutils", + "secret-service", + "security-framework", + "windows-sys 0.52.0", +] + [[package]] name = "lalrpop" version = "0.20.2" @@ -2287,9 +2614,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libm" @@ -2318,12 +2645,34 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-keyutils" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.7.3" @@ -2362,6 +2711,24 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -2401,7 +2768,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -2439,6 +2806,18 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + [[package]] name = "nom" version = "7.1.3" @@ -2459,6 +2838,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2486,6 +2879,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2527,6 +2929,17 @@ dependencies = [ "num-modular", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2543,7 +2956,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -2562,7 +2975,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.87", @@ -2679,6 +3092,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "overload" version = "0.1.1" @@ -2706,12 +3129,18 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.87", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2958,6 +3387,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.2.0", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -2985,6 +3425,36 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.2", + "windows-sys 0.61.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3040,13 +3510,23 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit", + "toml_edit 0.22.22", ] [[package]] @@ -3419,6 +3899,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.41" @@ -3428,10 +3922,23 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -3511,7 +4018,7 @@ version = "2.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.87", @@ -3593,6 +4100,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secret-service" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5204d39df37f06d1944935232fd2dfe05008def7ca599bf28c0800366c8a8f9" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.5", + "serde", + "sha2", + "zbus", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -3681,6 +4207,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -3835,6 +4372,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -3920,7 +4467,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -4276,9 +4823,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.2.0", "once_cell", - "rustix", + "rustix 0.38.41", "windows-sys 0.59.0", ] @@ -4401,7 +4948,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", ] @@ -4485,7 +5032,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.22", ] [[package]] @@ -4497,6 +5044,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.22" @@ -4507,7 +5065,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -4622,6 +5180,17 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + [[package]] name = "uint" version = "0.9.5" @@ -4793,6 +5362,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -4948,6 +5523,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-sys" version = "0.48.0" @@ -4975,6 +5556,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5096,6 +5686,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.6.20" @@ -5155,6 +5754,16 @@ dependencies = [ "tap", ] +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "yansi" version = "0.5.1" @@ -5191,6 +5800,72 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -5309,3 +5984,41 @@ dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/codegenerator/cli/Cargo.toml b/codegenerator/cli/Cargo.toml index 9a006a5cc..383653517 100644 --- a/codegenerator/cli/Cargo.toml +++ b/codegenerator/cli/Cargo.toml @@ -19,6 +19,7 @@ serde_json = "1.0.95" serde_yaml = "0.9.19" regex = "1.5" reqwest = "0.11" +keyring = "2.3.3" strum = { version = "0.26", features = ["derive"] } strum_macros = "0.26" tokio = { version = "1.28.2", features = [ diff --git a/codegenerator/cli/CommandLineHelp.md b/codegenerator/cli/CommandLineHelp.md index be0319377..d070ab0cf 100644 --- a/codegenerator/cli/CommandLineHelp.md +++ b/codegenerator/cli/CommandLineHelp.md @@ -26,7 +26,11 @@ This document contains the help content for the `envio` command-line program. * [`envio local db-migrate up`↴](#envio-local-db-migrate-up) * [`envio local db-migrate down`↴](#envio-local-db-migrate-down) * [`envio local db-migrate setup`↴](#envio-local-db-migrate-setup) +* [`envio local hypersync`↴](#envio-local-hypersync) +* [`envio local hypersync connect`↴](#envio-local-hypersync-connect) * [`envio start`↴](#envio-start) +* [`envio login`↴](#envio-login) +* [`envio logout`↴](#envio-logout) ## `envio` @@ -41,6 +45,8 @@ This document contains the help content for the `envio` command-line program. * `benchmark-summary` — Prints a summary of the benchmark data after running the indexer with envio start --bench flag or setting 'ENVIO_SAVE_BENCHMARK_DATA=true' * `local` — Prepare local environment for envio testing * `start` — Start the indexer without any automatic codegen +* `login` — Authenticate with envio hosted services and save credentials for authenticated actions +* `logout` — Logout and clear stored credentials ###### **Options:** @@ -248,6 +254,7 @@ Prepare local environment for envio testing * `docker` — Local Envio environment commands * `db-migrate` — Local Envio database commands +* `hypersync` — Internal: HyperSync utilities @@ -318,6 +325,26 @@ Setup database by dropping schema and then running migrations +## `envio local hypersync` + +Internal: HyperSync utilities + +**Usage:** `envio local hypersync ` + +###### **Subcommands:** + +* `connect` — Connect HyperSync: create user (idempotent), create API token, store it + + + +## `envio local hypersync connect` + +Connect HyperSync: create user (idempotent), create API token, store it + +**Usage:** `envio local hypersync connect` + + + ## `envio start` Start the indexer without any automatic codegen @@ -331,4 +358,20 @@ Start the indexer without any automatic codegen +## `envio login` + +Authenticate with envio hosted services and save credentials for authenticated actions + +**Usage:** `envio login` + + + +## `envio logout` + +Logout and clear stored credentials + +**Usage:** `envio logout` + + + diff --git a/codegenerator/cli/npm/envio/evm.schema.json b/codegenerator/cli/npm/envio/evm.schema.json index db814fd3e..1117e8a7b 100644 --- a/codegenerator/cli/npm/envio/evm.schema.json +++ b/codegenerator/cli/npm/envio/evm.schema.json @@ -113,6 +113,17 @@ "boolean", "null" ] + }, + "address_format": { + "description": "Address format for Ethereum addresses: 'checksum' or 'lowercase' (default: checksum)", + "anyOf": [ + { + "$ref": "#/$defs/AddressFormat" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -653,6 +664,13 @@ "viem", "hypersync-client" ] + }, + "AddressFormat": { + "type": "string", + "enum": [ + "checksum", + "lowercase" + ] } } } diff --git a/codegenerator/cli/npm/envio/src/Address.res b/codegenerator/cli/npm/envio/src/Address.res index 503ecd708..19c482665 100644 --- a/codegenerator/cli/npm/envio/src/Address.res +++ b/codegenerator/cli/npm/envio/src/Address.res @@ -10,6 +10,29 @@ external unsafeFromString: string => t = "%identity" module Evm = { @module("viem") external fromStringOrThrow: string => t = "getAddress" + + // NOTE: the function is named to be overshadowed by the one below, so that we don't have to import viem in the handler code + @module("viem") + external fromStringLowercaseOrThrow: string => bool = "isAddress" + + // Reassign since the function might be used in the handler code + // and we don't want to have a "viem" import there. It's needed to keep "viem" a dependency + // of generated code instead of adding it to the indexer project dependencies. + // Also, we want a custom error message, which is searchable in our codebase. + // Validate that the string is a proper address but return a lowercased value + let fromStringLowercaseOrThrow = string => { + if (fromStringLowercaseOrThrow(string)) { + unsafeFromString(string->Js.String2.toLowerCase) + } else { + Js.Exn.raiseError( + `Address "${string}" is invalid. Expected a 20-byte hex string starting with 0x.`, + ) + } + } + + let fromAddressLowercaseOrThrow = address => + address->toString->fromStringLowercaseOrThrow + // Reassign since the function might be used in the handler code // and we don't want to have a "viem" import there. It's needed to keep "viem" a dependency // of generated code instead of adding it to the indexer project dependencies. diff --git a/codegenerator/cli/npm/envio/src/sources/HyperSyncClient.res b/codegenerator/cli/npm/envio/src/sources/HyperSyncClient.res index 264bc0395..d588e57db 100644 --- a/codegenerator/cli/npm/envio/src/sources/HyperSyncClient.res +++ b/codegenerator/cli/npm/envio/src/sources/HyperSyncClient.res @@ -448,10 +448,16 @@ type t = { } @module("@envio-dev/hypersync-client") @scope("HypersyncClient") external make: cfg => t = "new" -let make = (~url, ~apiToken, ~httpReqTimeoutMillis, ~maxNumRetries) => +let make = ( + ~url, + ~apiToken, + ~httpReqTimeoutMillis, + ~maxNumRetries, + ~enableChecksumAddresses=true, +) => make({ url, - enableChecksumAddresses: true, + enableChecksumAddresses, bearerToken: apiToken, httpReqTimeoutMillis, maxNumRetries, diff --git a/codegenerator/cli/npm/envio/src/sources/HyperSyncSource.res b/codegenerator/cli/npm/envio/src/sources/HyperSyncSource.res index 973286b66..aeb0fe76f 100644 --- a/codegenerator/cli/npm/envio/src/sources/HyperSyncSource.res +++ b/codegenerator/cli/npm/envio/src/sources/HyperSyncSource.res @@ -151,9 +151,10 @@ type options = { allEventSignatures: array, shouldUseHypersyncClientDecoder: bool, eventRouter: EventRouter.t, - apiToken: option, + apiToken: string, clientMaxRetries: int, clientTimeoutMillis: int, + lowercaseAddresses: bool, } let make = ( @@ -167,19 +168,19 @@ let make = ( apiToken, clientMaxRetries, clientTimeoutMillis, + lowercaseAddresses, }: options, ): t => { let name = "HyperSync" let getSelectionConfig = memoGetSelectionConfig(~chain) - let apiToken = apiToken->Belt.Option.getWithDefault("3dc856dd-b0ea-494f-b27e-017b8b6b7e07") - let client = HyperSyncClient.make( ~url=endpointUrl, ~apiToken, ~maxNumRetries=clientMaxRetries, ~httpReqTimeoutMillis=clientTimeoutMillis, + ~enableChecksumAddresses=!lowercaseAddresses, ) let hscDecoder: ref> = ref(None) @@ -193,7 +194,11 @@ let make = ( ~msg="Failed to instantiate a decoder from hypersync client, please double check your ABI or try using 'event_decoder: viem' config option", ) | decoder => - decoder.enableChecksummedAddresses() + if lowercaseAddresses { + decoder.disableChecksummedAddresses() + } else { + decoder.enableChecksummedAddresses() + } decoder } } diff --git a/codegenerator/cli/src/cli_args/clap_definitions.rs b/codegenerator/cli/src/cli_args/clap_definitions.rs index 94efe906c..ccc4621e0 100644 --- a/codegenerator/cli/src/cli_args/clap_definitions.rs +++ b/codegenerator/cli/src/cli_args/clap_definitions.rs @@ -66,6 +66,12 @@ pub enum CommandType { ///Start the indexer without any automatic codegen Start(StartArgs), + ///Authenticate with envio hosted services and save credentials for authenticated actions + Login, + + ///Logout and clear stored credentials + Logout, + #[clap(hide = true)] #[command(subcommand)] Script(Script), @@ -106,6 +112,10 @@ pub enum LocalCommandTypes { /// Local Envio database commands #[command(subcommand)] DbMigrate(DbMigrateSubcommands), + + /// Internal: HyperSync utilities + #[command(subcommand)] + Hypersync(HypersyncLocalSubcommands), } #[derive(Subcommand, Debug, Clone)] @@ -126,6 +136,12 @@ pub enum DbMigrateSubcommands { Setup, } +#[derive(Subcommand, Debug)] +pub enum HypersyncLocalSubcommands { + ///Connect HyperSync: create user (idempotent), create API token, store it + Connect, +} + #[derive(Args, Debug, Clone)] pub struct InitArgs { ///The name of your project diff --git a/codegenerator/cli/src/cli_args/init_config.rs b/codegenerator/cli/src/cli_args/init_config.rs index 5d1561398..4f47cc711 100644 --- a/codegenerator/cli/src/cli_args/init_config.rs +++ b/codegenerator/cli/src/cli_args/init_config.rs @@ -171,6 +171,7 @@ pub mod evm { field_selection: None, raw_events: None, preload_handlers: Some(true), + address_format: None, }) } diff --git a/codegenerator/cli/src/cli_args/interactive_init/mod.rs b/codegenerator/cli/src/cli_args/interactive_init/mod.rs index c758c6f77..b30cad2c6 100644 --- a/codegenerator/cli/src/cli_args/interactive_init/mod.rs +++ b/codegenerator/cli/src/cli_args/interactive_init/mod.rs @@ -162,38 +162,23 @@ pub async fn prompt_missing_init_args( let api_token: Option = match init_args.api_token { Some(k) => Ok::<_, anyhow::Error>(Some(k)), None if ecosystem.uses_hypersync() => { - let select = Select::new( - "Add an API token for HyperSync to your .env file?", - ApiTokenInput::iter().collect(), - ) - .prompt() - .context("Prompting for add API token")?; - - let token_prompt = Text::new("Add your API token: ") - .with_help_message("See tokens at: https://envio.dev/app/api-tokens"); - - match select { - ApiTokenInput::Create => { - open::that_detached("https://envio.dev/app/api-tokens")?; + // New flow: try keyring vault first; if not present, provision via API using JWT + match crate::commands::hypersync::provision_and_get_token().await { + Ok(token) => Ok(Some(token)), + Err(e) => { + eprintln!("Warning: automatic HyperSync token provision failed: {}", e); + // Fallback to manual prompt without opening browser + let token_prompt = Text::new("Add your API token: ") + .with_help_message("See tokens at: https://envio.dev/app/api-tokens"); Ok(token_prompt .prompt_skippable() - .context("Prompting for create token")?) - } - ApiTokenInput::AddExisting => Ok(token_prompt - .prompt_skippable() - .context("Prompting for add existing token")?), - ApiTokenInput::Skip => { - println!( - "You can always visit 'https://envio.dev/app/api-tokens' and add a token \ - later to your .env file." - ); - Ok(None) + .context("Prompting for API token after provision failure")?) } } } None => Ok(None), } - .context("Prompting for API Token")?; + .context("Resolving API Token for init flow")?; Ok(InitConfig { name, diff --git a/codegenerator/cli/src/commands.rs b/codegenerator/cli/src/commands.rs index a8dfb7a0f..c647db279 100644 --- a/codegenerator/cli/src/commands.rs +++ b/codegenerator/cli/src/commands.rs @@ -29,6 +29,41 @@ async fn execute_command( )) } +async fn execute_command_with_env( + cmd: &str, + args: Vec<&str>, + current_dir: &Path, + envs: Vec<(&str, String)>, +) -> anyhow::Result { + let mut command = tokio::process::Command::new(cmd); + command + .args(&args) + .current_dir(current_dir) + .stdin(std::process::Stdio::null()) + .kill_on_drop(true); + + for (key, val) in envs { + command.env(key, val); + } + + command + .spawn() + .context(format!( + "Failed to spawn command {} {} at {} as child process", + cmd, + args.join(" "), + current_dir.to_str().unwrap_or("bad_path") + ))? + .wait() + .await + .context(format!( + "Failed to exit command {} {} at {} from child process", + cmd, + args.join(" "), + current_dir.to_str().unwrap_or("bad_path") + )) +} + pub mod rescript { use super::execute_command; use anyhow::Result; @@ -152,9 +187,11 @@ pub mod codegen { } pub mod start { - use super::execute_command; + use super::{execute_command, execute_command_with_env}; use crate::config_parsing::system_config::SystemConfig; + use crate::utils::token_manager::{TokenManager, HYPERSYNC_ACCOUNT, SERVICE_NAME}; use anyhow::anyhow; + use std::fs; pub async fn start_indexer( config: &SystemConfig, @@ -171,7 +208,43 @@ pub mod start { } let cmd = "npm"; let args = vec!["run", "start"]; - let exit = execute_command(cmd, args, &config.parsed_project_paths.generated).await?; + + // Determine whether to inject ENVIO_API_TOKEN from vault + let project_root = &config.parsed_project_paths.project_root; + let env_path = project_root.join(".env"); + let mut should_inject_token = true; + + if let Ok(contents) = fs::read_to_string(&env_path) { + // If .env contains an ENVIO_API_TOKEN definition, do not inject + if contents + .lines() + .any(|l| l.trim_start().starts_with("ENVIO_API_TOKEN=")) + { + should_inject_token = false; + } + } + + let exit = if should_inject_token { + // Attempt to load HyperSync token from vault and inject if present + match TokenManager::new(SERVICE_NAME, HYPERSYNC_ACCOUNT).get_token() { + Ok(Some(token)) => { + execute_command_with_env( + cmd, + args, + &config.parsed_project_paths.generated, + vec![("ENVIO_API_TOKEN", token)], + ) + .await? + } + _ => { + // No token available; run without injection + execute_command(cmd, args, &config.parsed_project_paths.generated).await? + } + } + } else { + // .env already defines ENVIO_API_TOKEN; run without injection + execute_command(cmd, args, &config.parsed_project_paths.generated).await? + }; if !exit.success() { return Err(anyhow!( @@ -280,3 +353,328 @@ pub mod benchmark { Ok(()) } } + +/// manages the login flow to Envio via GitHub OAuth. +pub mod login { + use crate::utils::token_manager::{TokenManager, JWT_ACCOUNT, SERVICE_NAME}; + use anyhow::{anyhow, Context, Result}; + use open; + use reqwest::StatusCode; + use serde::{Deserialize, Serialize}; + use std::time::Duration; + use tokio::time::sleep; + + /// Default UI/API base URL. Change this constant to point to your deployment. + pub const AUTH_BASE_URL: &str = "https://envio.dev"; + + fn get_api_base_url() -> String { + // Allow override via ENVIO_API_URL, otherwise use the constant above. + std::env::var("ENVIO_API_URL").unwrap_or_else(|_| AUTH_BASE_URL.to_string()) + } + + /// Default UI/API base URL. Change this constant to point to your deployment. + pub const HYPERSYNC_TOKEN_API_URL: &str = "https://hypersync-tokens.hyperquery.xyz"; + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct CliAuthSession { + code: String, + auth_url: String, + expires_in: i32, + } + + #[derive(Debug, Deserialize)] + struct CliAuthStatus { + completed: bool, + error: Option, + token: Option, + } + + #[derive(Debug, Serialize)] + struct EmptyBody {} + + pub async fn run_login() -> Result<()> { + let base = get_api_base_url(); + let client = reqwest::Client::new(); + + // 1) Create a CLI auth session + let create_url = format!("{}/api/auth/cli-session", base); + let session: CliAuthSession = client + .post(&create_url) + .json(&EmptyBody {}) + .send() + .await + .with_context(|| format!("Failed to POST {}", create_url))? + .error_for_status() + .with_context(|| format!("Non-200 from {}", create_url))? + .json() + .await + .context("Failed to decode CLI session response")?; + + println!( + "Opening browser for authentication...\nIf it doesn't open, visit: {}", + session.auth_url + ); + let _ = open::that_detached(&session.auth_url); + + // 2) Poll for completion + let poll_url = format!("{}/api/auth/cli-session?code={}", base, session.code); + let poll_time_seconds = 2; + let poll_interval = Duration::from_secs(poll_time_seconds); + // Add a small grace window to handle cold starts or UI recompiles wiping in-memory state + let extra_grace_attempts = 15; // ~30s grace + let max_attempts = + (session.expires_in.max(0) as u64) / poll_time_seconds + extra_grace_attempts; + + // Give the UI a brief warm-up before first poll + sleep(Duration::from_secs(poll_time_seconds)).await; + + let mut consecutive_not_found = 0u32; + + for _ in 0..max_attempts { + sleep(poll_interval).await; + + let resp = match client.get(&poll_url).send().await { + Ok(r) => r, + Err(_) => { + // transient network error; try again + continue; + } + }; + + if resp.status() == StatusCode::NOT_FOUND { + consecutive_not_found += 1; + // Keep polling; in-memory session store may not be ready yet + if consecutive_not_found % 10 == 1 { + // Print a lightweight status occasionally + eprintln!("Waiting for session to become available..."); + } + continue; + } else { + consecutive_not_found = 0; + } + + if resp.status().is_success() { + let status: CliAuthStatus = match resp.json().await { + Ok(s) => s, + Err(_) => continue, + }; + + if let Some(err) = status.error { + if !err.is_empty() { + return Err(anyhow!("authentication error: {}", err)); + } + } + + if status.completed { + if let Some(token) = status.token { + let tm = TokenManager::new(SERVICE_NAME, JWT_ACCOUNT); + if let Err(e) = tm.store_token(&token) { + eprintln!("Warning: failed to store token in keyring: {}", e); + } + + println!("Successfully logged in to Envio."); + + return Ok(()); + } + } + } + } + + Err(anyhow!("authentication timed out")) + } + + pub fn get_stored_jwt() -> Result> { + TokenManager::new(SERVICE_NAME, JWT_ACCOUNT).get_token() + } +} + +/// manages the flow of getting hypersync api tokens +pub mod hypersync { + use super::login::{get_stored_jwt, HYPERSYNC_TOKEN_API_URL}; + use crate::utils::token_manager::{TokenManager, HYPERSYNC_ACCOUNT, SERVICE_NAME}; + use anyhow::{anyhow, Context, Result}; + use reqwest::StatusCode; + use serde::{Deserialize, Serialize}; + + fn get_hypersync_tokens_base_url() -> String { + // Allow override specific for tokens service; else fall back to the constant + std::env::var("ENVIO_HYPERSYNC_TOKENS_URL") + .unwrap_or_else(|_| HYPERSYNC_TOKEN_API_URL.to_string()) + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct TokenResponse { + #[serde(rename = "user_token")] + user_token: String, + } + + async fn create_user_if_needed(client: &reqwest::Client, base: &str, jwt: &str) -> Result<()> { + let url = format!("{}/user/create", base); + let resp = client + .post(&url) + .header("authorization", format!("Bearer {}", jwt)) + .header("content-type", "application/json") + .send() + .await + .with_context(|| format!("POST {} failed", url))?; + if resp.status() == StatusCode::OK || resp.status() == StatusCode::CONFLICT { + Ok(()) + } else { + Err(anyhow!("Failed to create user: {}", resp.status())) + } + } + + async fn create_api_token(client: &reqwest::Client, base: &str, jwt: &str) -> Result { + #[derive(Serialize)] + struct Body { + enable_active_networks: bool, + } + let url = format!("{}/token/create", base); + let resp = client + .post(&url) + .header("authorization", format!("Bearer {}", jwt)) + .header("content-type", "application/json") + .json(&Body { + enable_active_networks: true, + }) + .send() + .await + .with_context(|| format!("POST {} failed", url))?; + if resp.status().is_success() || resp.status() == StatusCode::CONFLICT { + // Accept JSON or plain text + let bytes = resp.bytes().await.context("Read token response body")?; + let body_str = std::str::from_utf8(&bytes).unwrap_or("").trim(); + if body_str.starts_with('{') { + let json: TokenResponse = + serde_json::from_slice(&bytes).context("Decode token response")?; + Ok(json.user_token) + } else if !body_str.is_empty() { + Ok(body_str.to_string()) + } else { + Err(anyhow!("Empty token response")) + } + } else { + Err(anyhow!("Failed to create token: {}", resp.status())) + } + } + + async fn list_api_tokens( + client: &reqwest::Client, + base: &str, + jwt: &str, + ) -> Result> { + #[derive(Debug, serde::Deserialize)] + struct UserTokensResponse { + tokens: Vec, + } + + let url = format!("{}/token/get-user-tokens", base); + let resp = client + .get(&url) + .header("authorization", format!("Bearer {}", jwt)) + .header("content-type", "application/json") + .send() + .await + .with_context(|| format!("GET {} failed", url))?; + + if resp.status().is_success() { + let body: UserTokensResponse = + resp.json().await.context("Decode user tokens response")?; + Ok(body.tokens) + } else { + Ok(vec![]) + } + } + + fn store_api_token(token: &str) -> Result<()> { + TokenManager::new(SERVICE_NAME, HYPERSYNC_ACCOUNT).store_token(token) + } + + /// get the hypersync api token from the keyring or the api + /// doesn't perform any login actions if the user is not logged in. + pub async fn get_hypersync_token() -> Result> { + // 1) If we already have a token in keyring, use it + if let Some(existing) = TokenManager::new(SERVICE_NAME, HYPERSYNC_ACCOUNT).get_token()? { + return Ok(Some(existing)); + } + + // 2) If we have a JWT, try to list existing tokens without creating a new one + let jwt = match get_stored_jwt()? { + Some(t) => t, + None => return Ok(None), // Not logged in; do not login or provision + }; + + let base = get_hypersync_tokens_base_url(); + let client = reqwest::Client::new(); + let tokens = list_api_tokens(&client, &base, &jwt) + .await + .unwrap_or_default(); + if let Some(token) = tokens.get(0) { + store_api_token(token)?; + return Ok(Some(token.clone())); + } + Ok(None) + } + + /// provision a new hypersync api token + /// this will create a new user if needed and create a new token + /// this will also store the token in the keyring + pub async fn provision_and_get_token() -> Result { + let base = get_hypersync_tokens_base_url(); + let jwt = match get_stored_jwt()? { + Some(t) => t, + None => super::login::run_login().await.and_then(|_| { + super::login::get_stored_jwt()?.ok_or_else(|| anyhow!("JWT missing after login")) + })?, + }; + + let client = reqwest::Client::new(); + create_user_if_needed(&client, &base, &jwt).await?; + + // Prefer existing tokens; create only if none exist + let mut selected: Option = match list_api_tokens(&client, &base, &jwt).await { + Ok(tokens) if !tokens.is_empty() => Some(tokens[0].clone()), + _ => None, + }; + + if selected.is_none() { + selected = Some(create_api_token(&client, &base, &jwt).await?); + } + + let api_token = selected.expect("token must be set"); + store_api_token(&api_token)?; + Ok(api_token) + } + + pub async fn connect() -> Result<()> { + let api_token = provision_and_get_token().await; + + match api_token { + Ok(_token) => { + println!("Token: {}", _token); + println!("Successfully authenticated with HyperSync"); + } + Err(e) => { + eprintln!("Failed to authenticate with HyperSync: {}", e); + } + } + + Ok(()) + } +} + +pub mod logout { + use crate::utils::token_manager::{TokenManager, HYPERSYNC_ACCOUNT, JWT_ACCOUNT, SERVICE_NAME}; + use anyhow::Result; + + pub async fn run_logout() -> Result<()> { + let jwt_tm = TokenManager::new(SERVICE_NAME, JWT_ACCOUNT); + let hs_tm = TokenManager::new(SERVICE_NAME, HYPERSYNC_ACCOUNT); + let _ = jwt_tm.clear_token(); + let _ = hs_tm.clear_token(); + println!("Logged out and cleared stored credentials."); + Ok(()) + } +} diff --git a/codegenerator/cli/src/config_parsing/entity_parsing.rs b/codegenerator/cli/src/config_parsing/entity_parsing.rs index 040102654..d59915bf0 100644 --- a/codegenerator/cli/src/config_parsing/entity_parsing.rs +++ b/codegenerator/cli/src/config_parsing/entity_parsing.rs @@ -190,7 +190,7 @@ impl Schema { } } - fn try_get_type_def(&self, name: &String) -> anyhow::Result { + fn try_get_type_def(&self, name: &String) -> anyhow::Result> { match (self.entities.get(name), self.enums.get(name)) { (None, None) => Err(anyhow!("No type definition '{}' exists in schema", name)), (Some(_), Some(_)) => Err(anyhow!( diff --git a/codegenerator/cli/src/config_parsing/graph_migration/mod.rs b/codegenerator/cli/src/config_parsing/graph_migration/mod.rs index c3fa38172..2f7f16bda 100644 --- a/codegenerator/cli/src/config_parsing/graph_migration/mod.rs +++ b/codegenerator/cli/src/config_parsing/graph_migration/mod.rs @@ -285,6 +285,7 @@ pub async fn generate_config_from_subgraph_id( field_selection: None, raw_events: None, preload_handlers: Some(true), + address_format: None, }; let mut networks: Vec = vec![]; diff --git a/codegenerator/cli/src/config_parsing/human_config.rs b/codegenerator/cli/src/config_parsing/human_config.rs index 20073e6bb..1e566ee00 100644 --- a/codegenerator/cli/src/config_parsing/human_config.rs +++ b/codegenerator/cli/src/config_parsing/human_config.rs @@ -209,6 +209,18 @@ pub mod evm { description = "Makes handlers run twice to enable preload optimisations. Removes handlerWithLoader API, since it's not needed. (recommended, default: false)" )] pub preload_handlers: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars( + description = "Address format for Ethereum addresses: 'checksum' or 'lowercase' (default: checksum)" + )] + pub address_format: Option, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema)] + #[serde(rename_all = "lowercase")] + pub enum AddressFormat { + Checksum, + Lowercase, } impl Display for HumanConfig { diff --git a/codegenerator/cli/src/config_parsing/system_config.rs b/codegenerator/cli/src/config_parsing/system_config.rs index 1d4dadede..9105cce04 100644 --- a/codegenerator/cli/src/config_parsing/system_config.rs +++ b/codegenerator/cli/src/config_parsing/system_config.rs @@ -414,6 +414,7 @@ pub struct SystemConfig { pub enable_raw_events: bool, pub preload_handlers: bool, pub human_config: HumanConfig, + pub lowercase_addresses: bool, } //Getter methods for system config @@ -721,6 +722,10 @@ impl SystemConfig { field_selection, enable_raw_events: evm_config.raw_events.unwrap_or(false), preload_handlers: evm_config.preload_handlers.unwrap_or(false), + lowercase_addresses: match evm_config.address_format { + Some(super::human_config::evm::AddressFormat::Lowercase) => true, + _ => false, + }, human_config, }) } @@ -858,6 +863,7 @@ impl SystemConfig { field_selection: FieldSelection::fuel(), enable_raw_events: fuel_config.raw_events.unwrap_or(false), preload_handlers: fuel_config.preload_handlers.unwrap_or(false), + lowercase_addresses: false, human_config, }) } @@ -2085,6 +2091,7 @@ mod test { field_selection: None, raw_events: None, preload_handlers: None, + address_format: None, }; let project_paths = ParsedProjectPaths::new(".", "generated", "config.yaml").unwrap(); @@ -2131,6 +2138,7 @@ mod test { field_selection: None, raw_events: None, preload_handlers: None, + address_format: None, }; let system_config_with_output = SystemConfig::from_human_config( diff --git a/codegenerator/cli/src/executor/dev.rs b/codegenerator/cli/src/executor/dev.rs index 8e9a350dd..431dcc899 100644 --- a/codegenerator/cli/src/executor/dev.rs +++ b/codegenerator/cli/src/executor/dev.rs @@ -1,3 +1,4 @@ +use crate::config_parsing::system_config::{DataSource, MainEvmDataSource}; use crate::{ commands, config_parsing::system_config::SystemConfig, @@ -6,6 +7,7 @@ use crate::{ service_health::{self, EndpointHealth}, }; use anyhow::{anyhow, Context, Result}; +use std::fs; pub async fn run_dev(project_paths: ParsedProjectPaths) -> Result<()> { let config = @@ -64,6 +66,41 @@ pub async fn run_dev(project_paths: ParsedProjectPaths) -> Result<()> { .await .context("Failed running codegen")?; } + + // If any network uses HyperSync as the main sync source, ensure a HyperSync API token exists + let uses_hypersync = config.get_networks().iter().any(|n| match &n.sync_source { + DataSource::Evm { main, .. } => matches!(main, MainEvmDataSource::HyperSync { .. }), + DataSource::Fuel { .. } => true, + }); + + // If the current indexer uses HyperSync and there is no token present and it is not logged in, then prompt the user to log in to get a token. + if uses_hypersync { + let env_token_present = { + let env_path = config.parsed_project_paths.project_root.join(".env"); + if let Ok(contents) = fs::read_to_string(&env_path) { + contents + .lines() + .any(|l| l.trim_start().starts_with("ENVIO_API_TOKEN=")) + } else { + false + } + }; + + if env_token_present { + println!("HyperSync is enabled. Detected ENVIO_API_TOKEN in .env; skipping login."); + } else { + println!( + "HyperSync is enabled but no ENVIO_API_TOKEN was found. Attempting to log in to provision or retrieve a HyperSync API token..." + ); + if let Err(e) = crate::commands::hypersync::provision_and_get_token().await { + // Best-effort: log and continue; start can still run if RPC fallback exists + eprintln!( + "Warning: could not obtain HyperSync token automatically: {}", + e + ); + } + } + } // if hasura healhz check returns not found assume docker isnt running and start it up { let hasura_health_check_is_error = service_health::fetch_hasura_healthz().await.is_err(); diff --git a/codegenerator/cli/src/executor/local.rs b/codegenerator/cli/src/executor/local.rs index dc97cc081..037fa69f7 100644 --- a/codegenerator/cli/src/executor/local.rs +++ b/codegenerator/cli/src/executor/local.rs @@ -1,5 +1,7 @@ use crate::{ - cli_args::clap_definitions::{DbMigrateSubcommands, LocalCommandTypes, LocalDockerSubcommands}, + cli_args::clap_definitions::{ + DbMigrateSubcommands, HypersyncLocalSubcommands, LocalCommandTypes, LocalDockerSubcommands, + }, commands, config_parsing::system_config::SystemConfig, persisted_state::PersistedState, @@ -11,19 +13,22 @@ pub async fn run_local( local_commands: &LocalCommandTypes, project_paths: &ParsedProjectPaths, ) -> Result<()> { - let config = - SystemConfig::parse_from_project_files(project_paths).context("Failed parsing config")?; - match local_commands { - LocalCommandTypes::Docker(subcommand) => match subcommand { - LocalDockerSubcommands::Up => { - commands::docker::docker_compose_up_d(&config).await?; - } - LocalDockerSubcommands::Down => { - commands::docker::docker_compose_down_v(&config).await?; + LocalCommandTypes::Docker(subcommand) => { + let config = SystemConfig::parse_from_project_files(project_paths) + .context("Failed parsing config")?; + match subcommand { + LocalDockerSubcommands::Up => { + commands::docker::docker_compose_up_d(&config).await?; + } + LocalDockerSubcommands::Down => { + commands::docker::docker_compose_down_v(&config).await?; + } } - }, + } LocalCommandTypes::DbMigrate(subcommand) => { + let config = SystemConfig::parse_from_project_files(project_paths) + .context("Failed parsing config")?; //Use a closure just so running local dow doesn't need to construct persisted state let get_persisted_state = || -> Result { let persisted_state = PersistedState::get_current_state(&config) @@ -48,6 +53,13 @@ pub async fn run_local( } } } + LocalCommandTypes::Hypersync(subcommand) => match subcommand { + HypersyncLocalSubcommands::Connect => { + commands::hypersync::connect() + .await + .context("Failed connecting HyperSync")?; + } + }, } Ok(()) } diff --git a/codegenerator/cli/src/executor/mod.rs b/codegenerator/cli/src/executor/mod.rs index fd448081f..9c6ee7765 100644 --- a/codegenerator/cli/src/executor/mod.rs +++ b/codegenerator/cli/src/executor/mod.rs @@ -76,6 +76,20 @@ pub async fn execute(command_line_args: CommandLineArgs) -> Result<()> { commands::db_migrate::run_db_setup(&config, &persisted_state).await?; } + // During `start`, attempt to retrieve an existing HyperSync token without logging in or creating one. + { + use crate::config_parsing::system_config::{DataSource, MainEvmDataSource}; + let uses_hypersync = config.get_networks().iter().any(|n| match &n.sync_source { + DataSource::Evm { main, .. } => { + matches!(main, MainEvmDataSource::HyperSync { .. }) + } + DataSource::Fuel { .. } => true, + }); + if uses_hypersync { + let _ = commands::hypersync::get_hypersync_token().await; + } + } + const SHOULD_OPEN_HASURA: bool = false; commands::start::start_indexer(&config, SHOULD_OPEN_HASURA).await?; } @@ -116,6 +130,14 @@ pub async fn execute(command_line_args: CommandLineArgs) -> Result<()> { .await .context("Failed print missing networks script")?; } + CommandType::Login => { + commands::login::run_login() + .await + .context("Failed running login flow")?; + } + CommandType::Logout => { + commands::logout::run_logout().await?; + } }; Ok(()) diff --git a/codegenerator/cli/src/hbs_templating/codegen_templates.rs b/codegenerator/cli/src/hbs_templating/codegen_templates.rs index 282c3d723..0d4e9717d 100644 --- a/codegenerator/cli/src/hbs_templating/codegen_templates.rs +++ b/codegenerator/cli/src/hbs_templating/codegen_templates.rs @@ -43,12 +43,6 @@ pub struct EventParamTypeTemplate { pub is_eth_address: bool, } -#[derive(Serialize, Debug, PartialEq, Clone)] -pub struct EventRecordTypeTemplate { - pub name: CapitalizedOptions, - pub params: Vec, -} - #[derive(Serialize, Debug, PartialEq, Clone)] pub struct GraphQlEnumTypeTemplate { pub name: CapitalizedOptions, @@ -1123,7 +1117,8 @@ impl NetworkConfigTemplate { format!( "NetworkSources.evm(~chain, ~contracts=[{contracts_code}], ~hyperSync={hyper_sync_code}, \ ~allEventSignatures=[{all_event_signatures}]->Belt.Array.concatMany, \ - ~shouldUseHypersyncClientDecoder={is_client_decoder}, ~rpcs=[{rpcs}])" + ~shouldUseHypersyncClientDecoder={is_client_decoder}, ~rpcs=[{rpcs}], ~lowercaseAddresses={})", + if config.lowercase_addresses { "true" } else { "false" } ), deprecated_sync_source_code, ) @@ -1279,6 +1274,7 @@ pub struct ProjectTemplate { types_code: String, //Used for the package.json reference to handlers in generated relative_path_to_root_from_generated: String, + lowercase_addresses: bool, } impl ProjectTemplate { @@ -1399,6 +1395,7 @@ type chain = [{chain_id_type}]"#, types_code, //Used for the package.json reference to handlers in generated relative_path_to_root_from_generated, + lowercase_addresses: cfg.lowercase_addresses, }) } } @@ -1520,7 +1517,7 @@ mod test { network_config: network1, codegen_contracts: vec![contract1], is_fuel: false, - sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}])".to_string(), + sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}], ~lowercaseAddresses=false)".to_string(), deprecated_sync_source_code: "Rpc({syncConfig: Config.getSyncConfig({accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,})})".to_string(), }; @@ -1575,14 +1572,14 @@ mod test { network_config: network1, codegen_contracts: vec![contract1], is_fuel: false, - sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}])".to_string(), + sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}], ~lowercaseAddresses=false)".to_string(), deprecated_sync_source_code: "Rpc({syncConfig: Config.getSyncConfig({accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,})})".to_string(), }; let chain_config_2 = super::NetworkConfigTemplate { network_config: network2, codegen_contracts: vec![contract2], is_fuel: false, - sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract2\",events: [Types.Contract2.NewGravatar.register(), Types.Contract2.UpdatedGravatar.register()],abi: Types.Contract2.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract2.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}, {url: \"https://eth.com/fallback\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}])".to_string(), + sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract2\",events: [Types.Contract2.NewGravatar.register(), Types.Contract2.UpdatedGravatar.register()],abi: Types.Contract2.abi}], ~hyperSync=None, ~allEventSignatures=[Types.Contract2.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://eth.com\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}, {url: \"https://eth.com/fallback\", sourceFor: Sync, syncConfig: {accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,}}], ~lowercaseAddresses=false)".to_string(), deprecated_sync_source_code: "Rpc({syncConfig: Config.getSyncConfig({accelerationAdditive: 2000,initialBlockInterval: 10000,backoffMultiplicative: 0.8,intervalCeiling: 10000,backoffMillis: 5000,queryTimeoutMillis: 20000,})})".to_string(), }; @@ -1615,7 +1612,7 @@ mod test { network_config: network1, codegen_contracts: vec![contract1], is_fuel: false, - sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=Some(\"https://1.hypersync.xyz\"), ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://fallback.eth.com\", sourceFor: Fallback, syncConfig: {}}])".to_string(), + sources_code: "NetworkSources.evm(~chain, ~contracts=[{name: \"Contract1\",events: [Types.Contract1.NewGravatar.register(), Types.Contract1.UpdatedGravatar.register()],abi: Types.Contract1.abi}], ~hyperSync=Some(\"https://1.hypersync.xyz\"), ~allEventSignatures=[Types.Contract1.eventSignatures]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[{url: \"https://fallback.eth.com\", sourceFor: Fallback, syncConfig: {}}], ~lowercaseAddresses=false)".to_string(), deprecated_sync_source_code: "HyperSync({endpointUrl: \"https://1.hypersync.xyz\"})".to_string(), }; @@ -1645,7 +1642,7 @@ mod test { sources_code: format!( "NetworkSources.evm(~chain, ~contracts=[], ~hyperSync=Some(\"https://myskar.com\"), \ ~allEventSignatures=[]->Belt.Array.concatMany, \ - ~shouldUseHypersyncClientDecoder=true, ~rpcs=[])" + ~shouldUseHypersyncClientDecoder=true, ~rpcs=[], ~lowercaseAddresses=false)" ), deprecated_sync_source_code: format!( "HyperSync({{endpointUrl: \"https://myskar.com\"}})" @@ -1656,7 +1653,7 @@ mod test { network_config: network2, codegen_contracts: vec![], is_fuel: false, - sources_code: format!("NetworkSources.evm(~chain, ~contracts=[], ~hyperSync=Some(\"https://137.hypersync.xyz\"), ~allEventSignatures=[]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[])"), + sources_code: format!("NetworkSources.evm(~chain, ~contracts=[], ~hyperSync=Some(\"https://137.hypersync.xyz\"), ~allEventSignatures=[]->Belt.Array.concatMany, ~shouldUseHypersyncClientDecoder=true, ~rpcs=[], ~lowercaseAddresses=false)"), deprecated_sync_source_code: format!("HyperSync({{endpointUrl: \"https://137.hypersync.xyz\"}})"), }; diff --git a/codegenerator/cli/src/utils/mod.rs b/codegenerator/cli/src/utils/mod.rs index b8f2d8ebc..0edce1034 100644 --- a/codegenerator/cli/src/utils/mod.rs +++ b/codegenerator/cli/src/utils/mod.rs @@ -2,3 +2,4 @@ pub mod file_system; pub mod normalized_list; pub mod text; pub mod unique_hashmap; +pub mod token_manager; diff --git a/codegenerator/cli/src/utils/token_manager.rs b/codegenerator/cli/src/utils/token_manager.rs new file mode 100644 index 000000000..22c1d503c --- /dev/null +++ b/codegenerator/cli/src/utils/token_manager.rs @@ -0,0 +1,49 @@ +use anyhow::{anyhow, Context, Result}; +use keyring::Entry; + +pub struct TokenManager { + service: String, + account: String, +} + +impl TokenManager { + pub fn new(service: &str, account: &str) -> Self { + Self { + service: service.to_string(), + account: account.to_string(), + } + } + + fn entry(&self) -> Result { + Entry::new(&self.service, &self.account).context("Failed to open keyring entry") + } + + pub fn store_token(&self, token: &str) -> Result<()> { + self.entry() + .and_then(|e| e.set_password(token).map_err(|e| anyhow!(e))) + .context("Failed storing token in keyring") + } + + pub fn get_token(&self) -> Result> { + let entry = self.entry()?; + match entry.get_password() { + Ok(p) => Ok(Some(p)), + Err(keyring::Error::NoEntry) => Ok(None), + Err(e) => Err(anyhow!(e)), + } + } + + pub fn clear_token(&self) -> Result<()> { + let entry = self.entry()?; + match entry.delete_password() { + Ok(_) => Ok(()), + Err(keyring::Error::NoEntry) => Ok(()), + Err(e) => Err(anyhow::Error::new(e)), + } + } +} + +pub const SERVICE_NAME: &str = "envio-cli"; +pub const JWT_ACCOUNT: &str = "oauth_token"; +pub const HYPERSYNC_ACCOUNT: &str = "hypersync_api_token"; + diff --git a/codegenerator/cli/templates/dynamic/codegen/src/ConfigYAML.res.hbs b/codegenerator/cli/templates/dynamic/codegen/src/ConfigYAML.res.hbs index d3a21d546..53c41730b 100644 --- a/codegenerator/cli/templates/dynamic/codegen/src/ConfigYAML.res.hbs +++ b/codegenerator/cli/templates/dynamic/codegen/src/ConfigYAML.res.hbs @@ -27,6 +27,7 @@ type configYaml = { startBlock: int, confirmedBlockThreshold: int, contracts: dict, + lowercaseAddresses: bool, } let publicConfig = ChainMap.fromArrayUnsafe([ @@ -60,7 +61,8 @@ let publicConfig = ChainMap.fromArrayUnsafe([ confirmedBlockThreshold: {{chain_config.network_config.confirmed_block_threshold}}, syncSource: {{chain_config.deprecated_sync_source_code}}, startBlock: {{chain_config.network_config.start_block}}, - contracts + contracts, + lowercaseAddresses: {{#if ../lowercase_addresses}}true{{else}}false{{/if}} } ) }, diff --git a/codegenerator/cli/templates/dynamic/codegen/src/RegisterHandlers.res.hbs b/codegenerator/cli/templates/dynamic/codegen/src/RegisterHandlers.res.hbs index 0d5e6a0e3..e9c463f5c 100644 --- a/codegenerator/cli/templates/dynamic/codegen/src/RegisterHandlers.res.hbs +++ b/codegenerator/cli/templates/dynamic/codegen/src/RegisterHandlers.res.hbs @@ -35,7 +35,11 @@ let registerContractHandlers = ( addresses: [ {{#each contract.addresses as | address |}} {{#if ../../../is_evm_ecosystem}} + {{#if ../../../../lowercase_addresses}} + "{{address}}"->Address.Evm.fromStringLowercaseOrThrow + {{else}} "{{address}}"->Address.Evm.fromStringOrThrow + {{/if}} {{else}} "{{address}}"->Address.unsafeFromString {{/if}}, @@ -72,6 +76,7 @@ let registerContractHandlers = ( ~chains, ~enableRawEvents={{enable_raw_events}}, ~preloadHandlers={{preload_handlers}}, + ~lowercaseAddresses={{lowercase_addresses}}, {{#if chain_config.is_fuel}} ~ecosystem=Fuel, {{/if}} diff --git a/codegenerator/cli/templates/static/codegen/src/Config.res b/codegenerator/cli/templates/static/codegen/src/Config.res index c04d6fd48..ff1b1e6d6 100644 --- a/codegenerator/cli/templates/static/codegen/src/Config.res +++ b/codegenerator/cli/templates/static/codegen/src/Config.res @@ -114,6 +114,7 @@ type t = { addContractNameToContractNameMapping: dict, maxAddrInPartition: int, registrations: option, + lowercaseAddresses: bool, } let make = ( @@ -126,6 +127,7 @@ let make = ( ~persistence=codegenPersistence, ~ecosystem=InternalConfig.Evm, ~registrations=?, + ~lowercaseAddresses=false, ) => { let chainMap = chains @@ -168,6 +170,7 @@ let make = ( maxAddrInPartition: Env.maxAddrInPartition, registrations, preloadHandlers, + lowercaseAddresses, } } diff --git a/codegenerator/cli/templates/static/codegen/src/UserContext.res b/codegenerator/cli/templates/static/codegen/src/UserContext.res index c65db9fa2..792486352 100644 --- a/codegenerator/cli/templates/static/codegen/src/UserContext.res +++ b/codegenerator/cli/templates/static/codegen/src/UserContext.res @@ -287,8 +287,12 @@ let contractRegisterTraps: Utils.Proxy.traps = { let addFunction = (contractAddress: Address.t) => { let validatedAddress = if params.config.ecosystem === Evm { // The value is passed from the user-land, - // so we need to validate and checksum the address. - contractAddress->Address.Evm.fromAddressOrThrow + // so we need to validate and checksum/lowercase the address. + if params.config.lowercaseAddresses { + contractAddress->Address.Evm.fromAddressLowercaseOrThrow + } else { + contractAddress->Address.Evm.fromAddressOrThrow + } } else { // TODO: Ideally we should do the same for other ecosystems contractAddress diff --git a/codegenerator/cli/templates/static/codegen/src/eventFetching/NetworkSources.res b/codegenerator/cli/templates/static/codegen/src/eventFetching/NetworkSources.res index dc5d11013..a0157f469 100644 --- a/codegenerator/cli/templates/static/codegen/src/eventFetching/NetworkSources.res +++ b/codegenerator/cli/templates/static/codegen/src/eventFetching/NetworkSources.res @@ -13,6 +13,7 @@ let evm = ( ~allEventSignatures, ~shouldUseHypersyncClientDecoder, ~rpcs: array, + ~lowercaseAddresses, ) => { let eventRouter = contracts @@ -20,21 +21,33 @@ let evm = ( ->EventRouter.fromEvmEventModsOrThrow(~chain) let sources = switch hyperSync { - | Some(endpointUrl) => [ - HyperSyncSource.make({ - chain, - contracts, - endpointUrl, - allEventSignatures, - eventRouter, - shouldUseHypersyncClientDecoder: Env.Configurable.shouldUseHypersyncClientDecoder->Option.getWithDefault( - shouldUseHypersyncClientDecoder, - ), - apiToken: Env.envioApiToken, - clientMaxRetries: Env.hyperSyncClientMaxRetries, - clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis, - }), - ] + | Some(endpointUrl) => + switch Env.envioApiToken { + | Some(apiToken) => [ + HyperSyncSource.make({ + chain, + contracts, + endpointUrl, + allEventSignatures, + eventRouter, + shouldUseHypersyncClientDecoder: Env.Configurable.shouldUseHypersyncClientDecoder->Option.getWithDefault( + shouldUseHypersyncClientDecoder, + ), + apiToken, + clientMaxRetries: Env.hyperSyncClientMaxRetries, + clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis, + lowercaseAddresses, + }), + ] + | None => { + Js.Console.error("HyperSync is configured as a datasource but ENVIO_API_TOKEN is not set.") + Js.Console.error( + "Please run 'envio login` to log in, or alternatively add your ENVIO_API_TOKEN to your project .env file.", + ) + NodeJs.process->NodeJs.exitWithCode(Failure) + [] + } + } | _ => [] } rpcs->Js.Array2.forEach(({?syncConfig, url, sourceFor}) => { diff --git a/codegenerator/cli/templates/static/erc20_template/javascript/config.yaml b/codegenerator/cli/templates/static/erc20_template/javascript/config.yaml index a2b33ee27..4f901c69f 100644 --- a/codegenerator/cli/templates/static/erc20_template/javascript/config.yaml +++ b/codegenerator/cli/templates/static/erc20_template/javascript/config.yaml @@ -19,3 +19,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/erc20_template/rescript/config.yaml b/codegenerator/cli/templates/static/erc20_template/rescript/config.yaml index 9da217803..291f2845f 100644 --- a/codegenerator/cli/templates/static/erc20_template/rescript/config.yaml +++ b/codegenerator/cli/templates/static/erc20_template/rescript/config.yaml @@ -19,3 +19,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/erc20_template/typescript/config.yaml b/codegenerator/cli/templates/static/erc20_template/typescript/config.yaml index 93200940d..b6a406669 100644 --- a/codegenerator/cli/templates/static/erc20_template/typescript/config.yaml +++ b/codegenerator/cli/templates/static/erc20_template/typescript/config.yaml @@ -19,3 +19,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeter_template/javascript/config.yaml b/codegenerator/cli/templates/static/greeter_template/javascript/config.yaml index a65e55c31..d1e1b4f45 100644 --- a/codegenerator/cli/templates/static/greeter_template/javascript/config.yaml +++ b/codegenerator/cli/templates/static/greeter_template/javascript/config.yaml @@ -30,3 +30,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeter_template/rescript/config.yaml b/codegenerator/cli/templates/static/greeter_template/rescript/config.yaml index 43fdce771..2f38a02aa 100644 --- a/codegenerator/cli/templates/static/greeter_template/rescript/config.yaml +++ b/codegenerator/cli/templates/static/greeter_template/rescript/config.yaml @@ -30,3 +30,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeter_template/typescript/config.yaml b/codegenerator/cli/templates/static/greeter_template/typescript/config.yaml index 5a05a8458..2bfefec71 100644 --- a/codegenerator/cli/templates/static/greeter_template/typescript/config.yaml +++ b/codegenerator/cli/templates/static/greeter_template/typescript/config.yaml @@ -30,3 +30,6 @@ unordered_multichain_mode: true # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeteronfuel_template/javascript/config.yaml b/codegenerator/cli/templates/static/greeteronfuel_template/javascript/config.yaml index 226e47366..710a19047 100644 --- a/codegenerator/cli/templates/static/greeteronfuel_template/javascript/config.yaml +++ b/codegenerator/cli/templates/static/greeteronfuel_template/javascript/config.yaml @@ -16,3 +16,6 @@ networks: # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeteronfuel_template/rescript/config.yaml b/codegenerator/cli/templates/static/greeteronfuel_template/rescript/config.yaml index a4a50a078..e805d0699 100644 --- a/codegenerator/cli/templates/static/greeteronfuel_template/rescript/config.yaml +++ b/codegenerator/cli/templates/static/greeteronfuel_template/rescript/config.yaml @@ -16,3 +16,6 @@ networks: # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/codegenerator/cli/templates/static/greeteronfuel_template/typescript/config.yaml b/codegenerator/cli/templates/static/greeteronfuel_template/typescript/config.yaml index 96cb25fb7..42d203d66 100644 --- a/codegenerator/cli/templates/static/greeteronfuel_template/typescript/config.yaml +++ b/codegenerator/cli/templates/static/greeteronfuel_template/typescript/config.yaml @@ -16,3 +16,6 @@ networks: # But be aware that your handlers will run twice, # so make sure to use Effect API for external calls. preload_handlers: true +# Address format for Ethereum addresses: 'checksum' or 'lowercase' +# using lowercase addresses is slightly faster +address_format: lowercase \ No newline at end of file diff --git a/scenarios/test_codegen/pnpm-lock.yaml b/scenarios/test_codegen/pnpm-lock.yaml index 485adabe9..45da450b6 100644 --- a/scenarios/test_codegen/pnpm-lock.yaml +++ b/scenarios/test_codegen/pnpm-lock.yaml @@ -35,10 +35,6 @@ importers: viem: specifier: 2.21.0 version: 2.21.0(typescript@5.5.4) - optionalDependencies: - generated: - specifier: ./generated - version: link:generated devDependencies: '@glennsl/rescript-jest': specifier: ^0.9.2 @@ -94,6 +90,10 @@ importers: ts-node: specifier: ^10.9.1 version: 10.9.2(@types/node@18.19.47)(typescript@5.5.4) + optionalDependencies: + generated: + specifier: ./generated + version: link:generated ../helpers: dependencies: @@ -143,7 +143,7 @@ importers: specifier: 16.4.5 version: 16.4.5 envio: - specifier: file:/Users/dzakh/code/envio/hyperindex/codegenerator/target/debug/envio/../../../cli/npm/envio + specifier: file:/home/jasoons/Documents/code/envio/indexer/codegenerator/target/debug/envio/../../../cli/npm/envio version: file:../../codegenerator/cli/npm/envio(typescript@5.5.4) ethers: specifier: 6.8.0 diff --git a/scenarios/test_codegen/rescript.json b/scenarios/test_codegen/rescript.json index fe0d69505..74410c570 100644 --- a/scenarios/test_codegen/rescript.json +++ b/scenarios/test_codegen/rescript.json @@ -36,5 +36,7 @@ "helpers", "envio" ], - "bsc-flags": ["-open RescriptSchema"] + "bsc-flags": [ + "-open RescriptSchema" + ] } diff --git a/scenarios/test_codegen/test/AddressLowercase_test.res b/scenarios/test_codegen/test/AddressLowercase_test.res new file mode 100644 index 000000000..038d6c8c2 --- /dev/null +++ b/scenarios/test_codegen/test/AddressLowercase_test.res @@ -0,0 +1,19 @@ +open RescriptMocha + +describe("Address lowercase helpers", () => { + it("lowercases a valid address", () => { + let input = "0x2C169DFe5fBbA12957Bdd0Ba47d9CEDbFE260CA7" + let out = input->Address.Evm.fromStringLowercaseOrThrow->Address.toString + Assert.strictEqual(out, "0x2c169dfe5fbba12957bdd0ba47d9cedbfe260ca7") + }) + + it("throws on invalid address", () => { + Assert.throws(() => Address.Evm.fromStringLowercaseOrThrow("invalid-address")->ignore) + }) + + it("fromAddressLowercaseOrThrow returns lowercase", () => { + let mixed = Address.Evm.fromStringOrThrow("0x2c169dfe5fbba12957bdd0ba47d9cedbfe260ca7") + let out = mixed->Address.Evm.fromAddressLowercaseOrThrow->Address.toString + Assert.strictEqual(out, "0x2c169dfe5fbba12957bdd0ba47d9cedbfe260ca7") + }) +}) \ No newline at end of file diff --git a/scenarios/test_codegen/test/Config_test.res b/scenarios/test_codegen/test/Config_test.res index 2160e727f..163ee2b74 100644 --- a/scenarios/test_codegen/test/Config_test.res +++ b/scenarios/test_codegen/test/Config_test.res @@ -9,6 +9,7 @@ describe("getGeneratedByChainId Test", () => { syncSource: HyperSync({endpointUrl: "https://1.hypersync.xyz"}), startBlock: 1, confirmedBlockThreshold: 200, + lowercaseAddresses: false, contracts: Js.Dict.fromArray([ ( "Noop",