From 293f372c84ecf96b51b0ad81d5c572dceb6339dd Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Sun, 20 Apr 2025 01:43:55 +0900 Subject: [PATCH 1/8] docker file deployment --- Dockerfile | 21 +++++++++++++++++++-- docker-compose.yml | 3 +++ packages/frontend/next.config.ts | 9 +++++++-- sysbox/on-start.sh | 5 ++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index daa2224..3b80e17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start from a rust base image -FROM rust:1.76.0 as base +FROM rust:1.86.0 as base # Set the current directory WORKDIR /app @@ -20,13 +20,16 @@ RUN cargo install cargo-make RUN apt-get --yes update RUN apt-get --yes upgrade ENV NVM_DIR /usr/local/nvm -ENV NODE_VERSION v18.16.1 +ENV NODE_VERSION v22.14.0 RUN mkdir -p /usr/local/nvm && apt-get update && echo "y" | apt-get install curl RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION" ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/bin ENV PATH $NODE_PATH:$PATH +# Create public directory if it doesn't exist +RUN mkdir -p /app/packages/frontend/public + # Install dependencies RUN cargo make deps-wasm RUN cargo make deps-npm @@ -35,6 +38,7 @@ RUN cargo make deps-npm RUN cargo make build-server RUN cargo make build-bindings RUN cargo make build-app +RUN cargo make build-frontend RUN cargo make build-backend @@ -45,8 +49,21 @@ FROM nestybox/ubuntu-jammy-systemd-docker:latest # Copy the built files COPY --from=builder /app/packages/app/dist /app/packages/app/dist +COPY --from=builder /app/packages/frontend/.next /app/packages/frontend/.next +COPY --from=builder /app/packages/frontend/public /app/packages/frontend/public +COPY --from=builder /app/packages/frontend/package.json /app/packages/frontend/package.json COPY --from=builder /app/target/release/backend /app/target/release/backend +# Install Node.js in the final image for running Next.js +RUN apt-get update && apt-get install -y curl +ENV NVM_DIR /usr/local/nvm +ENV NODE_VERSION v22.14.0 +RUN mkdir -p /usr/local/nvm +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash +RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION" +ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/bin +ENV PATH $NODE_PATH:$PATH + # Startup scripts COPY sysbox/on-start.sh /usr/bin RUN chmod +x /usr/bin/on-start.sh diff --git a/docker-compose.yml b/docker-compose.yml index f648c97..a9129a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,4 +8,7 @@ services: - /tmp:/tmp ports: - "9000:9000" + - "3000:3000" # Expose Next.js port + environment: + - DOCKER_ENV=true runtime: sysbox-runc diff --git a/packages/frontend/next.config.ts b/packages/frontend/next.config.ts index da728e3..15878c0 100644 --- a/packages/frontend/next.config.ts +++ b/packages/frontend/next.config.ts @@ -3,14 +3,19 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ async rewrites() { + // In Docker, use the backend service running on the same container + const backendUrl = process.env.DOCKER_ENV + ? "http://localhost:9000" + : "http://localhost:4444"; + return [ { source: "/compile", - destination: "http://localhost:4444/compile", + destination: `${backendUrl}/compile`, }, { source: "/health", - destination: "http://localhost:4444/health", + destination: `${backendUrl}/health`, }, ]; }, diff --git a/sysbox/on-start.sh b/sysbox/on-start.sh index 06b05a1..e8c039b 100755 --- a/sysbox/on-start.sh +++ b/sysbox/on-start.sh @@ -7,5 +7,8 @@ sleep 2 # pull solang image docker pull ghcr.io/hyperledger/solang@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a +# Start Next.js frontend in the background +cd /app/packages/frontend && npm install --production && npm start & + # start backend server -./app/target/release/backend --port 9000 --host 0.0.0.0 --frontend_folder /app/packages/app/dist +./app/target/release/backend --port 9000 --host 0.0.0.0 --frontend_folder /app/packages/app/dist From dfa30ab057110c013b1617c99dabfbef629caca0 Mon Sep 17 00:00:00 2001 From: salaheldinsoliman Date: Wed, 30 Apr 2025 18:51:04 +0200 Subject: [PATCH 2/8] migrate to soroban --- Makefile.toml | 4 ++-- crates/backend/src/services/sandbox.rs | 11 ++++------- packages/app/src/app.ts | 3 ++- packages/frontend/package.json | 6 +++--- packages/frontend/src/lib/utils.ts | 2 +- packages/frontend/src/pages/_error.js | 16 ++++++++++++++++ solidity/counter_demo.abi | 1 + solidity/counter_demo.sol | 12 ++++++++++++ solidity/counter_demo.wasm | Bin 0 -> 1253 bytes solidity/hello_world.wasm | Bin 0 -> 849 bytes solidity/soroban_token_contract.wasm | Bin 0 -> 7646 bytes sysbox/on-start.sh | 8 ++------ 12 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 packages/frontend/src/pages/_error.js create mode 100644 solidity/counter_demo.abi create mode 100755 solidity/counter_demo.sol create mode 100644 solidity/counter_demo.wasm create mode 100755 solidity/hello_world.wasm create mode 100755 solidity/soroban_token_contract.wasm diff --git a/Makefile.toml b/Makefile.toml index 8db8519..2fcd8fa 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -14,7 +14,7 @@ npm install [tasks.deps-docker] script = ''' -docker pull ghcr.io/hyperledger/solang@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a +docker pull ghcr.io/hyperledger/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d ''' [tasks.deps] @@ -118,7 +118,7 @@ docker build -t solang-playground . script = ''' docker run \ --runtime=sysbox-runc \ - --name playground \ + --name playground-makee \ --detach \ --volume /tmp:/tmp \ --publish 9000:9000 \ diff --git a/crates/backend/src/services/sandbox.rs b/crates/backend/src/services/sandbox.rs index b11d1f1..1886bce 100644 --- a/crates/backend/src/services/sandbox.rs +++ b/crates/backend/src/services/sandbox.rs @@ -14,7 +14,7 @@ use tokio::process::Command; use crate::services::{CompilationRequest, CompilationResult}; const TIMEOUT: Duration = Duration::from_secs(60); -const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger/solang"; +const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d"; const DOCKER_WORKDIR: &str = "/builds/contract/"; const DOCKER_OUTPUT: &str = "/playground-result"; @@ -65,15 +65,12 @@ pub fn build_compile_command(input_file: &Path, output_dir: &Path) -> Command { cmd.arg("--volume").arg(&mount_output_dir); // Using the solang image - cmd.arg(format!( - "{}@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a", - DOCKER_IMAGE_BASE_NAME - )); + cmd.arg(DOCKER_IMAGE_BASE_NAME); // Building the compile command let remove_command = format!("rm -rf {}*.wasm {}*.contract", DOCKER_OUTPUT, DOCKER_OUTPUT); let compile_command = format!( - "solang compile --target polkadot -o /playground-result {} > /playground-result/stdout.log 2> /playground-result/stderr.log", + "solang compile --target soroban -o /playground-result {} > /playground-result/stdout.log 2> /playground-result/stderr.log", file_name ); let sh_command = format!("{} && {}", remove_command, compile_command); @@ -122,7 +119,7 @@ impl Sandbox { .context("failed to read output directory")? .flatten() .map(|entry| entry.path()) - .find(|path| path.extension() == Some(OsStr::new("contract"))); + .find(|path| path.extension() == Some(OsStr::new("wasm"))); // The file `stdout.log` is in the same directory as the contract file let compile_log_stdout_file_path = fs::read_dir(&self.output_dir) diff --git a/packages/app/src/app.ts b/packages/app/src/app.ts index ea22d4f..c3dd768 100644 --- a/packages/app/src/app.ts +++ b/packages/app/src/app.ts @@ -58,7 +58,7 @@ export const downloadBlob = (code: number[]): void => { const blob = new Blob([new Uint8Array(code).buffer]); const a = document.createElement('a'); - a.download = 'result.contract'; + a.download = 'result.wasm'; a.href = URL.createObjectURL(blob); a.dataset.downloadurl = ['application/json', a.download, a.href].join(':'); a.style.display = 'none'; @@ -171,6 +171,7 @@ export default class App { ); console.log("Compilation result: ", result); + client.printToConsole(proto.MessageType.Info, "Compilation result: " + JSON.stringify(result)); // If the compilation was successful, download the wasm blob and print a success message if (result.type === 'OK') { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 10c5a2d..66a7b20 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "set NODE_ENV=production & next build", "start": "next start", "lint": "next lint" }, @@ -37,8 +37,8 @@ "next": "15.0.3", "next-auth": "^5.0.0-beta.25", "next-themes": "^0.4.4", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^18", + "react-dom": "^18", "react-icons": "^5.4.0", "sonner": "^1.7.4", "tailwind-merge": "^2.5.5", diff --git a/packages/frontend/src/lib/utils.ts b/packages/frontend/src/lib/utils.ts index 5b19755..abcf8d0 100644 --- a/packages/frontend/src/lib/utils.ts +++ b/packages/frontend/src/lib/utils.ts @@ -9,7 +9,7 @@ export const downloadBlob = (code: number[]): void => { const blob = new Blob([new Uint8Array(code).buffer]); const a = document.createElement("a"); - a.download = "result.contract"; + a.download = "result.wasm"; a.href = URL.createObjectURL(blob); a.dataset.downloadurl = ["application/json", a.download, a.href].join(":"); a.style.display = "none"; diff --git a/packages/frontend/src/pages/_error.js b/packages/frontend/src/pages/_error.js new file mode 100644 index 0000000..cc05f55 --- /dev/null +++ b/packages/frontend/src/pages/_error.js @@ -0,0 +1,16 @@ +function Error({ statusCode }) { + return ( +

+ {statusCode + ? `An error ${statusCode} occurred on server` + : 'An error occurred on client'} +

+ ) + } + + Error.getInitialProps = ({ res, err }) => { + const statusCode = res ? res.statusCode : err ? err.statusCode : 404 + return { statusCode } + } + + export default Error \ No newline at end of file diff --git a/solidity/counter_demo.abi b/solidity/counter_demo.abi new file mode 100644 index 0000000..26e5c03 --- /dev/null +++ b/solidity/counter_demo.abi @@ -0,0 +1 @@ +[{"name":"count","type":"function","inputs":[],"outputs":[{"name":"count","type":"uint64","internalType":"uint64"}],"stateMutability":"view"},{"name":"decrement","type":"function","inputs":[],"outputs":[{"name":"","type":"uint64","internalType":"uint64"}],"stateMutability":"nonpayable"}] \ No newline at end of file diff --git a/solidity/counter_demo.sol b/solidity/counter_demo.sol new file mode 100755 index 0000000..cb2b489 --- /dev/null +++ b/solidity/counter_demo.sol @@ -0,0 +1,12 @@ +contract counter_demo { + + + uint64 public count = 1; + + function decrement() public returns (uint64){ + count -= 1; + return count; + } + + +} \ No newline at end of file diff --git a/solidity/counter_demo.wasm b/solidity/counter_demo.wasm new file mode 100644 index 0000000000000000000000000000000000000000..61d8543d9625bb60584fd01107df3ee3aa507588 GIT binary patch literal 1253 zcmbVL&2G~`5T02(e>EkcNF0zrZcm_MC(eI-YFzOOT;270lLq_~*_+Tx$PJ~cijWXz zxbOlz0#cug6PvaqX*pEw_%O3G-^_fwo&lPM2mtsjw@0!JvpqGFBDHd<0QLYg@>szD zEr~lo7f{+R5GAK7s;cHx#7ZS!!Xxa@X6K+4yj(uyVVoQiuvk2a1W#OFI&AyqDN02$ zVIod!yW6%p4a;wVlDj~GTJkwdc*r9GxpmiN@d6F`weLhFpzR`N4menuNr6ouJAGIO z9h_PA$;qFTwf^k04E%ERQ%6170_Q~HeikG^t$duPJO%AHBFV_k{9BfCif0mi^kEBi zc(V?&DmtK}Q#_SrWxwN#J-7~D-t2MZk`{g(9 zpWn2to>5;ExO4xu-K-nUt|ajMk(?NjP$qaZ4Y{Dx+SR3=T<9P8@E;%@bGC@igWBq_ zU1hDRs}Wte<|~w6oB&ovcj3K>xiQk$a0I@f8qtuWUjQXAEmRInmq^Q1?!Hp$CEeVT zRsMSO!UjUwbyGpvJ9iw9<{MjDLnxR@>{CJEE0)I6oG_lGq!`d>IH5z1xC;-YpYb&1 zzTq8Wj?H7#YMV8)rN7u0Vw~F5>To3XC!WdTuqtPaM$`zXmsW#d8X7YHEoZ`%7`@i$ z`pr(=V}8x+)(3-5yX&>A7Gs@`KkzKxpp9Oe(e6ulRTeZEa^Vg*6_bRgWV;+r1oZ-L S41?ICLAqT`N5jbA2jVYoqEqt# literal 0 HcmV?d00001 diff --git a/solidity/hello_world.wasm b/solidity/hello_world.wasm new file mode 100755 index 0000000000000000000000000000000000000000..f7232c0f6653154e364a75b6c758152bbbade604 GIT binary patch literal 849 zcmZ8f!EVz)5S`hvT~jwS6%r~)pmkcLR6?l}+Bh7LY9A0EfVd#!I<})WZIY5i0Ypg* ztpZ2>0B$|;5B-GxQJJ-qw6KSnH}l@U9j_O{K?eZj>VqXlX&4>Ysr8(aHZooZ>fS z`A~iTX$d8~E}%pyPD|WScz&iD`U={c%7*V^)dH}JQ&oU<2a4Zwqyd)#ENcrP|Jjm% zsEoqn<0O_J4u=OaeDV3qQ`yX7#1Y=!je6Z-Ka7ThlO!7PD_X4VHE>0<4u&}}=5_PG zI=ZQmp#hKA=H^Lv)JcZn!vB6I?JW-fht&InQPQ88RV$5ZIBJE}hU?Uy$F-emt6FU}okq=l=CtZ{x4FBUG@Kgz1^i;K{Qv*} literal 0 HcmV?d00001 diff --git a/solidity/soroban_token_contract.wasm b/solidity/soroban_token_contract.wasm new file mode 100755 index 0000000000000000000000000000000000000000..2be33cb4874835c2c2457c83974ddc2c8c4fdf94 GIT binary patch literal 7646 zcmcgxeQaH28GqjQoO|2uwCg*lSXP7Yz09No9d}#Tc8J<@0yta{#s-PukM^#;9qroN z+TN8dZoQprhaWHzK~YptK`=67LJ<8!C^4ECAz3s=;vY(kV&o5FG@5`h_6{7x9#JJV@|be3%Nqd6qZwF<4_4qMu_Dj$mEh+9{^Q0Qj2{ z+ezR#Oi~;*?FDfNBNJkf0z3!IvmYY|#U!3bv695J*p2nmqKx)Yv6DhjC>c6~B!Zng za`~2AYug5~Aqv|&Hg>8_?He!Xys)dQz3Xix+FOD^2nyr{B)Vi6h8MlPyFC{O+LXUI zsEc@MX_cZn#V?$+?-%*q@~pTZKV6xgnL8$_V|aLUraE7nJ2G0EnWM|X>4|EME^n)p z=Z_7W_O`b9N^Q72h7MiPR-T%gxxHK+tYr?nX#R%wYhS2e!MbA zeI33zJU%xwP5t4)BXd<6Xycb1+TA)<8J(CePtDVnVYNJ6q4(zJk4+z(nWC$po1qQE z!(-)IdAL#?3q&+Ld|RbFJAAM_UkT)fSGVryB25Wo{MjY(($C+i?p|72qUc>1RG;Oh z7FwQ7^ywsA)`4p%Of1pn=+{CrKrS`iSRF2kEv6}l{GX;?NgKm}gbbqiPfM#y6t}4R zc)O;pL96;aHw&^IvwtzOx%Fmcq(%Jq#`%E&oW(+Y!R;4O{Gyp}v-=AYjN=3~5VYD> zD{qnUV`dd*W2x@v&7K85LQPr$w2WRAAzWQ~96M>5i>VOEpR>XF%u#?&A!w&4(lU8XoJyXfo3KX4t0z~X ztiY^}19dOc(gHdw+I1l>Lthm`v+t*79|pRMxrPDAoaj$H+Aq5`>D)k|@Cu=nL=>P^ z%vw!qCwXT?6m`NlTBy7D$VBXmc90y>Qis=e5N3I<84)Q))TepV=IAGY#$s5K6U|dN}7F5#VknNUYNJN{V$Mfhe8@gApq(TQ?rG8oJYbhU} zGV5<^^fVxm94C|n;6x6A8UabxXN#f%keWLNi5O;pJx}{O1nj83#FW{dvA4b!(I$#t zH7;_w`k|eFU#5c01wfS#Ht2MLf^g2qdCO*udWzAvWgv|oIW4Zz;bP#J4;Xz)1LF)x zMo5sbMp+yP6r|FSb*~PBD}`7n*vcXyD_24Cut8NAEA}ur+N2JhhlT86-rkKKcFu(Y zUX5sAJuHZO;hgXy^%a(do4q#ztK86yu7`f9kW1Ku2Pd}%q`qp_8*eLydyDy=0yxw_ zXpi+Y0~zOV@WFlw$>UXObi8F7P@VkZQFeo(jDO6Y0cb@Z1Rn&1LG};a!O$bLTG3+d z33dXZR+tUIT&UBg<#xbYX1QMr1yXW~qrFkSDDVIqf}(a(ck)(_8__?7klq8%!FD-o zJBfgh&`oTxn{{wwPce_!=Ts10kLE*60pQ9pO^p`}+pVE5wzwG_w1Xu(*sF8UytQ~i zUipqih$S}jfsJ$CCQ(Qb;Z{%p4UvEu{;bmRq4_8fyS&3f#wY1m6(O9ji| z)y;^*e_)g&!Hx6QFcE1o1ZLq)JYoWnb1CUvN?OkWSTR(B)eV`?Sk`b#iU7$AVDsJ> z>PJ@WFoe~^MosWQjzkcYA&$i9&1Z7iWl(_+u0E!w*(zBE1*Ja8bT&toEYOu2f)oqg zjUWYsX?Se62Pu0#3{=rYnkDDwQLQnOqh`v2A*pXLBQP`*X5)rH zR8n67M$tOXL6G>t$1LBhPL1s7GHP+$4&KsQBTNl>1Ks8mcf?+!R#px zjJ}>ri!XLT6kn!L%+g!tHHkDJ^MbCfaUFojc}^4KlGBsOyrCNj1~PDhfnnh?toT5E zi}_VwJ)zB+66u|pV;`e16#QR)=W zZpNg?rIVzGj5?Mc`h#oS{SQ)S%;M-36!BTP&&fbooPj4>YAf4gIr7nl0%5s zSMG41YpWNG1Fe6t%l%Q4& zCb`V)m3^qMUD1uA94ce&GBaQTMWEG+DGAf+n@@(R?H3BbB1;20jIPfB33m}z>;|ju z>mW{cf-TY--(`YSCupD|dziUL(>rA%qdh__p10W}Q(6B=;ZFa7FT%MfL$bvyXKQgjw6Ty* z<2b6)TBiTk^(tT8A|5y$8b5>XZ32Zb@C4w19YmXw&DIwfQU(&<7c^oK%()_)6QRq@ z4YteT(DNqohm2e4TpKc5*R~q*8sY}A0UY34aY=K5QA$0w%DeG(`{Cy7y8;I>sq3DK?za ztZ0*ay`qt+T1^b)1PtwRmgX25Hfa}nSC1Jdf*YW1<0zOhy(i5~kTT}vKa|N2?4`xo zS~DbP{`~TNPk-+>e}h$!TrzVAbQpGNErS6tnW@`@E_$+A32%-zoAx~Na7>UP&nkq^ zg3mHTtdO;E6Tmgxgj9wx9##(bta{GNeJ7jQe!ZN>X(-EimOXYolk+Tl zKIdiL5IBa|MuGaiVN^C-=tG%>5S<-%5ILXvR~xWsq9zcBuS9E0wN`J0tYZ2JS6UEEFIw`VfsxHO5Y9%lOLiA<4^`#N?Nv6s(dTIJ~M#9X;HF;g9$s*D}NxzN zH{l)o3*W@v@$RpcYn7O9gGX@}Tp80NH_Y<>E3Z6Ky?w4c+q-o{&s23;k9>HhS{cy` z<*6gMw>3A#?z)$+dfh!Q(c_MvC-7$eo^t2YFE(t%%``5NM{AH_bfJVv|7p6lHp9G` zKRJ6TWSXFvYxnR|ZBO_9A7Cz5VJ)@Dr0)6ZLNnlU8cHJ^s7 zbTcm8Y%>kNso}bP&HA=#<#u-sZ^r%E>Avi0-VAwOLv#I3btao@^p8fr z;T3E@p7(a-F_lJD&f><`1AXiNK3|+!KUZE zEj`a1#WJ;=LvuZO{GJvUD0v5udHixeXQC&!vN_wsoC&J7=V@`Tks&>k2HiE2xw-iTBnR`!Z{Ie!eOt+P@NAu%pC3DH zr?1$tV_Rw4&P#WdcaD~Kmd7iD /var/log/dockerd.log 2>&1 & sleep 2 # pull solang image -docker pull ghcr.io/hyperledger/solang@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a +docker pull ghcr.io/hyperledger/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d -# Start Next.js frontend in the background -cd /app/packages/frontend && npm install --production && npm start & - -# start backend server -./app/target/release/backend --port 9000 --host 0.0.0.0 --frontend_folder /app/packages/app/dist +cargo make run From f912a6dd9f21e347b42e3303f6cc54ba07342163 Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Mon, 5 May 2025 19:07:51 +0100 Subject: [PATCH 3/8] feat: deploy to network directly --- packages/frontend/src/components/Header.tsx | 29 ++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/Header.tsx b/packages/frontend/src/components/Header.tsx index 870f4b5..9c4627d 100644 --- a/packages/frontend/src/components/Header.tsx +++ b/packages/frontend/src/components/Header.tsx @@ -1,16 +1,18 @@ "use client"; -import { cn, downloadBlob } from "@/lib/utils"; +import { useEffect, useId, useRef } from "react"; import { FaPlay, FaTimes } from "react-icons/fa"; -import { useAddConsole } from "@/app/state"; -import { useExplorerItem, useFileContent } from "@/state/hooks"; -import { useSelector } from "@xstate/store/react"; +import { toast } from "sonner"; +import { Keypair, Networks } from "@stellar/stellar-sdk"; +import deployStellerContract from "@/lib/deploy-steller"; +import generateIdl from "@/lib/idl-wasm"; +import { cn } from "@/lib/utils"; import { store } from "@/state"; -import IconButton from "./IconButton"; -import { useEffect, useRef } from "react"; +import { useExplorerItem, useFileContent } from "@/state/hooks"; import { logger } from "@/state/utils"; +import { useSelector } from "@xstate/store/react"; import Hide from "./Hide"; -import DeployToSteller from "./DeployToSteller"; +import IconButton from "./IconButton"; function TabItem({ path }: { path: string }) { const file = useExplorerItem(path); @@ -74,6 +76,7 @@ function TabHome({ path }: { path: string }) { } function Header() { + const toastId = useId(); const code = useFileContent(); const tabs = useSelector(store, (state) => state.context.tabs); const containerRef = useRef(null); @@ -114,11 +117,19 @@ function Header() { }; }); + const keypair = Keypair.random(); + if (success) { if (result.type === "SUCCESS") { const wasm = result.payload.wasm; - downloadBlob(wasm); logger.info("Contract compiled successfully!"); + + toast.loading("Deploying contract...", { id: toastId }); + const idl = await generateIdl(wasm); + store.send({ type: "updateContract", methods: idl }); + const contractAddress = await deployStellerContract(wasm, keypair, Networks.TESTNET); + toast.success("Contract deployed successfully", { id: toastId }); + contractAddress && store.send({ type: "updateContract", address: contractAddress }); } else { const message = result.payload.compile_stderr; logger.error(message); @@ -135,7 +146,7 @@ function Header() { - + {/* */}
{[...tabs].map((tab) => ( From 2f94eb8394acb220aa10b8d2949c1173ed872ae1 Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Wed, 7 May 2025 06:47:55 +0100 Subject: [PATCH 4/8] feat: split compile & deploy --- packages/frontend/src/components/Header.tsx | 43 ++++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/components/Header.tsx b/packages/frontend/src/components/Header.tsx index 9c4627d..4c9d3ab 100644 --- a/packages/frontend/src/components/Header.tsx +++ b/packages/frontend/src/components/Header.tsx @@ -1,16 +1,16 @@ "use client"; -import { useEffect, useId, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { FaPlay, FaTimes } from "react-icons/fa"; -import { toast } from "sonner"; import { Keypair, Networks } from "@stellar/stellar-sdk"; +import { useSelector } from "@xstate/store/react"; import deployStellerContract from "@/lib/deploy-steller"; import generateIdl from "@/lib/idl-wasm"; import { cn } from "@/lib/utils"; import { store } from "@/state"; import { useExplorerItem, useFileContent } from "@/state/hooks"; import { logger } from "@/state/utils"; -import { useSelector } from "@xstate/store/react"; +import DeployToSteller from "./DeployToSteller"; import Hide from "./Hide"; import IconButton from "./IconButton"; @@ -76,10 +76,10 @@ function TabHome({ path }: { path: string }) { } function Header() { - const toastId = useId(); const code = useFileContent(); const tabs = useSelector(store, (state) => state.context.tabs); const containerRef = useRef(null); + const [contract, setContract] = useState(null); async function handleCompile() { if (!code) { @@ -117,19 +117,11 @@ function Header() { }; }); - const keypair = Keypair.random(); - if (success) { if (result.type === "SUCCESS") { const wasm = result.payload.wasm; logger.info("Contract compiled successfully!"); - - toast.loading("Deploying contract...", { id: toastId }); - const idl = await generateIdl(wasm); - store.send({ type: "updateContract", methods: idl }); - const contractAddress = await deployStellerContract(wasm, keypair, Networks.TESTNET); - toast.success("Contract deployed successfully", { id: toastId }); - contractAddress && store.send({ type: "updateContract", address: contractAddress }); + setContract(wasm); } else { const message = result.payload.compile_stderr; logger.error(message); @@ -139,14 +131,35 @@ function Header() { } } + async function handleDeploy() { + if (!contract) { + return; + } + + const keypair = Keypair.random(); + + logger.info("Deploying contract..."); + const idl = await generateIdl(contract); + store.send({ type: "updateContract", methods: idl }); + const contractAddress = await deployStellerContract(contract, keypair, Networks.TESTNET); + logger.info("Contract deployed successfully!"); + contractAddress && store.send({ type: "updateContract", address: contractAddress }); + } + return (
- +
+
+
- {/* */}
{[...tabs].map((tab) => ( From b744ac619545d821008eb4fcc41eaec2d40158f9 Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Thu, 8 May 2025 15:25:12 +0000 Subject: [PATCH 5/8] feat: add multi deployed contracts functionality to the IDE --- packages/frontend/package.json | 2 +- .../src/components/ContractExplorer.tsx | 41 +++++++- packages/frontend/src/components/Header.tsx | 72 +++----------- .../src/components/InvokeFunction.tsx | 2 +- .../frontend/src/components/SidePanel.tsx | 23 +++++ packages/frontend/src/components/Sidebar.tsx | 1 + packages/frontend/src/hooks/useCompile.tsx | 93 +++++++++++++++++++ packages/frontend/src/hooks/useDeploy.tsx | 43 +++++++++ packages/frontend/src/state/context.ts | 1 + packages/frontend/src/state/events.ts | 5 + packages/frontend/src/types/idl.ts | 5 + 11 files changed, 221 insertions(+), 67 deletions(-) create mode 100644 packages/frontend/src/hooks/useCompile.tsx create mode 100644 packages/frontend/src/hooks/useDeploy.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 66a7b20..02f448c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "next dev", "build": "set NODE_ENV=production & next build", - "start": "next start", + "sr": "npm run build && next start", "lint": "next lint" }, "dependencies": { diff --git a/packages/frontend/src/components/ContractExplorer.tsx b/packages/frontend/src/components/ContractExplorer.tsx index 79e8d68..d8ed504 100644 --- a/packages/frontend/src/components/ContractExplorer.tsx +++ b/packages/frontend/src/components/ContractExplorer.tsx @@ -2,12 +2,28 @@ import { store } from "@/state"; import { useSelector } from "@xstate/store/react"; -import React from "react"; +import React, { useEffect, useState } from "react"; import Hide from "./Hide"; import InvokeFunction from "./InvokeFunction"; function ContractExplorer() { const idl = useSelector(store, (state) => state.context.contract?.methods) || []; + const deployed = useSelector(store, (state) => state.context.contract?.deployed) || {}; + const [keys, setKeys] = useState([]); + + useEffect(() => { + const ks = Object.keys(deployed) + setKeys(ks) + }, [deployed]) + + const toggleCollapsed = (e: React.MouseEvent, k: string) => { + const target = e.target as HTMLElement; + const div = target.nextElementSibling as HTMLElement | null; + + if(div) { + div.style.display = div.style.display === 'none' ? 'block' : 'none' + } + } return (
@@ -16,9 +32,26 @@ function ContractExplorer() {
- {idl.map((item) => ( - - ))} + { + keys.map(k => ( +
+

toggleCollapsed(e, k)} + > + {`${k.substring(0, 5)}..${k.substring(50)}`} +

+
+ { + deployed[k].map(item => ( + + )) + } +
+
+ ) + ) + }
diff --git a/packages/frontend/src/components/Header.tsx b/packages/frontend/src/components/Header.tsx index 4c9d3ab..d3de583 100644 --- a/packages/frontend/src/components/Header.tsx +++ b/packages/frontend/src/components/Header.tsx @@ -13,6 +13,8 @@ import { logger } from "@/state/utils"; import DeployToSteller from "./DeployToSteller"; import Hide from "./Hide"; import IconButton from "./IconButton"; +import useCompile from "@/hooks/useCompile"; +import useDeploy from "@/hooks/useDeploy"; function TabItem({ path }: { path: string }) { const file = useExplorerItem(path); @@ -76,74 +78,22 @@ function TabHome({ path }: { path: string }) { } function Header() { + const { compileFile } = useCompile(); + const { deployWasm } = useDeploy(); const code = useFileContent(); const tabs = useSelector(store, (state) => state.context.tabs); const containerRef = useRef(null); const [contract, setContract] = useState(null); - async function handleCompile() { - if (!code) { - return logger.error("Error: No Source Code Found"); - } - - logger.info("Compiling contract..."); - - const opts: RequestInit = { - method: "POST", - mode: "cors", - credentials: "same-origin", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - source: code, - }), - }; - - const { result, success, message } = await fetch("/compile", opts).then(async (res) => { - console.log(res); - const result = await res.json().catch(() => null); - - if (!result) { - return { - success: false, - message: res.statusText, - result: null, - }; - } - - return { - success: res.ok, - message: res.statusText, - result: result, - }; - }); - - if (success) { - if (result.type === "SUCCESS") { - const wasm = result.payload.wasm; - logger.info("Contract compiled successfully!"); - setContract(wasm); - } else { - const message = result.payload.compile_stderr; - logger.error(message); - } - } else { - logger.error(message); - } + const handleCompile = async () => { + const result = await compileFile() + setContract(result.data) + console.log('[tur] scompilation result', result) } - async function handleDeploy() { - if (!contract) { - return; - } - - const keypair = Keypair.random(); - - logger.info("Deploying contract..."); - const idl = await generateIdl(contract); - store.send({ type: "updateContract", methods: idl }); - const contractAddress = await deployStellerContract(contract, keypair, Networks.TESTNET); - logger.info("Contract deployed successfully!"); - contractAddress && store.send({ type: "updateContract", address: contractAddress }); + const handleDeploy = async () => { + const result = await deployWasm(contract) + console.log('[tur] deployed?', result) } return ( diff --git a/packages/frontend/src/components/InvokeFunction.tsx b/packages/frontend/src/components/InvokeFunction.tsx index f7c00f3..8d5e54f 100644 --- a/packages/frontend/src/components/InvokeFunction.tsx +++ b/packages/frontend/src/components/InvokeFunction.tsx @@ -165,7 +165,7 @@ function InvokeFunction({ method }: { method: FunctionSpec }) { - diff --git a/packages/frontend/src/components/SidePanel.tsx b/packages/frontend/src/components/SidePanel.tsx index 7855127..03838dd 100644 --- a/packages/frontend/src/components/SidePanel.tsx +++ b/packages/frontend/src/components/SidePanel.tsx @@ -6,8 +6,25 @@ import { Button } from "./ui/button"; import { FaCog } from "react-icons/fa"; import { SidebarView, useAppStore } from "@/app/state"; import { Files, FunctionSquare, LucideFiles, SquareFunction, Star } from "lucide-react"; +import useCompile from "@/hooks/useCompile"; +import useDeploy from "@/hooks/useDeploy"; +import { useState } from "react"; function SidePanel() { + const { compileFile } = useCompile(); + const { deployWasm } = useDeploy(); + const [contract, setContract] = useState(null); + + const handleCompile = async () => { + const result = await compileFile() + setContract(result.data) + console.log('[tur] compilation result', result) + } + + const handleDeploy = async () => { + const result = await deployWasm(contract) + console.log('[tur] deployed?', result) + } const setSidebar = useAppStore((state) => state.setSidebar); return (
@@ -21,6 +38,12 @@ function SidePanel() { + +
From 49a3358710b4da0f360721e8c4c24129aaeef1bc Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Sat, 10 May 2025 11:45:06 +0000 Subject: [PATCH 7/8] chore: add more functionalities to deploy and compile explorers in the side panel --- crates/backend/src/services/sandbox.rs | 46 +++-- packages/frontend/src/app/state.ts | 3 +- .../src/components/CompileExplorer.tsx | 85 +++++++++ .../src/components/ContractExplorer.tsx | 67 ------- .../src/components/DeployExplorer.tsx | 173 ++++++++++++++++++ .../src/components/InvokeFunction.tsx | 4 +- .../frontend/src/components/SidePanel.tsx | 24 +-- packages/frontend/src/components/Sidebar.tsx | 11 +- packages/frontend/src/hooks/useCompile.tsx | 5 +- packages/frontend/src/hooks/useDeploy.tsx | 45 +++-- packages/frontend/src/state/context.ts | 10 + packages/frontend/src/state/events.ts | 27 ++- packages/frontend/src/types/contracts.ts | 9 + tobedeleted.txt | 19 ++ 14 files changed, 404 insertions(+), 124 deletions(-) create mode 100644 packages/frontend/src/components/CompileExplorer.tsx delete mode 100644 packages/frontend/src/components/ContractExplorer.tsx create mode 100644 packages/frontend/src/components/DeployExplorer.tsx create mode 100644 packages/frontend/src/types/contracts.ts create mode 100644 tobedeleted.txt diff --git a/crates/backend/src/services/sandbox.rs b/crates/backend/src/services/sandbox.rs index 1886bce..6f2c5a6 100644 --- a/crates/backend/src/services/sandbox.rs +++ b/crates/backend/src/services/sandbox.rs @@ -14,7 +14,8 @@ use tokio::process::Command; use crate::services::{CompilationRequest, CompilationResult}; const TIMEOUT: Duration = Duration::from_secs(60); -const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d"; +// const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d"; +const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang:latest"; const DOCKER_WORKDIR: &str = "/builds/contract/"; const DOCKER_OUTPUT: &str = "/playground-result"; @@ -29,11 +30,12 @@ macro_rules! docker_command { /// Builds the compile command using solang docker image pub fn build_compile_command(input_file: &Path, output_dir: &Path) -> Command { + println!("ip file: {:?}\nop dir: {:?}", input_file, output_dir); // Base docker command let mut cmd = docker_command!( "run", "--detach", - "--rm", + // "--rm", "-it", "--cap-drop=ALL", "--cap-add=DAC_OVERRIDE", @@ -99,7 +101,9 @@ impl Sandbox { fs::set_permissions(&output_dir, PermissionsExt::from_mode(0o777)) .context("failed to set output permissions")?; - + + File::create(&input_file).context("failed to create input file")?; + Ok(Sandbox { scratch, input_file, @@ -112,9 +116,10 @@ impl Sandbox { self.write_source_code(&req.source)?; let command = build_compile_command(&self.input_file, &self.output_dir); - println!("Executing command: \n{:#?}", command); + // println!("Executing command: \n{:#?}", command); let output = run_command(command)?; + println!("out: {:?}", output); let file = fs::read_dir(&self.output_dir) .context("failed to read output directory")? .flatten() @@ -184,9 +189,16 @@ impl Sandbox { /// A helper function to write the source code to the input file fn write_source_code(&self, code: &str) -> Result<()> { + println!("writing to {:?}", self.input_file); fs::write(&self.input_file, code).context("failed to write source code")?; + match fs::read_to_string(&self.input_file) { + Ok(content) => println!("Successfully read: {:?}", content), + Err(e) => eprintln!("Error reading file: {}", e), + } fs::set_permissions(&self.input_file, PermissionsExt::from_mode(0o777)) .context("failed to set source permissions")?; + let s: String = code.chars().take(40).collect(); + println!("Code: {:?}", s); println!("Wrote {} bytes of source to {}", code.len(), self.input_file.display()); Ok(()) } @@ -194,6 +206,7 @@ impl Sandbox { /// Reads a file from the given path fn read(path: &Path) -> Result>> { + println!("reading: {:?}", path); let f = match File::open(path) { Ok(f) => f, Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(None), @@ -201,7 +214,7 @@ fn read(path: &Path) -> Result>> { }; let mut f = BufReader::new(f); let metadata = fs::metadata(path).expect("failed to read metadata"); - + println!("meta: {:?}", metadata); let mut buffer = vec![0; metadata.len() as usize]; f.read_exact(&mut buffer).expect("buffer overflow"); Ok(Some(buffer)) @@ -216,14 +229,15 @@ async fn run_command(mut command: Command) -> Result { use std::os::unix::process::ExitStatusExt; let timeout = TIMEOUT; - println!("executing command!"); + println!("executing command: {:?}", command); let output = command.output().await.context("failed to start compiler")?; let stdout = String::from_utf8_lossy(&output.stdout); let id = stdout.lines().next().context("missing compiler ID")?.trim(); let stderr = &output.stderr; - + let mut command = docker_command!("wait", id); + println!("ID: {:?}\nwait: {:?}", id, command); let timed_out = match tokio::time::timeout(timeout, command.output()).await { Ok(Ok(o)) => { @@ -236,17 +250,19 @@ async fn run_command(mut command: Command) -> Result { }; let mut command = docker_command!("logs", id); + println!("logs: {:?}", command); let mut output = command.output().await.context("failed to get output from compiler")?; - - let mut command = docker_command!( - "rm", // Kills container if still running - "--force", id - ); - command.stdout(std::process::Stdio::null()); - command.status().await.context("failed to remove compiler")?; + println!("op: {:?}", output); + // let mut command = docker_command!( + // "rm", // Kills container if still running + // "--force", id + // ); + // println!("rm: {:?}", command); + // command.stdout(std::process::Stdio::null()); + // command.status().await.context("failed to remove compiler")?; let code = timed_out.context("compiler timed out")?; - + println!("timedout: {:?}", code); output.status = code; output.stderr = stderr.to_owned(); diff --git a/packages/frontend/src/app/state.ts b/packages/frontend/src/app/state.ts index 66fe0e3..85d5318 100644 --- a/packages/frontend/src/app/state.ts +++ b/packages/frontend/src/app/state.ts @@ -30,7 +30,8 @@ export const useSettingsStore = create( export enum SidebarView { FILE_EXPLORER = "FILE-EXPLORER", SETTINGS = "SETTINGS", - CONTRACT = "CONTRACT", + COMPILE = "COMPILE", + DEPLOY = "DEPLOY", } export const useAppStore = create( diff --git a/packages/frontend/src/components/CompileExplorer.tsx b/packages/frontend/src/components/CompileExplorer.tsx new file mode 100644 index 0000000..7323faf --- /dev/null +++ b/packages/frontend/src/components/CompileExplorer.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { store } from "@/state"; +import { useSelector } from "@xstate/store/react"; +import React, { useEffect, useState } from "react"; +import Hide from "./Hide"; +import { Button } from "./ui/button"; +import { useExplorerItem } from "@/state/hooks"; +import { FileType } from "@/types/explorer"; +import { get } from "lodash"; +import useCompile from "@/hooks/useCompile"; + +function CompileExplorer() { + const { compileFile } = useCompile(); + + const selected = useSelector(store, (state) => state.context.currentFile); + const obj = useSelector(store, (state) => get(state.context, selected || '')) as FileType; + const [name, setName] = useState(''); + + + const [isSelected, setSelected] = useState(!1); + + console.log('[tur] selected', selected) + + useEffect(() => { + if(selected && selected !== 'home') { + setName(obj.name); + setSelected(!0); + } + }, [selected]) + + const handleCompile = async () => { + const result = await compileFile(); + selected && selected !== 'home' && + store.send({ type: "addCompiled", path: selected, name }); + console.log('[tur] compilation result', result); + } + + return ( +
+
+

Compile Explorer

+
+
+ + {/*
+ { + keys.map(k => ( +
+

toggleCollapsed(e, k)} + > + {`${k.substring(0, 5)}..${k.substring(50)}`} +

+
+ { + deployed[k].map(item => ( + + )) + } +
+
+ ) + ) + } +
*/} + + {/* +
+

No Function or IDL Specified

+
+
*/} +
+
+ ); +} + +export default CompileExplorer; diff --git a/packages/frontend/src/components/ContractExplorer.tsx b/packages/frontend/src/components/ContractExplorer.tsx deleted file mode 100644 index d8ed504..0000000 --- a/packages/frontend/src/components/ContractExplorer.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import { store } from "@/state"; -import { useSelector } from "@xstate/store/react"; -import React, { useEffect, useState } from "react"; -import Hide from "./Hide"; -import InvokeFunction from "./InvokeFunction"; - -function ContractExplorer() { - const idl = useSelector(store, (state) => state.context.contract?.methods) || []; - const deployed = useSelector(store, (state) => state.context.contract?.deployed) || {}; - const [keys, setKeys] = useState([]); - - useEffect(() => { - const ks = Object.keys(deployed) - setKeys(ks) - }, [deployed]) - - const toggleCollapsed = (e: React.MouseEvent, k: string) => { - const target = e.target as HTMLElement; - const div = target.nextElementSibling as HTMLElement | null; - - if(div) { - div.style.display = div.style.display === 'none' ? 'block' : 'none' - } - } - - return ( -
-
-

Contract Explorer

-
-
-
- { - keys.map(k => ( -
-

toggleCollapsed(e, k)} - > - {`${k.substring(0, 5)}..${k.substring(50)}`} -

-
- { - deployed[k].map(item => ( - - )) - } -
-
- ) - ) - } -
- - -
-

No Function or IDL Specified

-
-
-
-
- ); -} - -export default ContractExplorer; diff --git a/packages/frontend/src/components/DeployExplorer.tsx b/packages/frontend/src/components/DeployExplorer.tsx new file mode 100644 index 0000000..8667c33 --- /dev/null +++ b/packages/frontend/src/components/DeployExplorer.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { store } from "@/state"; +import { useSelector } from "@xstate/store/react"; +import React, { useEffect, useState } from "react"; +import Hide from "./Hide"; +import InvokeFunction from "./InvokeFunction"; +import useDeploy from "@/hooks/useDeploy"; +import { Button } from "./ui/button"; +import { Select, SelectContent, SelectIcon, SelectItem, SelectItemText, SelectPortal, SelectTrigger, SelectValue, SelectViewport } from "@radix-ui/react-select"; +import { ArchiveX, ChevronDownIcon, Copy, LucideDelete } from "lucide-react"; +import useCompile from "@/hooks/useCompile"; + +function DeployExplorer() { + const {compileFile} = useCompile(); + + const {deployWasm} = useDeploy(); + const currFileTabSelected = useSelector(store, (state) => state.context.currentFile); + const compiled = useSelector(store, (state) => state.context.compiled); + const currWasm = useSelector(store, (state) => state.context.currentWasm); + const deployed = useSelector(store, (state) => state.context.contract?.deployed) || {}; + const [keys, setKeys] = useState([]); + const [copied, setCopied] = useState(!1); + const [selected, setSelected] = useState(compiled.length ? compiled[0].path : ''); + + useEffect(() => { + console.log('[tur] current file tab:', currFileTabSelected) + currFileTabSelected && setSelected(currFileTabSelected || '') + }, [currFileTabSelected]) + + useEffect(() => { + console.log('[tur] compiled updated:', compiled) + }, [compiled]) + + const handleSelect = (event: React.ChangeEvent) => { + const v = event.target.value; + console.log("[tur] Custom selected:", v); + setSelected(v); + store.send({ type: "setCurrentPath", path: v }); + }; + + useEffect(() => { + const ks = Object.keys(deployed) + console.log('[tur] useEffect deployed:', ks) + setKeys(ks || []) + }, [deployed]) + + const toggleCollapsed = (e: React.MouseEvent, k: string) => { + // const target = e.target as HTMLElement; + // const div = target.nextElementSibling as HTMLElement | null; + + // if(div) { + // div.style.display = div.style.display === 'none' ? 'block' : 'none' + // } + const span = e.currentTarget as HTMLElement; + const parentP = span.parentElement; + const containerDiv = parentP?.parentElement; + + const hiddenDiv = containerDiv?.querySelector('div') as HTMLElement | null; + + if (hiddenDiv) { + hiddenDiv.style.display = hiddenDiv.style.display === 'none' ? 'block' : 'none'; + } + } + + const handleDeploy = async () => { + let contract: Buffer | null = null; + console.log('[tur] selected path', selected) + console.log('[tur] curr wasm path', currWasm.path) + if(selected.indexOf(currWasm.path) == -1) { + console.log('[tur] so compiling..') + const res = await compileFile(); + contract = res.data; + } + const result = await deployWasm(contract) + console.log('[tur] deployed?', result) + } + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(!0); + setTimeout(() => setCopied(!1), 1500); + console.log('[tur] copied!') + } catch (err) { + console.error("Copy failed", err); + } + }; + + const handleRemoveDeployed = async (k: string) => { + console.log('[tur] keys:', keys) + store.send({ type: 'deleteDeployed', addr: k}) + } + + return ( +
+
+

Deploy Explorer

+
+
+
+
+

CONTRACTS

+ + +
+
+
+
+ +
+
+ {keys.length ?

DEPLOYED

: <>} +
+ { + keys.map(k => ( +
+

+ toggleCollapsed(e, k)}> + {`${k.substring(0, 5)}..${k.substring(50)}`} + + handleCopy(k)} /> + {/* {copied && Copied!} */} + handleRemoveDeployed(k)}/> +

+
+ { + deployed[k] && deployed[k].map(item => ( + + )) + } +
+
+ ) + ) + } +
+ + {/* +
+

No Function or IDL Specified

+
+
*/} +
+
+ ); +} + +export default DeployExplorer; diff --git a/packages/frontend/src/components/InvokeFunction.tsx b/packages/frontend/src/components/InvokeFunction.tsx index b809b0d..77392d7 100644 --- a/packages/frontend/src/components/InvokeFunction.tsx +++ b/packages/frontend/src/components/InvokeFunction.tsx @@ -58,10 +58,10 @@ const defaultState = { result: { type: "", value: "" }, name: "", }; -function InvokeFunction({ method }: { method: FunctionSpec }) { +function InvokeFunction({ contractAddress, method }: { contractAddress: string, method: FunctionSpec }) { const [sg, setSignature] = useState>(defaultState); const [args, setArgs] = useState>({}); - const contractAddress = useSelector(store, (state) => state.context.contract?.address); + // const contractAddress = useSelector(store, (state) => state.context.contract?.address); const [logs, setLogs] = useState(["Hello wrold", "Mango World"]); const toastId = useId(); const [block, setBlock] = useState(false); diff --git a/packages/frontend/src/components/SidePanel.tsx b/packages/frontend/src/components/SidePanel.tsx index 03838dd..14fec63 100644 --- a/packages/frontend/src/components/SidePanel.tsx +++ b/packages/frontend/src/components/SidePanel.tsx @@ -5,7 +5,7 @@ import SolangLogo from "@/assets/image/solang-logo.png"; import { Button } from "./ui/button"; import { FaCog } from "react-icons/fa"; import { SidebarView, useAppStore } from "@/app/state"; -import { Files, FunctionSquare, LucideFiles, SquareFunction, Star } from "lucide-react"; +import { Files, FunctionSquare, LucideFiles, SquareFunction, Star, RefreshCcw, SquarePlay } from "lucide-react"; import useCompile from "@/hooks/useCompile"; import useDeploy from "@/hooks/useDeploy"; import { useState } from "react"; @@ -15,16 +15,7 @@ function SidePanel() { const { deployWasm } = useDeploy(); const [contract, setContract] = useState(null); - const handleCompile = async () => { - const result = await compileFile() - setContract(result.data) - console.log('[tur] compilation result', result) - } - - const handleDeploy = async () => { - const result = await deployWasm(contract) - console.log('[tur] deployed?', result) - } + const setSidebar = useAppStore((state) => state.setSidebar); return (
@@ -35,14 +26,11 @@ function SidePanel() { - - -
diff --git a/packages/frontend/src/components/Sidebar.tsx b/packages/frontend/src/components/Sidebar.tsx index 77a0042..cb6339e 100644 --- a/packages/frontend/src/components/Sidebar.tsx +++ b/packages/frontend/src/components/Sidebar.tsx @@ -8,7 +8,8 @@ import { store } from "@/state"; // import ContractExplorer from "./ContractExplorer"; import dynamic from "next/dynamic"; -const ContractExplorer = dynamic(() => import("./ContractExplorer"), { ssr: false }); +const CompileExplorer = dynamic(() => import("./CompileExplorer"), { ssr: false }); +const DeployExplorer = dynamic(() => import("./DeployExplorer"), { ssr: false }); function Sidebar() { const { sidebar } = useAppStore(); @@ -18,8 +19,12 @@ function Sidebar() { return ; } - if (sidebar === SidebarView.CONTRACT) { - return ; + if (sidebar === SidebarView.COMPILE) { + return ; + } + + if (sidebar === SidebarView.DEPLOY) { + return ; } return ( diff --git a/packages/frontend/src/hooks/useCompile.tsx b/packages/frontend/src/hooks/useCompile.tsx index 5520b0a..6b163a8 100644 --- a/packages/frontend/src/hooks/useCompile.tsx +++ b/packages/frontend/src/hooks/useCompile.tsx @@ -15,8 +15,10 @@ export interface ICompilationResult { function useCompile() { const code = useFileContent(); + const selected = useSelector(store, (state) => state.context.currentFile); const compileFile = async (): Promise => { + console.log('[tur] [compileFile] code:', code) if (!code) { const err ="Error: No Source Code Found" logger.error(err); @@ -63,6 +65,7 @@ function useCompile() { if (success) { if (result.type === "SUCCESS") { const wasm = result.payload.wasm; + store.send({ type: "updateCurrentWasm", path: selected || '', buff: wasm }); logger.info("Contract compiled successfully!"); return { data: wasm, @@ -77,7 +80,7 @@ function useCompile() { logger.error(message); err = message } - + console.log('[tur] compilatiion error:', err) return { data: null, err diff --git a/packages/frontend/src/hooks/useDeploy.tsx b/packages/frontend/src/hooks/useDeploy.tsx index 15d3a8b..b608adf 100644 --- a/packages/frontend/src/hooks/useDeploy.tsx +++ b/packages/frontend/src/hooks/useDeploy.tsx @@ -9,30 +9,43 @@ import { Keypair, Networks } from "@stellar/stellar-sdk"; import generateIdl from "@/lib/idl-wasm"; import deployStellerContract from "@/lib/deploy-steller"; import { FunctionSpec } from "@/types/idl"; +import useCompile from "./useCompile"; function useDeploy() { + const {compileFile} = useCompile(); + const selected = useSelector(store, (state) => state.context.currentFile); + const currWasm = useSelector(store, (state) => state.context.currentWasm); const deployWasm = async (contract: null | Buffer) => { + console.log('[tur] deploying', contract) + + if(currWasm.path.indexOf(selected || '') > -1) { + contract = currWasm.buff + } else if(!contract && selected && selected !== 'explorer') { + const r = await compileFile(); + contract = r.data + } + if (!contract) { return; - } - try { - const keypair = Keypair.random(); - - logger.info("Deploying contract..."); - const idl = await generateIdl(contract); - const fltrd = idl.filter((i: FunctionSpec) => i.name.indexOf('constructor') == -1); - store.send({ type: "updateContract", methods: fltrd }); - const contractAddress = await deployStellerContract(contract, keypair, Networks.TESTNET); - logger.info("Contract deployed successfully!"); - contractAddress && store.send({ type: "updateContract", address: contractAddress }); - - } catch { - return !1 - } - return !0 + } + try { + const keypair = Keypair.random(); + + logger.info("Deploying contract..."); + const idl = await generateIdl(contract); + const fltrd = idl.filter((i: FunctionSpec) => i.name.indexOf('constructor') == -1); + store.send({ type: "updateContract", methods: fltrd }); + const contractAddress = await deployStellerContract(contract, keypair, Networks.TESTNET); + logger.info("Contract deployed successfully!"); + contractAddress && store.send({ type: "updateContract", address: contractAddress }); + + } catch { + return !1 + } + return !0 } return { diff --git a/packages/frontend/src/state/context.ts b/packages/frontend/src/state/context.ts index 7c82d29..83859d0 100644 --- a/packages/frontend/src/state/context.ts +++ b/packages/frontend/src/state/context.ts @@ -2,6 +2,7 @@ import { ExpNodeType, FolderType } from "@/types/explorer"; import { LogType } from "@/types/log"; import { Monaco } from "@monaco-editor/react"; import { Contract, IDL } from "@/types/idl"; +import { ICompiled, ICurrentWasm } from "@/types/contracts"; export * from "@stellar/stellar-sdk"; export * as contract from "@stellar/stellar-sdk/contract"; export * as rpc from "@stellar/stellar-sdk/rpc"; @@ -37,8 +38,17 @@ export const context = { invoking: false, address: null, methods: [], + // for deploy explorer's deployed instance list + // to invoke functions therein deployed: {}, } as Contract, + // for deploy explorer's drop down list + // if the list has something, we can select one and press deploy btn + compiled: [] as ICompiled[], + currentWasm: { + path: '', + buff: null, + } as ICurrentWasm, }; export type Context = typeof context; diff --git a/packages/frontend/src/state/events.ts b/packages/frontend/src/state/events.ts index 79186ab..7a9ef73 100644 --- a/packages/frontend/src/state/events.ts +++ b/packages/frontend/src/state/events.ts @@ -7,7 +7,8 @@ import { Monaco } from "@monaco-editor/react"; import { createPath } from "./utils"; import { MessageType } from "vscode-languageserver-protocol"; import { nanoid } from "nanoid"; -import { Contract, IDL } from "@/types/idl"; +import { Contract, ContractsDeployed, IDL } from "@/types/idl"; +import { ICompiled, ICurrentWasm } from "@/types/contracts"; export const events = { toggleFolder: (context: Context, event: { path: string }) => { @@ -66,6 +67,7 @@ export const events = { } satisfies FolderType; }, + deleteFile(context: Context, event: { path: string; basePath: string }) { const folder = get(context, event.basePath) as FolderType; const file = get(context, event.path) as FileType; @@ -73,6 +75,7 @@ export const events = { events.removeTab(context, { path: event.path }); delete folder.items[file.name]; delete context.files[event.path]; + context.compiled = context.compiled.filter(c => c.path !== event.path) }, deleteFolder(context: Context, event: { path: string }) { unset(context, event.path); @@ -137,4 +140,26 @@ export const events = { } Object.assign(context.contract, event); }, + + deleteDeployed(context: Context, event: {addr: string}) { + console.log('[tur] remove deployed:', event.addr); + const copy = { ...context.contract.deployed }; + console.log('[tur] copy:', copy, copy[event.addr]); + + delete copy[event.addr]; + context.contract.deployed = copy; + + }, + + addCompiled(context: Context, event: Partial) { + const d = {} as ICompiled; + Object.assign(d, event); + const x = context.compiled.filter(c => c.path == event.path); + if(x.length == 0) context.compiled.push(d); + }, + + updateCurrentWasm(context: Context, event: Partial) { + console.log('[tur] event updateCurrentWasm:', event) + Object.assign(context.currentWasm, event) + } }; diff --git a/packages/frontend/src/types/contracts.ts b/packages/frontend/src/types/contracts.ts new file mode 100644 index 0000000..d0b3686 --- /dev/null +++ b/packages/frontend/src/types/contracts.ts @@ -0,0 +1,9 @@ +export interface ICompiled { + path: string, + name: string, +} + +export interface ICurrentWasm { + path: string, + buff: Buffer | null, +} \ No newline at end of file diff --git a/tobedeleted.txt b/tobedeleted.txt new file mode 100644 index 0000000..b79aff8 --- /dev/null +++ b/tobedeleted.txt @@ -0,0 +1,19 @@ +contract flipper { + uint64 public value; + + + function flip() public { + if(value == 0) + value = 1; + else value = 0; + } + +} + + +contract abc { + function mul(uint64 y) public returns(uint64) { + uint64 x = 112; + return x * y; + } +} From c53a5ffa0b335324832f2a083587580b680de94d Mon Sep 17 00:00:00 2001 From: slycompiler <161673729+slycompiler@users.noreply.github.com> Date: Sat, 10 May 2025 14:02:29 +0000 Subject: [PATCH 8/8] fix: add start script in package.json --- packages/frontend/package.json | 2 +- tobedeleted.txt | 19 ------------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 tobedeleted.txt diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 02f448c..66a7b20 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "next dev", "build": "set NODE_ENV=production & next build", - "sr": "npm run build && next start", + "start": "next start", "lint": "next lint" }, "dependencies": { diff --git a/tobedeleted.txt b/tobedeleted.txt deleted file mode 100644 index b79aff8..0000000 --- a/tobedeleted.txt +++ /dev/null @@ -1,19 +0,0 @@ -contract flipper { - uint64 public value; - - - function flip() public { - if(value == 0) - value = 1; - else value = 0; - } - -} - - -contract abc { - function mul(uint64 y) public returns(uint64) { - uint64 x = 112; - return x * y; - } -}