fugthchat commited on
Commit
03d1966
Β·
verified Β·
1 Parent(s): 36161ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +717 -226
app.py CHANGED
@@ -1,246 +1,737 @@
1
  import streamlit as st
2
- from web3 import Web3
3
  from solcx import compile_source, install_solc
4
  import json
5
- import os
6
  import streamlit.components.v1 as components
7
 
8
- # --- 1. PAGE CONFIG (Dark IDE Theme) ---
9
- st.set_page_config(layout="wide", page_title="Vivara Remix", page_icon="⚑")
 
 
 
 
 
10
 
11
- # CSS HACKS TO MAKE IT LOOK LIKE AN IDE
12
  st.markdown(
13
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  <style>
15
- /* Main Background to Dark Gray */
16
- .stApp { background-color: #1a1a1a; color: #ffffff; }
17
-
18
- /* Sidebar (File Explorer) */
19
- section[data-testid="stSidebar"] { background-color: #262626; border-right: 1px solid #333; }
20
-
21
- /* Remove whitespace at top */
22
- .block-container { padding-top: 1rem; padding-bottom: 0rem; }
23
-
24
- /* Editor Look */
25
- .stTextArea textarea {
26
- font-family: 'Courier New', monospace;
27
- background-color: #1e1e1e;
28
  color: #d4d4d4;
29
- border: 1px solid #333;
30
- }
 
 
 
31
 
32
- /* Buttons Remix Style */
33
- .stButton button {
34
- background-color: #2f3136;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  border: 1px solid #444;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  border-radius: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  font-size: 14px;
39
- }
40
- .stButton button:hover { background-color: #36393f; border-color: #00ff41; }
41
-
42
- /* Terminal Area */
43
- .terminal {
44
- background-color: #000000;
45
- color: #00ff41;
46
- font-family: 'Courier New', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  padding: 10px;
48
- border-top: 1px solid #333;
49
- height: 150px;
50
- overflow-y: scroll;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  font-size: 12px;
52
- }
 
 
53
  </style>
54
- """,
55
- unsafe_allow_html=True,
56
- )
57
-
58
- # --- 2. SIDEBAR (FILE EXPLORER) ---
59
- with st.sidebar:
60
- st.image("https://api.dicebear.com/7.x/shapes/svg?seed=vivara", width=40)
61
- st.caption("Vivara Forge v2.0.1")
62
-
63
- st.markdown("### πŸ“‚ File Explorer")
64
- st.markdown("πŸ“„ **Vivara.sol**")
65
- st.markdown("πŸ“„ *Token.sol*")
66
- st.markdown("πŸ“ *artifacts/*")
67
-
68
- <style>
69
- /* Strip Streamlit chrome */
70
- header { visibility: hidden; }
71
- footer { visibility: hidden; }
72
- .stApp { background-color: #0e1117; color: #e6e8ec; }
73
- section[data-testid="stSidebar"] { background-color: #141821; border-right: 1px solid #1f2530; }
74
- .block-container { padding-top: 0.5rem; padding-bottom: 0rem; }
75
-
76
- /* Editor Look */
77
- .stTextArea textarea {
78
- font-family: 'JetBrains Mono', 'Courier New', monospace;
79
- background-color: #1e1e1e;
80
- color: #d4d4d4;
81
- border: 1px solid #333;
82
- }
83
-
84
- /* Buttons Remix Style */
85
- .stButton button {
86
- background-color: #2f3136;
87
- color: white;
88
- border: 1px solid #444;
89
- border-radius: 4px;
90
- font-size: 14px;
91
- }
92
- .stButton button:hover { background-color: #36393f; border-color: #00ff41; }
93
-
94
- /* Terminal Area */
95
- .terminal {
96
- background-color: #000000;
97
- color: #00ff41;
98
- font-family: 'JetBrains Mono', monospace;
99
- padding: 10px;
100
- border-top: 1px solid #333;
101
- height: 150px;
102
- overflow-y: scroll;
103
- font-size: 12px;
104
- }
105
- </style>
106
- st.divider()
107
-
108
- st.markdown("### βš™οΈ Environment")
109
- env = st.selectbox(
110
- "Environment",
111
- ["Injected Provider - MetaMask", "Vivara VM (London)", "Vivara VM (Berlin)"],
112
- )
113
- st.text_input("Account", value="0x... (Connect Wallet)", disabled=True)
114
- st.number_input("Gas Limit", value=3000000)
115
- st.text_input("Value (Wei)", value="0")
116
-
117
- # --- 3. MAIN WORKSPACE (SPLIT VIEW) ---
118
- # Create 3 columns: Editor (Wide) | Compiler/Deploy (Narrow)
119
- col_editor, col_tools = st.columns([3, 1])
120
-
121
- # --- LEFT: CODE EDITOR ---
122
- with col_editor:
123
- st.markdown("#### Vivara.sol")
124
- default_sol = """// SPDX-License-Identifier: MIT
125
- pragma solidity ^0.8.20;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 
 
 
 
 
128
 
129
- // 🦁 THE VIVARA PROTOCOL
130
- contract VivaraToken is ERC20 {
131
- constructor() ERC20("Vivara", "VIVR") {
132
- st.markdown("### 🦊 Wallet")
133
- st.caption("Use injected MetaMask in browser")
134
- _mint(msg.sender, 1000000000 * 10**18);
135
- }
136
- }"""
137
- code = st.text_area(
138
- "Write Solidity Code",
139
- value=default_sol,
140
- height=550,
141
- label_visibility="collapsed",
142
- )
143
-
144
- # --- RIGHT: TOOLS PANEL ---
145
- with col_tools:
146
- tab_compile, tab_deploy = st.tabs(["Compile", "Deploy"])
147
-
148
- # TAB 1: COMPILER
149
- with tab_compile:
150
- st.markdown("##### Solidity Compiler")
151
- st.selectbox("Compiler Version", ["0.8.20", "0.8.19", "0.8.18"])
152
- st.checkbox("Auto compile", value=True)
153
- st.checkbox("Enable Optimization")
154
-
155
- if st.button("Compile Vivara.sol", type="primary", use_container_width=True):
156
- with st.spinner("Compiling..."):
157
- try:
158
- install_solc("0.8.20")
159
- compiled = compile_source(
160
- code, output_values=["abi", "bin"], solc_version="0.8.20"
161
- )
162
- contract_id, iface = list(compiled.items())[-1]
163
- st.session_state["abi"] = iface["abi"]
164
- st.session_state["bin"] = iface["bin"]
165
- st.session_state["name"] = contract_id
166
- st.success(f"βœ… Compiled")
167
- except Exception as e:
168
- st.error("Compilation Failed")
169
- st.exception(e)
170
-
171
- # TAB 2: DEPLOYER
172
- with tab_deploy:
173
- st.markdown("##### Deploy & Run")
174
-
175
- if "bin" in st.session_state:
176
- st.markdown(f"**Contract:** {st.session_state['name']}")
177
-
178
- # THE ORANGE BUTTON (Remix Style)
179
- abi_json = json.dumps(st.session_state["abi"])
180
- bytecode = st.session_state["bin"]
181
-
182
- html_script = f"""
183
- <html>
184
- <head><script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"></script></head>
185
- <body style="background-color:transparent; margin:0; padding:0;">
186
- <button id="btn" onclick="deploy()" style="
187
- background-color: #f6851b; color: white; border: none;
188
- padding: 10px; width: 100%; border-radius: 4px;
189
- font-weight: bold; cursor: pointer; font-family: sans-serif; text-transform: uppercase;">
190
- Deploy
191
- </button>
192
- <div id="status" style="margin-top:5px; color:#d4d4d4; font-family:monospace; font-size:11px;"></div>
193
-
194
- <script>
195
- async function deploy() {{
196
- const btn = document.getElementById("btn");
197
- const status = document.getElementById("status");
198
-
199
- if (!window.ethereum) {{ status.innerHTML = "❌ No MetaMask"; return; }}
200
-
201
- try {{
202
- btn.innerText = "PENDING...";
203
- const provider = new ethers.providers.Web3Provider(window.ethereum);
204
- await provider.send("eth_requestAccounts", []);
205
- const signer = provider.getSigner();
206
- const factory = new ethers.ContractFactory({abi_json}, "{bytecode}", signer);
207
-
208
- const contract = await factory.deploy();
209
- status.innerText = "Creation pending...";
210
-
211
- await contract.deployed();
212
- status.innerText = "βœ… " + contract.address.slice(0,10) + "...";
213
- btn.innerText = "DEPLOYED";
214
- btn.style.backgroundColor = "#2e8b57";
215
-
216
- }} catch (e) {{
217
- status.innerText = e.message;
218
- btn.innerText = "FAILED";
219
- btn.style.backgroundColor = "#cd5c5c";
220
- }}
221
- }}
222
- </script>
223
- </body>
224
- </html>
225
- """
226
- components.html(html_script, height=100)
227
-
228
- st.markdown("---")
229
- st.caption("Deployed Contracts:")
230
- st.markdown(f"`{st.session_state['name']} at 0x...`")
231
- else:
232
- st.info("Compile code first.")
233
-
234
- # --- 4. BOTTOM TERMINAL (LOGS) ---
235
- st.markdown("#### πŸ“Ÿ Terminal")
236
- st.markdown(
237
- """
238
- <div class="terminal">
239
- [INFO] Welcome to Vivara Forge v2.0.1<br>
240
- [INFO] Connected to Hugging Face Cloud Engine<br>
241
- [INFO] Environment: Injected Web3<br>
242
- > _
243
- </div>
244
- """,
245
- unsafe_allow_html=True,
246
- )
 
1
  import streamlit as st
 
2
  from solcx import compile_source, install_solc
3
  import json
 
4
  import streamlit.components.v1 as components
5
 
6
+ # --- PAGE CONFIG ---
7
+ st.set_page_config(
8
+ layout="wide",
9
+ page_title="Vivara Forge",
10
+ page_icon="🦁",
11
+ initial_sidebar_state="collapsed",
12
+ )
13
 
14
+ # --- HIDE ALL STREAMLIT UI ---
15
  st.markdown(
16
  """
17
+ <style>
18
+ #MainMenu, header, footer, [data-testid="stToolbar"], [data-testid="stDecoration"],
19
+ [data-testid="stStatusWidget"], .stDeployButton, div[data-testid="stSidebarNav"],
20
+ section[data-testid="stSidebar"], .viewerBadge_container__1QSob,
21
+ [data-testid="stHeader"], [data-testid="stFooter"] {
22
+ display: none !important;
23
+ visibility: hidden !important;
24
+ }
25
+ .stApp { background: #1e1e1e !important; }
26
+ .block-container { padding: 0 !important; max-width: 100% !important; }
27
+ [data-testid="stAppViewContainer"] { padding: 0 !important; }
28
+
29
+ /* Hide the form completely */
30
+ .stForm { position: absolute; left: -9999px; }
31
+ </style>
32
+ """,
33
+ unsafe_allow_html=True,
34
+ )
35
+
36
+ # --- SESSION STATE ---
37
+ if "code" not in st.session_state:
38
+ st.session_state.code = """// SPDX-License-Identifier: MIT
39
+ pragma solidity ^0.8.20;
40
+
41
+ contract MyToken {
42
+ string public name = "Vivara Coin";
43
+ string public symbol = "VIVR";
44
+ uint8 public decimals = 18;
45
+ uint256 public totalSupply;
46
+
47
+ mapping(address => uint256) public balanceOf;
48
+ mapping(address => mapping(address => uint256)) public allowance;
49
+
50
+ event Transfer(address indexed from, address indexed to, uint256 value);
51
+ event Approval(address indexed owner, address indexed spender, uint256 value);
52
+
53
+ constructor(uint256 _initialSupply) {
54
+ totalSupply = _initialSupply * 10 ** decimals;
55
+ balanceOf[msg.sender] = totalSupply;
56
+ emit Transfer(address(0), msg.sender, totalSupply);
57
+ }
58
+
59
+ function transfer(address to, uint256 amount) public returns (bool) {
60
+ require(balanceOf[msg.sender] >= amount, "Insufficient balance");
61
+ balanceOf[msg.sender] -= amount;
62
+ balanceOf[to] += amount;
63
+ emit Transfer(msg.sender, to, amount);
64
+ return true;
65
+ }
66
+
67
+ function approve(address spender, uint256 amount) public returns (bool) {
68
+ allowance[msg.sender][spender] = amount;
69
+ emit Approval(msg.sender, spender, amount);
70
+ return true;
71
+ }
72
+
73
+ function transferFrom(address from, address to, uint256 amount) public returns (bool) {
74
+ require(balanceOf[from] >= amount, "Insufficient balance");
75
+ require(allowance[from][msg.sender] >= amount, "Allowance exceeded");
76
+ allowance[from][msg.sender] -= amount;
77
+ balanceOf[from] -= amount;
78
+ balanceOf[to] += amount;
79
+ emit Transfer(from, to, amount);
80
+ return true;
81
+ }
82
+ }"""
83
+
84
+ if "abi" not in st.session_state:
85
+ st.session_state.abi = None
86
+ if "bytecode" not in st.session_state:
87
+ st.session_state.bytecode = None
88
+ if "contract_name" not in st.session_state:
89
+ st.session_state.contract_name = None
90
+ if "logs" not in st.session_state:
91
+ st.session_state.logs = []
92
+ if "compile_trigger" not in st.session_state:
93
+ st.session_state.compile_trigger = False
94
+
95
+
96
+ # --- COMPILE FUNCTION ---
97
+ def do_compile():
98
+ try:
99
+ st.session_state.logs.append("[INFO] Installing solc 0.8.20...")
100
+ install_solc("0.8.20")
101
+
102
+ st.session_state.logs.append("[INFO] Compiling Solidity code...")
103
+ compiled = compile_source(
104
+ st.session_state.code, output_values=["abi", "bin"], solc_version="0.8.20"
105
+ )
106
+
107
+ contract_id, interface = list(compiled.items())[-1]
108
+ st.session_state.abi = interface["abi"]
109
+ st.session_state.bytecode = interface["bin"]
110
+ st.session_state.contract_name = contract_id.split(":")[-1]
111
+
112
+ st.session_state.logs.append(
113
+ f"[SUCCESS] Compiled: {st.session_state.contract_name}"
114
+ )
115
+ return True
116
+ except Exception as e:
117
+ st.session_state.logs.append(f"[ERROR] {str(e)}")
118
+ return False
119
+
120
+
121
+ # Check for compile trigger
122
+ if st.session_state.compile_trigger:
123
+ do_compile()
124
+ st.session_state.compile_trigger = False
125
+
126
+ # --- FULL REMIX-LIKE HTML APP ---
127
+ abi_json = json.dumps(st.session_state.abi) if st.session_state.abi else "null"
128
+ bytecode = st.session_state.bytecode or ""
129
+ contract_name = st.session_state.contract_name or "No Contract"
130
+ logs_html = (
131
+ "\\n".join(st.session_state.logs[-15:])
132
+ if st.session_state.logs
133
+ else "[INFO] Vivara Forge Ready - Connect MetaMask to deploy"
134
+ )
135
+
136
+ # Escape the code for JS
137
+ code_escaped = (
138
+ st.session_state.code.replace("\\", "\\\\")
139
+ .replace("`", "\\`")
140
+ .replace("${", "\\${")
141
+ )
142
+
143
+ html = f"""
144
+ <!DOCTYPE html>
145
+ <html>
146
+ <head>
147
+ <meta charset="UTF-8">
148
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@400;600&display=swap" rel="stylesheet">
149
+ <script src="https://cdn.ethers.io/lib/ethers-5.7.umd.min.js"></script>
150
  <style>
151
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
152
+ body {{
153
+ font-family: 'Inter', sans-serif;
154
+ background: #1e1e1e;
 
 
 
 
 
 
 
 
 
155
  color: #d4d4d4;
156
+ height: 100vh;
157
+ display: flex;
158
+ flex-direction: column;
159
+ overflow: hidden;
160
+ }}
161
 
162
+ /* HEADER */
163
+ .header {{
164
+ height: 44px;
165
+ background: linear-gradient(180deg, #2d2d2d 0%, #252526 100%);
166
+ border-bottom: 1px solid #3c3c3c;
167
+ display: flex;
168
+ align-items: center;
169
+ padding: 0 16px;
170
+ gap: 12px;
171
+ }}
172
+ .logo {{
173
+ font-family: 'JetBrains Mono';
174
+ font-weight: 600;
175
+ font-size: 14px;
176
+ color: #fff;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 8px;
180
+ }}
181
+ .logo .dot {{ width: 10px; height: 10px; background: #3fc04f; border-radius: 50%; box-shadow: 0 0 8px #3fc04f; }}
182
+
183
+ .header-btn {{
184
+ background: #0e639c;
185
  color: white;
186
+ border: none;
187
+ padding: 7px 16px;
188
+ border-radius: 4px;
189
+ cursor: pointer;
190
+ font-size: 12px;
191
+ font-weight: 600;
192
+ display: flex;
193
+ align-items: center;
194
+ gap: 6px;
195
+ transition: all 0.15s;
196
+ }}
197
+ .header-btn:hover {{ background: #1177bb; transform: translateY(-1px); }}
198
+ .header-btn.orange {{ background: linear-gradient(180deg, #e8850f 0%, #d4730f 100%); }}
199
+ .header-btn.orange:hover {{ background: linear-gradient(180deg, #f59622 0%, #e8850f 100%); }}
200
+ .header-btn.green {{ background: linear-gradient(180deg, #3ab55f 0%, #2ea44f 100%); }}
201
+ .header-btn.green:hover {{ background: linear-gradient(180deg, #4cc76f 0%, #3ab55f 100%); }}
202
+
203
+ .wallet-status {{
204
+ margin-left: auto;
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 12px;
208
+ font-size: 12px;
209
+ }}
210
+ .wallet-badge {{
211
+ background: #333;
212
+ padding: 5px 12px;
213
+ border-radius: 20px;
214
+ font-family: 'JetBrains Mono';
215
+ font-size: 11px;
216
  border: 1px solid #444;
217
+ }}
218
+ .wallet-badge.connected {{ border-color: #3fc04f; color: #3fc04f; }}
219
+
220
+ /* MAIN LAYOUT */
221
+ .main {{ flex: 1; display: flex; min-height: 0; }}
222
+
223
+ /* LEFT SIDEBAR */
224
+ .sidebar {{
225
+ width: 240px;
226
+ background: #252526;
227
+ border-right: 1px solid #3c3c3c;
228
+ display: flex;
229
+ flex-direction: column;
230
+ overflow-y: auto;
231
+ }}
232
+ .sidebar-section {{ padding: 14px; border-bottom: 1px solid #3c3c3c; }}
233
+ .sidebar-title {{
234
+ font-size: 11px;
235
+ text-transform: uppercase;
236
+ color: #858585;
237
+ margin-bottom: 10px;
238
+ letter-spacing: 1px;
239
+ font-weight: 600;
240
+ }}
241
+ .file-item {{
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 8px;
245
+ padding: 8px 10px;
246
+ cursor: pointer;
247
  border-radius: 4px;
248
+ font-size: 13px;
249
+ margin-bottom: 2px;
250
+ }}
251
+ .file-item:hover {{ background: #2a2d2e; }}
252
+ .file-item.active {{ background: #37373d; border-left: 2px solid #0e639c; }}
253
+
254
+ .env-select {{
255
+ width: 100%;
256
+ background: #3c3c3c;
257
+ border: 1px solid #555;
258
+ color: #d4d4d4;
259
+ padding: 10px;
260
+ border-radius: 6px;
261
+ font-size: 12px;
262
+ margin-bottom: 12px;
263
+ cursor: pointer;
264
+ }}
265
+ .env-select:focus {{ border-color: #0e639c; outline: none; }}
266
+
267
+ .input-group {{ margin-bottom: 12px; }}
268
+ .input-group label {{
269
+ display: block;
270
+ font-size: 11px;
271
+ color: #858585;
272
+ margin-bottom: 6px;
273
+ text-transform: uppercase;
274
+ }}
275
+ .input-group input {{
276
+ width: 100%;
277
+ background: #3c3c3c;
278
+ border: 1px solid #555;
279
+ color: #d4d4d4;
280
+ padding: 8px 10px;
281
+ border-radius: 6px;
282
+ font-size: 12px;
283
+ font-family: 'JetBrains Mono';
284
+ }}
285
+ .input-group input:focus {{ border-color: #0e639c; outline: none; }}
286
+
287
+ /* EDITOR AREA */
288
+ .editor-container {{ flex: 1; display: flex; flex-direction: column; min-width: 0; }}
289
+
290
+ .tab-bar {{
291
+ height: 38px;
292
+ background: #252526;
293
+ display: flex;
294
+ align-items: flex-end;
295
+ border-bottom: 1px solid #3c3c3c;
296
+ padding-left: 8px;
297
+ }}
298
+ .tab {{
299
+ padding: 8px 20px;
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 8px;
303
+ font-size: 13px;
304
+ cursor: pointer;
305
+ border: 1px solid transparent;
306
+ border-bottom: none;
307
+ margin-bottom: -1px;
308
+ border-radius: 6px 6px 0 0;
309
+ background: #2d2d2d;
310
+ }}
311
+ .tab.active {{
312
+ background: #1e1e1e;
313
+ border-color: #3c3c3c;
314
+ border-top: 2px solid #0e639c;
315
+ }}
316
+ .tab .close {{
317
+ opacity: 0.5;
318
+ font-size: 16px;
319
+ margin-left: 4px;
320
+ }}
321
+ .tab .close:hover {{ opacity: 1; }}
322
+
323
+ .editor {{
324
+ flex: 1;
325
+ display: flex;
326
+ background: #1e1e1e;
327
+ overflow: hidden;
328
+ }}
329
+ .line-numbers {{
330
+ width: 55px;
331
+ background: #1e1e1e;
332
+ border-right: 1px solid #333;
333
+ padding: 14px 10px;
334
+ text-align: right;
335
+ font-family: 'JetBrains Mono';
336
+ font-size: 13px;
337
+ color: #6e7681;
338
+ line-height: 1.6;
339
+ user-select: none;
340
+ }}
341
+ .code-area {{
342
+ flex: 1;
343
+ padding: 14px 16px;
344
+ font-family: 'JetBrains Mono';
345
+ font-size: 13px;
346
+ line-height: 1.6;
347
+ overflow: auto;
348
+ white-space: pre;
349
+ color: #d4d4d4;
350
+ }}
351
+ .keyword {{ color: #569cd6; }}
352
+ .string {{ color: #ce9178; }}
353
+ .comment {{ color: #6a9955; font-style: italic; }}
354
+ .number {{ color: #b5cea8; }}
355
+ .type {{ color: #4ec9b0; }}
356
+ .function-name {{ color: #dcdcaa; }}
357
+
358
+ /* RIGHT PANEL */
359
+ .right-panel {{
360
+ width: 320px;
361
+ background: #252526;
362
+ border-left: 1px solid #3c3c3c;
363
+ display: flex;
364
+ flex-direction: column;
365
+ }}
366
+ .panel-header {{
367
+ padding: 14px;
368
+ border-bottom: 1px solid #3c3c3c;
369
+ font-weight: 600;
370
+ font-size: 14px;
371
+ display: flex;
372
+ align-items: center;
373
+ gap: 8px;
374
+ }}
375
+ .panel-content {{ flex: 1; padding: 14px; overflow-y: auto; }}
376
+
377
+ .deploy-btn {{
378
+ width: 100%;
379
+ background: linear-gradient(180deg, #e8850f 0%, #d4730f 100%);
380
+ color: white;
381
+ border: none;
382
+ padding: 14px;
383
+ border-radius: 6px;
384
+ cursor: pointer;
385
  font-size: 14px;
386
+ font-weight: 600;
387
+ margin-bottom: 14px;
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: center;
391
+ gap: 8px;
392
+ transition: all 0.15s;
393
+ }}
394
+ .deploy-btn:hover {{ transform: translateY(-1px); box-shadow: 0 4px 12px rgba(212, 115, 15, 0.3); }}
395
+ .deploy-btn:disabled {{ background: #555; cursor: not-allowed; transform: none; box-shadow: none; }}
396
+
397
+ .info-card {{
398
+ background: #1e1e1e;
399
+ padding: 12px;
400
+ border-radius: 6px;
401
+ margin-bottom: 14px;
402
+ border: 1px solid #333;
403
+ }}
404
+ .info-card .row {{
405
+ display: flex;
406
+ justify-content: space-between;
407
+ margin-bottom: 6px;
408
+ font-size: 12px;
409
+ }}
410
+ .info-card .row:last-child {{ margin-bottom: 0; }}
411
+ .info-card .label {{ color: #858585; }}
412
+ .info-card .value {{ font-family: 'JetBrains Mono'; color: #4ec9b0; }}
413
+
414
+ .contract-card {{
415
+ background: #1e1e1e;
416
  padding: 10px;
417
+ border-radius: 6px;
418
+ margin-bottom: 8px;
419
+ border: 1px solid #333;
420
+ font-size: 11px;
421
+ }}
422
+ .contract-card .name {{ color: #4ec9b0; font-weight: 600; margin-bottom: 4px; }}
423
+ .contract-card .address {{ color: #858585; font-family: 'JetBrains Mono'; word-break: break-all; }}
424
+
425
+ /* TERMINAL */
426
+ .terminal-container {{
427
+ height: 160px;
428
+ background: #1e1e1e;
429
+ border-top: 1px solid #3c3c3c;
430
+ display: flex;
431
+ flex-direction: column;
432
+ }}
433
+ .terminal-header {{
434
+ padding: 8px 14px;
435
+ background: #252526;
436
+ border-bottom: 1px solid #3c3c3c;
437
+ font-size: 12px;
438
+ display: flex;
439
+ align-items: center;
440
+ gap: 8px;
441
+ }}
442
+ .terminal {{
443
+ flex: 1;
444
+ font-family: 'JetBrains Mono';
445
+ font-size: 12px;
446
+ padding: 10px 14px;
447
+ overflow-y: auto;
448
+ color: #d4d4d4;
449
+ line-height: 1.5;
450
+ }}
451
+ .terminal .error {{ color: #f14c4c; }}
452
+ .terminal .success {{ color: #3fc04f; }}
453
+ .terminal .info {{ color: #858585; }}
454
+
455
+ /* STATUS BAR */
456
+ .status-bar {{
457
+ height: 26px;
458
+ background: #007acc;
459
+ display: flex;
460
+ align-items: center;
461
+ padding: 0 14px;
462
  font-size: 12px;
463
+ gap: 20px;
464
+ }}
465
+ .status-item {{ display: flex; align-items: center; gap: 6px; }}
466
  </style>
467
+ </head>
468
+ <body>
469
+ <div class="header">
470
+ <div class="logo"><div class="dot"></div> VIVARA FORGE</div>
471
+ <button class="header-btn green" onclick="connectWallet()">🦊 Connect Wallet</button>
472
+ <button class="header-btn orange" onclick="compileContract()">⚑ Compile</button>
473
+ <div class="wallet-status">
474
+ <span id="networkBadge" class="wallet-badge">Not Connected</span>
475
+ <span id="addressBadge" class="wallet-badge">0x...</span>
476
+ </div>
477
+ </div>
478
+
479
+ <div class="main">
480
+ <div class="sidebar">
481
+ <div class="sidebar-section">
482
+ <div class="sidebar-title">πŸ“ File Explorer</div>
483
+ <div class="file-item active">πŸ“„ Contract.sol</div>
484
+ <div class="file-item">πŸ“„ Token.sol</div>
485
+ <div class="file-item">πŸ“ artifacts</div>
486
+ </div>
487
+
488
+ <div class="sidebar-section">
489
+ <div class="sidebar-title">βš™οΈ Deploy Environment</div>
490
+ <select class="env-select" id="envSelect">
491
+ <option value="metamask">🦊 Injected - MetaMask</option>
492
+ <option value="sepolia">πŸ”— Sepolia Testnet</option>
493
+ <option value="polygon">🟣 Polygon Mumbai</option>
494
+ </select>
495
+
496
+ <div class="input-group">
497
+ <label>Account</label>
498
+ <input type="text" id="accountInput" value="Connect wallet..." readonly>
499
+ </div>
500
+
501
+ <div class="input-group">
502
+ <label>Gas Limit</label>
503
+ <input type="text" id="gasLimit" value="3000000">
504
+ </div>
505
+
506
+ <div class="input-group">
507
+ <label>Value (ETH)</label>
508
+ <input type="text" id="valueEth" value="0">
509
+ </div>
510
+ </div>
511
+
512
+ <div class="sidebar-section">
513
+ <div class="sidebar-title">πŸ”§ Compiler</div>
514
+ <select class="env-select" id="compilerVersion">
515
+ <option value="0.8.20">Solidity v0.8.20</option>
516
+ <option value="0.8.19">Solidity v0.8.19</option>
517
+ <option value="0.8.18">Solidity v0.8.18</option>
518
+ </select>
519
+ </div>
520
+ </div>
521
+
522
+ <div class="editor-container">
523
+ <div class="tab-bar">
524
+ <div class="tab active">πŸ“„ Contract.sol <span class="close">Γ—</span></div>
525
+ </div>
526
+ <div class="editor">
527
+ <div class="line-numbers" id="lineNumbers"></div>
528
+ <div class="code-area" id="codeDisplay"></div>
529
+ </div>
530
+ <div class="terminal-container">
531
+ <div class="terminal-header">πŸ“Ÿ Terminal</div>
532
+ <div class="terminal" id="terminal"></div>
533
+ </div>
534
+ </div>
535
+
536
+ <div class="right-panel">
537
+ <div class="panel-header">πŸš€ Deploy & Run</div>
538
+ <div class="panel-content">
539
+ <div class="info-card">
540
+ <div class="row">
541
+ <span class="label">Contract</span>
542
+ <span class="value" id="contractName">{contract_name}</span>
543
+ </div>
544
+ <div class="row">
545
+ <span class="label">Status</span>
546
+ <span class="value" id="compileStatus">{"βœ… Compiled" if st.session_state.bytecode else "⏳ Not Compiled"}</span>
547
+ </div>
548
+ <div class="row">
549
+ <span class="label">Bytecode</span>
550
+ <span class="value">{len(bytecode) if bytecode else 0} bytes</span>
551
+ </div>
552
+ </div>
553
+
554
+ <div class="input-group">
555
+ <label>Constructor Arguments</label>
556
+ <input type="text" id="constructorArgs" placeholder="e.g., 1000000">
557
+ </div>
558
+
559
+ <button class="deploy-btn" id="deployBtn" onclick="deployContract()" {"" if st.session_state.bytecode else "disabled"}>
560
+ πŸš€ Deploy Contract
561
+ </button>
562
+
563
+ <div class="sidebar-title" style="margin-top: 20px;">Deployed Contracts</div>
564
+ <div id="contractsList"></div>
565
+ </div>
566
+ </div>
567
+ </div>
568
+
569
+ <div class="status-bar">
570
+ <div class="status-item">🦁 Vivara Forge v2.0.1</div>
571
+ <div class="status-item" id="statusNetwork">Network: --</div>
572
+ <div class="status-item" id="statusBalance">Balance: --</div>
573
+ </div>
574
+
575
+ <script>
576
+ const contractABI = {abi_json};
577
+ const contractBytecode = "{bytecode}";
578
+
579
+ let provider = null;
580
+ let signer = null;
581
+ let userAddress = null;
582
+
583
+ const code = `{code_escaped}`;
584
+
585
+ function highlightCode(code) {{
586
+ return code
587
+ .replace(/&/g, '&amp;')
588
+ .replace(/</g, '&lt;')
589
+ .replace(/>/g, '&gt;')
590
+ .replace(/(\/\/.*$)/gm, '<span class="comment">$1</span>')
591
+ .replace(/(".*?"|'.*?')/g, '<span class="string">$1</span>')
592
+ .replace(/\\b(pragma|solidity|contract|function|public|private|external|internal|view|pure|payable|returns|return|if|else|for|while|mapping|event|emit|require|constructor|import|is|memory|storage|calldata|indexed|modifier|override|virtual)\\b/g, '<span class="keyword">$1</span>')
593
+ .replace(/\\b(address|uint256|uint8|uint128|int256|string|bool|bytes|bytes32|bytes4)\\b/g, '<span class="type">$1</span>')
594
+ .replace(/\\b(\\d+)\\b/g, '<span class="number">$1</span>');
595
+ }}
596
+
597
+ function updateEditor() {{
598
+ const lines = code.split('\\n');
599
+ document.getElementById('lineNumbers').innerHTML = lines.map((_, i) => i + 1).join('<br>');
600
+ document.getElementById('codeDisplay').innerHTML = highlightCode(code);
601
+ }}
602
+
603
+ function log(msg, type = '') {{
604
+ const terminal = document.getElementById('terminal');
605
+ const time = new Date().toLocaleTimeString();
606
+ terminal.innerHTML += `<div class="${{type}}">[${time}] ${msg}</div>`;
607
+ terminal.scrollTop = terminal.scrollHeight;
608
+ }}
609
+
610
+ async function connectWallet() {{
611
+ if (!window.ethereum) {{
612
+ log('❌ MetaMask not detected! Please install MetaMask extension.', 'error');
613
+ alert('MetaMask not found! Please install MetaMask browser extension.');
614
+ return;
615
+ }}
616
+
617
+ try {{
618
+ log('Connecting to MetaMask...', 'info');
619
+ provider = new ethers.providers.Web3Provider(window.ethereum);
620
+ await provider.send("eth_requestAccounts", []);
621
+ signer = provider.getSigner();
622
+ userAddress = await signer.getAddress();
623
+
624
+ const network = await provider.getNetwork();
625
+ const balance = await provider.getBalance(userAddress);
626
+ const balanceEth = parseFloat(ethers.utils.formatEther(balance)).toFixed(4);
627
+
628
+ document.getElementById('addressBadge').textContent = userAddress.slice(0,6) + '...' + userAddress.slice(-4);
629
+ document.getElementById('addressBadge').classList.add('connected');
630
+ document.getElementById('networkBadge').textContent = network.name || 'Chain ' + network.chainId;
631
+ document.getElementById('networkBadge').classList.add('connected');
632
+ document.getElementById('accountInput').value = userAddress.slice(0,12) + '...';
633
+ document.getElementById('statusNetwork').textContent = 'Network: ' + (network.name || network.chainId);
634
+ document.getElementById('statusBalance').textContent = 'Balance: ' + balanceEth + ' ETH';
635
+
636
+ log('βœ… Wallet connected: ' + userAddress, 'success');
637
+ log('Network: ' + (network.name || 'Chain ' + network.chainId), 'info');
638
+ log('Balance: ' + balanceEth + ' ETH', 'info');
639
+ }} catch (e) {{
640
+ log('❌ Connection failed: ' + e.message, 'error');
641
+ }}
642
+ }}
643
+
644
+ function compileContract() {{
645
+ log('⚑ Sending compile request to server...', 'info');
646
+ log('This will compile with Solidity 0.8.20', 'info');
647
+
648
+ // Trigger Streamlit rerun with compile flag
649
+ const url = new URL(window.parent.location.href);
650
+ url.searchParams.set('compile', 'true');
651
+ window.parent.location.href = url.toString();
652
+ }}
653
+
654
+ async function deployContract() {{
655
+ if (!signer) {{
656
+ log('❌ Please connect your wallet first!', 'error');
657
+ alert('Please connect MetaMask first!');
658
+ return;
659
+ }}
660
+
661
+ if (!contractBytecode || contractBytecode === '') {{
662
+ log('❌ Contract not compiled! Click Compile first.', 'error');
663
+ alert('Please compile the contract first!');
664
+ return;
665
+ }}
666
+
667
+ try {{
668
+ const btn = document.getElementById('deployBtn');
669
+ btn.disabled = true;
670
+ btn.innerHTML = '⏳ Deploying...';
671
+
672
+ log('πŸš€ Starting deployment...', 'info');
673
+
674
+ const factory = new ethers.ContractFactory(contractABI, contractBytecode, signer);
675
+
676
+ const argsInput = document.getElementById('constructorArgs').value.trim();
677
+ let args = [];
678
+ if (argsInput) {{
679
+ args = argsInput.split(',').map(a => {{
680
+ const trimmed = a.trim();
681
+ if (/^\\d+$/.test(trimmed)) return trimmed;
682
+ return trimmed;
683
+ }});
684
+ }}
685
+
686
+ log('Constructor args: [' + (args.length ? args.join(', ') : 'none') + ']', 'info');
687
+
688
+ const contract = await factory.deploy(...args);
689
+ log('πŸ“€ Transaction sent: ' + contract.deployTransaction.hash, 'info');
690
+ log('⏳ Waiting for confirmation...', 'info');
691
+
692
+ await contract.deployed();
693
+
694
+ log('βœ… CONTRACT DEPLOYED!', 'success');
695
+ log('πŸ“ Address: ' + contract.address, 'success');
696
+
697
+ const list = document.getElementById('contractsList');
698
+ list.innerHTML += `
699
+ <div class="contract-card">
700
+ <div class="name">${{document.getElementById('contractName').textContent}}</div>
701
+ <div class="address">${{contract.address}}</div>
702
+ </div>
703
+ `;
704
+
705
+ btn.disabled = false;
706
+ btn.innerHTML = 'πŸš€ Deploy Contract';
707
+
708
+ }} catch (e) {{
709
+ log('❌ Deployment failed: ' + e.message, 'error');
710
+ document.getElementById('deployBtn').disabled = false;
711
+ document.getElementById('deployBtn').innerHTML = 'πŸš€ Deploy Contract';
712
+ }}
713
+ }}
714
+
715
+ // Initialize
716
+ updateEditor();
717
+ log('Vivara Forge v2.0.1 Ready', 'info');
718
+ log('Connect MetaMask and compile to deploy contracts', 'info');
719
+
720
+ // Auto-connect if MetaMask already authorized
721
+ if (window.ethereum && window.ethereum.selectedAddress) {{
722
+ setTimeout(connectWallet, 500);
723
+ }}
724
+ </script>
725
+ </body>
726
+ </html>
727
+ """
728
 
729
+ # Check URL params for compile trigger
730
+ query_params = st.query_params
731
+ if query_params.get("compile") == "true":
732
+ do_compile()
733
+ # Clear the param
734
+ st.query_params.clear()
735
 
736
+ # Render full-screen HTML IDE
737
+ components.html(html, height=850, scrolling=False)