fugthchat/Vivara-Forge
0
1import streamlit as st2from solcx import compile_source, install_solc3import json4import streamlit.components.v1 as components5 6# --- PAGE CONFIG ---7st.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 ---15st.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 ---37if "code" not in st.session_state:38 st.session_state.code = """// SPDX-License-Identifier: MIT39pragma solidity ^0.8.20;40 41contract 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 84if "abi" not in st.session_state:85 st.session_state.abi = None86if "bytecode" not in st.session_state:87 st.session_state.bytecode = None88if "contract_name" not in st.session_state:89 st.session_state.contract_name = None90if "logs" not in st.session_state:91 st.session_state.logs = []92if "compile_trigger" not in st.session_state:93 st.session_state.compile_trigger = False94 95 96# --- COMPILE FUNCTION ---97def 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 True116 except Exception as e:117 st.session_state.logs.append(f"[ERROR] {str(e)}")118 return False119 120 121# Check for compile trigger122if st.session_state.compile_trigger:123 do_compile()124 st.session_state.compile_trigger = False125 126# --- FULL REMIX-LIKE HTML APP ---127abi_json = json.dumps(st.session_state.abi) if st.session_state.abi else "null"128bytecode = st.session_state.bytecode or ""129contract_name = st.session_state.contract_name or "No Contract"130logs_html = (131 "\\n".join(st.session_state.logs[-15:])132 if st.session_state.logs133 else "[INFO] Vivara Forge Ready - Connect MetaMask to deploy"134)135 136# Escape the code for JS137code_escaped = (138 st.session_state.code.replace("\\", "\\\\")139 .replace("`", "\\`")140 .replace("${", "\\${")141)142 143html = 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 Contract561 </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 code587 .replace(/&/g, '&')588 .replace(/</g, '<')589 .replace(/>/g, '>')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 flag649 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 // Initialize716 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 authorized721 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 trigger730query_params = st.query_params731if query_params.get("compile") == "true":732 do_compile()733 # Clear the param734 st.query_params.clear()735 736# Render full-screen HTML IDE737components.html(html, height=850, scrolling=False)738 