diff --git a/.vscode/settings.json b/.vscode/settings.json index a0b602b4..02cb465e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,18 @@ { "rust-analyzer.linkedProjects": [ "./api/Cargo.toml", - "./api/Cargo.toml", - "./api/Cargo.toml", - "./api/Cargo.toml" ], "github-actions.workflows.pinned.workflows": [ ".github/workflows/api-deploy.yml" ], "eslint.workingDirectories": [ "./plugin" + ], + "cSpell.words": [ + "Filemap", + "gibibytes", + "scarb", + "skiplist", + "testsingle" ] } diff --git a/DockerfileRocket b/DockerfileRocket index 3adffb8e..6f8f334f 100644 --- a/DockerfileRocket +++ b/DockerfileRocket @@ -26,7 +26,6 @@ RUN apt-get update RUN apt-get install grafana-agent-flow WORKDIR /opt/app - SHELL ["/bin/bash", "-lc"] # install Scarb (NOTE: a SPECIFIC version of Scarb is installed) @@ -44,15 +43,9 @@ RUN git submodule update --init # Build the API service WORKDIR /opt/app/api - RUN cargo build --release - RUN chmod +x ./docker_run.sh -# Build the cairo compiler -WORKDIR /opt/app/api/cairo_compilers -RUN chmod +x ./build.sh; ./build.sh - EXPOSE 8000 WORKDIR /opt/app/api diff --git a/api/Cargo.lock b/api/Cargo.lock index 0b9c02bc..65fac467 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -56,10 +56,14 @@ dependencies = [ "crossbeam-queue", "crossbeam-skiplist", "fmt", + "lazy_static", "prometheus", + "reqwest", "rocket", + "semver", "serde", "thiserror", + "tokio", "tracing", "tracing-appender", "tracing-log 0.1.4", @@ -136,12 +140,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "binascii" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -212,6 +228,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -296,13 +322,24 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags", + "bitflags 2.6.0", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -366,6 +403,30 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.30" @@ -573,6 +634,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -596,6 +670,145 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.4.0" @@ -613,6 +826,12 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "is-terminal" version = "0.4.13" @@ -657,6 +876,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -749,6 +974,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -799,6 +1041,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -869,6 +1155,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -972,7 +1264,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1039,6 +1331,46 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rocket" version = "0.5.1" @@ -1133,13 +1465,22 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -1152,6 +1493,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1164,6 +1514,35 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + [[package]] name = "serde" version = "1.0.208" @@ -1205,6 +1584,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1269,6 +1660,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.6.0" @@ -1289,6 +1686,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.12.0" @@ -1363,6 +1798,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.39.3" @@ -1373,6 +1818,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -1391,6 +1837,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -1589,6 +2045,29 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.10.0" @@ -1605,6 +2084,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1652,6 +2137,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.93" @@ -1681,6 +2178,16 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1721,6 +2228,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1869,6 +2385,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "yansi" version = "1.0.1" @@ -1878,6 +2416,30 @@ dependencies = [ "is-terminal", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1898,3 +2460,46 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/api/Cargo.toml b/api/Cargo.toml index a97a76f2..a4936db5 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -19,4 +19,8 @@ crossbeam-skiplist = "0.1.1" fmt = "0.1.0" thiserror = "1.0.50" chrono = "0.4.31" -prometheus = "0.13.4" \ No newline at end of file +prometheus = "0.13.4" +reqwest = { version = "0.11", features = ["json"] } +lazy_static = "1.4.0" +tokio = { version = "1.0", features = ["full"] } +semver = "1.0" \ No newline at end of file diff --git a/api/cairo b/api/cairo deleted file mode 160000 index ad5570f0..00000000 --- a/api/cairo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ad5570f0f25dbbffd9daedf02df140ff9e7291e4 diff --git a/api/cairo_compilers/build.sh b/api/cairo_compilers/build.sh deleted file mode 100644 index 6f8a71fd..00000000 --- a/api/cairo_compilers/build.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -echo "Building cairo compilers" - -directories=$(ls -d */) - -for dir in $directories -do - echo "Building $dir" - - if [[ ! -f "$dir/Cargo.toml" ]]; then - echo "Invalid cairo version provided $dir" - exit 1 - fi - - cd "$dir" || exit 1 - - cargo build --bin starknet-compile --release - cargo build --bin starknet-sierra-compile --release - - cd .. - - echo "Done building $dir" -done diff --git a/api/cairo_compilers/v2.6.0 b/api/cairo_compilers/v2.6.0 deleted file mode 160000 index b741c26c..00000000 --- a/api/cairo_compilers/v2.6.0 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b741c26c553fd9fa3246cee91fd5c637f225cdb9 diff --git a/api/cairo_compilers/v2.6.1 b/api/cairo_compilers/v2.6.1 deleted file mode 160000 index b741c26c..00000000 --- a/api/cairo_compilers/v2.6.1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b741c26c553fd9fa3246cee91fd5c637f225cdb9 diff --git a/api/cairo_compilers/v2.6.2 b/api/cairo_compilers/v2.6.2 deleted file mode 160000 index b741c26c..00000000 --- a/api/cairo_compilers/v2.6.2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b741c26c553fd9fa3246cee91fd5c637f225cdb9 diff --git a/api/cairo_compilers/v2.6.3 b/api/cairo_compilers/v2.6.3 deleted file mode 160000 index b741c26c..00000000 --- a/api/cairo_compilers/v2.6.3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b741c26c553fd9fa3246cee91fd5c637f225cdb9 diff --git a/api/cairo_compilers/v2.7.0 b/api/cairo_compilers/v2.7.0 deleted file mode 160000 index b741c26c..00000000 --- a/api/cairo_compilers/v2.7.0 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b741c26c553fd9fa3246cee91fd5c637f225cdb9 diff --git a/api/cairo_compilers/v2.8.2 b/api/cairo_compilers/v2.8.2 deleted file mode 160000 index 14b1d8c1..00000000 --- a/api/cairo_compilers/v2.8.2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14b1d8c1566b3346545eb7e65724e3d0cbb80a81 diff --git a/api/cairo_compilers/v2.8.4 b/api/cairo_compilers/v2.8.4 deleted file mode 160000 index 4a674b58..00000000 --- a/api/cairo_compilers/v2.8.4 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4a674b58e65f67a5c521f81ac4c34710dee53202 diff --git a/api/compiled_cairo_artifacts/contract.json b/api/compiled_cairo_artifacts/contract.json deleted file mode 100644 index 873eb161..00000000 --- a/api/compiled_cairo_artifacts/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"contract_definition":{"entry_points_by_type":{"EXTERNAL":[{"selector":"0x35a8bb8492337e79bdc674d6f31ac448f8017e26cc7bfe3144fb5d886fe5369","offset":"0xb"}],"L1_HANDLER":[],"CONSTRUCTOR":[]},"program":"H4sIAIHFfWQC/+1cW2/bOBb+K4GfdouuQVKUSA2wD07i6QTjJF3b3ZnZoiBkmU6E2JJHkpt4ivz3JXWxJVmyqYvbLLABkkjU4SHP5Ts8lEh+6z06bhj0frr41kPi7+dvPdubc3HVW/GV528/W+svF/+8CPjDigvCvjWf/+3vvfcXPcu2eRA4syVnge2tueTxucfYynJcxiRF/vrZt9Zr7geH95In465sl/k83Phu74sgWSy9Zxb6lv3kuA9sboVW1EtrvSuM7h98b7MWV1BU8RaLgIfiBryKO58vuC/4cubMIwlfX1+/yAezjbMMHTfusW+5D5zZj9x+ippNGvrcAy+YIossFnPxa1MAgOw4eEGAzkTJIvnlcSkGRJDA9EFcCNNnQJP15a/klZRSZMgq6ZN8FQrSphf7h6IKzTDie9qkZHGym0mjkneGNu3nXt69EII9StjPs12JSguMIAQ4kQnuS+XV7geCRb0feNDg7LwNggO5Z1m5IUr106z0wDDS62xvtXaW3GdfBSQcz5UABH0I+lqEtTD0ndkmjEEWeSmfbR6Y4y48UeJulktR5swFQp2FIzhEyAhCy396tnzety3H9/qijZXn9rnN1p4AfX9of5T/I9qFYMFcaxUhX7Xi+yhIzNL2XqK/URUWbtcRqwVfRnR5ZG5VKOGrJA2cv+RjJC5TyiD0N3bYi56W9/SJ27b1xMTTkPd/jW4uY8xPZJGyxKf4FBQQAGUNBFBNBZIUqZCiiFRTIdUiUqxCiiNSXYVUj0gNFVIjIiUqpCTrBLSWE8Sc01if8/Y5D0SZFSZAq+HvafPW0rGCGq1XOKFaRxTcsGm/frGCx4SXMiqOsDh3RIhG9WCzDNUgkfEdrY3vTJwHYaSNz1uq6oBPQV/rzYw98a2y1lYiA7Me+JmjaUGISyd8doK2qihw+Q6O88Isd862ysH0hb14vhq9FtOrkuOsLfR2Qe1+3dIQWRZFdywRpkaszBvqz46YRbZZKQ9efkfN6lmbkTY2ywXwplbLMynYzXHXm1BdboXhJW9KbxOerQGa1TM0ain6UYxEByOa2hB7fExTHl1nnrfs/zwYTYZR21+t5UZWBBketucG4Uke0/GnPAtYh8XKCh8z3VBTQabvdUSO2tp1t0ZTUZ0TLUVXLg/TikHo+WLA61tBwP2QIR2wmVMnp4t6W6jdsA+3g9/ZZHo/HnwYspvp8JZNbv6TtxnSjRNWq+I9uL4es8v7T3fXOYb/gMAgEBBk6gDqRNPFFdXFhBIjAE2sa0BDJiAawQBgpOs6pVSnooqJqVnHgeaOHbL4DU//WlwPokvlYFVRvRCm6mQ6a59/Zaka1MZ7lz+r16iXKx6YbRvY1nJ5oCs1n6zWVi23TPswGd5ds9vhZCIdc3rPRpBNhqPhlXDVnDNhTQOQmIASgwrHwZopXAeaBtVNhDVCiHCkuu676wN357dxcjr1RnCyDa5E+TH/qcuqOPXmS24L8Cg7VOgxaz73U0upONTa2i49a84SL1HLItNK67Cqb+8KqWTGEXFjR7wajEbs6v5uOh5cTcvtL8KGriGiYaIjBDBBmBrQhIRiikBTw1+Llj4MpkMWdaC8XQgNopsYGKItDQGDIk24HYEGxO3bFd7+y+DuejQcV7QuRMYieFKATIIo1pGQXzdEBDWpoRETQmqIviDUtCfSO688V76jDsf8z40IAY38voxPW6e3E361XX+xcW0ZxVidJiP/l8LIV+rKqNGytZQgU286pajwYC2MzjuwXMKoYDqfh7W0knzQCJWVUm++ryRLa2UcKmEPj6qJRF18FDWWsWPLJvaGbDgprGxndHM5Hoz/OBIzTaQRAyCDmFjELwAhkTkgNIzGATPX5qmgiTUDaZqIlUTH0KQi9SRAJ9gQGTShpq4THZoIUYSxAQ3daBw7R87Mt/yt1Hub0FnCpnXkXIpJA5MTzP/HzHJNt7VUp8Gh3AP+F2PD9fDj6P6PcliKxA0gLBJ2DBCFjVMnvl562zZ4y3P4AVAr5jUssFQ/EbxPtCVt4vmsIfRKOCjCMPqWK9XHFr63Yn9x31P72pbxLqO5dyWWa5HtFFgUrN8k4wRFldZNlmAFAyWTdPUmIFJLC412Gg8PENplKDz0gMyX3owyzcbK/DCcRomKyE/km7HhZFKRJmETyKkkoVAzNZGbYIyRjimQbzoMnUKRQuHGLzU+8FBGfe4PYl9uEzKreLUKntk35811fdCzFsGhklkxTERE9YLEWaTtRMpOoXvEU7oE8TFLdT2plHCeDP/1aXh3dQrRUDcRNAHVNIFe04REQFnXKNCpmP+YYrohH1EEgI41SlogeyLV6tpdgbuK3ZvA92Hn2kG8kt+BtAndjwF6sZtdids13I/4TseIP2a4c4D+cnR/9Su7+3R7WfWmAWIxWlMTAoNquo40AKAENkAa1gESI7rRAuOXS89+uttIS7WEdwmnN4HsXL/agbqMVUHGmSRhbkTzw+TsQL6uAVzuHR1jt8I+54Dt7jvS0aHaEAM1NnXNAGJ4xibEBCKoI5MAKFJxYCAMTSSHatoqA08mlB3l4OXc3kYWXuxbyzy8gl0nE/YzydyRrJ3n45Ve03VGXm2z8w3P05vb4WQ6uP1Y8QEVIznN1oiGDGQYWKcGlTjXdDHtJiYi4r8ORUHbgXrqrIRWrdW6i7H6gNnbGa4zXetgxD7kVjpohynZjxS4G0HPMnqX+cs5BvBSc50D29Pf2eTmw91g+mk8PJ56I2oINGMAiBjBCTQQIkTcmKTNsD192a19bwnnEk5vAsu5frUDchmroowpAVtyV33XUVrrey4hyAvUgU66Bny5R3WM9gqbliu7+WeHdEHqeDi4roC5SMBNYJiaJnJy+VpcLhUFFLVYSzCJV62OuTVvA+4SNm2/JdZa59SJt+eEaBEGyvgUtKG8uLWjIJfpUluROoVwud90id8KY3T9zTAF72/jm2nFII10iqhArQ4QJRjoQC7WNQxDTLM1rSV+f/OdsJWzxgy+K2bF3fde4z28vZmy4b+HdxXrakUCBQgwNQB1HWhiWoRNgmlT2wxXTjj8yt1mIXVfu61Vnvg2UM01YFpB8YN7cuqCKnvtdX9Mw3db6TR9uYk329c3QlK1GL13G/0VYWHb3sYNWeM1vCvrhS24+sr12immVj/FjNa/CHHcwIrXyqku+4l2m9uP8nwRZ66859z1XJt3v+381MTr5u7n+4pYoUECkKmZAAEMIYJAA6jdPEv6WuspVpbJG5ldxV1qO7HKcSlIFr6kB2o0SBFi1u/OKn47sbufNhW8pPMZU9FYtXL19PSh/iA2bkIhNResLREG8kT9gf9Q8r65SJDXYaYX4FQv+jer9dKxnfBkQznCFg2O47OUDvUfH+CU3sn/bM4XjutEG9UKXOSuxvuf2ej+ajCaKOxq3VUV41O8i9xOaOfcFmmiCB3xwUtfHf4cnX2zCx/JgukDNqdssyMpeLilPLjOupoh5vqkaPED0sY2l5yqrW7/dCFlUje+ZFff/PmDvfppwZxJ+5Zti8x5TOmux+OnhfWFsuz1ts4u4KhCWq9Bk3vHRgeOfcSlj0pR5SWnKx3uLarc+JpzgkLA3h+EdmS3YgNEHBXgFEbUK9dHzVHe1TgqKCpW6PuLdAqTvY+v3ylA7mhfqkEIlUC4d1cI6wfiAqDVLNXvxCY13aNbhzjiAkmekjV/hVfIJnck8TL0pk5RLxbHUz4RV6PTGbNxthftp3diFRYPy4Og3k90Ot/+pMWV5VoPyaKcXWkcFl9f/wvJMmS7bVIAAA==","abi":[{"name":"add","type":"function","inputs":[{"name":"a","type":"felt"},{"name":"b","type":"felt"}],"outputs":[{"name":"c","type":"felt"}],"stateMutability":"view"}]},"class_hash":"0x361af2ece54938621be8ecedf52d9343ab30c48ccc31c6e0e665b88f9b5b0a5","version":18} \ No newline at end of file diff --git a/api/scripts/deploy.js b/api/scripts/deploy.js deleted file mode 100644 index d38de0fb..00000000 --- a/api/scripts/deploy.js +++ /dev/null @@ -1,57 +0,0 @@ - - // Right click on the script name and hit "Run" to execute - (async () => { - try { - console.log('deploy to starknet...') - const compiledCairoContract = await remix.call('fileManager', 'readFile', 'compiled_cairo_artifacts/contract.json'); - const compiledContract = starknet.json.parse(compiledCairoContract); - const NetworkBaseUrls = { - 'goerli-alpha': 'https://alpha4.starknet.io', - 'mainnet-alpha': 'https://alpha-mainnet.starknet.io' - } - - const payload = { - compiledContract: compiledContract, - transactionInputs: [], // if you have constructor args please add your args - network: 'goerli-alpha' // mainnet-alpha or goerli-alpha or devnet - }; - - const baseUrl = payload['network'] ? NetworkBaseUrls[payload['network']] : payload['baseUrl']; - - const response = await fetch(baseUrl + '/gateway/add_transaction', { - method: 'POST', - headers: { - accept: 'application/json', - }, - body: JSON.stringify({ - type: 'DEPLOY', - contract_address_salt: '0x01319c1c1f0400688eafde419346d0b9876cd3d6a4daaa9f4768a3f5a810c543', - contract_definition: payload.compiledContract.contract_definition, - constructor_calldata: payload.transactionInputs - }) - }); - - const responseData = await response.json(); - - // const methodResponse = await callContract({ - // contract_address: responseData.address, - // entry_point_selector: getSelectorFromName("YOUR_FUNCTION_NAME"), - // calldata: ["1"], - // }); - - // const result = methodResponse.result[0]; - // result contains the return value of the method you gave to callContract - if(response.status === 200) { - console.log('Deployed contract address: ', responseData.address) - console.log('Deployed contract transaction hash: ', responseData.transaction_hash) - console.log('Deployment successful.') - } else { - console.log('Deployed contract error: ', responseData) - console.log('Deployment failed.') - } - - } catch (exception) { - console.log(exception.message) - } - })() - \ No newline at end of file diff --git a/api/src/errors.rs b/api/src/errors.rs index a7d1db9f..878a1d20 100644 --- a/api/src/errors.rs +++ b/api/src/errors.rs @@ -1,37 +1,115 @@ -use std::io::Error as IoError; +use std::io; #[derive(Debug, thiserror::Error)] pub enum ApiError { + #[error(transparent)] + System(SystemError), + #[error(transparent)] + Cmd(CmdError), + #[error(transparent)] + File(FileError), + #[error(transparent)] + Execution(ExecutionError), + #[error(transparent)] + Network(NetworkError), +} + +#[derive(Debug, thiserror::Error)] +pub enum SystemError { + #[error("Task queue is full")] + QueueIsFull, + #[error("Rate limiter is not in the Rocket state")] + RateLimiterNotInState, + #[error("Error while trying to unlock mutex")] + MutexUnlockError, + #[error("Failed to parse file path")] + FailedToParseFilePath(String), +} + +impl From for ApiError { + fn from(error: SystemError) -> Self { + ApiError::System(error) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CmdError { #[error("Failed to execute command: {0}")] - FailedToExecuteCommand(IoError), + FailedToExecuteCommand(io::Error), #[error("Failed to read output: {0}")] - FailedToReadOutput(IoError), - #[error("UTF8 error: {0}")] - UTF8Error(#[from] std::string::FromUtf8Error), + FailedToReadOutput(io::Error), +} + +impl From for ApiError { + fn from(error: CmdError) -> Self { + ApiError::Cmd(error) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum FileError { #[error("Failed to read dir: {0}")] - FailedToReadDir(IoError), + FailedToReadDir(io::Error), #[error("Failed to read file: {0}")] - FailedToReadFile(IoError), - #[error("Failed to parse string")] - FailedToParseString, - #[error("File extension <{0}> not supported")] - FileExtensionNotSupported(String), - #[error("Cairo version {0} not found")] - CairoVersionNotFound(String), + FailedToReadFile(io::Error), #[error("Failed to save file: {0}")] - FailedToSaveFile(IoError), + FailedToSaveFile(io::Error), #[error("Failed to read filename")] FailedToReadFilename, - #[error("Task queue is full")] - QueueIsFull, - #[error("Rate limiter is not in the Rocket state")] - RateLimiterNotInState, + #[error("Failed to write file: {0}")] + FailedToWriteFile(io::Error), + #[error("Failed to initialize directories: {0}")] + FailedToInitializeDirectories(io::Error), + #[error("UTF8 error: {0}")] + UTF8Error(#[from] std::string::FromUtf8Error), +} + +impl From for ApiError { + fn from(error: FileError) -> Self { + ApiError::File(error) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ExecutionError { + #[error("Failed to get Cairo version: {0}")] + CairoVersionNotFound(String), + #[error("Failed to get Scarb version: {0}")] + ScarbVersionNotFound(String), + #[error("Compilation timed out")] + CompilationTimeout, + #[error("File extension <{0}> not supported")] + FileExtensionNotSupported(String), + #[error("Invalid request")] + InvalidRequest, + #[error("Version not allowed")] + VersionNotAllowed, + #[error("Not found")] + NotFound(String), +} + +impl From for ApiError { + fn from(error: ExecutionError) -> Self { + ApiError::Execution(error) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum NetworkError { + #[error("Failed to fetch releases")] + FailedToFetchReleases(reqwest::Error), + #[error("Failed to parse releases")] + FailedToParseReleases(reqwest::Error), #[error("Failed to fetch client IP from the request")] FailedToGetClientIp, #[error("Too many requests")] TooManyRequests, - #[error("Error while trying to unlock mutex")] - MutexUnlockError, +} + +impl From for ApiError { + fn from(error: NetworkError) -> Self { + ApiError::Network(error) + } } pub type Result = std::result::Result; diff --git a/api/src/handlers/allowed_versions.rs b/api/src/handlers/allowed_versions.rs new file mode 100644 index 00000000..18db985d --- /dev/null +++ b/api/src/handlers/allowed_versions.rs @@ -0,0 +1,125 @@ +use crate::errors::{NetworkError, Result}; +use lazy_static::lazy_static; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::RwLock; +use std::time::SystemTime; +use tracing::instrument; + +use super::types::ApiResponse; + +lazy_static! { + static ref CACHED_VERSIONS: RwLock = RwLock::new(CachedVersions { + versions: Vec::new(), + last_updated: SystemTime::now(), + }); +} + +#[instrument] +#[get("/allowed-versions")] +pub async fn get_allowed_versions() -> ApiResponse> { + do_get_allowed_versions().await +} + +#[derive(Debug, Deserialize)] +struct GitHubRelease { + tag_name: String, +} + +#[derive(Debug, Serialize, Clone)] +struct CachedVersions { + versions: Vec, + last_updated: SystemTime, +} + +async fn fetch_github_releases() -> Result> { + let client = reqwest::Client::new(); + let releases: Vec = client + .get("https://api.github.com/repos/starkware-libs/cairo/releases") + .header("User-Agent", "starknet-remix-plugin") + .send() + .await + .map_err(NetworkError::FailedToFetchReleases)? + .json() + .await + .map_err(NetworkError::FailedToParseReleases)?; + + let mut version_map: HashMap<(u64, u64), Version> = HashMap::new(); + + for release in releases { + if let Ok(version) = Version::parse(&release.tag_name.replace("v", "")) { + if version.pre.is_empty() { + // Skip pre-releases (alpha, beta, rc) + let key = (version.major, version.minor); + + match version_map.get(&key) { + Some(existing_version) => { + if version > *existing_version { + version_map.insert(key, version); + } + } + None => { + version_map.insert(key, version); + } + } + } + } + } + + let mut versions: Vec = version_map.into_values().map(|v| v.to_string()).collect(); + + versions.sort_by(|a, b| { + let a_ver = Version::parse(a).unwrap(); + let b_ver = Version::parse(b).unwrap(); + b_ver.cmp(&a_ver) + }); + + versions.truncate(3); + + Ok(versions) +} + +pub async fn is_version_allowed(version: &str) -> bool { + let allowed_versions = do_get_allowed_versions().await; + allowed_versions + .data + .unwrap_or_default() + .contains(&version.to_string()) +} + +pub async fn do_get_allowed_versions() -> ApiResponse> { + let should_fetch = { + let cache = CACHED_VERSIONS.read().unwrap(); + cache.versions.is_empty() + }; + + if should_fetch { + if let Ok(versions) = fetch_github_releases().await { + let mut cache = CACHED_VERSIONS.write().unwrap(); + cache.versions = versions; + cache.last_updated = SystemTime::now(); + return ApiResponse::ok(cache.versions.clone()); + } + return ApiResponse::ok(vec![]); + } + + let cache = CACHED_VERSIONS.read().unwrap(); + ApiResponse::ok(cache.versions.clone()) +} + +pub async fn start_version_updater() { + tokio::spawn(async move { + loop { + if let Ok(versions) = fetch_github_releases().await { + let mut cache = CACHED_VERSIONS.write().unwrap(); + cache.versions = versions; + cache.last_updated = SystemTime::now(); + } + + tracing::info!("Updated allowed versions"); + + tokio::time::sleep(tokio::time::Duration::from_secs(24 * 60 * 60)).await; + } + }); +} diff --git a/api/src/handlers/cairo_version.rs b/api/src/handlers/cairo_version.rs deleted file mode 100644 index 152a377e..00000000 --- a/api/src/handlers/cairo_version.rs +++ /dev/null @@ -1,97 +0,0 @@ -use rocket::tokio::fs::read_dir; -use rocket::State; -use std::path::Path; -use std::process::{Command, Stdio}; -use tracing::{error, info, instrument}; - -use crate::errors::{ApiError, Result}; -use crate::handlers::process::{do_process_command, fetch_process_result}; -use crate::handlers::types::{ApiCommand, ApiCommandResult}; -use crate::rate_limiter::RateLimited; -use crate::utils::lib::{CAIRO_COMPILERS_DIR, DEFAULT_CAIRO_DIR}; -use crate::worker::WorkerEngine; - -// Read the version from the cairo Cargo.toml file. -#[instrument(skip(_rate_limited))] -#[get("/cairo_version")] -pub async fn cairo_version(_rate_limited: RateLimited) -> String { - info!("/cairo_version"); - do_cairo_version().unwrap_or_else(|e| format!("Failed to get cairo version: {:?}", e)) -} - -// Read the version from the cairo Cargo.toml file. -#[instrument(skip(engine, _rate_limited))] -#[get("/cairo_version_async")] -pub async fn cairo_version_async( - engine: &State, - _rate_limited: RateLimited, -) -> String { - info!("/cairo_version_async"); - do_process_command(ApiCommand::CairoVersion, engine) -} - -#[instrument(skip(engine))] -#[get("/cairo_version_result/")] -pub async fn get_cairo_version_result(process_id: String, engine: &State) -> String { - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::CairoVersion(version) => version.to_string(), - _ => String::from("Result not available"), - }) -} - -/// Run Cairo --version to return Cairo version string -/// -/// ## Note -/// (default Cairo version will be used) -pub fn do_cairo_version() -> Result { - let mut version_caller = Command::new("cargo"); - version_caller.current_dir(DEFAULT_CAIRO_DIR); - match String::from_utf8( - version_caller - .arg("run") - .arg("-q") - .arg("--release") - .arg("--bin") - .arg("cairo-compile") - .arg("--") - .arg("--version") - .stdout(Stdio::piped()) - .spawn() - .map_err(ApiError::FailedToExecuteCommand)? - .wait_with_output() - .map_err(ApiError::FailedToReadOutput)? - .stdout, - ) { - Ok(version) => Ok(version), - Err(e) => { - error!("{:?}", e.to_string()); - Err(ApiError::UTF8Error(e)) - } - } -} - -#[instrument] -#[get("/cairo_versions")] -pub async fn cairo_versions() -> String { - do_cairo_versions() - .await - .unwrap_or_else(|e| format!("Failed to get cairo versions: {:?}", e)) -} - -/// Get cairo versions -pub async fn do_cairo_versions() -> crate::errors::Result { - let path = Path::new(CAIRO_COMPILERS_DIR); - - let mut dir = read_dir(path).await.map_err(ApiError::FailedToReadDir)?; - let mut result = vec![]; - - while let Ok(Some(entry)) = dir.next_entry().await { - let entry = entry; - let path = entry.path(); - if path.is_dir() { - result.push(entry.file_name().to_string_lossy().to_string()); - } - } - - Ok(format!("{:?}", result)) -} diff --git a/api/src/handlers/compile.rs b/api/src/handlers/compile.rs new file mode 100644 index 00000000..8460185e --- /dev/null +++ b/api/src/handlers/compile.rs @@ -0,0 +1,210 @@ +use crate::errors::{CmdError, ExecutionError, FileError, Result}; +use crate::handlers::allowed_versions::is_version_allowed; +use crate::handlers::process::{do_process_command, fetch_process_result}; +use crate::handlers::types::{ApiCommand, CompileResponseGetter, IntoTypedResponse}; +use crate::handlers::types::{CompileResponse, FileContentMap}; +use crate::handlers::utils::{get_files_recursive, init_directories, AutoCleanUp}; +use crate::metrics::Metrics; +use crate::rate_limiter::RateLimited; +use crate::worker::WorkerEngine; +use rocket::serde::json::Json; +use rocket::{tokio, State}; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use tracing::{error, instrument}; + +use super::types::{ApiResponse, CompilationRequest}; + +pub fn scarb_toml_with_version(version: &str) -> String { + format!( + r#"[package] +name = "___testsingle" +version = "0.1.0" + +[dependencies] +starknet = "{}" + +[[target.starknet-contract]] +sierra = true +casm = true +"#, + version + ) +} + +pub fn default_scarb_toml() -> String { + scarb_toml_with_version("2.6.4") +} + +#[instrument(skip(request_json, _rate_limited, engine))] +#[post("/compile-async", data = "")] +pub async fn compile_async( + request_json: Json, + _rate_limited: RateLimited, + engine: &State, +) -> ApiResponse { + tracing::info!("/compile/{:?}", request_json.0.file_names()); + do_process_command( + ApiCommand::Compile { + compilation_request: request_json.0, + }, + engine, + ) +} + +#[instrument(skip(engine))] +#[get("/compile-async/")] +pub async fn get_compile_result(process_id: &str, engine: &State) -> CompileResponse { + tracing::info!("/compile-result/{:?}", process_id); + fetch_process_result::(process_id, engine) + .map(|result| result.0) + .unwrap_or_else(|err| err.into_typed()) +} + +async fn ensure_scarb_toml( + mut compilation_request: CompilationRequest, +) -> Result { + // Check if Scarb.toml exists in the root + if !compilation_request.has_scarb_toml() { + // number of files cairo files in the request + let cairo_files_count = compilation_request + .files + .iter() + .filter(|f| f.file_name.ends_with(".cairo")) + .count(); + + if cairo_files_count != 1 { + error!( + "Invalid request: Expected exactly one Cairo file, found {}", + cairo_files_count + ); + return Err(ExecutionError::InvalidRequest.into()); + } + + tracing::debug!("No Scarb.toml found, creating default one"); + compilation_request.files.push(FileContentMap { + file_name: "Scarb.toml".to_string(), + file_content: match compilation_request.version { + Some(ref version) => scarb_toml_with_version(version), + None => default_scarb_toml(), + }, + }); + + // change the name of the file to the first cairo file to src/lib.cairo + if let Some(first_cairo_file) = compilation_request + .files + .iter_mut() + .find(|f| f.file_name.ends_with(".cairo")) + { + first_cairo_file.file_name = "src/lib.cairo".to_string(); + } + } + + Ok(compilation_request) +} + +fn is_single_file_compilation(compilation_request: &CompilationRequest) -> bool { + compilation_request.files.len() == 1 + && compilation_request.files[0].file_name.ends_with(".cairo") +} + +/// Run Scarb to compile a project +/// +/// # Errors +/// Returns ApiError if: +/// - Failed to create/write temporary directories +/// - Failed to execute scarb command +/// - Failed to read command output +/// - Command times out +/// - Version is not allowed +/// - Invalid compilation request format +pub async fn do_compile( + compilation_request: CompilationRequest, + _metrics: &Metrics, +) -> Result { + // Verify version is in the allowed versions + let version = compilation_request.version.as_deref().unwrap_or(""); + if !is_version_allowed(version).await && is_single_file_compilation(&compilation_request) { + error!("Version not allowed: {}", version); + return Err(ExecutionError::VersionNotAllowed.into()); + } + + // Ensure Scarb.toml exists + let compilation_request = ensure_scarb_toml(compilation_request).await?; + + // Create temporary directories + let temp_dir = init_directories(compilation_request.clone()) + .await + .map_err(|e| { + error!("Failed to initialize directories: {:?}", e); + e + })?; + + let auto_clean_up = AutoCleanUp { + dirs: vec![&temp_dir], + }; + + let mut compile = Command::new("scarb"); + compile + .current_dir(&temp_dir) + .arg("build") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + tracing::debug!("Executing scarb command: {:?}", compile); + + let result = tokio::time::timeout(std::time::Duration::from_secs(300), async { + let child = compile.spawn().map_err(|e| { + error!("Failed to execute scarb command: {:?}", e); + CmdError::FailedToExecuteCommand(e) + })?; + + child.wait_with_output().map_err(|e| { + error!("Failed to read scarb command output: {:?}", e); + CmdError::FailedToReadOutput(e) + }) + }) + .await + .map_err(|_| { + error!("Compilation timed out after 300 seconds"); + ExecutionError::CompilationTimeout + })??; + + let file_content_map_array = get_files_recursive(&PathBuf::from(&temp_dir).join("target/dev")) + .map_err(|e| { + error!("Failed to read compilation output files: {:?}", e); + e + })?; + + let output = result; + let message = { + let stdout = String::from_utf8(output.stdout).map_err(|e| { + error!("Failed to parse stdout as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + let stderr = String::from_utf8(output.stderr).map_err(|e| { + error!("Failed to parse stderr as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + format!("{}{}", stdout, stderr).replace(&temp_dir, "") + }; + + let (status, code) = match output.status.code() { + Some(0) => ("Success", 200), + Some(code) => { + error!("Compilation failed with exit code: {}", code); + ("CompilationFailed", 400) + } + None => { + error!("Compilation process terminated by signal"); + ("UnknownError", 500) + } + }; + + auto_clean_up.clean_up().await; + + Ok(ApiResponse::ok(file_content_map_array) + .with_status(status.to_string()) + .with_code(code) + .with_message(message)) +} diff --git a/api/src/handlers/compile_casm.rs b/api/src/handlers/compile_casm.rs deleted file mode 100644 index 7b112a84..00000000 --- a/api/src/handlers/compile_casm.rs +++ /dev/null @@ -1,187 +0,0 @@ -use rocket::fs::NamedFile; -use rocket::serde::json; -use rocket::serde::json::Json; -use rocket::tokio::fs; -use rocket::State; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use tracing::{debug, info, instrument}; - -use crate::errors::{ApiError, Result}; -use crate::handlers::process::{do_process_command, fetch_process_result}; -use crate::handlers::types::{ApiCommand, ApiCommandResult, CompileResponse}; -use crate::handlers::utils::do_metered_action; -use crate::metrics::COMPILATION_LABEL_VALUE; -use crate::rate_limiter::RateLimited; -use crate::utils::lib::{get_file_ext, get_file_path, CAIRO_COMPILERS_DIR, CASM_ROOT}; -use crate::worker::WorkerEngine; - -#[instrument(skip(engine, _rate_limited))] -#[get("/compile-to-casm//")] -pub async fn compile_to_casm( - version: String, - remix_file_path: PathBuf, - engine: &State, - _rate_limited: RateLimited, -) -> Json { - info!("/compile-to-casm/{:?}", remix_file_path); - do_metered_action( - do_compile_to_casm(version.clone(), remix_file_path), - COMPILATION_LABEL_VALUE, - &engine.metrics, - ) - .await - .unwrap_or_else(|e| { - Json(CompileResponse { - file_content: "".to_string(), - message: format!("Failed to compile to casm: {:?}", e), - status: "CompilationFailed".to_string(), - cairo_version: version, - }) - }) -} - -#[instrument(skip(engine, _rate_limited))] -#[get("/compile-to-casm-async//")] -pub async fn compile_to_casm_async( - version: String, - remix_file_path: PathBuf, - engine: &State, - _rate_limited: RateLimited, -) -> String { - info!("/compile-to-casm-async/{:?}", remix_file_path); - do_process_command( - ApiCommand::CasmCompile { - remix_file_path, - version, - }, - engine, - ) -} - -#[instrument(skip(engine))] -#[get("/compile-to-casm-result/")] -pub async fn compile_to_casm_result(process_id: String, engine: &State) -> String { - info!("/compile-to-casm-result/{:?}", process_id); - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::CasmCompile(casm_result) => { - json::to_string(&casm_result).unwrap_or("Failed to fetch result".to_string()) - } - _ => String::from("Result not available"), - }) -} - -/// Compile source file to CASM -/// -pub async fn do_compile_to_casm( - cairo_version: String, - remix_file_path: PathBuf, -) -> Result> { - let remix_file_path = remix_file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(); - - // check if the file has .sierra extension - match get_file_ext(&remix_file_path) { - ext if ext == "sierra" => { - debug!("LOG: File extension is sierra"); - } - ext => { - debug!("LOG: File extension not supported"); - return Err(ApiError::FileExtensionNotSupported(ext)); - } - } - - let file_path = get_file_path(&remix_file_path); - - let casm_remix_path = remix_file_path.replace(&get_file_ext(&remix_file_path), "casm"); - - let mut compile = Command::new("cargo"); - - let path_to_cairo_compiler = Path::new(CAIRO_COMPILERS_DIR).join(&cairo_version); - - if path_to_cairo_compiler.exists() { - compile.current_dir(path_to_cairo_compiler); - } else { - return Err(ApiError::CairoVersionNotFound(cairo_version)); - } - - let casm_path = Path::new(CASM_ROOT).join(&casm_remix_path); - - // create directory for casm file - match casm_path.parent() { - Some(parent) => match fs::create_dir_all(parent).await { - Ok(_) => { - debug!("LOG: Created directory: {:?}", parent); - } - Err(e) => { - debug!("LOG: Error creating directory: {:?}", e); - } - }, - None => { - debug!("LOG: Error creating directory"); - } - } - - let result = compile - .arg("run") - .arg("--release") - .arg("--bin") - .arg("starknet-sierra-compile") - .arg("--") - .arg(&file_path) - .arg(&casm_path) - .stderr(Stdio::piped()) - .spawn() - .map_err(ApiError::FailedToExecuteCommand)?; - - debug!("LOG: ran command:{:?}", compile); - - let output = result - .wait_with_output() - .map_err(ApiError::FailedToReadOutput)?; - - let file_content = fs::read_to_string( - NamedFile::open(&casm_path) - .await - .map_err(ApiError::FailedToReadFile)? - .path() - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - ) - .await - .map_err(ApiError::FailedToReadFile)?; - - let message = String::from_utf8(output.stderr) - .map_err(ApiError::UTF8Error)? - .replace( - &file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &remix_file_path, - ) - .replace( - &casm_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &casm_remix_path, - ); - - let status = match output.status.code() { - Some(0) => "Success", - Some(_) => "SierraCompilationFailed", - None => "UnknownError", - } - .to_string(); - - Ok(Json(CompileResponse { - file_content, - message, - status, - cairo_version, - })) -} diff --git a/api/src/handlers/compile_sierra.rs b/api/src/handlers/compile_sierra.rs deleted file mode 100644 index f7bc8239..00000000 --- a/api/src/handlers/compile_sierra.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::errors::{ApiError, Result}; -use crate::handlers::process::{do_process_command, fetch_process_result}; -use crate::handlers::types::{ApiCommand, ApiCommandResult, CompileResponse}; -use crate::rate_limiter::RateLimited; -use crate::utils::lib::{get_file_ext, get_file_path, CAIRO_COMPILERS_DIR, SIERRA_ROOT}; -use crate::worker::WorkerEngine; -use rocket::fs::NamedFile; -use rocket::serde::json; -use rocket::serde::json::Json; -use rocket::tokio::fs; -use rocket::State; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use tracing::{debug, info, instrument}; - -#[instrument(skip(_rate_limited))] -#[get("/compile-to-sierra//")] -pub async fn compile_to_sierra( - version: String, - remix_file_path: PathBuf, - _rate_limited: RateLimited, -) -> Json { - info!("/compile-to-sierra"); - - let res = do_compile_to_sierra(version.clone(), remix_file_path).await; - - match res { - Ok(res) => res, - Err(e) => Json(CompileResponse { - file_content: "".to_string(), - message: format!("Failed to compile to sierra: {:?}", e), - status: "CompilationFailed".to_string(), - cairo_version: version, - }), - } -} - -#[instrument(skip(engine, _rate_limited))] -#[get("/compile-to-sierra-async//")] -pub async fn compile_to_siera_async( - version: String, - remix_file_path: PathBuf, - engine: &State, - _rate_limited: RateLimited, -) -> String { - info!("/compile-to-sierra-async"); - do_process_command( - ApiCommand::SierraCompile { - version, - remix_file_path, - }, - engine, - ) -} - -#[instrument(skip(engine))] -#[get("/compile-to-sierra-result/")] -pub async fn get_siera_compile_result(process_id: String, engine: &State) -> String { - info!("/compile-to-sierra-result"); - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::SierraCompile(sierra_result) => { - json::to_string(&sierra_result).unwrap_or("Failed to fetch result".to_string()) - } - _ => String::from("Result not available"), - }) -} - -/// Compile a given file to Sierra bytecode -/// -pub async fn do_compile_to_sierra( - cairo_version: String, - remix_file_path: PathBuf, -) -> Result> { - let remix_file_path = remix_file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(); - - // check if the file has .cairo extension - match get_file_ext(&remix_file_path) { - ext if ext == "cairo" => { - debug!("LOG: File extension is cairo"); - } - ext => { - debug!("LOG: File extension not supported"); - return Err(ApiError::CairoVersionNotFound(ext)); - } - } - - let file_path = get_file_path(&remix_file_path); - - let sierra_remix_path = remix_file_path.replace(&get_file_ext(&remix_file_path), "sierra"); - - let mut compile = Command::new("cargo"); - - let path_to_cairo_compiler = Path::new(CAIRO_COMPILERS_DIR).join(&cairo_version); - if path_to_cairo_compiler.exists() { - compile.current_dir(path_to_cairo_compiler); - } else { - return Err(ApiError::CairoVersionNotFound(cairo_version)); - } - - // replace .cairo with - let sierra_path = Path::new(SIERRA_ROOT).join(&sierra_remix_path); - - // create directory for sierra file - match sierra_path.parent() { - Some(parent) => match fs::create_dir_all(parent).await { - Ok(_) => { - debug!("LOG: Created directory: {:?}", parent); - } - Err(e) => { - debug!("LOG: Error creating directory: {:?}", e); - } - }, - None => { - debug!("LOG: Error creating directory"); - } - } - - let result = compile - .arg("run") - .arg("--release") - .arg("--bin") - .arg("starknet-compile") - .arg("--") - .arg(&file_path) - .arg(&sierra_path) - .arg("--single-file") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .map_err(ApiError::FailedToExecuteCommand)?; - - debug!("LOG: ran command:{:?}", compile); - - let output = result - .wait_with_output() - .map_err(ApiError::FailedToReadOutput)?; - - let remix_file_path_without_hash = remix_file_path - .split('/') - .collect::>() - .split_first() - .ok_or(ApiError::FailedToParseString)? - .1 - .join("/"); - - let sierra_remix_path_without_hash = sierra_remix_path - .split('/') - .collect::>() - .split_first() - .ok_or(ApiError::FailedToParseString)? - .1 - .join("/"); - - let message = String::from_utf8(output.stderr) - .map_err(ApiError::UTF8Error)? - .replace( - &file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &remix_file_path_without_hash, - ) - .replace( - &sierra_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &sierra_remix_path_without_hash, - ); - - let status = match output.status.code() { - Some(0) => "Success", - Some(_) => "CompilationFailed", - None => "UnknownError", - } - .to_string(); - - // Prepare response based on the compilation result - match output.status.code() { - Some(0) => { - let file_content = fs::read_to_string( - NamedFile::open(&sierra_path) - .await - .map_err(ApiError::FailedToReadFile)? - .path() - .to_str() - .ok_or(ApiError::FailedToParseString)?, - ) - .await - .map_err(ApiError::FailedToReadFile)?; - - Ok(Json(CompileResponse { - file_content, - message, - status, - cairo_version, - })) - } - _ => Ok(Json(CompileResponse { - file_content: String::from(""), - message, - status, - cairo_version, - })), - } -} diff --git a/api/src/handlers/mod.rs b/api/src/handlers/mod.rs index aeb506c1..d41b9ce9 100644 --- a/api/src/handlers/mod.rs +++ b/api/src/handlers/mod.rs @@ -1,28 +1,14 @@ -pub mod cairo_version; -pub mod compile_casm; -pub mod compile_sierra; +pub mod allowed_versions; +pub mod compile; pub mod process; -pub mod save_code; -pub mod scarb_compile; pub mod scarb_test; +pub mod scarb_version; pub mod types; pub mod utils; -use rocket::serde::json::Json; -use std::path::Path; use tracing::info; use tracing::instrument; -use crate::errors::{ApiError, Result}; -use crate::handlers::cairo_version::do_cairo_version; -use crate::handlers::compile_casm::do_compile_to_casm; -use crate::handlers::compile_sierra::do_compile_to_sierra; -use crate::handlers::scarb_compile::do_scarb_compile; -use crate::handlers::scarb_test::do_scarb_test; -use crate::handlers::types::{ApiCommand, ApiCommandResult, FileContentMap}; -use crate::handlers::utils::do_metered_action; -use crate::metrics::{Metrics, COMPILATION_LABEL_VALUE}; - #[instrument] #[get("/health")] pub async fn health() -> &'static str { @@ -36,82 +22,3 @@ pub async fn who_is_this() -> &'static str { info!("/who_is_this"); "Who are you?" } - -pub async fn dispatch_command(command: ApiCommand, metrics: &Metrics) -> Result { - match command { - ApiCommand::CairoVersion => match do_cairo_version() { - Ok(result) => Ok(ApiCommandResult::CairoVersion(result)), - Err(e) => Err(e), - }, - ApiCommand::ScarbCompile { remix_file_path } => { - match do_metered_action( - do_scarb_compile(remix_file_path), - COMPILATION_LABEL_VALUE, - metrics, - ) - .await - { - Ok(result) => Ok(ApiCommandResult::ScarbCompile(result.into_inner())), - Err(e) => Err(e), - } - } - ApiCommand::SierraCompile { - remix_file_path, - version, - } => match do_compile_to_sierra(version, remix_file_path).await { - Ok(compile_response) => Ok(ApiCommandResult::SierraCompile( - compile_response.into_inner(), - )), - Err(e) => Err(e), - }, - ApiCommand::CasmCompile { - remix_file_path, - version, - } => match do_metered_action( - do_compile_to_casm(version, remix_file_path), - COMPILATION_LABEL_VALUE, - metrics, - ) - .await - { - Ok(Json(compile_response)) => Ok(ApiCommandResult::CasmCompile(compile_response)), - Err(e) => Err(e), - }, - ApiCommand::Shutdown => Ok(ApiCommandResult::Shutdown), - ApiCommand::ScarbTest { remix_file_path } => match do_scarb_test(remix_file_path).await { - Ok(result) => Ok(ApiCommandResult::ScarbTest(result.into_inner())), - Err(e) => Err(e), - }, - } -} - -fn get_files_recursive(base_path: &Path) -> Result> { - let mut file_content_map_array: Vec = Vec::new(); - - if base_path.is_dir() { - for entry in base_path - .read_dir() - .map_err(ApiError::FailedToReadDir)? - .flatten() - { - let path = entry.path(); - if path.is_dir() { - file_content_map_array.extend(get_files_recursive(&path)?); - } else if let Ok(content) = std::fs::read_to_string(&path) { - let file_name = path - .file_name() - .ok_or(ApiError::FailedToReadFilename)? - .to_string_lossy() - .to_string(); - let file_content = content; - let file_content_map = FileContentMap { - file_name, - file_content, - }; - file_content_map_array.push(file_content_map); - } - } - } - - Ok(file_content_map_array) -} diff --git a/api/src/handlers/process.rs b/api/src/handlers/process.rs index 60132de1..79fcd625 100644 --- a/api/src/handlers/process.rs +++ b/api/src/handlers/process.rs @@ -1,80 +1,93 @@ +use crate::errors::ApiError; use crate::handlers::types::{ApiCommand, ApiCommandResult}; use crate::worker::{ProcessState, WorkerEngine}; use rocket::State; -use tracing::{info, instrument}; +use tracing::{error, info, instrument}; use uuid::Uuid; +use super::types::ApiResponse; + #[instrument(skip(engine))] #[get("/process_status/")] -pub async fn get_process_status(process_id: String, engine: &State) -> String { +pub async fn get_process_status( + process_id: String, + engine: &State, +) -> ApiResponse { info!("/process_status/{:?}", process_id); // get status of process by ID match Uuid::parse_str(&process_id) { Ok(process_uuid) => { if engine.arc_process_states.contains_key(&process_uuid) { - format!( - "{:}", + ApiResponse::ok( engine .arc_process_states .get(&process_uuid) .unwrap() .value() + .to_string(), ) } else { - // TODO can we return HTTP status code here? - format!("Process with id={} not found", process_id) + ApiResponse::not_found(format!("Process with id={} not found", process_id)) } } - Err(e) => { - // TODO can we return HTTP status code here? - e.to_string() - } + Err(e) => ApiResponse::bad_request(e.to_string()), } } -pub fn do_process_command(command: ApiCommand, engine: &State) -> String { +pub fn do_process_command( + command: ApiCommand, + engine: &State, +) -> ApiResponse { // queue the new Scarb command match engine.enqueue_command(command) { - Ok(uuid) => { - // return the process ID - format!("{}", uuid) - } - Err(e) => { - // TODO can we return HTTP status code here? - e - } + Ok(uuid) => ApiResponse::ok(uuid.to_string()), + Err(e) => ApiResponse::internal_server_error(e.to_string()), } } -pub fn fetch_process_result( - process_id: String, +pub fn fetch_process_result( + process_id: &str, engine: &State, - do_work: F, -) -> String +) -> Result>> where - F: FnOnce(&ApiCommandResult) -> String, + T: TryFrom, { - // get status of process by ID - match Uuid::parse_str(&process_id) { - Ok(process_uuid) => { - if engine.arc_process_states.contains_key(&process_uuid) { - match engine - .arc_process_states - .get(&process_uuid) - .unwrap() - .value() - { - ProcessState::Completed(result) => do_work(result), - _ => String::from("Result not available"), - } - } else { - // TODO can we return HTTP status code here? - "Process id not found".to_string() - } + let process_uuid = Uuid::parse_str(process_id).map_err(|e| { + error!("Failed to parse process UUID: {}", e); + ApiResponse::<()>::bad_request(format!("Failed to parse process UUID: {}", e)) + })?; + + let process_state = engine + .arc_process_states + .get(&process_uuid) + .ok_or_else(|| { + error!("Process not found: {}", process_id); + ApiResponse::<()>::not_found(format!("Process id not found: {}", process_id)) + })?; + + match process_state.value() { + ProcessState::Completed(result) => { + let result = result.clone(); + T::try_from(result).map_err(|e| { + error!("Failed to convert result type: {:?}", e); + Box::new(ApiResponse::bad_request(format!( + "Failed to convert result type: {:?}", + e + ))) + }) + } + ProcessState::Error(e) => { + error!("Process error: {:?}", e); + Err(Box::new(ApiResponse::not_found(format!( + "Process error: {}", + e + )))) } - Err(e) => { - // TODO can we return HTTP status code here? - e.to_string() + _ => { + error!("Process result not available: {}", process_id); + Err(Box::new(ApiResponse::not_found( + "Result not available".to_string(), + ))) } } } diff --git a/api/src/handlers/save_code.rs b/api/src/handlers/save_code.rs deleted file mode 100644 index c7b3bca3..00000000 --- a/api/src/handlers/save_code.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::errors::{ApiError, Result}; -use crate::utils::lib::get_file_path; -use rocket::data::ToByteUnit; -use rocket::tokio::fs; -use rocket::Data; -use std::path::PathBuf; -use tracing::{info, instrument}; - -#[instrument(skip(file))] -#[post("/save_code/", data = "")] -pub async fn save_code(file: Data<'_>, remix_file_path: PathBuf) -> String { - info!("/save_code/{:?}", remix_file_path); - do_save_code(file, remix_file_path) - .await - .unwrap_or_else(|e| format!("Failed to save code: {:?}", e)) -} - -/// Upload a data file -/// -pub async fn do_save_code(file: Data<'_>, remix_file_path: PathBuf) -> Result { - let remix_file_path = remix_file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(); - - let file_path = get_file_path(&remix_file_path); - - // create file directory from file path - match file_path.parent() { - Some(parent) => match fs::create_dir_all(parent).await { - Ok(_) => { - debug!("LOG: Created directory: {:?}", parent); - } - Err(e) => { - debug!("LOG: Error creating directory: {:?}", e); - } - }, - None => { - debug!("LOG: Error creating directory"); - } - } - - // Modify to zip and unpack. - let _ = file - .open(128_i32.gibibytes()) - .into_file(&file_path) - .await - .map_err(ApiError::FailedToSaveFile)?; - - Ok(file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string()) -} diff --git a/api/src/handlers/scarb_compile.rs b/api/src/handlers/scarb_compile.rs deleted file mode 100644 index 2499c6f8..00000000 --- a/api/src/handlers/scarb_compile.rs +++ /dev/null @@ -1,122 +0,0 @@ -use rocket::serde::json; -use rocket::serde::json::Json; -use rocket::State; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use tracing::{debug, info, instrument}; - -use crate::errors::{ApiError, Result}; -use crate::handlers::get_files_recursive; -use crate::handlers::process::{do_process_command, fetch_process_result}; -use crate::handlers::types::{ApiCommand, ApiCommandResult, ScarbCompileResponse}; -use crate::handlers::utils::do_metered_action; -use crate::metrics::COMPILATION_LABEL_VALUE; -use crate::rate_limiter::RateLimited; -use crate::utils::lib::get_file_path; -use crate::worker::WorkerEngine; - -#[instrument(skip(engine, _rate_limited))] -#[get("/compile-scarb/")] -pub async fn scarb_compile( - remix_file_path: PathBuf, - engine: &State, - _rate_limited: RateLimited, -) -> Json { - info!("/compile-scarb/{:?}", remix_file_path); - do_metered_action( - do_scarb_compile(remix_file_path), - COMPILATION_LABEL_VALUE, - &engine.metrics, - ) - .await - .unwrap_or_else(|e| { - Json(ScarbCompileResponse { - file_content_map_array: vec![], - message: format!("Failed to compile to scarb: {:?}", e), - status: "CompilationFailed".to_string(), - }) - }) -} - -#[instrument(skip(engine, _rate_limited))] -#[get("/compile-scarb-async/")] -pub async fn scarb_compile_async( - remix_file_path: PathBuf, - engine: &State, - _rate_limited: RateLimited, -) -> String { - info!("/compile-scarb-async/{:?}", remix_file_path); - do_process_command(ApiCommand::ScarbCompile { remix_file_path }, engine) -} - -#[instrument(skip(engine))] -#[get("/compile-scarb-result/")] -pub async fn get_scarb_compile_result(process_id: String, engine: &State) -> String { - info!("/compile-scarb-result/{:?}", process_id); - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::ScarbCompile(scarb_result) => json::to_string(&scarb_result) - .unwrap_or_else(|e| format!("Failed to fetch result: {:?}", e)), - _ => String::from("Result not available"), - }) -} - -/// Run Scarb to compile a project -/// -pub async fn do_scarb_compile(remix_file_path: PathBuf) -> Result> { - let remix_file_path = remix_file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(); - - let file_path = get_file_path(&remix_file_path); - - let mut compile = Command::new("scarb"); - compile.current_dir(&file_path); - - let result = compile - .arg("build") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(ApiError::FailedToExecuteCommand)?; - - debug!("LOG: ran command:{:?}", compile); - - let output = result - .wait_with_output() - .map_err(ApiError::FailedToReadOutput)?; - - let file_content_map_array = get_files_recursive(&file_path.join("target/dev"))?; - - let message = String::from_utf8(output.stdout) - .map_err(ApiError::UTF8Error)? - .replace( - &file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &remix_file_path, - ) - + &String::from_utf8(output.stderr) - .map_err(ApiError::UTF8Error)? - .replace( - &file_path - .to_str() - .ok_or(ApiError::FailedToParseString)? - .to_string(), - &remix_file_path, - ); - - let status = match output.status.code() { - Some(0) => "Success", - Some(_) => "SierraCompilationFailed", - None => "UnknownError", - } - .to_string(); - - Ok(Json(ScarbCompileResponse { - file_content_map_array, - message, - status, - })) -} diff --git a/api/src/handlers/scarb_test.rs b/api/src/handlers/scarb_test.rs index e5aac5d4..56374e9d 100644 --- a/api/src/handlers/scarb_test.rs +++ b/api/src/handlers/scarb_test.rs @@ -1,45 +1,56 @@ -use crate::errors::ApiError; -use crate::errors::Result; +use crate::errors::{CmdError, FileError, Result, SystemError}; use crate::handlers::process::{do_process_command, fetch_process_result}; -use crate::handlers::types::{ApiCommand, ApiCommandResult, ScarbTestResponse}; +use crate::handlers::types::TestResponseGetter; +use crate::handlers::types::{ApiCommand, IntoTypedResponse, TestResponse}; use crate::rate_limiter::RateLimited; use crate::utils::lib::get_file_path; use crate::worker::WorkerEngine; -use rocket::serde::json; -use rocket::serde::json::Json; use rocket::State; use std::path::PathBuf; use std::process::{Command, Stdio}; -use tracing::{debug, info, instrument}; +use tracing::{debug, error, info, instrument}; + +use super::types::ApiResponse; #[instrument(skip(engine, _rate_limited))] -#[get("/scarb-test-async/")] +#[post("/scarb-test-async/")] pub async fn scarb_test_async( remix_file_path: PathBuf, engine: &State, _rate_limited: RateLimited, -) -> String { +) -> ApiResponse { info!("/scarb-test-async/{:?}", remix_file_path); do_process_command(ApiCommand::ScarbTest { remix_file_path }, engine) } #[instrument(skip(engine))] -#[get("/scarb-test-result/")] -pub async fn get_scarb_test_result(process_id: String, engine: &State) -> String { - info!("/scarb-test-result/{:?}", process_id); - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::ScarbTest(scarb_result) => json::to_string(&scarb_result) - .unwrap_or_else(|e| format!("Failed to fetch result: {:?}", e)), - _ => String::from("Result not available"), - }) +#[get("/scarb-test-async/")] +pub async fn get_scarb_test_result( + process_id: &str, + engine: &State, +) -> ApiResponse<()> { + info!("/scarb-test-async/{:?}", process_id); + fetch_process_result::(process_id, engine) + .map(|result| result.0) + .unwrap_or_else(|err| err.into_typed()) } /// Run Scarb to test a project /// -pub async fn do_scarb_test(remix_file_path: PathBuf) -> Result> { +/// # Errors +/// Returns ApiError if: +/// - Failed to parse file path +/// - Failed to execute scarb command +/// - Failed to read command output +/// - Failed to parse command output as UTF-8 +/// - Command returned non-zero status +pub async fn do_scarb_test(remix_file_path: PathBuf) -> Result { let remix_file_path = remix_file_path .to_str() - .ok_or(ApiError::FailedToParseString)? + .ok_or_else(|| { + error!("Failed to parse remix file path: {:?}", remix_file_path); + SystemError::FailedToParseFilePath(format!("{:?}", remix_file_path)) + })? .to_string(); let file_path = get_file_path(&remix_file_path); @@ -47,44 +58,65 @@ pub async fn do_scarb_test(remix_file_path: PathBuf) -> Result "Success", - Some(_) => "SierraCompilationFailed", - None => "UnknownError", - } - .to_string(); - - Ok(Json(ScarbTestResponse { message, status })) + .map_err(|e| { + error!("Failed to execute scarb test command: {:?}", e); + CmdError::FailedToExecuteCommand(e) + })?; + + debug!("Executed command: {:?}", compile); + + let output = result.wait_with_output().map_err(|e| { + error!("Failed to read scarb test output: {:?}", e); + CmdError::FailedToReadOutput(e) + })?; + + // Convert file path to string once to avoid repetition and potential inconsistencies + let file_path_str = file_path + .to_str() + .ok_or_else(|| { + error!("Failed to convert file path to string: {:?}", file_path); + SystemError::FailedToParseFilePath(format!("{:?}", file_path)) + })? + .to_string(); + + let stdout = String::from_utf8(output.stdout).map_err(|e| { + error!("Failed to parse stdout as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + + let stderr = String::from_utf8(output.stderr).map_err(|e| { + error!("Failed to parse stderr as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + + let message = format!( + "{}{}", + stdout.replace(&file_path_str, &remix_file_path), + stderr.replace(&file_path_str, &remix_file_path) + ); + + let (status, code) = match output.status.code() { + Some(0) => ("Success", 200), + Some(code) => { + error!("Test failed with exit code: {}", code); + ("SierraCompilationFailed", 400) + } + None => { + error!("Test terminated by signal"); + ("UnknownError", 500) + } + }; + + Ok(ApiResponse::ok(()) + .with_status(status.to_string()) + .with_code(code) + .with_message(message) + .with_timestamp(chrono::Utc::now().to_rfc3339())) } diff --git a/api/src/handlers/scarb_version.rs b/api/src/handlers/scarb_version.rs new file mode 100644 index 00000000..aec2bd6f --- /dev/null +++ b/api/src/handlers/scarb_version.rs @@ -0,0 +1,103 @@ +use rocket::State; +use std::process::{Command, Stdio}; +use tracing::{error, info, instrument}; + +use crate::errors::{ApiError, CmdError, ExecutionError, FileError, Result}; +use crate::handlers::process::{do_process_command, fetch_process_result}; +use crate::handlers::types::{ + ApiCommand, ApiCommandResult, IntoTypedResponse, VersionResponseGetter, +}; +use crate::rate_limiter::RateLimited; +use crate::worker::WorkerEngine; + +use super::types::ApiResponse; + +#[instrument(skip(engine, _rate_limited))] +#[post("/scarb-version-async")] +pub async fn scarb_version_async( + engine: &State, + _rate_limited: RateLimited, +) -> ApiResponse { + info!("/scarb_version_async"); + do_process_command(ApiCommand::ScarbVersion, engine) +} + +#[instrument(skip(engine))] +#[get("/scarb-version-async/")] +pub fn get_scarb_version_result( + process_id: &str, + engine: &State, +) -> ApiResponse { + info!("/scarb-version-async/{:?}", process_id); + fetch_process_result::(process_id, engine) + .map(|result| result.0) + .unwrap_or_else(|err| err.into_typed()) +} + +impl TryFrom for ApiResponse { + type Error = ApiError; + + fn try_from(value: ApiCommandResult) -> Result { + match value { + ApiCommandResult::ScarbVersion(response) => Ok(response), + _ => { + error!("Expected ScarbVersion result, got {:?}", value); + Err(ExecutionError::InvalidRequest.into()) + } + } + } +} + +/// Run Scarb --version to return Scarb version string +/// +/// # Errors +/// Returns ApiError if: +/// - Failed to execute scarb command +/// - Failed to read command output +/// - Command returned non-zero status +/// - Failed to parse command output as UTF-8 +pub fn do_scarb_version() -> Result> { + let version_caller = Command::new("scarb") + .arg("--version") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| { + error!("Failed to execute scarb version command: {:?}", e); + CmdError::FailedToExecuteCommand(e) + })?; + + let output = version_caller.wait_with_output().map_err(|e| { + error!("Failed to read scarb version output: {:?}", e); + CmdError::FailedToReadOutput(e) + })?; + + if output.status.success() { + let result = String::from_utf8(output.stdout).map_err(|e| { + error!("Failed to parse stdout as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + + let result_with_stderr = String::from_utf8(output.stderr).map_err(|e| { + error!("Failed to parse stderr as UTF-8: {:?}", e); + FileError::UTF8Error(e) + })?; + + Ok(ApiResponse::ok(result.clone()) + .with_message(format!("{}{}", result_with_stderr, result)) + .with_success(true) + .with_status("Success".to_string()) + .with_code(200)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + error!( + "Failed to get Scarb version: status={}, stderr={}", + output.status, stderr + ); + Err(ExecutionError::ScarbVersionNotFound(format!( + "Status: {}, Error: {}", + output.status, stderr + )) + .into()) + } +} diff --git a/api/src/handlers/types.rs b/api/src/handlers/types.rs index 20f19e58..6f9e00ed 100644 --- a/api/src/handlers/types.rs +++ b/api/src/handlers/types.rs @@ -1,63 +1,191 @@ +use rocket::http::{ContentType, Status}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -pub trait Successable { +use crate::errors::{ApiError, ExecutionError}; + +pub trait Successful { fn is_successful(&self) -> bool; } -#[derive(Debug, Deserialize, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct CompileResponse { +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ApiResponse { + pub success: bool, pub status: String, + pub code: u16, pub message: String, - pub file_content: String, - pub cairo_version: String, + pub data: Option, + pub error: Option, + pub timestamp: String, + pub request_id: String, } -impl Successable for CompileResponse { - fn is_successful(&self) -> bool { - self.status == "Success" +impl<'r, T: serde::Serialize> rocket::response::Responder<'r, 'static> for ApiResponse { + fn respond_to(self, _: &'r rocket::Request<'_>) -> rocket::response::Result<'static> { + let json = rocket::serde::json::to_string(&self).unwrap(); + + rocket::Response::build() + .sized_body(json.len(), std::io::Cursor::new(json)) + .header(ContentType::JSON) + .status(Status::from_code(self.code).unwrap_or(Status::InternalServerError)) + .ok() + } +} + +impl Default for ApiResponse { + fn default() -> Self { + Self { + success: false, + status: "".to_string(), + code: 0, + message: "".to_string(), + data: None, + error: None, + timestamp: chrono::Utc::now().to_rfc3339(), + request_id: "".to_string(), + } + } +} + +impl ApiResponse { + pub fn internal_server_error(error: String) -> Self { + Self { + status: "InternalServerError".to_string(), + code: 500, + error: Some(error), + ..Default::default() + } + } + + pub fn not_found(error: String) -> Self { + Self { + status: "NotFound".to_string(), + code: 404, + error: Some(error), + ..Default::default() + } + } + + pub fn bad_request(error: String) -> Self { + Self { + status: "BadRequest".to_string(), + code: 400, + error: Some(error), + ..Default::default() + } + } + + pub fn ok(data: T) -> Self { + Self { + success: true, + status: "Ok".to_string(), + code: 200, + data: Some(data), + ..Default::default() + } + } + + pub fn not_available(message: String) -> Self { + Self { + status: "NotAvailable".to_string(), + code: 404, + message, + ..Default::default() + } + } + + pub fn with_message(mut self, message: String) -> Self { + self.message = message; + self + } + + pub fn with_data(mut self, data: T) -> Self { + self.data = Some(data); + self + } + + pub fn with_error(mut self, error: String) -> Self { + self.error = Some(error); + self + } + + pub fn with_timestamp(mut self, timestamp: String) -> Self { + self.timestamp = timestamp; + self + } + + pub fn with_request_id(mut self, request_id: String) -> Self { + self.request_id = request_id; + self + } + + pub fn with_status(mut self, status: String) -> Self { + self.status = status; + self + } + + pub fn with_code(mut self, code: u16) -> Self { + self.code = code; + self + } + + pub fn with_success(mut self, success: bool) -> Self { + self.success = success; + self + } + + pub fn not_allowed(error: String) -> Self { + Self { + status: "NotAllowed".to_string(), + code: 403, + error: Some(error), + ..Default::default() + } } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct FileContentMap { pub file_name: String, pub file_content: String, } -#[derive(Debug, Serialize, Deserialize)] -pub struct ScarbCompileResponse { - pub status: String, - pub message: String, - pub file_content_map_array: Vec, -} +pub type CompileResponse = ApiResponse>; + +pub type TestResponse = ApiResponse<()>; + +pub type VersionResponse = ApiResponse; -impl Successable for ScarbCompileResponse { +impl Successful for ApiResponse { fn is_successful(&self) -> bool { - self.status == "Success" + self.success } } -#[derive(Debug, Serialize, Deserialize)] -pub struct ScarbTestResponse { - pub status: String, - pub message: String, +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +#[serde(crate = "rocket::serde")] +pub struct CompilationRequest { + pub files: Vec, + pub version: Option, +} + +impl CompilationRequest { + pub fn has_scarb_toml(&self) -> bool { + self.files + .iter() + .any(|f| f.file_name.ends_with("Scarb.toml")) + } + + pub fn file_names(&self) -> Vec { + self.files.iter().map(|f| f.file_name.clone()).collect() + } } #[derive(Debug)] pub enum ApiCommand { - CairoVersion, - SierraCompile { - remix_file_path: PathBuf, - version: String, - }, - CasmCompile { - remix_file_path: PathBuf, - version: String, - }, - ScarbCompile { - remix_file_path: PathBuf, + ScarbVersion, + Compile { + compilation_request: CompilationRequest, }, ScarbTest { remix_file_path: PathBuf, @@ -67,12 +195,89 @@ pub enum ApiCommand { } #[derive(Debug)] +pub struct ShutdownGetter; + +#[derive(Debug)] +pub struct VersionResponseGetter(pub VersionResponse); + +#[derive(Debug)] +pub struct CompileResponseGetter(pub CompileResponse); + +#[derive(Debug)] +pub struct TestResponseGetter(pub TestResponse); + +#[derive(Debug, Clone)] pub enum ApiCommandResult { - CairoVersion(String), - CasmCompile(CompileResponse), - SierraCompile(CompileResponse), - ScarbCompile(ScarbCompileResponse), - ScarbTest(ScarbTestResponse), + ScarbVersion(VersionResponse), + Compile(CompileResponse), + Test(TestResponse), #[allow(dead_code)] Shutdown, } + +impl TryFrom for VersionResponseGetter { + type Error = ApiError; + + fn try_from(value: ApiCommandResult) -> Result { + if let ApiCommandResult::ScarbVersion(response) = value { + Ok(VersionResponseGetter(response)) + } else { + Err(ExecutionError::InvalidRequest.into()) + } + } +} + +impl TryFrom for CompileResponseGetter { + type Error = ApiError; + + fn try_from(value: ApiCommandResult) -> Result { + if let ApiCommandResult::Compile(response) = value { + Ok(CompileResponseGetter(response)) + } else { + Err(ExecutionError::InvalidRequest.into()) + } + } +} + +impl TryFrom for TestResponseGetter { + type Error = ApiError; + + fn try_from(value: ApiCommandResult) -> Result { + if let ApiCommandResult::Test(response) = value { + Ok(TestResponseGetter(response)) + } else { + Err(ExecutionError::InvalidRequest.into()) + } + } +} + +impl TryFrom for ShutdownGetter { + type Error = ApiError; + + fn try_from(value: ApiCommandResult) -> Result { + if let ApiCommandResult::Shutdown = value { + Ok(ShutdownGetter) + } else { + Err(ExecutionError::InvalidRequest.into()) + } + } +} + +pub trait IntoTypedResponse { + fn into_typed(self) -> ApiResponse; +} + +impl IntoTypedResponse for ApiResponse<()> { + fn into_typed(self) -> ApiResponse { + ApiResponse { + data: None, + success: self.success, + status: self.status, + code: self.code, + message: self.message, + error: self.error, + timestamp: self.timestamp, + request_id: self.request_id, + } + } +} diff --git a/api/src/handlers/utils.rs b/api/src/handlers/utils.rs index 64b8021a..1d1ad4c0 100644 --- a/api/src/handlers/utils.rs +++ b/api/src/handlers/utils.rs @@ -1,11 +1,19 @@ -use rocket::serde::json::Json; -use std::future::Future; +use rocket::tokio; +use std::path::Path; use std::time::Instant; +use std::{future::Future, path::PathBuf}; use tracing::{info, instrument}; -use crate::errors::Result; -use crate::handlers::types::Successable; -use crate::metrics::Metrics; +use crate::errors::{ApiError, FileError, Result, SystemError}; +use crate::metrics::{Metrics, COMPILATION_LABEL_VALUE}; + +use super::scarb_version::do_scarb_version; +use super::types::{CompilationRequest, FileContentMap, Successful}; +use super::{ + compile::do_compile, + scarb_test::do_scarb_test, + types::{ApiCommand, ApiCommandResult}, +}; #[instrument] #[post("/on-plugin-launched")] @@ -13,11 +21,11 @@ pub async fn on_plugin_launched() { info!("/on-plugin-launched"); } -pub(crate) async fn do_metered_action( - action: impl Future>>, +pub(crate) async fn do_metered_action( + action: impl Future>, action_label_value: &str, metrics: &Metrics, -) -> Result> { +) -> Result { let start_time = Instant::now(); let result = action.await; let elapsed_time = start_time.elapsed().as_secs_f64(); @@ -54,3 +62,142 @@ pub(crate) async fn do_metered_action( } } } + +pub struct AutoCleanUp<'a> { + pub(crate) dirs: Vec<&'a str>, +} + +impl Drop for AutoCleanUp<'_> { + fn drop(&mut self) { + self.clean_up_sync(); + } +} + +impl AutoCleanUp<'_> { + pub async fn clean_up(&self) { + for path in self.dirs.iter() { + debug!("Removing path: {:?}", path); + + // check if the path exists + if !Path::new(path).exists() { + continue; + } + + if let Err(e) = tokio::fs::remove_dir_all(path).await { + tracing::info!("Failed to remove file: {:?}", e); + } + } + } + + pub fn clean_up_sync(&self) { + for path in self.dirs.iter() { + debug!("Removing path: {:?}", path); + + // check if the path exists + if !Path::new(path).exists() { + continue; + } + + if let Err(e) = std::fs::remove_dir_all(path) { + tracing::info!("Failed to remove file: {:?}", e); + } + } + } +} + +pub async fn dispatch_command(command: ApiCommand, metrics: &Metrics) -> Result { + match command { + ApiCommand::ScarbVersion => match do_scarb_version() { + Ok(result) => Ok(ApiCommandResult::ScarbVersion(result)), + Err(e) => Err(e), + }, + ApiCommand::Shutdown => Ok(ApiCommandResult::Shutdown), + ApiCommand::ScarbTest { remix_file_path } => match do_scarb_test(remix_file_path).await { + Ok(result) => Ok(ApiCommandResult::Test(result)), + Err(e) => Err(e), + }, + ApiCommand::Compile { + compilation_request, + } => match do_metered_action( + do_compile(compilation_request, metrics), + COMPILATION_LABEL_VALUE, + metrics, + ) + .await + { + Ok(result) => Ok(ApiCommandResult::Compile(result)), + Err(e) => Err(e), + }, + } +} + +pub async fn create_temp_dir() -> Result { + let temp_dir = std::env::temp_dir(); + let folder_name = uuid::Uuid::new_v4().to_string(); + let folder_path = temp_dir.join(&folder_name); + + tokio::fs::create_dir_all(&folder_path) + .await + .map_err(FileError::FailedToInitializeDirectories)?; + + Ok(folder_path) +} + +pub async fn init_directories(compilation_request: CompilationRequest) -> Result { + let temp_dir = create_temp_dir().await?; + + for file in compilation_request.files.iter() { + let file_path = temp_dir.join(&file.file_name); + + if let Some(parent) = file_path.parent() { + tokio::fs::create_dir_all(parent) + .await + .map_err(FileError::FailedToInitializeDirectories)?; + } + + tokio::fs::write(&file_path, &file.file_content) + .await + .map_err(FileError::FailedToSaveFile)?; + } + + temp_dir + .to_str() + .ok_or_else(|| { + ApiError::System(SystemError::FailedToParseFilePath(format!( + "{:?}", + temp_dir + ))) + }) + .map(|s| s.to_string()) +} + +pub fn get_files_recursive(base_path: &Path) -> Result> { + let mut file_content_map_array: Vec = Vec::new(); + + if base_path.is_dir() { + for entry in base_path + .read_dir() + .map_err(FileError::FailedToReadDir)? + .flatten() + { + let path = entry.path(); + if path.is_dir() { + file_content_map_array.extend(get_files_recursive(&path)?); + } else if let Ok(content) = std::fs::read_to_string(&path) { + let file_name = path + .file_name() + .ok_or(FileError::FailedToReadFilename)? + .to_string_lossy() + .to_string(); + let file_content = content; + let file_content_map = FileContentMap { + file_name, + file_content, + }; + file_content_map_array.push(file_content_map); + } + } + } + + Ok(file_content_map_array) +} diff --git a/api/src/main.rs b/api/src/main.rs index ae7c16c1..e1b4b338 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -11,17 +11,11 @@ pub mod utils; pub mod worker; use anyhow::Context; -use handlers::cairo_version::{ - cairo_version, cairo_version_async, cairo_versions, get_cairo_version_result, -}; -use handlers::compile_casm::{compile_to_casm, compile_to_casm_async, compile_to_casm_result}; -use handlers::compile_sierra::{ - compile_to_siera_async, compile_to_sierra, get_siera_compile_result, -}; +use handlers::allowed_versions::{get_allowed_versions, start_version_updater}; +use handlers::compile::{compile_async, get_compile_result}; use handlers::process::get_process_status; -use handlers::save_code::save_code; -use handlers::scarb_compile::{get_scarb_compile_result, scarb_compile, scarb_compile_async}; use handlers::scarb_test::{get_scarb_test_result, scarb_test_async}; +use handlers::scarb_version::{get_scarb_version_result, scarb_version_async}; use handlers::utils::on_plugin_launched; use handlers::{health, who_is_this}; use prometheus::Registry; @@ -83,26 +77,17 @@ fn create_app(metrics: Metrics) -> Rocket { .mount( "/", routes![ - compile_to_sierra, - compile_to_siera_async, - get_siera_compile_result, - compile_to_casm, - compile_to_casm_async, - compile_to_casm_result, - scarb_compile, - scarb_compile_async, - get_scarb_compile_result, - save_code, - cairo_versions, - cairo_version, - cairo_version_async, - get_cairo_version_result, + compile_async, + get_compile_result, + scarb_version_async, + get_scarb_version_result, get_process_status, health, who_is_this, get_scarb_test_result, scarb_test_async, on_plugin_launched, + get_allowed_versions, ], ) } @@ -111,6 +96,9 @@ fn create_app(metrics: Metrics) -> Rocket { async fn main() -> anyhow::Result<()> { init_logger().context("Failed to initialize logger")?; + // Start the version updater + start_version_updater().await; + let registry = Registry::new(); let metrics = initialize_metrics(registry.clone()).context("Failed to initialize metrics")?; diff --git a/api/src/rate_limiter.rs b/api/src/rate_limiter.rs index 07f8846a..8b96f5e4 100644 --- a/api/src/rate_limiter.rs +++ b/api/src/rate_limiter.rs @@ -1,4 +1,4 @@ -use crate::errors::{ApiError, Result}; +use crate::errors::{ApiError, NetworkError, Result, SystemError}; use crate::utils::lib::timestamp; use crate::worker::Timestamp; use crossbeam_queue::ArrayQueue; @@ -61,7 +61,7 @@ impl RateLimiter { if current_time - time <= 60 { // 1 minute - queue.push(time).map_err(|_| ApiError::QueueIsFull)?; + queue.push(time).map_err(|_| SystemError::QueueIsFull)?; break; } } @@ -80,7 +80,7 @@ impl RateLimiter { queue .push(current_time) - .map_err(|_| ApiError::QueueIsFull)?; + .map_err(|_| SystemError::QueueIsFull)?; Ok(()) } @@ -91,7 +91,7 @@ impl RateLimiter { let mut last_purge = self .last_purge .lock() - .map_err(|_| ApiError::MutexUnlockError)?; + .map_err(|_| SystemError::MutexUnlockError)?; if now - *last_purge > 60 * 60 { info!("Purging call queue in the rate limiter"); @@ -117,20 +117,28 @@ impl<'r> FromRequest<'r> for RateLimited { None => { return Outcome::Error(( Status::InternalServerError, - ApiError::RateLimiterNotInState, + SystemError::RateLimiterNotInState.into(), )) } Some(x) => x, }; let client_ip = match request.client_ip() { - None => return Outcome::Error((Status::BadRequest, ApiError::FailedToGetClientIp)), + None => { + return Outcome::Error(( + Status::BadRequest, + NetworkError::FailedToGetClientIp.into(), + )) + } Some(x) => x, }; match rate_limiter.do_rate_limit(client_ip) { Ok(_) => Outcome::Success(RateLimited), - Err(_) => Outcome::Error((Status::TooManyRequests, ApiError::TooManyRequests)), + Err(_) => Outcome::Error(( + Status::TooManyRequests, + NetworkError::TooManyRequests.into(), + )), } } } diff --git a/api/src/worker.rs b/api/src/worker.rs index be4776ae..9cd7238b 100644 --- a/api/src/worker.rs +++ b/api/src/worker.rs @@ -1,6 +1,6 @@ use crate::errors::ApiError; -use crate::handlers; use crate::handlers::types::{ApiCommand, ApiCommandResult}; +use crate::handlers::utils::dispatch_command; use crate::metrics::Metrics; use crate::utils::lib::DURATION_TO_PURGE; use crossbeam_queue::ArrayQueue; @@ -206,8 +206,12 @@ impl WorkerEngine { // update process state arc_process_states.insert(process_id, ProcessState::Running); - match handlers::dispatch_command(command, &metrics).await { + debug!("Processing command: {:?}", command); + + match dispatch_command(command, &metrics).await { Ok(result) => { + debug!("Command completed: {:?}", result); + arc_process_states .insert(process_id, ProcessState::Completed(result)); @@ -219,6 +223,8 @@ impl WorkerEngine { .unwrap(); } Err(e) => { + debug!("Command failed: {:?}", e); + arc_process_states.insert(process_id, ProcessState::Error(e)); arc_timestamps_to_purge diff --git a/cairo_upgrade.sh b/cairo_upgrade.sh deleted file mode 100755 index ef0cefd2..00000000 --- a/cairo_upgrade.sh +++ /dev/null @@ -1,18 +0,0 @@ -git rm -r api/cairo_compilers/v* - -versions=(2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.8.2 2.8.4) - -for version in "${versions[@]}" -do - echo $version - git submodule add https://github.com/starkware-libs/cairo api/cairo_compilers/v$version - cd api/cairo_compilers/v$version - git checkout $version - cd ../../../ -done - -git add -A -git submodule update --init --recursive - -git add -A -git commit -m "Add cairo compilers ${versions[*]}" diff --git a/examples/setter.cairo b/examples/setter.cairo deleted file mode 100644 index c8f8a1ef..00000000 --- a/examples/setter.cairo +++ /dev/null @@ -1,16 +0,0 @@ -#[starknet::contract] -mod ExampleConstructor { - use starknet::ContractAddress; - - #[storage] - struct Storage { - names: LegacyMap::, - } - - // The constructor is decorated with a `#[constructor]` attribute. - // It is not inside an `impl` block. - #[constructor] - fn constructor(ref self: ContractState, name: felt252, address: ContractAddress) { - self.names.write(address, name); - } -} \ No newline at end of file diff --git a/examples/setter.casm b/examples/setter.casm deleted file mode 100644 index 29bfa497..00000000 --- a/examples/setter.casm +++ /dev/null @@ -1,716 +0,0 @@ -{ - "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "compiler_version": "2.1.0", - "bytecode": [ - "0xa0680017fff8000", - "0x7", - "0x482680017ffa8000", - "0xffffffffffffffffffffffffffffbfb4", - "0x400280007ff97fff", - "0x10780017fff7fff", - "0x89", - "0x4825800180007ffa", - "0x404c", - "0x400280007ff97fff", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x92", - "0x482680017ff98000", - "0x1", - "0x20680017fff7ffd", - "0x6f", - "0x48127fff7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x1104800180018000", - "0xad", - "0x20680017fff7ffe", - "0x59", - "0x48307ffc80007ffd", - "0x4824800180007fff", - "0x0", - "0x20680017fff7fff", - "0x4", - "0x10780017fff7fff", - "0x11", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x400080007ffe7fff", - "0x480a7ff87fff8000", - "0x48127ff67fff8000", - "0x48127fca7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x482480017ff88000", - "0x1", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x15b", - "0x482480017fff8000", - "0x15a", - "0x480080007fff8000", - "0x480080007fff8000", - "0x482480017fff8000", - "0x0", - "0xa0680017fff8000", - "0x8", - "0x48307ffe80007fc7", - "0x482480017fff8000", - "0x100000000000000000000000000000000", - "0x400080007ff07fff", - "0x10780017fff7fff", - "0x23", - "0x48307ffe80007fc7", - "0x400080007ff17fff", - "0x482480017ff18000", - "0x1", - "0x48127ffe7fff8000", - "0x480a7ff87fff8000", - "0x480a7ffb7fff8000", - "0x48127fd17fff8000", - "0x48127ff07fff8000", - "0x1104800180018000", - "0xb3", - "0x20680017fff7ffd", - "0xd", - "0x40780017fff7fff", - "0x1", - "0x48127ffa7fff8000", - "0x48127ff77fff8000", - "0x48127ff77fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ffa7fff8000", - "0x48127ff97fff8000", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff97fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x480a7ff87fff8000", - "0x482480017fed8000", - "0x1", - "0x48127fc17fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x482480017ff88000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202332", - "0x400080007ffe7fff", - "0x480a7ff87fff8000", - "0x48127ff87fff8000", - "0x48127fcc7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x482480017ff88000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202331", - "0x400080007ffe7fff", - "0x480a7ff87fff8000", - "0x48127ffc7fff8000", - "0x48127feb7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x482480017ff88000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x480a7ff87fff8000", - "0x482680017ff98000", - "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x482480017ff88000", - "0x1", - "0x208b7fff7fff7ffe", - "0x48297ffc80007ffd", - "0x20680017fff7fff", - "0x4", - "0x10780017fff7fff", - "0xa", - "0x482680017ffc8000", - "0x1", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0x8", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x8", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x480680017fff8000", - "0x0", - "0x480080007ffa8000", - "0x208b7fff7fff7ffe", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffdb", - "0x20680017fff7ffe", - "0x2b", - "0xa0680017fff8004", - "0xe", - "0x4824800180047ffe", - "0x800000000000000000000000000000000000000000000000000000000000000", - "0x484480017ffe8000", - "0x110000000000000000", - "0x48307ffe7fff8002", - "0x480280007ffb7ffc", - "0x480280017ffb7ffc", - "0x402480017ffb7ffd", - "0xffffffffffffffeeffffffffffffffff", - "0x400280027ffb7ffd", - "0x10780017fff7fff", - "0x14", - "0x484480017fff8001", - "0x8000000000000000000000000000000", - "0x48307fff80007ffd", - "0x480280007ffb7ffd", - "0x480280017ffb7ffd", - "0x402480017ffc7ffe", - "0xf8000000000000000000000000000000", - "0x400280027ffb7ffe", - "0x40780017fff7fff", - "0x1", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff57fff8000", - "0x208b7fff7fff7ffe", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x6", - "0x480a7ffb7fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480a7ffd7fff8000", - "0x480a7ffc7fff8000", - "0x1104800180018000", - "0x18", - "0x20680017fff7ffd", - "0xd", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ff87fff8000", - "0x480a7ffa7fff8000", - "0x480a7ffc7fff8000", - "0x1104800180018000", - "0x38", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x53746f726167655772697465", - "0x400280007ffb7fff", - "0x400380017ffb7ff9", - "0x400280027ffb7ffe", - "0x400280037ffb7ffd", - "0x400380047ffb7ffd", - "0x480280067ffb8000", - "0x20680017fff7fff", - "0xd", - "0x480280057ffb8000", - "0x482680017ffb8000", - "0x7", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x10780017fff7fff", - "0x9", - "0x480280057ffb8000", - "0x482680017ffb8000", - "0x9", - "0x480680017fff8000", - "0x1", - "0x480280077ffb8000", - "0x480280087ffb8000", - "0x1104800180018000", - "0x40", - "0x20680017fff7ffd", - "0xd", - "0x48127ff07fff8000", - "0x48127ff57fff8000", - "0x48127fef7fff8000", - "0x48127ff47fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ff07fff8000", - "0x48127ff57fff8000", - "0x48127fef7fff8000", - "0x48127ff47fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x968a09a4841848cf6a616f8edef20d474b416f4e8fa338d2c6ff1c1b7cda16", - "0x400280007ffc7fff", - "0x400380017ffc7ffd", - "0x480280027ffc8000", - "0xa0680017fff8005", - "0xe", - "0x4824800180057ffe", - "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "0x484480017ffe8000", - "0x110000000000000000", - "0x48307ffe7fff8003", - "0x480280007ffb7ffc", - "0x480280017ffb7ffc", - "0x482480017ffb7ffd", - "0xffffffffffffffeefffffffffffffeff", - "0x400280027ffb7ffc", - "0x10780017fff7fff", - "0x11", - "0x48127ffe7fff8005", - "0x484480017ffe8000", - "0x8000000000000000000000000000000", - "0x48307ffe7fff8003", - "0x480280007ffb7ffd", - "0x482480017ffc7ffe", - "0xf0000000000000000000000000000100", - "0x480280017ffb7ffd", - "0x400280027ffb7ff9", - "0x402480017ffd7ff9", - "0xffffffffffffffffffffffffffffffff", - "0x20680017fff7ffd", - "0x4", - "0x402780017fff7fff", - "0x1", - "0x482680017ffb8000", - "0x3", - "0x482680017ffc8000", - "0x3", - "0x48127ffd7fff8000", - "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x9", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe" - ], - "hints": [ - [ - 0, - [ - { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x404c" - }, - "rhs": { - "Deref": { - "register": "FP", - "offset": -6 - } - }, - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 32, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 55, - [ - { - "TestLessThanOrEqual": { - "lhs": { - "Deref": { - "register": "AP", - "offset": -1 - } - }, - "rhs": { - "Deref": { - "register": "AP", - "offset": -56 - } - }, - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 76, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 96, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 112, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 127, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 142, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 200, - [ - { - "TestLessThan": { - "lhs": { - "Deref": { - "register": "AP", - "offset": -1 - } - }, - "rhs": { - "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" - }, - "dst": { - "register": "AP", - "offset": 4 - } - } - } - ] - ], - [ - 204, - [ - { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": 3 - } - }, - "scalar": { - "Immediate": "0x110000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -2 - }, - "y": { - "register": "AP", - "offset": -1 - } - } - } - ] - ], - [ - 214, - [ - { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": -2 - } - }, - "scalar": { - "Immediate": "0x8000000000000000000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -1 - }, - "y": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 295, - [ - { - "SystemCall": { - "system": { - "Deref": { - "register": "FP", - "offset": -5 - } - } - } - } - ] - ], - [ - 345, - [ - { - "TestLessThan": { - "lhs": { - "Deref": { - "register": "AP", - "offset": -1 - } - }, - "rhs": { - "Immediate": "0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00" - }, - "dst": { - "register": "AP", - "offset": 5 - } - } - } - ] - ], - [ - 349, - [ - { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": 4 - } - }, - "scalar": { - "Immediate": "0x110000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -2 - }, - "y": { - "register": "AP", - "offset": -1 - } - } - } - ] - ], - [ - 360, - [ - { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": 4 - } - }, - "scalar": { - "Immediate": "0x8000000000000000000000000000000" - }, - "max_x": { - "Immediate": "0xfffffffffffffffffffffffffffffffe" - }, - "x": { - "register": "AP", - "offset": -2 - }, - "y": { - "register": "AP", - "offset": -1 - } - } - } - ] - ] - ], - "entry_points_by_type": { - "EXTERNAL": [], - "L1_HANDLER": [], - "CONSTRUCTOR": [ - { - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "offset": 0, - "builtins": [ - "pedersen", - "range_check" - ] - } - ] - } -} \ No newline at end of file diff --git a/examples/setter.json b/examples/setter.json deleted file mode 100644 index e33c768d..00000000 --- a/examples/setter.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "sierra_program": [ - "0x1", - "0x3", - "0x0", - "0x2", - "0x1", - "0x0", - "0xcd", - "0x33", - "0x20", - "0x52616e6765436865636b", - "0x0", - "0x4761734275696c74696e", - "0x66656c74323532", - "0x4172726179", - "0x1", - "0x2", - "0x536e617073686f74", - "0x3", - "0x537472756374", - "0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62", - "0x4", - "0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3", - "0x456e756d", - "0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511", - "0x6", - "0x436f6e747261637441646472657373", - "0x3d37ad6eafb32512d2dd95a2917f6bf14858de22c27a1114392429f2e5c15d7", - "0x8", - "0x753332", - "0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672", - "0xb", - "0x5", - "0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6", - "0xd", - "0xc", - "0x506564657273656e", - "0x53797374656d", - "0x4275696c74696e436f737473", - "0x31a146a3f457e75b611d370ecf23056ad8ee8deffbf92d639e3aac354175065", - "0x3251da6c7f9622f14bef6d786303876bc98714f9641d95e1418ab89fd7c0ab9", - "0x12", - "0x13", - "0x3efb701152be9c00669301b7da59925463ee754682a9a528a9855d0365a6f82", - "0x14", - "0x426f78", - "0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7", - "0x16", - "0x296a4cee02809e2ad05943e743b17c81500a7ed27ea9c201203041ff3ac58a0", - "0x18", - "0x53746f726167654261736541646472657373", - "0x53746f7261676541646472657373", - "0x101dc0399934cc08fa0d6f6f2daead4e4a38cabeea1c743e1fc28d2d6e58e99", - "0xcc5e86243f861d2d64b08c35db21013e773ac5cf10097946fe0011304886d5", - "0x1d", - "0x1166fe35572d4e7764dac0caf1fd7fc591901fd01156db2561a07b68ab8dca2", - "0x68", - "0x7265766f6b655f61705f747261636b696e67", - "0x77697468647261775f676173", - "0x6272616e63685f616c69676e", - "0x73746f72655f74656d70", - "0x66756e6374696f6e5f63616c6c", - "0x656e756d5f6d61746368", - "0x7", - "0x9", - "0x7374727563745f6465636f6e737472756374", - "0x61727261795f6c656e", - "0x736e617073686f745f74616b65", - "0xa", - "0x64726f70", - "0x7533325f636f6e7374", - "0x72656e616d65", - "0x7533325f6571", - "0x61727261795f6e6577", - "0x66656c743235325f636f6e7374", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x61727261795f617070656e64", - "0x7374727563745f636f6e737472756374", - "0x656e756d5f696e6974", - "0xe", - "0xf", - "0x10", - "0x6765745f6275696c74696e5f636f737473", - "0x11", - "0x77697468647261775f6761735f616c6c", - "0x15", - "0x4f7574206f6620676173", - "0x4661696c656420746f20646573657269616c697a6520706172616d202332", - "0x4661696c656420746f20646573657269616c697a6520706172616d202331", - "0x61727261795f736e617073686f745f706f705f66726f6e74", - "0x17", - "0x6a756d70", - "0x756e626f78", - "0x21adb5788e32c84f69a1863d85ef9394b7bf761a0ce1190f826984e5075c371", - "0x19", - "0x73746f726167655f616464726573735f66726f6d5f62617365", - "0x73746f726167655f77726974655f73797363616c6c", - "0x1c", - "0x1e", - "0x636f6e74726163745f616464726573735f746f5f66656c74323532", - "0x968a09a4841848cf6a616f8edef20d474b416f4e8fa338d2c6ff1c1b7cda16", - "0x1f", - "0x706564657273656e", - "0xad292db4ff05a993c318438c1b6c8a8303266af2da151aa28ccece6726f1f1", - "0x1a", - "0x129", - "0xffffffffffffffff", - "0x7f", - "0x6f", - "0x5e", - "0x1b", - "0x28", - "0x21", - "0x22", - "0x23", - "0x24", - "0x25", - "0x26", - "0x27", - "0x29", - "0x2a", - "0x2b", - "0x4e", - "0x2c", - "0x2d", - "0x2e", - "0x2f", - "0x35", - "0x36", - "0x37", - "0x38", - "0x39", - "0x3a", - "0x3b", - "0x30", - "0x31", - "0x32", - "0x33", - "0x34", - "0x3c", - "0x46", - "0x3d", - "0x3e", - "0x3f", - "0x40", - "0x41", - "0x42", - "0x43", - "0x44", - "0x45", - "0x47", - "0x48", - "0x49", - "0x4a", - "0x4b", - "0x4c", - "0x4d", - "0x4f", - "0x50", - "0x51", - "0x52", - "0x53", - "0x54", - "0x55", - "0x56", - "0x57", - "0x58", - "0x59", - "0x5a", - "0x5b", - "0x5c", - "0x5d", - "0x5f", - "0x60", - "0x61", - "0x62", - "0x63", - "0x64", - "0x65", - "0x66", - "0x67", - "0x69", - "0x6a", - "0x6b", - "0x6c", - "0x6d", - "0x6e", - "0x70", - "0x71", - "0x72", - "0x73", - "0x74", - "0x75", - "0x76", - "0x77", - "0x78", - "0x79", - "0x7a", - "0x95", - "0x9a", - "0xa4", - "0xbd", - "0xb6", - "0xda", - "0xf3", - "0xf8", - "0x105", - "0x123", - "0x8e", - "0xab", - "0xc3", - "0xe2", - "0x10e", - "0x11d", - "0xbd2", - "0x7060f02090e0d02060a0c060b02070a090606080706060502040203020100", - "0x60d02070a090616060d02090a1502060a0214100613061202090e02111006", - "0x21060d02090a20061f02070a1e02060a021d021c021b1a0619061802090e17", - "0xe100620060d02090a100626062502090e070606241a0623062202090e1006", - "0x2d062c02090e10060d02070a090610062b02090e022a02291a062806270209", - "0x360606350206063306090634170606330232023102302f07062e02070a1a06", - "0x6063e0207063d3b06063c3b06063a07060639170606383706063507090634", - "0x70606430706063342070641070606400706063c1306063c023f3b0606333b", - "0x49460606334806063306060633470606330607460607451a06064416060644", - "0x4c0606350909063413060633210606332106064420060644024b4a06063302", - "0x3c4d07064102074606074519060644170606440906063c0906063a2306063c", - "0x6330c060633020751060745070606504f0706414e0706411706063c100606", - "0x7360607450706063e07060653510606350607510607451006064402525106", - "0x6380607370607453706063302073706074502540607360607453606063302", - "0x6063302074c0607452306064428060638550606350c090634200606332106", - "0x7455806063302075806074502570256170906342006063a06074c0607454c", - "0x33020755060745280606442d06063859060635100906345806063e06075806", - "0x25e025d5c0606385c0606445b070641025a0607550607452006063c550606", - "0x20260060759060745590606330207590607452d060644580606355f060633", - "0x661060c060902026106020702133607621017076107070607060202610602", - "0x7021906631a0661073b06100217066106170617023b3707610616060c0216", - "0x484746096106204a07360220066106370609024a0661061706170202610602", - "0x61064c063b024c066106470637020261060207022306642106610748061302", - "0x6106550616025506610602190202610651061a022851076106260616022606", - "0x6580647022d0661066506460258066106280646020261065f061a02655f07", - "0x61a06200202610621064a0202610602070202660261072d58074802580661", - "0x61065c590726025c0661065c064c025c066106022302590661060221020261", - "0x602065f026906610668065502680661060067072802670661060251020006", - "0x69062d026d066106090658026c066106100665026b066106460617026a0661", - "0x661066e065c026e066106025902026106020702666d6c6b6a170666066106", - "0x68027406610602670202610602070273720771706f0761076e10460900026e", - "0x27806610602065f027706610670066502760661066f061702750661067406", - "0x7c06610621066a027b0661061a064c027a0661067506690279066106090658", - "0x7028406838206610781066c0281807f7e7d1761067c7b7a79787776366b02", - "0x86066e028786076106850666028506610602210202610682066d0202610602", - "0x7f065f028a0661068906720289066106880670028806610687066f02026106", - "0x62d028d06610680065802830661067e0665028c0661067d0617028b066106", - "0x67f065f028f066106840655020261060207028e8d838c8b17068e0661068a", - "0x8f062d029306610680065802920661067e066502910661067d061702900661", - "0x261061a06200202610621064a020261060207027193929190170671066106", - "0x9606610695940726029506610695064c029506610602730294066106022102", - "0x6610602065f02990661069806550298066106969707280297066106025102", - "0x610699062d029d066106090658029c066106730665029b066106720617029a", - "0x200202610647067502026106230674020261060207029e9d9c9b9a17069e06", - "0x9f072602a0066106a0064c02a0066106027d029f0661060221020261061a06", - "0x5f02a30661066406550264066106a1a2072802a2066106025102a1066106a0", - "0x2a706610609065802a606610610066502a506610646061702a40661060206", - "0x3706750202610619067402026106020702a8a7a6a5a41706a8066106a3062d", - "0x6aaa9072602aa066106aa064c02aa066106027e02a9066106022102026106", - "0x2065f02ad066106ac065502ac066106ab2f0728022f066106025102ab0661", - "0x62d02b106610609065802b006610610066502af06610617061702ae066106", - "0x661060221020261060c06750202610602070263b1b0afae170663066106ad", - "0x66106025102b4066106b3b2072602b3066106b3064c02b3066106027302b2", - "0x636061702b806610602065f02b7066106b6065502b6066106b4b5072802b5", - "0xb9b81706bc066106b7062d02bb06610609065802ba06610613066502b90661", - "0x80020261060207020c06bd090707610706067f020606610602063702bcbbba", - "0x2070202be0602770236066106170676021006610607068102170661060906", - "0x637067602100661060c068102370661061306790213066106027802026106", - "0x21a06bf1606610736067a023b0661063b0609023b06610610066f02360661", - "0x90247066106460682024606610619067c021906610616067b020261060207", - "0x261061a0674020261060207024a4807064a06610647068402480661063b06", - "0x4c06610621068402230661063b060902210661062006850220066106027802", - "0x6c0170661070906100209070761060c060c020c066106060609024c230706", - "0x6130687020261060207023706c11336076107170207860202610602070210", - "0x1a160906190661063b0688021a0661060706090216066106360617023b0661", - "0x2480661063706170247066106460689024606610602780202610602070219", - "0x610068902026106020702204a48090620066106470688024a066106070609", - "0x4c23090626066106210688024c066106070609022306610602061702210661", - "0x610607065f0246066106060665021906610602061702360661060c068a0226", - "0x617064c022006610610066a024a06610636068b0248066106090658024706", - "0x6c2230661071a0683021a163b371317610621204a48474619368c02210661", - "0x22806610602780202610651067402512607610623068d020261060207024c", - "0x6610613061702650661065f068f025f0661062855078e0255066106260668", - "0x6106650690025c06610616065802590661063b065f022d0661063706650258", - "0x6806610613061702670661064c069102026106020702005c592d5817060006", - "0x66106670690026b066106160658026a0661063b065f026906610637066502", - "0x610607065f021a0661060206170213360761060c0692026c6b6a696817066c", - "0x163b370961064746191a0c93024706610617066a024606610613068b021906", - "0x10484a09061794024a0661064a0647024a0661060219024806610616067102", - "0x280661065106950251066106027802026106020702264c2309c32120076107", - "0x202c40602770265066106280696025f066106210658025506610620066502", - "0x96025f0661064c065802550661062306650258066106260697020261060207", - "0xc55c0661072d069a022d066106590699025906610665069802650661065806", - "0x610668069d02680661066736079c02670661065c069b020261060207020006", - "0x65f0658026c0661063b065f026b066106550665026a066106370617026906", - "0x2610636069f02026106020702666d6c6b6a17066606610669069e026d0661", - "0x661063b065f0270066106550665026f066106370617026e0661060006a002", - "0x2610607069f02747372706f1706740661066e069e02730661065f06580272", - "0x360661061006a30210066106170664021706610602a2020c0661060906a102", - "0x3b06a3023b0661063706640237130761060c360609a4023606610636064c02", - "0x2460661061a061702191a076106160207a5021606610616064c0216066106", - "0x706c6060661070206a7024847460906480661061906a6024706610613065f", - "0x6170661060c06aa020c0661060906a902090661060606a802026106020702", - "0x661063606ab02360661060710072802100661060251020261060207021706", - "0x20c0907060246480602471717480602471736370606370661061306aa0213", - "0x4c48470602171307214847060236c8060237170209170207c7023617071706", - "0xcca10170c090706025548470602170713204847060236c910170c09070602", - "0xcc0259065806cb090706025f47020913204702" - ], - "sierra_program_debug_info": { - "type_names": [], - "libfunc_names": [], - "user_func_names": [] - }, - "contract_class_version": "0.1.0", - "entry_points_by_type": { - "EXTERNAL": [], - "L1_HANDLER": [], - "CONSTRUCTOR": [ - { - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "function_idx": 0 - } - ] - }, - "abi": [ - { - "type": "constructor", - "name": "constructor", - "inputs": [ - { - "name": "name", - "type": "core::felt252" - }, - { - "name": "address", - "type": "core::starknet::contract_address::ContractAddress" - } - ] - }, - { - "type": "event", - "name": "setter::setter::ExampleConstructor::Event", - "kind": "enum", - "variants": [] - } - ] -} diff --git a/install.sh b/install.sh deleted file mode 100644 index 009381a1..00000000 --- a/install.sh +++ /dev/null @@ -1,30 +0,0 @@ -sudo apt-get update -sudo apt-get install -y curl -sudo apt-get install -y git -sudo apt-get install -y software-properties-common -sudo apt-get install -y libgmp-dev -sudo apt-get install screen -y - -sudo add-apt-repository ppa:deadsnakes/ppa -y - -# Install python3.9 and pip3.9 -sudo apt install -y python3.9 python3.9-dev python3.9-distutils python3.9-venv -sudo apt-get install -y python3-pip - -# Install Rust -sudo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -source "$HOME/.cargo/env" - - -# Install nodejs -sudo curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt-get install -y nodejs - -# Install pnpm -sudo npm install -g pnpm - -python3.9 -m venv starknet-venv -source starknet-venv/bin/activate -pip3 install starknet-devnet - -deactivate \ No newline at end of file diff --git a/plugin/.eslintrc.json b/plugin/.eslintrc.json index 5604be0d..199e048f 100644 --- a/plugin/.eslintrc.json +++ b/plugin/.eslintrc.json @@ -12,8 +12,55 @@ }, "plugins": ["react"], "rules": { - "semi": [2, "never"], + "semi": [2, "always"], + "quotes": [2, "double"], + "no-tabs": "off", + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], + "indent": ["error", "tab", { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "MemberExpression": 1, + "FunctionDeclaration": { "parameters": 1, "body": 1 }, + "FunctionExpression": { "parameters": 1, "body": 1 }, + "CallExpression": { "arguments": 1 }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": false, + "ignoreComments": false + }], + "@typescript-eslint/indent": ["error", "tab", { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "MemberExpression": 1, + "FunctionDeclaration": { "parameters": 1, "body": 1 }, + "FunctionExpression": { "parameters": 1, "body": 1 }, + "CallExpression": { "arguments": 1 }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": false, + "ignoreComments": false + }], + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/quotes": ["error", "double"], "@typescript-eslint/space-before-function-paren": "off", - "@typescript-eslint/member-delimiter-style": "off" + "@typescript-eslint/member-delimiter-style": ["error", { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + }] + }, + "settings": { + "react": { + "version": "detect" + } } } diff --git a/plugin/.prettierrc b/plugin/.prettierrc index 960146a6..dae79a0b 100644 --- a/plugin/.prettierrc +++ b/plugin/.prettierrc @@ -1,7 +1,11 @@ { - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, - "semi": false, - "trailingComma": "none" + "tabWidth": 4, + "useTabs": true, + "singleQuote": false, + "semi": true, + "trailingComma": "all", + "printWidth": 100, + "endOfLine": "lf", + "bracketSpacing": true, + "arrowParens": "always" } diff --git a/plugin/package.json b/plugin/package.json index 453a59f7..5689a051 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -15,6 +15,7 @@ "@remixproject/plugin-webview": "^0.3.38", "@starknet-react/chains": "^0.1.7", "@starknet-react/core": "^2.5.0", + "@tanstack/react-query": "^5.62.2", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -25,17 +26,19 @@ "form-data": "^4.0.0", "formik": "^2.4.5", "get-starknet": "^3.0.1", + "immer": "^10.1.1", "jotai": "^2.6.0", "lucide-react": "^0.294.0", "nanoid": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.12.0", - "starknet-abi-forms": "0.1.9", "starknet": "6.6.6", + "starknet-abi-forms": "0.1.9", "vite-plugin-svgr": "^4.2.0", "web-vitals": "^2.1.4", - "yup": "^1.3.2" + "yup": "^1.3.2", + "zustand": "^5.0.1" }, "devDependencies": { "@babel/eslint-parser": "^7.23.3", diff --git a/plugin/pnpm-lock.yaml b/plugin/pnpm-lock.yaml index 7147d7c7..c6af1aa2 100644 --- a/plugin/pnpm-lock.yaml +++ b/plugin/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@starknet-react/core': specifier: ^2.5.0 version: 2.5.0(get-starknet-core@4.0.0)(react@18.2.0)(starknet@6.6.6) + '@tanstack/react-query': + specifier: ^5.62.2 + version: 5.62.2(react@18.2.0) '@testing-library/jest-dom': specifier: ^5.17.0 version: 5.17.0 @@ -74,6 +77,9 @@ importers: get-starknet: specifier: ^3.0.1 version: 3.0.1(starknet@6.6.6) + immer: + specifier: ^10.1.1 + version: 10.1.1 jotai: specifier: ^2.6.0 version: 2.6.0(@types/react@18.2.45)(react@18.2.0) @@ -107,6 +113,9 @@ importers: yup: specifier: ^1.3.2 version: 1.3.2 + zustand: + specifier: ^5.0.1 + version: 5.0.1(@types/react@18.2.45)(immer@10.1.1)(react@18.2.0) devDependencies: '@babel/eslint-parser': specifier: ^7.23.3 @@ -1674,13 +1683,13 @@ packages: peerDependencies: '@svgr/core': '*' - '@tanstack/query-core@5.13.4': - resolution: {integrity: sha512-8+rJucXvC/xlr4OrxHhEIob/cTlbT4fgmz1VsvB0D12FRStKaXeLORNGcOhSAynRd2NL74SV/Qq0IIb4DedLcA==} + '@tanstack/query-core@5.62.2': + resolution: {integrity: sha512-LcwVcC5qpsDpHcqlXUUL5o9SaOBwhNkGeV+B06s0GBoyBr8FqXPuXT29XzYXR36lchhnerp6XO+CWc84/vh7Zg==} - '@tanstack/react-query@5.13.4': - resolution: {integrity: sha512-3HjvkFFriEQwffUXtKHPiwkfFXUGbs46YATTzzyK1+Pw6Ekd3kwzS50e45qdamWuEXmXxyo5S1zp534LdFG0Rw==} + '@tanstack/react-query@5.62.2': + resolution: {integrity: sha512-fkTpKKfwTJtVPKVR+ag7YqFgG/7TRVVPzduPAUF9zRCiiA8Wu305u+KJl8rCrh98Qce77vzIakvtUyzWLtaPGA==} peerDependencies: - react: ^18.0.0 + react: ^18 || ^19 '@testing-library/dom@8.20.1': resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} @@ -2682,6 +2691,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + immutable@4.3.4: resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} @@ -3836,6 +3848,24 @@ packages: zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + zustand@5.0.1: + resolution: {integrity: sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -5742,7 +5772,7 @@ snapshots: '@starknet-react/core@2.5.0(get-starknet-core@4.0.0)(react@18.2.0)(starknet@6.6.6)': dependencies: '@starknet-react/chains': 0.1.7 - '@tanstack/react-query': 5.13.4(react@18.2.0) + '@tanstack/react-query': 5.62.2(react@18.2.0) eventemitter3: 5.0.1 get-starknet-core: 4.0.0 immutable: 4.3.4 @@ -5820,11 +5850,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/query-core@5.13.4': {} + '@tanstack/query-core@5.62.2': {} - '@tanstack/react-query@5.13.4(react@18.2.0)': + '@tanstack/react-query@5.62.2(react@18.2.0)': dependencies: - '@tanstack/query-core': 5.13.4 + '@tanstack/query-core': 5.62.2 react: 18.2.0 '@testing-library/dom@8.20.1': @@ -7135,6 +7165,8 @@ snapshots: image-size@0.5.5: optional: true + immer@10.1.1: {} + immutable@4.3.4: {} import-fresh@3.3.0: @@ -8304,3 +8336,9 @@ snapshots: type-fest: 2.19.0 zod@3.22.4: {} + + zustand@5.0.1(@types/react@18.2.45)(immer@10.1.1)(react@18.2.0): + optionalDependencies: + '@types/react': 18.2.45 + immer: 10.1.1 + react: 18.2.0 diff --git a/plugin/src/App.tsx b/plugin/src/App.tsx index 64813ca4..0fd892f2 100644 --- a/plugin/src/App.tsx +++ b/plugin/src/App.tsx @@ -1,26 +1,26 @@ -import React from 'react' +import React from "react"; -import './App.css' -import Plugin from './features/Plugin' -import Loader from './components/ui_components/CircularLoader' -import FullScreenOverlay from './components/ui_components/FullScreenOverlay' -import { pluginLoaded } from './atoms/remixClient' -import { useAtomValue } from 'jotai' +import "./App.css"; +import Plugin from "./features/Plugin"; +import Loader from "./components/ui_components/CircularLoader"; +import FullScreenOverlay from "./components/ui_components/FullScreenOverlay"; +import { pluginLoaded } from "./atoms/remixClient"; +import { useAtomValue } from "jotai"; const App: React.FC = () => { - return ( -
- {useAtomValue(pluginLoaded) - ? ( - - ) - : ( - - - - )} -
- ) -} + return ( +
+ {useAtomValue(pluginLoaded) + ? ( + + ) + : ( + + + + )} +
+ ); +}; -export default App +export default App; diff --git a/plugin/src/atoms/cairoVersion.ts b/plugin/src/atoms/cairoVersion.ts index 4e4c74e0..2a351eda 100644 --- a/plugin/src/atoms/cairoVersion.ts +++ b/plugin/src/atoms/cairoVersion.ts @@ -1,7 +1,7 @@ -import { atom } from 'jotai' +import { atom } from "jotai"; -const cairoVersionAtom = atom('v2.8.4') +const cairoVersionAtom = atom(null); -const versionsAtom = atom([]) +const versionsAtom = atom([]); -export { cairoVersionAtom, versionsAtom } +export { cairoVersionAtom, versionsAtom }; diff --git a/plugin/src/atoms/compilation.ts b/plugin/src/atoms/compilation.ts index e29591d3..23901f05 100644 --- a/plugin/src/atoms/compilation.ts +++ b/plugin/src/atoms/compilation.ts @@ -1,82 +1,33 @@ // status: 'Compiling...' as string, // setStatus: ((_: string) => {}) as React.Dispatch>, -import { atom } from 'jotai' +import { atom } from "jotai"; -const statusAtom = atom('Compiling....') - -// currentFilename: '' as string, -// setCurrentFilename: ((_: string) => {}) as React.Dispatch>, - -const currentFilenameAtom = atom('') -// isCompiling: false as boolean, -// setIsCompiling: ((_: boolean) => {}) as React.Dispatch>, - -const isCompilingAtom = atom(false) - -// isValidCairo: false as boolean, -// setIsValidCairo: ((_: boolean) => {}) as React.Dispatch>, - -const isValidCairoAtom = atom(false) -// noFileSelected: false as boolean, -// setNoFileSelected: ((_: boolean) => {}) as React.Dispatch>, - -const noFileSelectedAtom = atom(false) -// hashDir: '' as string, -// setHashDir: ((_: string) => {}) as React.Dispatch>, +enum CompilationStatus { + Compiling = "Compiling...", + Success = "Success", + Error = "Error", + Idle = "Idle" +} -const hashDirAtom = atom('') +const statusAtom = atom(CompilationStatus.Idle); -// tomlPaths: [] as string[], -// setTomlPaths: ((_: string[]) => {}) as React.Dispatch>, -const tomlPathsAtom = atom([]) +const currentFilenameAtom = atom(""); -// activeTomlPath: '' as string, -// setActiveTomlPath: ((_: string) => {}) as React.Dispatch> -const activeTomlPathAtom = atom('') +const isCompilingAtom = atom(false); -type Keys = 'status' | 'currentFilename' | 'isCompiling' | 'isValidCairo' | 'noFileSelected' | 'hashDir' | 'tomlPaths' | 'activeTomlPath' +const noFileSelectedAtom = atom(false); -interface SetCompilationValue { - key: Keys - value: string | boolean | string[] -} +const tomlPathsAtom = atom([]); -const compilationAtom = atom( - (get) => { - return { - status: get(statusAtom), - currentFilename: get(currentFilenameAtom), - isCompiling: get(isCompilingAtom), - isValidCairo: get(isValidCairoAtom), - noFileSelected: get(noFileSelectedAtom), - hashDir: get(hashDirAtom), - tomlPaths: get(tomlPathsAtom), - activeTomlPath: get(activeTomlPathAtom) - } - }, - (_get, set, newValue: SetCompilationValue) => { - switch (newValue?.key) { - case 'status': typeof newValue?.value === 'string' && set(statusAtom, newValue?.value); break - case 'currentFilename': typeof newValue?.value === 'string' && set(currentFilenameAtom, newValue?.value); break - case 'isCompiling': typeof newValue?.value === 'boolean' && set(isCompilingAtom, newValue?.value); break - case 'isValidCairo': typeof newValue?.value === 'boolean' && set(isValidCairoAtom, newValue?.value); break - case 'noFileSelected': typeof newValue?.value === 'boolean' && set(noFileSelectedAtom, newValue?.value); break - case 'hashDir': typeof newValue?.value === 'string' && set(hashDirAtom, newValue?.value); break - case 'tomlPaths': Array.isArray(newValue?.value) && set(tomlPathsAtom, newValue?.value); break - case 'activeTomlPath': typeof newValue?.value === 'string' && set(activeTomlPathAtom, newValue?.value); break - } - } -) +const activeTomlPathAtom = atom(""); export { - statusAtom, - currentFilenameAtom, - isCompilingAtom, - isValidCairoAtom, - noFileSelectedAtom, - hashDirAtom, - tomlPathsAtom, - activeTomlPathAtom, - compilationAtom -} + CompilationStatus, + statusAtom, + currentFilenameAtom, + noFileSelectedAtom, + tomlPathsAtom, + activeTomlPathAtom, + isCompilingAtom +}; diff --git a/plugin/src/atoms/compiledContracts.ts b/plugin/src/atoms/compiledContracts.ts index bd98a4df..363d9d84 100644 --- a/plugin/src/atoms/compiledContracts.ts +++ b/plugin/src/atoms/compiledContracts.ts @@ -1,16 +1,33 @@ -import { atom } from 'jotai' -import { atomWithStorage } from 'jotai/utils' +import { atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; -import { type Contract } from '../utils/types/contracts' +import { type Contract } from "../utils/types/contracts"; const compiledContractsAtom = atomWithStorage( - 'contracts', - [], - // use localStorage - undefined, - // fetch saved data on initialization - { getOnInit: true } -) -const selectedCompiledContract = atom(null) + "contracts", + [], + // use localStorage + undefined, + // fetch saved data on initialization + { getOnInit: true } +); -export { compiledContractsAtom, selectedCompiledContract } +const selectedCompiledContract = atom(null); + +const deployedContractsAtom = atomWithStorage( + "deployedContracts", + [], + // use localStorage + undefined, + // fetch saved data on initialization + { getOnInit: true } +); + +const selectedDeployedContract = atom(null); + +export { + compiledContractsAtom, + deployedContractsAtom, + selectedCompiledContract, + selectedDeployedContract +}; diff --git a/plugin/src/atoms/connection.ts b/plugin/src/atoms/connection.ts index 672c3872..42eed728 100644 --- a/plugin/src/atoms/connection.ts +++ b/plugin/src/atoms/connection.ts @@ -1,13 +1,10 @@ -import { atom } from 'jotai' -import { - type AccountInterface, - type ProviderInterface -} from 'starknet' +import { atom } from "jotai"; +import { type AccountInterface, type ProviderInterface } from "starknet"; -import { type StarknetWindowObject } from 'get-starknet' +import { type StarknetWindowObject } from "get-starknet"; -const account = atom(null) -const provider = atom(null) -const starknetWindowObject = atom(null) +const account = atom(null); +const provider = atom(null); +const starknetWindowObject = atom(null); -export { account, provider, starknetWindowObject } +export { account, provider, starknetWindowObject }; diff --git a/plugin/src/atoms/deployment.ts b/plugin/src/atoms/deployment.ts index 9d4d3f53..ceef18cc 100644 --- a/plugin/src/atoms/deployment.ts +++ b/plugin/src/atoms/deployment.ts @@ -1,107 +1,101 @@ -import { atom } from 'jotai' -import { type CallDataObject, type Input } from '../utils/types/contracts' +import { atom } from "jotai"; +import { type CallDataObject, type Input } from "../utils/types/contracts"; -export type Status = 'IDLE' | 'IN_PROGRESS' | 'ERROR' | 'DONE' -const isDeployingAtom = atom(false) +export type Status = "IDLE" | "IN_PROGRESS" | "ERROR" | "DONE"; -const deployStatusAtom = atom('IDLE') +const isDeployingAtom = atom(false); +const deployStatusAtom = atom("IDLE"); -const isDelcaringAtom = atom(false) +const isDelcaringAtom = atom(false); -const declStatusAtom = atom('IDLE') +const declStatusAtom = atom("IDLE"); -const constructorCalldataAtom = atom({}) +const constructorCalldataAtom = atom({}); -const constructorInputsAtom = atom([]) +const constructorInputsAtom = atom([]); -const notEnoughInputsAtom = atom(false) +const notEnoughInputsAtom = atom(false); -const declTxHashAtom = atom('') +const declTxHashAtom = atom(""); -const deployTxHashAtom = atom('') +const deployTxHashAtom = atom(""); type Key = - | 'isDeploying' - | 'deployStatus' - | 'isDeclaring' - | 'declStatus' - | 'constructorCalldata' - | 'constructorInputs' - | 'notEnoughInputs' - | 'declTxHash' - | 'deployTxHash' + | "isDeploying" + | "deployStatus" + | "isDeclaring" + | "declStatus" + | "constructorCalldata" + | "constructorInputs" + | "notEnoughInputs" + | "declTxHash" + | "deployTxHash"; interface SetDeploymentAtom { - key: Key - value: string | boolean | CallDataObject | Input[] + key: Key; + value: string | boolean | CallDataObject | Input[]; } const deploymentAtom = atom( - (get) => { - return { - isDeploying: get(isDeployingAtom), - deployStatus: get(deployStatusAtom), - isDeclaring: get(isDelcaringAtom), - declStatus: get(declStatusAtom), - constructorCalldata: get(constructorCalldataAtom), - constructorInputs: get(constructorInputsAtom), - notEnoughInputs: get(notEnoughInputsAtom), - declTxHash: get(declTxHashAtom), - deployTxHash: get(deployTxHashAtom) - } - }, - (_get, set, newValue: SetDeploymentAtom) => { - switch (newValue?.key) { - case 'isDeploying': - typeof newValue?.value === 'boolean' && - set(isDeployingAtom, newValue?.value) - break - case 'deployStatus': - typeof newValue?.value === 'string' && - set(deployStatusAtom, newValue?.value as Status) - break - case 'isDeclaring': - typeof newValue?.value === 'boolean' && - set(isDelcaringAtom, newValue?.value) - break - case 'declStatus': - typeof newValue?.value === 'string' && - set(declStatusAtom, newValue?.value as Status) - break - case 'constructorCalldata': - typeof newValue?.value === 'object' && - !Array.isArray(newValue?.value) && - set(constructorCalldataAtom, newValue?.value) - break - case 'constructorInputs': - Array.isArray(newValue?.value) && - set(constructorInputsAtom, newValue?.value) - break - case 'notEnoughInputs': - typeof newValue?.value === 'boolean' && - set(notEnoughInputsAtom, newValue?.value) - break - case 'declTxHash': - typeof newValue?.value === 'string' && - set(declTxHashAtom, newValue?.value) - break - case 'deployTxHash': - typeof newValue?.value === 'string' && - set(deployTxHashAtom, newValue?.value) - break - } - } -) + (get) => { + return { + isDeploying: get(isDeployingAtom), + deployStatus: get(deployStatusAtom), + isDeclaring: get(isDelcaringAtom), + declStatus: get(declStatusAtom), + constructorCalldata: get(constructorCalldataAtom), + constructorInputs: get(constructorInputsAtom), + notEnoughInputs: get(notEnoughInputsAtom), + declTxHash: get(declTxHashAtom), + deployTxHash: get(deployTxHashAtom) + }; + }, + (_get, set, newValue: SetDeploymentAtom) => { + switch (newValue?.key) { + case "isDeploying": + typeof newValue?.value === "boolean" && set(isDeployingAtom, newValue?.value); + break; + case "deployStatus": + typeof newValue?.value === "string" && + set(deployStatusAtom, newValue?.value as Status); + break; + case "isDeclaring": + typeof newValue?.value === "boolean" && set(isDelcaringAtom, newValue?.value); + break; + case "declStatus": + typeof newValue?.value === "string" && + set(declStatusAtom, newValue?.value as Status); + break; + case "constructorCalldata": + typeof newValue?.value === "object" && + !Array.isArray(newValue?.value) && + set(constructorCalldataAtom, newValue?.value); + break; + case "constructorInputs": + Array.isArray(newValue?.value) && set(constructorInputsAtom, newValue?.value); + break; + case "notEnoughInputs": + typeof newValue?.value === "boolean" && set(notEnoughInputsAtom, newValue?.value); + break; + case "declTxHash": + typeof newValue?.value === "string" && set(declTxHashAtom, newValue?.value); + break; + case "deployTxHash": + typeof newValue?.value === "string" && set(deployTxHashAtom, newValue?.value); + break; + } + } +); export { - isDeployingAtom, - deployStatusAtom, - isDelcaringAtom, - declStatusAtom, - constructorCalldataAtom, - constructorInputsAtom, - notEnoughInputsAtom, - deploymentAtom, - declTxHashAtom, - deployTxHashAtom -} + isDeployingAtom, + deployStatusAtom, + isDelcaringAtom, + declStatusAtom, + constructorCalldataAtom, + constructorInputsAtom, + notEnoughInputsAtom, + deploymentAtom, + declTxHashAtom, + deployTxHashAtom +}; diff --git a/plugin/src/atoms/environment.ts b/plugin/src/atoms/environment.ts index f5379c98..1c947eeb 100644 --- a/plugin/src/atoms/environment.ts +++ b/plugin/src/atoms/environment.ts @@ -1,40 +1,40 @@ -import { atom } from 'jotai' +import { atom } from "jotai"; -import { type Devnet, devnets, type DevnetAccount } from '../utils/network' +import { type Devnet, devnets, type DevnetAccount } from "../utils/network"; -const devnetAtom = atom(devnets[1]) +const devnetAtom = atom(devnets[1]); -export type Env = 'remoteDevnet' | 'wallet' | 'manual' | 'localDevnet' | 'localKatanaDevnet' +export type Env = "remoteDevnet" | "wallet" | "manual" | "localDevnet" | "localKatanaDevnet"; export const envName = (env: Env): string => { - switch (env) { - case 'remoteDevnet': - return 'Remote Devnet' - case 'wallet': - return 'Wallet' - case 'manual': - return 'Manual' - case 'localDevnet': - return 'Local Devnet' - case 'localKatanaDevnet': - return 'Local Katana Devnet' - default: - return 'Unknown' - } -} - -const envAtom = atom('remoteDevnet') - -const isDevnetAliveAtom = atom(true) - -const selectedDevnetAccountAtom = atom(null) - -const availableDevnetAccountsAtom = atom([]) + switch (env) { + case "remoteDevnet": + return "Remote Devnet"; + case "wallet": + return "Wallet"; + case "manual": + return "Manual"; + case "localDevnet": + return "Local Devnet"; + case "localKatanaDevnet": + return "Local Katana Devnet"; + default: + return "Unknown"; + } +}; + +const envAtom = atom("remoteDevnet"); + +const isDevnetAliveAtom = atom(true); + +const selectedDevnetAccountAtom = atom(null); + +const availableDevnetAccountsAtom = atom([]); export { - devnetAtom, - envAtom, - isDevnetAliveAtom, - selectedDevnetAccountAtom, - availableDevnetAccountsAtom -} + devnetAtom, + envAtom, + isDevnetAliveAtom, + selectedDevnetAccountAtom, + availableDevnetAccountsAtom +}; diff --git a/plugin/src/atoms/explorer.ts b/plugin/src/atoms/explorer.ts index ab2d9953..5b65783e 100644 --- a/plugin/src/atoms/explorer.ts +++ b/plugin/src/atoms/explorer.ts @@ -1,4 +1,4 @@ -import { type networkExplorerUrls as EXPLORERS } from '../utils/constants' -import { atom } from 'jotai' +import { type networkExplorerUrls as EXPLORERS } from "../utils/constants"; +import { atom } from "jotai"; -export const currentExplorerAtom = atom('voyager') +export const currentExplorerAtom = atom("voyager"); diff --git a/plugin/src/atoms/index.ts b/plugin/src/atoms/index.ts deleted file mode 100644 index f1f49a13..00000000 --- a/plugin/src/atoms/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { atomWithStorage } from 'jotai/utils' -import { type AbiElement, type Input } from '../utils/types/contracts' -import { - type InvokeFunctionResponse, - type CallContractResponse -} from 'starknet' - -const STORAGE_KEYS = { - INTERACT: 'INTERACT' -} - -export type EnhancedInput = Input & { - rawInput?: string -} - -export type EnhancedAbiElement = Omit & { - callResponse?: CallContractResponse - invocationResponse?: InvokeFunctionResponse - inputs: EnhancedInput[] -} - -export interface UiAbiState { - readState: EnhancedAbiElement[] - writeState: EnhancedAbiElement[] -} - -const interactAtom = atomWithStorage>( - STORAGE_KEYS.INTERACT, - {} -) - -export { interactAtom } diff --git a/plugin/src/atoms/interaction.ts b/plugin/src/atoms/interaction.ts index 77688c2e..ff92edd1 100644 --- a/plugin/src/atoms/interaction.ts +++ b/plugin/src/atoms/interaction.ts @@ -1,4 +1,7 @@ -import { atom } from 'jotai' +import { atom } from "jotai"; -export const invokeTxHashAtom = atom('') -export const isInvokingAtom = atom(false) +const invokeTxHashAtom = atom(""); + +const isInvokingAtom = atom(false); + +export { invokeTxHashAtom, isInvokingAtom }; diff --git a/plugin/src/atoms/manualAccount.ts b/plugin/src/atoms/manualAccount.ts index dee1a3c5..7f0c9b6e 100644 --- a/plugin/src/atoms/manualAccount.ts +++ b/plugin/src/atoms/manualAccount.ts @@ -1,15 +1,11 @@ -import { atom } from 'jotai' -import { type ManualAccount } from '../utils/types/accounts' -import { networks } from '../utils/constants' +import { atom } from "jotai"; +import { type ManualAccount } from "../utils/types/accounts"; +import { networks } from "../utils/constants"; -const accountAtom = atom([]) +const accountAtom = atom([]); -const selectedAccountAtom = atom(null) +const selectedAccountAtom = atom(null); -const networkAtom = atom(networks[0].name) +const networkAtom = atom(networks[0].name); -export { - accountAtom, - selectedAccountAtom, - networkAtom -} +export { accountAtom, selectedAccountAtom, networkAtom }; diff --git a/plugin/src/atoms/remixClient.ts b/plugin/src/atoms/remixClient.ts index 23958826..d576dedf 100644 --- a/plugin/src/atoms/remixClient.ts +++ b/plugin/src/atoms/remixClient.ts @@ -1,6 +1,5 @@ -import { atom } from 'jotai' +import { atom } from "jotai"; -// Is plugin loaded -const pluginLoaded = atom(true) +const pluginLoaded = atom(true); -export { pluginLoaded } +export { pluginLoaded }; diff --git a/plugin/src/atoms/transactions.ts b/plugin/src/atoms/transactions.ts index e3b85433..727378b7 100644 --- a/plugin/src/atoms/transactions.ts +++ b/plugin/src/atoms/transactions.ts @@ -1,7 +1,6 @@ -import { atom } from 'jotai' -import { type Transaction } from '../utils/types/transaction' +import { atom } from "jotai"; +import { type Transaction } from "../utils/types/transaction"; -// Transaction History Context state variables -const transactions = atom([]) +const transactions = atom([]); -export default transactions +export default transactions; diff --git a/plugin/src/components/BackgroundNotices/index.tsx b/plugin/src/components/BackgroundNotices/index.tsx index b26062af..2f0c41c7 100644 --- a/plugin/src/components/BackgroundNotices/index.tsx +++ b/plugin/src/components/BackgroundNotices/index.tsx @@ -1,34 +1,37 @@ -import { nanoid } from 'nanoid' -import React from 'react' -import './style.css' +import { nanoid } from "nanoid"; +import React from "react"; +import "./style.css"; const Notices = [ - 'The starknet Remix Plugin is in Alpha', - 'Cairo contracts and Scarb workspaces are compiled on a server hosted by Nethermind', - 'Declaration of contracts with some wallets will be supported when they update to the latest starknet.js version', - 'Sepolia support is experimental' -] + "The starknet Remix Plugin is in Alpha", + "Cairo contracts and Scarb workspaces are compiled on a server hosted by Nethermind", + "Declaration of contracts with some wallets will be supported when they update to the latest starknet.js version", + "Sepolia support is experimental" +]; const BackgroundNotices: React.FC = () => { - return ( -
-

Notices

- { -
    - {Notices.map((notice, index) => { - return ( -
  • - - {index + 1} - - {notice} -
  • - ) - })} -
- } -
- ) -} + return ( +
+

Notices

+ { +
    + {Notices.map((notice, index) => { + return ( +
  • + + {index + 1} + + {notice} +
  • + ); + })} +
+ } +
+ ); +}; -export default BackgroundNotices +export default BackgroundNotices; diff --git a/plugin/src/components/Card/index.tsx b/plugin/src/components/Card/index.tsx index 0533f12b..7916ac68 100644 --- a/plugin/src/components/Card/index.tsx +++ b/plugin/src/components/Card/index.tsx @@ -1,23 +1,23 @@ -import { type ReactNode } from 'react' -import React from 'react' -import './card.css' +import { type ReactNode } from "react"; +import React from "react"; +import "./card.css"; export interface CardProps { - header?: string - rightItem?: ReactNode - children: ReactNode + header?: string; + rightItem?: ReactNode; + children: ReactNode; } export const Card: React.FC = ({ header, children, rightItem }) => { - return ( -
- {header !== undefined && ( -
-
{header}
- {rightItem} -
- )} -
{children}
-
- ) -} + return ( +
+ {header !== undefined && ( +
+
{header}
+ {rightItem} +
+ )} +
{children}
+
+ ); +}; diff --git a/plugin/src/components/CompiledContracts/index.tsx b/plugin/src/components/CompiledContracts/index.tsx index a929554a..fcf9f576 100644 --- a/plugin/src/components/CompiledContracts/index.tsx +++ b/plugin/src/components/CompiledContracts/index.tsx @@ -1,97 +1,129 @@ -import React, { useState } from 'react' +import React, { useState } from "react"; +import { getContractNameFromFullName, getShortenedHash } from "../../utils/utils"; +import { useAtom } from "jotai"; import { - getContractNameFromFullName, - getShortenedHash -} from '../../utils/utils' -import { useAtom } from 'jotai' -import { compiledContractsAtom, selectedCompiledContract } from '../../atoms/compiledContracts' -import * as Select from '../../components/ui_components/Select' -import { ChevronDownIcon, TrashIcon } from 'lucide-react' + compiledContractsAtom, + selectedCompiledContract, + deployedContractsAtom, + selectedDeployedContract +} from "../../atoms/compiledContracts"; +import * as Select from "../../components/ui_components/Select"; +import { ChevronDownIcon, TrashIcon } from "lucide-react"; interface CompiledContractsProps { - show: 'class' | 'contract' + show: "class" | "contract"; } const CompiledContracts: React.FC = (props): JSX.Element => { - const [contracts, setContracts] = useAtom(compiledContractsAtom) - const [selectedContract, setSelectedContract] = useAtom(selectedCompiledContract) + const [compiledContracts, setCompiledContracts] = useAtom(compiledContractsAtom); + const [selectedCompiled, setSelectedCompiled] = useAtom(selectedCompiledContract); + const [deployedContracts, setDeployedContracts] = useAtom(deployedContractsAtom); + const [selectedDeployed, setSelectedDeployed] = useAtom(selectedDeployedContract); - const [selectedContractIdx, setSelectedContractIdx] = useState('0') + const contracts = props.show === "class" ? compiledContracts : deployedContracts; + const selectedContract = props.show === "class" ? selectedCompiled : selectedDeployed; + const setSelectedContract = props.show === "class" ? setSelectedCompiled : setSelectedDeployed; + const setContracts = props.show === "class" ? setCompiledContracts : setDeployedContracts; - const handleCompiledContractSelectionChange = (value: string): void => { - console.log('handleCompiledContractSelectionChange', value) - setSelectedContract(contracts[parseInt(value)]) - setSelectedContractIdx(value) - } + const [selectedContractIdx, setSelectedContractIdx] = useState("0"); - const handleDeleteContract = (event: React.MouseEvent, index: number): void => { - event.stopPropagation() - setContracts((prevContracts) => prevContracts.filter((_, i) => i !== index)) + const handleContractSelectionChange = (value: string): void => { + console.log("handleContractSelectionChange", value); + setSelectedContract(contracts[parseInt(value)]); + setSelectedContractIdx(value); + }; - if (contracts.length === 0) { - setSelectedContract(null) - } else { - setSelectedContract(contracts[0]) - setSelectedContractIdx('0') - } - } + const handleDeleteContract = ( + event: React.MouseEvent, + index: number + ): void => { + event.stopPropagation(); + setContracts((prevContracts) => prevContracts.filter((_, i) => i !== index)); - if (contracts.length === 0) return <> + if (contracts.length === 0) { + setSelectedContract(null); + } else { + setSelectedContract(contracts[0]); + setSelectedContractIdx("0"); + } + }; - return ( - { handleCompiledContractSelectionChange(value) }}> - - - - - - - - - - {contracts.map((contract, index) => ( - - {`${getContractNameFromFullName(contract.name)} (${getShortenedHash( - contract.classHash ?? '', - 6, - 4 - )})`} - - ))} - - - - - ) -} + if (contracts.length === 0) return <>; -const SelectItemWithDelete = React.forwardRef( - ({ children, onDelete, index, value, ...props }: any, ref: React.Ref): JSX.Element => ( -
- - {children} - + return ( + { + handleContractSelectionChange(value); + }} + > + + + + + + + + + + {contracts.map((contract, index) => ( + + {`${getContractNameFromFullName(contract.name)} (${props.show === "class" + ? getShortenedHash( + contract.classHash ?? "", + 6, + 4 + ) + : getShortenedHash( + contract.address ?? "", + 6, + 4 + )})`} + + ))} + + + + + ); +}; - +const SelectItemWithDelete = React.forwardRef( + ( + { children, onDelete, index, value, ...props }: any, + ref: React.Ref + ): JSX.Element => ( +
+ + {children} + -
- ) -) + +
+ ) +); -SelectItemWithDelete.displayName = 'SelectItemWithDelete' +SelectItemWithDelete.displayName = "SelectItemWithDelete"; -export default CompiledContracts +export default CompiledContracts; diff --git a/plugin/src/components/CurrentEnv/index.tsx b/plugin/src/components/CurrentEnv/index.tsx index 51271ef6..b9cc4ef4 100644 --- a/plugin/src/components/CurrentEnv/index.tsx +++ b/plugin/src/components/CurrentEnv/index.tsx @@ -1,47 +1,54 @@ -import React from 'react' -import './currentEnv.css' -import { useAtomValue } from 'jotai' -import { envAtom, envName, selectedDevnetAccountAtom } from '../../atoms/environment' -import { selectedAccountAtom } from '../../atoms/manualAccount' -import { getShortenedHash } from '../../utils/utils' -import { ethers } from 'ethers' -import { DevnetStatus } from '../DevnetStatus' -import { account } from '../../atoms/connection' +import React from "react"; +import "./currentEnv.css"; +import { useAtomValue } from "jotai"; +import { envAtom, envName, selectedDevnetAccountAtom } from "../../atoms/environment"; +import { selectedAccountAtom } from "../../atoms/manualAccount"; +import { getShortenedHash } from "../../utils/utils"; +import { ethers } from "ethers"; +import { DevnetStatus } from "../DevnetStatus"; +import { account } from "../../atoms/connection"; export const CurrentEnv: React.FC = () => { - const env = useAtomValue(envAtom) + const env = useAtomValue(envAtom); - const selectedAccountManual = useAtomValue(selectedAccountAtom) - const selectedAccountDevnet = useAtomValue(selectedDevnetAccountAtom) - const walletAccount = useAtomValue(account) - // const walletProvider = useAtomValue(provider) + const selectedAccountManual = useAtomValue(selectedAccountAtom); + const selectedAccountDevnet = useAtomValue(selectedDevnetAccountAtom); + const walletAccount = useAtomValue(account); + // const walletProvider = useAtomValue(provider) - const selectedAccount = (env === 'wallet') - ? { address: walletAccount?.address, balance: 0 } - : (env === 'manual') - ? { address: selectedAccountManual?.address, balance: selectedAccountManual?.balance } - : { address: selectedAccountDevnet?.address, balance: selectedAccountDevnet?.initial_balance } + const selectedAccount = + env === "wallet" + ? { address: walletAccount?.address, balance: 0 } + : env === "manual" + ? { + address: selectedAccountManual?.address, + balance: selectedAccountManual?.balance + } + : { + address: selectedAccountDevnet?.address, + balance: selectedAccountDevnet?.initial_balance + }; - const selectedAccountAddress = (selectedAccount.address != null) - ? getShortenedHash(selectedAccount.address, 6, 4) - : 'No account selected' + const selectedAccountAddress = + selectedAccount.address != null + ? getShortenedHash(selectedAccount.address, 6, 4) + : "No account selected"; - const selectedAccountBalance = ethers.utils.formatEther(selectedAccount.balance ?? 0) + const selectedAccountBalance = ethers.utils.formatEther(selectedAccount.balance ?? 0); - return ( - //
{ envName(env) }, { selectedAccountAddress }, { selectedAccountBalance } ETH
-
-
- -
-
- - {envName(env)} - - - {selectedAccountAddress} {(selectedAccount != null) ? `(${selectedAccountBalance} ETH)` : ''} - -
-
- ) -} + return ( + //
{ envName(env) }, { selectedAccountAddress }, { selectedAccountBalance } ETH
+
+
+ +
+
+ {envName(env)} + + {selectedAccountAddress}{" "} + {selectedAccount != null ? `(${selectedAccountBalance} ETH)` : ""} + +
+
+ ); +}; diff --git a/plugin/src/components/DevnetAccountSelector/index.tsx b/plugin/src/components/DevnetAccountSelector/index.tsx index b9d94585..22b4924e 100644 --- a/plugin/src/components/DevnetAccountSelector/index.tsx +++ b/plugin/src/components/DevnetAccountSelector/index.tsx @@ -1,253 +1,125 @@ +import { getRoundedNumber, getShortenedHash, weiToEth } from "../../utils/utils"; +import React, { useState } from "react"; +import { Account, RpcProvider } from "starknet"; +import { MdCheck, MdCopyAll } from "react-icons/md"; +import "./devnetAccountSelector.css"; +import copy from "copy-to-clipboard"; +import { useAtom, useAtomValue } from "jotai"; import { - getRoundedNumber, - getShortenedHash, - weiToEth -} from '../../utils/utils' -import { getAccounts } from '../../utils/network' -import React, { useEffect, useState } from 'react' -import { Account, RpcProvider } from 'starknet' -import { MdCheck, MdCopyAll, MdRefresh } from 'react-icons/md' -import './devnetAccountSelector.css' -import copy from 'copy-to-clipboard' -import { useAtom, useAtomValue, useSetAtom } from 'jotai' -import { availableDevnetAccountsAtom, devnetAtom, envAtom, isDevnetAliveAtom, selectedDevnetAccountAtom } from '../../atoms/environment' -import useAccount from '../../hooks/useAccount' -import useProvider from '../../hooks/useProvider' -import useRemixClient from '../../hooks/useRemixClient' -import { BsCheck, BsChevronDown } from 'react-icons/bs' -import { declTxHashAtom, deployTxHashAtom } from '../../atoms/deployment' -import { invokeTxHashAtom } from '../../atoms/interaction' -import * as Select from '../../components/ui_components/Select' + availableDevnetAccountsAtom, + devnetAtom, + envAtom, + isDevnetAliveAtom, + selectedDevnetAccountAtom +} from "../../atoms/environment"; +import useAccount from "../../hooks/useAccount"; +import useProvider from "../../hooks/useProvider"; +import { BsCheck, BsChevronDown } from "react-icons/bs"; +import * as Select from "../../components/ui_components/Select"; const DevnetAccountSelector: React.FC = () => { - const { account, setAccount } = useAccount() - const { provider, setProvider } = useProvider() - const { remixClient } = useRemixClient() - const env = useAtomValue(envAtom) - const devnet = useAtomValue(devnetAtom) - const [isDevnetAlive, setIsDevnetAlive] = useAtom(isDevnetAliveAtom) - const [selectedDevnetAccount, setSelectedDevnetAccount] = useAtom(selectedDevnetAccountAtom) - const [availableDevnetAccounts, setAvailableDevnetAccounts] = useAtom(availableDevnetAccountsAtom) - - const setDeclTxHash = useSetAtom(declTxHashAtom) - const setDeployTxHash = useSetAtom(deployTxHashAtom) - const setInvokeTxHash = useSetAtom(invokeTxHashAtom) - - const checkDevnetUrl = async (): Promise => { - try { - const isKatanaEnv = (env === 'localKatanaDevnet') - const response = await fetch(`${devnet.url}/${isKatanaEnv ? '' : 'is_alive'}`, { - method: 'GET', - redirect: 'follow', - headers: { - 'Content-Type': 'application/json' - } - }) - const status = await response.text() - if (isKatanaEnv) { - const jsonStatus: { health: boolean } = JSON.parse(status) - if (jsonStatus.health) { - setIsDevnetAlive(true) - } else { - setIsDevnetAlive(false) - } - } else if (status !== 'Alive!!!' || response.status !== 200) { - setIsDevnetAlive(false) - } else { - setIsDevnetAlive(true) - } - } catch (error) { - setIsDevnetAlive(false) - } - } - - // devnet live status - useEffect(() => { - const interval = setInterval(() => { - checkDevnetUrl().catch(e => { - console.error(e) - }) - }, 3000) - return () => { - clearInterval(interval) - } - }, [devnet]) - - const notifyDevnetStatus = async (): Promise => { - try { - await remixClient.call( - 'notification' as any, - 'toast', - `❗️ Server ${devnet.name} - ${devnet.url} is not healthy or not reachable at the moment` - ) - } catch (e) { - console.error(e) - } - } - - useEffect(() => { - if (!isDevnetAlive) { - notifyDevnetStatus().catch((e) => { - console.error(e) - }) - } - }, [isDevnetAlive]) - - const refreshDevnetAccounts = async (): Promise => { - setAccountRefreshing(true) - try { - const accounts = await getAccounts(devnet.url, (env === 'localKatanaDevnet')) - if ( - JSON.stringify(accounts) !== JSON.stringify(availableDevnetAccounts) - ) { - if (accounts !== undefined) setAvailableDevnetAccounts(accounts) - else setAvailableDevnetAccounts([]) - } - } catch (e) { - setAvailableDevnetAccounts([]) - await remixClient.terminal.log({ - type: 'error', - value: `Failed to get accounts information from ${devnet.url}` - }) - } - setAccountRefreshing(false) - } - - useEffect(() => { - setTimeout(() => { - if (!isDevnetAlive) { - setAvailableDevnetAccounts([]) - setAccount(null) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - setSelectedDevnetAccount(null) - } else { - refreshDevnetAccounts().catch(e => { - console.error(e) - }) - } - }, 1) - }, [devnet, isDevnetAlive]) - - useEffect(() => { - if ( - !( - selectedDevnetAccount !== null && - availableDevnetAccounts.includes(selectedDevnetAccount) - ) && - (availableDevnetAccounts.length > 0) - ) { - setSelectedDevnetAccount(availableDevnetAccounts[0]) - } - }, [availableDevnetAccounts, devnet]) - - useEffect(() => { - const newProvider = new RpcProvider({ nodeUrl: devnet.url }) - if (selectedDevnetAccount != null) { - setAccount( - new Account( - newProvider, - selectedDevnetAccount.address, - selectedDevnetAccount.private_key - ) - ) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - } - setProvider(newProvider) - }, [devnet, selectedDevnetAccount]) - - function handleAccountChange (value: number): void { - if (value === -1) { - return - } - setAccountIdx(value) - setSelectedDevnetAccount(availableDevnetAccounts[value]) - const newProvider = new RpcProvider({ nodeUrl: devnet.url }) - if (provider == null) setProvider(newProvider) - setAccount( - new Account( - provider ?? newProvider, - availableDevnetAccounts[value].address, - availableDevnetAccounts[value].private_key - ) - ) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - } - - const [accountRefreshing, setAccountRefreshing] = useState(false) - const [showCopied, setCopied] = useState(false) - - const [accountIdx, setAccountIdx] = useState(0) - - useEffect(() => { - setAccountIdx(0) - }, [env]) - - return ( -
- -
- { handleAccountChange(parseInt(value)) }}> - - - {(availableDevnetAccounts !== undefined && availableDevnetAccounts.length !== 0 && availableDevnetAccounts[accountIdx]?.address !== undefined) - ? getShortenedHash(availableDevnetAccounts[accountIdx]?.address, 6, 4) - : 'No accounts found'} - - - - - - - - - {isDevnetAlive && (availableDevnetAccounts !== undefined && availableDevnetAccounts.length > 0) - ? availableDevnetAccounts.map((account, index) => ( - - -
-
{`${getShortenedHash(account.address ?? '', 6, 4)} (${getRoundedNumber(weiToEth((env === 'localKatanaDevnet') ? account.balance : account.initial_balance), 2)} ether)`}
- {accountIdx === index && } -
-
-
- )) - : No accounts found} -
-
-
-
-
- -
- -
-
- ) -} - -export default DevnetAccountSelector + const { account, setAccount } = useAccount(); + const { provider, setProvider } = useProvider(); + const env = useAtomValue(envAtom); + const devnet = useAtomValue(devnetAtom); + const isDevnetAlive = useAtomValue(isDevnetAliveAtom); + const [, setSelectedDevnetAccount] = useAtom(selectedDevnetAccountAtom); + const availableDevnetAccounts = useAtomValue(availableDevnetAccountsAtom); + + const [showCopied, setCopied] = useState(false); + const [accountIdx, setAccountIdx] = useState(0); + + function handleAccountChange(value: number): void { + if (value === -1) { + return; + } + setAccountIdx(value); + setSelectedDevnetAccount(availableDevnetAccounts[value]); + const newProvider = new RpcProvider({ nodeUrl: devnet.url }); + if (provider == null) setProvider(newProvider); + setAccount( + new Account( + provider ?? newProvider, + availableDevnetAccounts[value].address, + availableDevnetAccounts[value].private_key + ) + ); + } + + return ( +
+ +
+ { + handleAccountChange(parseInt(value)); + }} + > + + + {availableDevnetAccounts !== undefined && + availableDevnetAccounts.length !== 0 && + availableDevnetAccounts[accountIdx]?.address !== undefined + ? getShortenedHash( + availableDevnetAccounts[accountIdx]?.address, + 6, + 4 + ) + : "No accounts found"} + + + + + + + + + {isDevnetAlive && + availableDevnetAccounts !== undefined && + availableDevnetAccounts.length > 0 + ? ( + availableDevnetAccounts.map((account, index) => ( + + +
+
{`${getShortenedHash(account.address ?? "", 6, 4)} (${getRoundedNumber(weiToEth(env === "localKatanaDevnet" ? account.balance : account.initial_balance), 2)} ether)`}
+ {accountIdx === index && } +
+
+
+ )) + ) + : ( + + No accounts found + + )} +
+
+
+
+
+ +
+
+
+ ); +}; + +export default DevnetAccountSelector; diff --git a/plugin/src/components/DevnetStatus/index.tsx b/plugin/src/components/DevnetStatus/index.tsx index 933c4461..af802657 100644 --- a/plugin/src/components/DevnetStatus/index.tsx +++ b/plugin/src/components/DevnetStatus/index.tsx @@ -1,37 +1,25 @@ -import { RxDotFilled } from 'react-icons/rx' -import React from 'react' -import { useAtomValue } from 'jotai' -import { envAtom, isDevnetAliveAtom } from '../../atoms/environment' +import { RxDotFilled } from "react-icons/rx"; +import React from "react"; +import { useAtomValue } from "jotai"; +import { envAtom, isDevnetAliveAtom } from "../../atoms/environment"; export const DevnetStatus: React.FC = () => { - const env = useAtomValue(envAtom) - const isDevnetAlive = useAtomValue(isDevnetAliveAtom) + const env = useAtomValue(envAtom); + const isDevnetAlive = useAtomValue(isDevnetAliveAtom); - return ( - <> - {env === 'wallet' - ? ( - - ) - : isDevnetAlive - ? ( - - ) - : ( - - )} - - ) -} + return ( + <> + {env === "wallet" + ? ( + + ) + : isDevnetAlive + ? ( + + ) + : ( + + )} + + ); +}; diff --git a/plugin/src/components/EnvCard/index.tsx b/plugin/src/components/EnvCard/index.tsx index 25aa9f1f..4ff1aa11 100644 --- a/plugin/src/components/EnvCard/index.tsx +++ b/plugin/src/components/EnvCard/index.tsx @@ -1,56 +1,56 @@ /* eslint-disable react/prop-types */ -import { type DisconnectOptions } from 'get-starknet' -import { type ReactNode, useState } from 'react' -import React from 'react' -import './envCard.css' -import { useAtomValue } from 'jotai' -import { envAtom } from '../../atoms/environment' +import { type DisconnectOptions } from "get-starknet"; +import { type ReactNode, useState } from "react"; +import React from "react"; +import "./envCard.css"; +import { useAtomValue } from "jotai"; +import { envAtom } from "../../atoms/environment"; interface EnvCardProps { - header: string - setEnv: (env: string) => void - disconnectWalletHandler: (options?: DisconnectOptions) => Promise - children: ReactNode + header: string; + setEnv: (env: string) => void; + disconnectWalletHandler: (options?: DisconnectOptions) => Promise; + children: ReactNode; } export const EnvCard: React.FC = ({ - header, - setEnv, - disconnectWalletHandler, - children + header, + setEnv, + disconnectWalletHandler, + children }) => { - const env = useAtomValue(envAtom) - const [prevEnv, setPrevEnv] = useState(env) + const env = useAtomValue(envAtom); + const [prevEnv, setPrevEnv] = useState(env); - return ( -
- {header !== '' && ( -
- {/*
{header}
*/} - - -
- )} -
{children}
-
- ) -} + return ( +
+ {header !== "" && ( +
+ {/*
{header}
*/} + + +
+ )} +
{children}
+
+ ); +}; diff --git a/plugin/src/components/EnvironmentSelector/index.tsx b/plugin/src/components/EnvironmentSelector/index.tsx index da966637..30351019 100644 --- a/plugin/src/components/EnvironmentSelector/index.tsx +++ b/plugin/src/components/EnvironmentSelector/index.tsx @@ -1,77 +1,83 @@ -import React from 'react' -import { devnets } from '../../utils/network' +import React from "react"; +import { devnets } from "../../utils/network"; -import './styles.css' -import { devnetAtom, envAtom } from '../../atoms/environment' -import { useAtom, useSetAtom } from 'jotai' -import useProvider from '../../hooks/useProvider' -import { BsChevronDown } from 'react-icons/bs' -import * as Select from '../../components/ui_components/Select' +import "./styles.css"; +import { devnetAtom, envAtom } from "../../atoms/environment"; +import { useAtom, useSetAtom } from "jotai"; +import useProvider from "../../hooks/useProvider"; +import { BsChevronDown } from "react-icons/bs"; +import * as Select from "../../components/ui_components/Select"; const EnvironmentSelector: React.FC = () => { - const { setProvider } = useProvider() - const [env, setEnv] = useAtom(envAtom) - const setDevnet = useSetAtom(devnetAtom) + const { setProvider } = useProvider(); + const [env, setEnv] = useAtom(envAtom); + const setDevnet = useSetAtom(devnetAtom); - const handleEnvironmentChange = (ipValue: string): void => { - const value = parseInt(ipValue) - if (!isNaN(value) && value > 0) { - setDevnet(devnets[value - 1]) - switch (value) { - case 1: - setEnv('localDevnet') - break - case 2: - setEnv('remoteDevnet') - break - case 3: - setEnv('localKatanaDevnet') - break - } - setProvider(null) - return - } - setEnv('wallet') - } + const handleEnvironmentChange = (ipValue: string): void => { + const value = parseInt(ipValue); + if (!isNaN(value) && value > 0) { + setDevnet(devnets[value - 1]); + switch (value) { + case 1: + setEnv("localDevnet"); + break; + case 2: + setEnv("remoteDevnet"); + break; + case 3: + setEnv("localKatanaDevnet"); + break; + } + setProvider(null); + return; + } + setEnv("wallet"); + }; - const getActiveEnv = (lEnv: typeof env): string => { - switch (lEnv) { - case 'manual': return 'Manual' - case 'localDevnet': return 'Local Devnet' - case 'remoteDevnet': return 'Remote Devnet' - case 'localKatanaDevnet': return 'Local Katana Devnet' - case 'wallet': return 'Wallet' - } - } + const getActiveEnv = (lEnv: typeof env): string => { + switch (lEnv) { + case "manual": + return "Manual"; + case "localDevnet": + return "Local Devnet"; + case "remoteDevnet": + return "Remote Devnet"; + case "localKatanaDevnet": + return "Local Katana Devnet"; + case "wallet": + return "Wallet"; + } + }; - return ( -
- - - - {getActiveEnv(env)} - - - - - + return ( +
+ + + {getActiveEnv(env)} + + + + - - - - - Wallet - - {devnets.map((devnet, i) => ( - - {devnet?.name} - - ))} - - - - -
- ) -} + + + + + Wallet + + {devnets.map((devnet, i) => ( + + {devnet?.name} + + ))} + + + +
+
+ ); +}; -export default EnvironmentSelector +export default EnvironmentSelector; diff --git a/plugin/src/components/ExplorerSelector/index.tsx b/plugin/src/components/ExplorerSelector/index.tsx index 57e696ba..b35a9210 100644 --- a/plugin/src/components/ExplorerSelector/index.tsx +++ b/plugin/src/components/ExplorerSelector/index.tsx @@ -1,86 +1,87 @@ -import React from 'react' -import { networkExplorerUrls as EXPLORERS } from '../../utils/constants' +import React from "react"; +import { networkExplorerUrls as EXPLORERS } from "../../utils/constants"; -import './index.css' -import { type IExplorerSelector, type IUseCurrentExplorer } from '../../utils/misc' -import { BsChevronDown } from 'react-icons/bs' -import { useAtom } from 'jotai' -import { currentExplorerAtom } from '../../atoms/explorer' -import * as Select from '../ui_components/Select' +import "./index.css"; +import { type IExplorerSelector, type IUseCurrentExplorer } from "../../utils/misc"; +import { BsChevronDown } from "react-icons/bs"; +import { useAtom } from "jotai"; +import { currentExplorerAtom } from "../../atoms/explorer"; +import * as Select from "../ui_components/Select"; -const VOYAGER_LOGO = 'https://voyager.online/favicons/favicon-32x32.png' -const STARKSCAN_LOGO = 'https://starkscan.co/img/company/favicon.ico' +const VOYAGER_LOGO = "https://voyager.online/favicons/favicon-32x32.png"; +const STARKSCAN_LOGO = "https://starkscan.co/img/company/favicon.ico"; const explorerToLogo = (explorer: keyof typeof EXPLORERS): string => { - switch (explorer) { - case 'starkscan': - return STARKSCAN_LOGO - case 'voyager': - default: - return VOYAGER_LOGO - } -} + switch (explorer) { + case "starkscan": + return STARKSCAN_LOGO; + case "voyager": + default: + return VOYAGER_LOGO; + } +}; export const useCurrentExplorer = (): IUseCurrentExplorer => { - const [currentExplorerKey, setCurrentExplorerKey] = - useAtom(currentExplorerAtom) + const [currentExplorerKey, setCurrentExplorerKey] = useAtom(currentExplorerAtom); - return { - explorer: currentExplorerKey, - setExplorer: setCurrentExplorerKey - } -} + return { + explorer: currentExplorerKey, + setExplorer: setCurrentExplorerKey + }; +}; -const ExplorerSelector: React.FC = ({ - isInline, - controlHook -}) => { - const { explorer, setExplorer } = controlHook - return ( -
{ - e.stopPropagation() - }} - > -
- { setExplorer(value as any) }}> - - -
{controlHook.explorer}
-
- -
- -
- -
-
-
- - - - {Object.keys(EXPLORERS).map((v, i) => ( - - - -

{v}

-
-
- ))} -
-
-
-
-
-
- ) -} +const ExplorerSelector: React.FC = ({ isInline, controlHook }) => { + const { explorer, setExplorer } = controlHook; + return ( +
{ + e.stopPropagation(); + }} + > +
+ { + setExplorer(value as any); + }} + > + + +
{controlHook.explorer}
+
+ +
+ +
+ +
+
+
+ + + + {Object.keys(EXPLORERS).map((v, i) => ( + + + +

{v}

+
+
+ ))} +
+
+
+
+
+
+ ); +}; -export default ExplorerSelector +export default ExplorerSelector; diff --git a/plugin/src/components/JSONView/index.tsx b/plugin/src/components/JSONView/index.tsx index d740cc65..8332250b 100644 --- a/plugin/src/components/JSONView/index.tsx +++ b/plugin/src/components/JSONView/index.tsx @@ -1,46 +1,50 @@ -import React, { useState } from 'react' +import React, { useState } from "react"; const JSONView = (data: JSON): JSX.Element => { - const [expandedRows, setExpandedRows] = useState([]) + const [expandedRows, setExpandedRows] = useState([]); - const toggleRow = (rowId: string): void => { - const isExpanded = expandedRows.includes(rowId) - if (isExpanded) { - setExpandedRows(expandedRows.filter((id) => id !== rowId)) - } else { - setExpandedRows([...expandedRows, rowId]) - } - } + const toggleRow = (rowId: string): void => { + const isExpanded = expandedRows.includes(rowId); + if (isExpanded) { + setExpandedRows(expandedRows.filter((id) => id !== rowId)); + } else { + setExpandedRows([...expandedRows, rowId]); + } + }; - const renderRow = (row: { key: string, value: any }, level = 0): JSX.Element => { - console.log(row) - const rowId = `${level}-${row.key}` + const renderRow = (row: { key: string; value: any }, level = 0): JSX.Element => { + console.log(row); + const rowId = `${level}-${row.key}`; - return ( - - - { toggleRow(rowId) }}> - {expandedRows.includes(rowId) ? '-' : '+'} - - {row.key} - {row.value} - - - ) - } + return ( + + + { + toggleRow(rowId); + }} + > + {expandedRows.includes(rowId) ? "-" : "+"} + + {row.key} + {row.value} + + + ); + }; - return ( - - - - - - - - - {Object.entries(data).map(([key, value]) => renderRow({ key, value }))} -
KeyValue
- ) -} + return ( + + + + + + + + + {Object.entries(data).map(([key, value]) => renderRow({ key, value }))} +
KeyValue
+ ); +}; -export default JSONView +export default JSONView; diff --git a/plugin/src/components/LoadingDots/index.tsx b/plugin/src/components/LoadingDots/index.tsx new file mode 100644 index 00000000..ce9ce0b0 --- /dev/null +++ b/plugin/src/components/LoadingDots/index.tsx @@ -0,0 +1,25 @@ +import React, { useEffect, useState } from "react"; + +const LoadingDots: React.FC<{ message: string }> = ({ message }) => { + const [dots, setDots] = useState(""); + + useEffect(() => { + const interval = setInterval(() => { + setDots((prev) => { + return prev.length >= 3 ? "" : prev + "."; + }); + }, 500); + + return () => { + clearInterval(interval); + }; + }, []); + + return ( + + {message}{dots} + + ); +}; + +export default LoadingDots; diff --git a/plugin/src/components/ManualAccount/index.tsx b/plugin/src/components/ManualAccount/index.tsx index e7a1b45f..8fe2ac21 100644 --- a/plugin/src/components/ManualAccount/index.tsx +++ b/plugin/src/components/ManualAccount/index.tsx @@ -1,517 +1,491 @@ -import React, { useEffect, useState } from 'react' -import { Account, CallData, RpcProvider, ec, hash, stark } from 'starknet' +import React, { useEffect, useState } from "react"; +import { Account, CallData, RpcProvider, ec, hash, stark } from "starknet"; import { - type Network, - networks as networkConstants, - networkEquivalents -} from '../../utils/constants' -import { ethers } from 'ethers' - -import storage from '../../utils/storage' - -import './index.css' -import { BiCopy, BiPlus } from 'react-icons/bi' -import { getExplorerUrl, getShortenedHash, trimStr } from '../../utils/utils' -import { MdRefresh, MdCheckCircleOutline, MdCheck } from 'react-icons/md' -import copy from 'copy-to-clipboard' -import { useCurrentExplorer } from '../ExplorerSelector' -import { useAtom, useAtomValue, useSetAtom } from 'jotai' - -import transactionsAtom from '../../atoms/transactions' -import { - accountAtom, - networkAtom, - selectedAccountAtom -} from '../../atoms/manualAccount' -import { envAtom } from '../../atoms/environment' -import useAccount from '../../hooks/useAccount' -import useProvider from '../../hooks/useProvider' -import useRemixClient from '../../hooks/useRemixClient' -import { getProvider } from '../../utils/misc' -import { declTxHashAtom, deployTxHashAtom } from '../../atoms/deployment' -import { invokeTxHashAtom } from '../../atoms/interaction' - -import { BsChevronDown } from 'react-icons/bs' -import * as Select from '../ui_components/Select' + type Network, + networks as networkConstants, + networkEquivalents +} from "../../utils/constants"; +import { ethers } from "ethers"; + +import storage from "../../utils/storage"; + +import "./index.css"; +import { BiCopy, BiPlus } from "react-icons/bi"; +import { getExplorerUrl, getShortenedHash, trimStr } from "../../utils/utils"; +import { MdRefresh, MdCheckCircleOutline, MdCheck } from "react-icons/md"; +import copy from "copy-to-clipboard"; +import { useCurrentExplorer } from "../ExplorerSelector"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; + +import transactionsAtom from "../../atoms/transactions"; +import { accountAtom, networkAtom, selectedAccountAtom } from "../../atoms/manualAccount"; +import { envAtom } from "../../atoms/environment"; +import useAccount from "../../hooks/useAccount"; +import useProvider from "../../hooks/useProvider"; +import useRemixClient from "../../hooks/useRemixClient"; +import { getProvider } from "../../utils/misc"; +import { declTxHashAtom, deployTxHashAtom } from "../../atoms/deployment"; +import { invokeTxHashAtom } from "../../atoms/interaction"; + +import { BsChevronDown } from "react-icons/bs"; +import * as Select from "../ui_components/Select"; const ManualAccount: React.FC = () => { - const OZaccountClassHash = - '0x2794ce20e5f2ff0d40e632cb53845b9f4e526ebd8471983f7dbd355b721d5a' - - const balanceContractAddress = - '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7' - - const { account, setAccount } = useAccount() - const { provider, setProvider } = useProvider() - - const setDeclTxHash = useSetAtom(declTxHashAtom) - const setDeployTxHash = useSetAtom(deployTxHashAtom) - const setInvokeTxHash = useSetAtom(invokeTxHashAtom) - - const [accountDeploying, setAccountDeploying] = useState(false) - const [showCopied, setCopied] = useState(false) - - const { remixClient } = useRemixClient() - - const [transactions, setTransactions] = useAtom(transactionsAtom) - - const env = useAtomValue(envAtom) - - const [accounts, setAccounts] = useAtom(accountAtom) - const [selectedAccount, setSelectedAccount] = useAtom(selectedAccountAtom) - const [networkName, setNetworkName] = useAtom(networkAtom) - - useEffect(() => { - setNetworkName(networkConstants[0].value) - }, [setNetworkName]) - - useEffect(() => { - const prov = getProvider(networkName) - setProvider(prov) - }, [setProvider, networkName]) - - useEffect(() => { - const manualAccounts = storage.get('manualAccounts') - if ( - manualAccounts != null && - accounts.length === 0 && - selectedAccount == null - ) { - const parsedAccounts = JSON.parse(manualAccounts) - setAccounts(parsedAccounts) - } - }) - - useEffect(() => { - if (selectedAccount != null && account != null) { - if (account.address === selectedAccount.address) return - const accountExist = accounts.find( - (acc) => acc.address === selectedAccount.address - ) - if (accountExist != null) { - if (provider != null) { - setAccount( - new Account( - provider, - selectedAccount.address, - selectedAccount.private_key - ) - ) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - } - } - return - } - if (accounts.length > 0) { - setSelectedAccount(accounts[0]) - if (provider != null) { - setAccount( - new Account(provider, accounts[0].address, accounts[0].private_key) - ) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - } - } else { - setSelectedAccount(null) - } - }, [accounts]) - - const updateBalance = async (): Promise => { - if (account != null && provider != null && selectedAccount != null) { - console.log(account, provider) - const resp = await account.callContract({ - contractAddress: balanceContractAddress, - entrypoint: 'balanceOf', - calldata: [account.address] - }) - console.log(resp) - const balance = resp[0] - const newAccount = { ...selectedAccount, balance } - const newAccounts = accounts.map((acc) => { - if (acc.address === selectedAccount.address) { - return newAccount - } - return acc - }) - setSelectedAccount(newAccount) - setAccounts(newAccounts) - setBalanceRefreshing(false) - storage.set('manualAccounts', JSON.stringify(accounts)) - } - } - - useEffect(() => { - updateBalance().catch((err) => { - console.log(err) - }) - }, [account, provider]) - - const createTestnetAccount = async (): Promise => { - const privateKey = stark.randomAddress() - const starkKeyPub = ec.starkCurve.getStarkKey(privateKey) - const OZaccountConstructorCallData = CallData.compile({ - publicKey: starkKeyPub - }) - const OZcontractAddress = hash.calculateContractAddressFromHash( - starkKeyPub, - OZaccountClassHash, - OZaccountConstructorCallData, - 0 - ) - const newAccounts = [ - ...accounts, - { - address: OZcontractAddress, - private_key: privateKey, - public_key: starkKeyPub, - balance: 0, - deployed_networks: [] - } - ] - setAccounts(newAccounts) - storage.set('manualAccounts', JSON.stringify(newAccounts)) - } - - function handleProviderChange ( - networkName: string - ): void { - const chainId = networkEquivalents.get(networkName) - if (chainId) { - setNetworkName(networkName) - setProvider( - new RpcProvider({ - nodeUrl: networkName, - chainId - }) - ) - return - } - setProvider(null) - } - - function handleAccountChange ( - accountIndex: number - ): void { - if (accountIndex === -1) return - const selectedAccount = accounts[accountIndex] - setSelectedAccount(selectedAccount) - if (provider != null) { - setAccount( - new Account( - provider, - selectedAccount.address, - selectedAccount.private_key - ) - ) - setDeclTxHash('') - setDeployTxHash('') - setInvokeTxHash('') - } - } - - async function deployAccountHandler (): Promise { - if (account == null || provider == null || selectedAccount == null) { - return - } - - if (selectedAccount.deployed_networks.includes(networkName)) { - await remixClient.call( - 'notification' as any, - 'toast', - 'ℹ️ Account already deployed on this network' - ) - return - } - - setAccountDeploying(true) - - try { - const OZaccountConstructorCallData = CallData.compile({ - publicKey: await account.signer.getPubKey() - }) - - const { - transaction_hash: transactionHash, - contract_address: contractAddress - } = await account.deployAccount({ - classHash: OZaccountClassHash, - constructorCalldata: OZaccountConstructorCallData, - addressSalt: await account.signer.getPubKey() - }) - - console.log('transaction_hash=', transactionHash) - - await provider.waitForTransaction(transactionHash) - - setTransactions([ - { - type: 'deployAccount', - account, - provider, - txId: transactionHash, - env - }, - ...transactions - ]) - - const newAccount = { - ...selectedAccount, - deployed_networks: [...selectedAccount.deployed_networks, networkName] - } - const newAccounts = accounts.map((acc) => { - if (acc.address === selectedAccount.address) { - return newAccount - } - return acc - }) - setSelectedAccount(newAccount) - setAccounts(newAccounts) - storage.set('manualAccounts', JSON.stringify(newAccounts)) - console.log( - '✅ New OpenZeppelin account created.\n address =', - contractAddress - ) - } catch (e) { - console.error(e) - await remixClient.call( - 'notification' as any, - 'toast', - '❌ Error while deploying account, check account balance' - ) - if (e instanceof Error) { - await remixClient.terminal.log({ - type: 'error', - value: e.message - }) - } - } - setAccountDeploying(false) - } - - const [balanceRefreshing, setBalanceRefreshing] = useState(false) - - const explorerHook = useCurrentExplorer() - - return ( -
-
-
- { handleAccountChange(accounts.findIndex(acc => acc.address === value)) }}> -
- -
- - {(selectedAccount != null) ? trimStr(selectedAccount.address, 6) : 'Select Account'} - - -
-
-
-
-
- - - - {accounts.length > 0 - ? ( - accounts.map((account, index) => ( - - - {trimStr(account.address, 6)} - - - )) - ) - : ( - - - No account created yet - - - )} - - - -
-
- - {/*
*/} - - -
- - {selectedAccount != null && account != null && ( -
- - ADDRESS{' '} - - - - {getShortenedHash(selectedAccount.address, 6, 4)} - - - -
- )} - - {selectedAccount != null && account != null && provider != null && ( -
- - BALANCE{' '} - - - {parseFloat( - ethers.utils.formatEther(selectedAccount.balance) - )?.toFixed(8)}{' '} - ETH - - -
- )} - - {selectedAccount != null && (networkName === 'goerli' || networkName === 'sepolia') && ( - - )} - - { handleProviderChange(value) }}> -
- -
- {networkName} - - - -
-
-
- - - - {networkConstants.map((network) => ( - - - {network.value} - - - ))} - - - -
- - -
- ) -} - -export default ManualAccount + const OZaccountClassHash = "0x2794ce20e5f2ff0d40e632cb53845b9f4e526ebd8471983f7dbd355b721d5a"; + + const balanceContractAddress = + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"; + + const { account, setAccount } = useAccount(); + const { provider, setProvider } = useProvider(); + + const setDeclTxHash = useSetAtom(declTxHashAtom); + const setDeployTxHash = useSetAtom(deployTxHashAtom); + const setInvokeTxHash = useSetAtom(invokeTxHashAtom); + + const [accountDeploying, setAccountDeploying] = useState(false); + const [showCopied, setCopied] = useState(false); + + const { remixClient } = useRemixClient(); + + const [transactions, setTransactions] = useAtom(transactionsAtom); + + const env = useAtomValue(envAtom); + + const [accounts, setAccounts] = useAtom(accountAtom); + const [selectedAccount, setSelectedAccount] = useAtom(selectedAccountAtom); + const [networkName, setNetworkName] = useAtom(networkAtom); + + useEffect(() => { + setNetworkName(networkConstants[0].value); + }, [setNetworkName]); + + useEffect(() => { + const prov = getProvider(networkName); + setProvider(prov); + }, [setProvider, networkName]); + + useEffect(() => { + const manualAccounts = storage.get("manualAccounts"); + if (manualAccounts != null && accounts.length === 0 && selectedAccount == null) { + const parsedAccounts = JSON.parse(manualAccounts); + setAccounts(parsedAccounts); + } + }); + + useEffect(() => { + if (selectedAccount != null && account != null) { + if (account.address === selectedAccount.address) return; + const accountExist = accounts.find((acc) => acc.address === selectedAccount.address); + if (accountExist != null) { + if (provider != null) { + setAccount( + new Account(provider, selectedAccount.address, selectedAccount.private_key) + ); + setDeclTxHash(""); + setDeployTxHash(""); + setInvokeTxHash(""); + } + } + return; + } + if (accounts.length > 0) { + setSelectedAccount(accounts[0]); + if (provider != null) { + setAccount(new Account(provider, accounts[0].address, accounts[0].private_key)); + setDeclTxHash(""); + setDeployTxHash(""); + setInvokeTxHash(""); + } + } else { + setSelectedAccount(null); + } + }, [accounts]); + + const updateBalance = async (): Promise => { + if (account != null && provider != null && selectedAccount != null) { + console.log(account, provider); + const resp = await account.callContract({ + contractAddress: balanceContractAddress, + entrypoint: "balanceOf", + calldata: [account.address] + }); + console.log(resp); + const balance = resp[0]; + const newAccount = { ...selectedAccount, balance }; + const newAccounts = accounts.map((acc) => { + if (acc.address === selectedAccount.address) { + return newAccount; + } + return acc; + }); + setSelectedAccount(newAccount); + setAccounts(newAccounts); + setBalanceRefreshing(false); + storage.set("manualAccounts", JSON.stringify(accounts)); + } + }; + + useEffect(() => { + updateBalance().catch((err) => { + console.log(err); + }); + }, [account, provider]); + + const createTestnetAccount = async (): Promise => { + const privateKey = stark.randomAddress(); + const starkKeyPub = ec.starkCurve.getStarkKey(privateKey); + const OZaccountConstructorCallData = CallData.compile({ + publicKey: starkKeyPub + }); + const OZcontractAddress = hash.calculateContractAddressFromHash( + starkKeyPub, + OZaccountClassHash, + OZaccountConstructorCallData, + 0 + ); + const newAccounts = [ + ...accounts, + { + address: OZcontractAddress, + private_key: privateKey, + public_key: starkKeyPub, + balance: 0, + deployed_networks: [] + } + ]; + setAccounts(newAccounts); + storage.set("manualAccounts", JSON.stringify(newAccounts)); + }; + + function handleProviderChange(networkName: string): void { + const chainId = networkEquivalents.get(networkName); + if (chainId) { + setNetworkName(networkName); + setProvider( + new RpcProvider({ + nodeUrl: networkName, + chainId + }) + ); + return; + } + setProvider(null); + } + + function handleAccountChange(accountIndex: number): void { + if (accountIndex === -1) return; + const selectedAccount = accounts[accountIndex]; + setSelectedAccount(selectedAccount); + if (provider != null) { + setAccount(new Account(provider, selectedAccount.address, selectedAccount.private_key)); + setDeclTxHash(""); + setDeployTxHash(""); + setInvokeTxHash(""); + } + } + + async function deployAccountHandler(): Promise { + if (account == null || provider == null || selectedAccount == null) { + return; + } + + if (selectedAccount.deployed_networks.includes(networkName)) { + await remixClient.call( + "notification" as any, + "toast", + "ℹ️ Account already deployed on this network" + ); + return; + } + + setAccountDeploying(true); + + try { + const OZaccountConstructorCallData = CallData.compile({ + publicKey: await account.signer.getPubKey() + }); + + const { transaction_hash: transactionHash, contract_address: contractAddress } = + await account.deployAccount({ + classHash: OZaccountClassHash, + constructorCalldata: OZaccountConstructorCallData, + addressSalt: await account.signer.getPubKey() + }); + + console.log("transaction_hash=", transactionHash); + + await provider.waitForTransaction(transactionHash); + + setTransactions([ + { + type: "deployAccount", + account, + provider, + txId: transactionHash, + env + }, + ...transactions + ]); + + const newAccount = { + ...selectedAccount, + deployed_networks: [...selectedAccount.deployed_networks, networkName] + }; + const newAccounts = accounts.map((acc) => { + if (acc.address === selectedAccount.address) { + return newAccount; + } + return acc; + }); + setSelectedAccount(newAccount); + setAccounts(newAccounts); + storage.set("manualAccounts", JSON.stringify(newAccounts)); + console.log("✅ New OpenZeppelin account created.\n address =", contractAddress); + } catch (e) { + console.error(e); + await remixClient.call( + "notification" as any, + "toast", + "❌ Error while deploying account, check account balance" + ); + if (e instanceof Error) { + await remixClient.terminal.log({ + type: "error", + value: e.message + }); + } + } + setAccountDeploying(false); + } + + const [balanceRefreshing, setBalanceRefreshing] = useState(false); + + const explorerHook = useCurrentExplorer(); + + return ( +
+
+
+ { + handleAccountChange(accounts.findIndex((acc) => acc.address === value)); + }} + > +
+ +
+ + {selectedAccount != null + ? trimStr(selectedAccount.address, 6) + : "Select Account"} + + +
+ +
+
+
+
+
+ + + + {accounts.length > 0 + ? ( + accounts.map((account, index) => ( + + + {trimStr(account.address, 6)} + + + )) + ) + : ( + + + No account created yet + + + )} + + + +
+
+ + {/*
*/} + + +
+ + {selectedAccount != null && account != null && ( +
+ ADDRESS + + + {getShortenedHash(selectedAccount.address, 6, 4)} + + + +
+ )} + + {selectedAccount != null && account != null && provider != null && ( +
+ BALANCE + + {parseFloat(ethers.utils.formatEther(selectedAccount.balance))?.toFixed(8)}{" "} + ETH + + +
+ )} + + {selectedAccount != null && (networkName === "goerli" || networkName === "sepolia") && ( + + )} + + { + handleProviderChange(value); + }} + > +
+ +
+ {networkName} + + + +
+
+
+ + + + {networkConstants.map((network) => ( + + {network.value} + + ))} + + + +
+ + +
+ ); +}; + +export default ManualAccount; diff --git a/plugin/src/components/NM/index.tsx b/plugin/src/components/NM/index.tsx index 69a52210..6729a09c 100644 --- a/plugin/src/components/NM/index.tsx +++ b/plugin/src/components/NM/index.tsx @@ -1,47 +1,42 @@ -import React from 'react' +import React from "react"; -import './styles.css' -import { useIcon } from '../../hooks/useIcons' +import "./styles.css"; +import { useIcon } from "../../hooks/useIcons"; -const sizeToDimenstions = (size: 'lg' | 'sm' | 'xs' | 'md' | 'xl'): { - h: number - w: number +const sizeToDimenstions = ( + size: "lg" | "sm" | "xs" | "md" | "xl" +): { + h: number; + w: number; } => { - const baseW = 130 * 4.5 - const baseH = 19 * 4.5 - switch (size) { - case 'lg': - return { h: baseH * 1, w: baseW * 1 } - case 'xs': - return { h: baseH * 0.2, w: baseW * 0.2 } - case 'sm': - return { h: baseH * 0.5, w: baseW * 0.5 } - case 'md': - return { h: baseH * 0.7, w: baseW * 0.7 } - case 'xl': - return { h: baseH * 1.5, w: baseW * 1.5 } - } - return { h: baseH, w: baseW } -} + const baseW = 130 * 4.5; + const baseH = 19 * 4.5; + switch (size) { + case "lg": + return { h: baseH * 1, w: baseW * 1 }; + case "xs": + return { h: baseH * 0.2, w: baseW * 0.2 }; + case "sm": + return { h: baseH * 0.5, w: baseW * 0.5 }; + case "md": + return { h: baseH * 0.7, w: baseW * 0.7 }; + case "xl": + return { h: baseH * 1.5, w: baseW * 1.5 }; + } + return { h: baseH, w: baseW }; +}; interface INethermind { - size?: 'lg' | 'sm' | 'xl' | 'xs' | 'md' + size?: "lg" | "sm" | "xl" | "xs" | "md"; } -const Nethermind: React.FC = ({ - size = 'xs' -}): React.ReactElement => { - const sz = sizeToDimenstions(size) - return ( - - - - - ) -} +const Nethermind: React.FC = ({ size = "xs" }): React.ReactElement => { + const sz = sizeToDimenstions(size); + return ( + + + + ); +}; -export default Nethermind +export default Nethermind; diff --git a/plugin/src/components/Settings/index.tsx b/plugin/src/components/Settings/index.tsx index bded0429..d56d826e 100644 --- a/plugin/src/components/Settings/index.tsx +++ b/plugin/src/components/Settings/index.tsx @@ -1,56 +1,69 @@ -import React from 'react' -import './settings.css' -import { BsChevronDown } from 'react-icons/bs' -import { useAtom, useAtomValue } from 'jotai' -import { cairoVersionAtom, versionsAtom } from '../../atoms/cairoVersion' -import ExplorerSelector, { useCurrentExplorer } from '../ExplorerSelector' -import * as Select from '../ui_components/Select' +import React from "react"; +import "./settings.css"; +import { BsChevronDown } from "react-icons/bs"; +import { useAtom, useAtomValue } from "jotai"; +import { cairoVersionAtom, versionsAtom } from "../../atoms/cairoVersion"; +import ExplorerSelector, { useCurrentExplorer } from "../ExplorerSelector"; +import * as Select from "../ui_components/Select"; +import LoadingDots from "../LoadingDots"; export const Settings: React.FC = () => { - const [cairoVersion, setCairoVersion] = useAtom(cairoVersionAtom) - const getVersions = useAtomValue(versionsAtom) - const explorerHook = useCurrentExplorer() + const [cairoVersion, setCairoVersion] = useAtom(cairoVersionAtom); + const getVersions = useAtomValue(versionsAtom); + const explorerHook = useCurrentExplorer(); - return ( -
-
Settings
-
-
Cairo Version
-
-
- - - -
-

{cairoVersion}

-
-
- - - -
- - - - {getVersions.map((v, i) => ( - - {v} - - ))} - - - -
-
-
+ return ( +
+
Settings
+
+
Cairo Version
+
+
+ {cairoVersion !== null + ? ( + + + +
+

{cairoVersion}

+
+
+ + + +
+ + + + {getVersions.map((v, i) => ( + + {v} + + ))} + + + +
+ ) + : ( +
+ +
+ )} +
+
-
-
Explorer
-
-
- -
-
-
- ) -} +
+
Explorer
+
+
+ +
+
+
+ ); +}; diff --git a/plugin/src/components/StateAction/index.tsx b/plugin/src/components/StateAction/index.tsx index 450f7093..52c42cf3 100644 --- a/plugin/src/components/StateAction/index.tsx +++ b/plugin/src/components/StateAction/index.tsx @@ -1,29 +1,29 @@ -import React from 'react' +import React from "react"; -import './index.css' -import { MdCheckCircleOutline, MdErrorOutline } from 'react-icons/md' +import "./index.css"; +import { MdCheckCircleOutline, MdErrorOutline } from "react-icons/md"; interface IStateAction { - value?: 'loading' | 'success' | 'error' | '' + value?: "loading" | "success" | "error" | ""; } const StateAction: React.FC = ({ value }) => { - switch (value) { - case 'loading': - return ( -