Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from solcx import compile_source, install_solc | |
| import json | |
| import streamlit.components.v1 as components | |
| # --- PAGE CONFIG --- | |
| st.set_page_config( | |
| layout="wide", | |
| page_title="Vivara Forge", | |
| page_icon="π¦", | |
| initial_sidebar_state="collapsed", | |
| ) | |
| # --- HIDE ALL STREAMLIT UI --- | |
| st.markdown( | |
| """ | |
| <style> | |
| #MainMenu, header, footer, [data-testid="stToolbar"], [data-testid="stDecoration"], | |
| [data-testid="stStatusWidget"], .stDeployButton, div[data-testid="stSidebarNav"], | |
| section[data-testid="stSidebar"], .viewerBadge_container__1QSob, | |
| [data-testid="stHeader"], [data-testid="stFooter"] { | |
| display: none !important; | |
| visibility: hidden !important; | |
| } | |
| .stApp { background: #1e1e1e !important; } | |
| .block-container { padding: 0 !important; max-width: 100% !important; } | |
| [data-testid="stAppViewContainer"] { padding: 0 !important; } | |
| /* Hide the form completely */ | |
| .stForm { position: absolute; left: -9999px; } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # --- SESSION STATE --- | |
| if "code" not in st.session_state: | |
| st.session_state.code = """// SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.20; | |
| contract MyToken { | |
| string public name = "Vivara Coin"; | |
| string public symbol = "VIVR"; | |
| uint8 public decimals = 18; | |
| uint256 public totalSupply; | |
| mapping(address => uint256) public balanceOf; | |
| mapping(address => mapping(address => uint256)) public allowance; | |
| event Transfer(address indexed from, address indexed to, uint256 value); | |
| event Approval(address indexed owner, address indexed spender, uint256 value); | |
| constructor(uint256 _initialSupply) { | |
| totalSupply = _initialSupply * 10 ** decimals; | |
| balanceOf[msg.sender] = totalSupply; | |
| emit Transfer(address(0), msg.sender, totalSupply); | |
| } | |
| function transfer(address to, uint256 amount) public returns (bool) { | |
| require(balanceOf[msg.sender] >= amount, "Insufficient balance"); | |
| balanceOf[msg.sender] -= amount; | |
| balanceOf[to] += amount; | |
| emit Transfer(msg.sender, to, amount); | |
| return true; | |
| } | |
| function approve(address spender, uint256 amount) public returns (bool) { | |
| allowance[msg.sender][spender] = amount; | |
| emit Approval(msg.sender, spender, amount); | |
| return true; | |
| } | |
| function transferFrom(address from, address to, uint256 amount) public returns (bool) { | |
| require(balanceOf[from] >= amount, "Insufficient balance"); | |
| require(allowance[from][msg.sender] >= amount, "Allowance exceeded"); | |
| allowance[from][msg.sender] -= amount; | |
| balanceOf[from] -= amount; | |
| balanceOf[to] += amount; | |
| emit Transfer(from, to, amount); | |
| return true; | |
| } | |
| }""" | |
| if "abi" not in st.session_state: | |
| st.session_state.abi = None | |
| if "bytecode" not in st.session_state: | |
| st.session_state.bytecode = None | |
| if "contract_name" not in st.session_state: | |
| st.session_state.contract_name = None | |
| if "logs" not in st.session_state: | |
| st.session_state.logs = [] | |
| if "compile_trigger" not in st.session_state: | |
| st.session_state.compile_trigger = False | |
| # --- COMPILE FUNCTION --- | |
| def do_compile(): | |
| try: | |
| st.session_state.logs.append("[INFO] Installing solc 0.8.20...") | |
| install_solc("0.8.20") | |
| st.session_state.logs.append("[INFO] Compiling Solidity code...") | |
| compiled = compile_source( | |
| st.session_state.code, output_values=["abi", "bin"], solc_version="0.8.20" | |
| ) | |
| contract_id, interface = list(compiled.items())[-1] | |
| st.session_state.abi = interface["abi"] | |
| st.session_state.bytecode = interface["bin"] | |
| st.session_state.contract_name = contract_id.split(":")[-1] | |
| st.session_state.logs.append( | |
| f"[SUCCESS] Compiled: {st.session_state.contract_name}" | |
| ) | |
| return True | |
| except Exception as e: | |
| st.session_state.logs.append(f"[ERROR] {str(e)}") | |
| return False | |
| # Check for compile trigger | |
| if st.session_state.compile_trigger: | |
| do_compile() | |
| st.session_state.compile_trigger = False | |
| # --- FULL REMIX-LIKE HTML APP --- | |
| abi_json = json.dumps(st.session_state.abi) if st.session_state.abi else "null" | |
| bytecode = st.session_state.bytecode or "" | |
| contract_name = st.session_state.contract_name or "No Contract" | |
| logs_html = ( | |
| "\\n".join(st.session_state.logs[-15:]) | |
| if st.session_state.logs | |
| else "[INFO] Vivara Forge Ready - Connect MetaMask to deploy" | |
| ) | |
| # Escape the code for JS | |
| code_escaped = ( | |
| st.session_state.code.replace("\\", "\\\\") | |
| .replace("`", "\\`") | |
| .replace("${", "\\${") | |
| ) | |
| html = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@400;600&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.ethers.io/lib/ethers-5.7.umd.min.js"></script> | |
| <style> | |
| * {{ margin: 0; padding: 0; box-sizing: border-box; }} | |
| body {{ | |
| font-family: 'Inter', sans-serif; | |
| background: #1e1e1e; | |
| color: #d4d4d4; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| }} | |
| /* HEADER */ | |
| .header {{ | |
| height: 44px; | |
| background: linear-gradient(180deg, #2d2d2d 0%, #252526 100%); | |
| border-bottom: 1px solid #3c3c3c; | |
| display: flex; | |
| align-items: center; | |
| padding: 0 16px; | |
| gap: 12px; | |
| }} | |
| .logo {{ | |
| font-family: 'JetBrains Mono'; | |
| font-weight: 600; | |
| font-size: 14px; | |
| color: #fff; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| }} | |
| .logo .dot {{ width: 10px; height: 10px; background: #3fc04f; border-radius: 50%; box-shadow: 0 0 8px #3fc04f; }} | |
| .header-btn {{ | |
| background: #0e639c; | |
| color: white; | |
| border: none; | |
| padding: 7px 16px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-size: 12px; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| transition: all 0.15s; | |
| }} | |
| .header-btn:hover {{ background: #1177bb; transform: translateY(-1px); }} | |
| .header-btn.orange {{ background: linear-gradient(180deg, #e8850f 0%, #d4730f 100%); }} | |
| .header-btn.orange:hover {{ background: linear-gradient(180deg, #f59622 0%, #e8850f 100%); }} | |
| .header-btn.green {{ background: linear-gradient(180deg, #3ab55f 0%, #2ea44f 100%); }} | |
| .header-btn.green:hover {{ background: linear-gradient(180deg, #4cc76f 0%, #3ab55f 100%); }} | |
| .wallet-status {{ | |
| margin-left: auto; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| font-size: 12px; | |
| }} | |
| .wallet-badge {{ | |
| background: #333; | |
| padding: 5px 12px; | |
| border-radius: 20px; | |
| font-family: 'JetBrains Mono'; | |
| font-size: 11px; | |
| border: 1px solid #444; | |
| }} | |
| .wallet-badge.connected {{ border-color: #3fc04f; color: #3fc04f; }} | |
| /* MAIN LAYOUT */ | |
| .main {{ flex: 1; display: flex; min-height: 0; }} | |
| /* LEFT SIDEBAR */ | |
| .sidebar {{ | |
| width: 240px; | |
| background: #252526; | |
| border-right: 1px solid #3c3c3c; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| }} | |
| .sidebar-section {{ padding: 14px; border-bottom: 1px solid #3c3c3c; }} | |
| .sidebar-title {{ | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| color: #858585; | |
| margin-bottom: 10px; | |
| letter-spacing: 1px; | |
| font-weight: 600; | |
| }} | |
| .file-item {{ | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 10px; | |
| cursor: pointer; | |
| border-radius: 4px; | |
| font-size: 13px; | |
| margin-bottom: 2px; | |
| }} | |
| .file-item:hover {{ background: #2a2d2e; }} | |
| .file-item.active {{ background: #37373d; border-left: 2px solid #0e639c; }} | |
| .env-select {{ | |
| width: 100%; | |
| background: #3c3c3c; | |
| border: 1px solid #555; | |
| color: #d4d4d4; | |
| padding: 10px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| margin-bottom: 12px; | |
| cursor: pointer; | |
| }} | |
| .env-select:focus {{ border-color: #0e639c; outline: none; }} | |
| .input-group {{ margin-bottom: 12px; }} | |
| .input-group label {{ | |
| display: block; | |
| font-size: 11px; | |
| color: #858585; | |
| margin-bottom: 6px; | |
| text-transform: uppercase; | |
| }} | |
| .input-group input {{ | |
| width: 100%; | |
| background: #3c3c3c; | |
| border: 1px solid #555; | |
| color: #d4d4d4; | |
| padding: 8px 10px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| font-family: 'JetBrains Mono'; | |
| }} | |
| .input-group input:focus {{ border-color: #0e639c; outline: none; }} | |
| /* EDITOR AREA */ | |
| .editor-container {{ flex: 1; display: flex; flex-direction: column; min-width: 0; }} | |
| .tab-bar {{ | |
| height: 38px; | |
| background: #252526; | |
| display: flex; | |
| align-items: flex-end; | |
| border-bottom: 1px solid #3c3c3c; | |
| padding-left: 8px; | |
| }} | |
| .tab {{ | |
| padding: 8px 20px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 13px; | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| border-bottom: none; | |
| margin-bottom: -1px; | |
| border-radius: 6px 6px 0 0; | |
| background: #2d2d2d; | |
| }} | |
| .tab.active {{ | |
| background: #1e1e1e; | |
| border-color: #3c3c3c; | |
| border-top: 2px solid #0e639c; | |
| }} | |
| .tab .close {{ | |
| opacity: 0.5; | |
| font-size: 16px; | |
| margin-left: 4px; | |
| }} | |
| .tab .close:hover {{ opacity: 1; }} | |
| .editor {{ | |
| flex: 1; | |
| display: flex; | |
| background: #1e1e1e; | |
| overflow: hidden; | |
| }} | |
| .line-numbers {{ | |
| width: 55px; | |
| background: #1e1e1e; | |
| border-right: 1px solid #333; | |
| padding: 14px 10px; | |
| text-align: right; | |
| font-family: 'JetBrains Mono'; | |
| font-size: 13px; | |
| color: #6e7681; | |
| line-height: 1.6; | |
| user-select: none; | |
| }} | |
| .code-area {{ | |
| flex: 1; | |
| padding: 14px 16px; | |
| font-family: 'JetBrains Mono'; | |
| font-size: 13px; | |
| line-height: 1.6; | |
| overflow: auto; | |
| white-space: pre; | |
| color: #d4d4d4; | |
| }} | |
| .keyword {{ color: #569cd6; }} | |
| .string {{ color: #ce9178; }} | |
| .comment {{ color: #6a9955; font-style: italic; }} | |
| .number {{ color: #b5cea8; }} | |
| .type {{ color: #4ec9b0; }} | |
| .function-name {{ color: #dcdcaa; }} | |
| /* RIGHT PANEL */ | |
| .right-panel {{ | |
| width: 320px; | |
| background: #252526; | |
| border-left: 1px solid #3c3c3c; | |
| display: flex; | |
| flex-direction: column; | |
| }} | |
| .panel-header {{ | |
| padding: 14px; | |
| border-bottom: 1px solid #3c3c3c; | |
| font-weight: 600; | |
| font-size: 14px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| }} | |
| .panel-content {{ flex: 1; padding: 14px; overflow-y: auto; }} | |
| .deploy-btn {{ | |
| width: 100%; | |
| background: linear-gradient(180deg, #e8850f 0%, #d4730f 100%); | |
| color: white; | |
| border: none; | |
| padding: 14px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 600; | |
| margin-bottom: 14px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| transition: all 0.15s; | |
| }} | |
| .deploy-btn:hover {{ transform: translateY(-1px); box-shadow: 0 4px 12px rgba(212, 115, 15, 0.3); }} | |
| .deploy-btn:disabled {{ background: #555; cursor: not-allowed; transform: none; box-shadow: none; }} | |
| .info-card {{ | |
| background: #1e1e1e; | |
| padding: 12px; | |
| border-radius: 6px; | |
| margin-bottom: 14px; | |
| border: 1px solid #333; | |
| }} | |
| .info-card .row {{ | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 6px; | |
| font-size: 12px; | |
| }} | |
| .info-card .row:last-child {{ margin-bottom: 0; }} | |
| .info-card .label {{ color: #858585; }} | |
| .info-card .value {{ font-family: 'JetBrains Mono'; color: #4ec9b0; }} | |
| .contract-card {{ | |
| background: #1e1e1e; | |
| padding: 10px; | |
| border-radius: 6px; | |
| margin-bottom: 8px; | |
| border: 1px solid #333; | |
| font-size: 11px; | |
| }} | |
| .contract-card .name {{ color: #4ec9b0; font-weight: 600; margin-bottom: 4px; }} | |
| .contract-card .address {{ color: #858585; font-family: 'JetBrains Mono'; word-break: break-all; }} | |
| /* TERMINAL */ | |
| .terminal-container {{ | |
| height: 160px; | |
| background: #1e1e1e; | |
| border-top: 1px solid #3c3c3c; | |
| display: flex; | |
| flex-direction: column; | |
| }} | |
| .terminal-header {{ | |
| padding: 8px 14px; | |
| background: #252526; | |
| border-bottom: 1px solid #3c3c3c; | |
| font-size: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| }} | |
| .terminal {{ | |
| flex: 1; | |
| font-family: 'JetBrains Mono'; | |
| font-size: 12px; | |
| padding: 10px 14px; | |
| overflow-y: auto; | |
| color: #d4d4d4; | |
| line-height: 1.5; | |
| }} | |
| .terminal .error {{ color: #f14c4c; }} | |
| .terminal .success {{ color: #3fc04f; }} | |
| .terminal .info {{ color: #858585; }} | |
| /* STATUS BAR */ | |
| .status-bar {{ | |
| height: 26px; | |
| background: #007acc; | |
| display: flex; | |
| align-items: center; | |
| padding: 0 14px; | |
| font-size: 12px; | |
| gap: 20px; | |
| }} | |
| .status-item {{ display: flex; align-items: center; gap: 6px; }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <div class="logo"><div class="dot"></div> VIVARA FORGE</div> | |
| <button class="header-btn green" onclick="connectWallet()">π¦ Connect Wallet</button> | |
| <button class="header-btn orange" onclick="compileContract()">β‘ Compile</button> | |
| <div class="wallet-status"> | |
| <span id="networkBadge" class="wallet-badge">Not Connected</span> | |
| <span id="addressBadge" class="wallet-badge">0x...</span> | |
| </div> | |
| </div> | |
| <div class="main"> | |
| <div class="sidebar"> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">π File Explorer</div> | |
| <div class="file-item active">π Contract.sol</div> | |
| <div class="file-item">π Token.sol</div> | |
| <div class="file-item">π artifacts</div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">βοΈ Deploy Environment</div> | |
| <select class="env-select" id="envSelect"> | |
| <option value="metamask">π¦ Injected - MetaMask</option> | |
| <option value="sepolia">π Sepolia Testnet</option> | |
| <option value="polygon">π£ Polygon Mumbai</option> | |
| </select> | |
| <div class="input-group"> | |
| <label>Account</label> | |
| <input type="text" id="accountInput" value="Connect wallet..." readonly> | |
| </div> | |
| <div class="input-group"> | |
| <label>Gas Limit</label> | |
| <input type="text" id="gasLimit" value="3000000"> | |
| </div> | |
| <div class="input-group"> | |
| <label>Value (ETH)</label> | |
| <input type="text" id="valueEth" value="0"> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-title">π§ Compiler</div> | |
| <select class="env-select" id="compilerVersion"> | |
| <option value="0.8.20">Solidity v0.8.20</option> | |
| <option value="0.8.19">Solidity v0.8.19</option> | |
| <option value="0.8.18">Solidity v0.8.18</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="editor-container"> | |
| <div class="tab-bar"> | |
| <div class="tab active">π Contract.sol <span class="close">Γ</span></div> | |
| </div> | |
| <div class="editor"> | |
| <div class="line-numbers" id="lineNumbers"></div> | |
| <div class="code-area" id="codeDisplay"></div> | |
| </div> | |
| <div class="terminal-container"> | |
| <div class="terminal-header">π Terminal</div> | |
| <div class="terminal" id="terminal"></div> | |
| </div> | |
| </div> | |
| <div class="right-panel"> | |
| <div class="panel-header">π Deploy & Run</div> | |
| <div class="panel-content"> | |
| <div class="info-card"> | |
| <div class="row"> | |
| <span class="label">Contract</span> | |
| <span class="value" id="contractName">{contract_name}</span> | |
| </div> | |
| <div class="row"> | |
| <span class="label">Status</span> | |
| <span class="value" id="compileStatus">{"β Compiled" if st.session_state.bytecode else "β³ Not Compiled"}</span> | |
| </div> | |
| <div class="row"> | |
| <span class="label">Bytecode</span> | |
| <span class="value">{len(bytecode) if bytecode else 0} bytes</span> | |
| </div> | |
| </div> | |
| <div class="input-group"> | |
| <label>Constructor Arguments</label> | |
| <input type="text" id="constructorArgs" placeholder="e.g., 1000000"> | |
| </div> | |
| <button class="deploy-btn" id="deployBtn" onclick="deployContract()" {"" if st.session_state.bytecode else "disabled"}> | |
| π Deploy Contract | |
| </button> | |
| <div class="sidebar-title" style="margin-top: 20px;">Deployed Contracts</div> | |
| <div id="contractsList"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="status-bar"> | |
| <div class="status-item">π¦ Vivara Forge v2.0.1</div> | |
| <div class="status-item" id="statusNetwork">Network: --</div> | |
| <div class="status-item" id="statusBalance">Balance: --</div> | |
| </div> | |
| <script> | |
| const contractABI = {abi_json}; | |
| const contractBytecode = "{bytecode}"; | |
| let provider = null; | |
| let signer = null; | |
| let userAddress = null; | |
| const code = `{code_escaped}`; | |
| function highlightCode(code) {{ | |
| return code | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/(\/\/.*$)/gm, '<span class="comment">$1</span>') | |
| .replace(/(".*?"|'.*?')/g, '<span class="string">$1</span>') | |
| .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>') | |
| .replace(/\\b(address|uint256|uint8|uint128|int256|string|bool|bytes|bytes32|bytes4)\\b/g, '<span class="type">$1</span>') | |
| .replace(/\\b(\\d+)\\b/g, '<span class="number">$1</span>'); | |
| }} | |
| function updateEditor() {{ | |
| const lines = code.split('\\n'); | |
| document.getElementById('lineNumbers').innerHTML = lines.map((_, i) => i + 1).join('<br>'); | |
| document.getElementById('codeDisplay').innerHTML = highlightCode(code); | |
| }} | |
| function log(msg, type = '') {{ | |
| const terminal = document.getElementById('terminal'); | |
| const time = new Date().toLocaleTimeString(); | |
| terminal.innerHTML += `<div class="${{type}}">[${time}] ${msg}</div>`; | |
| terminal.scrollTop = terminal.scrollHeight; | |
| }} | |
| async function connectWallet() {{ | |
| if (!window.ethereum) {{ | |
| log('β MetaMask not detected! Please install MetaMask extension.', 'error'); | |
| alert('MetaMask not found! Please install MetaMask browser extension.'); | |
| return; | |
| }} | |
| try {{ | |
| log('Connecting to MetaMask...', 'info'); | |
| provider = new ethers.providers.Web3Provider(window.ethereum); | |
| await provider.send("eth_requestAccounts", []); | |
| signer = provider.getSigner(); | |
| userAddress = await signer.getAddress(); | |
| const network = await provider.getNetwork(); | |
| const balance = await provider.getBalance(userAddress); | |
| const balanceEth = parseFloat(ethers.utils.formatEther(balance)).toFixed(4); | |
| document.getElementById('addressBadge').textContent = userAddress.slice(0,6) + '...' + userAddress.slice(-4); | |
| document.getElementById('addressBadge').classList.add('connected'); | |
| document.getElementById('networkBadge').textContent = network.name || 'Chain ' + network.chainId; | |
| document.getElementById('networkBadge').classList.add('connected'); | |
| document.getElementById('accountInput').value = userAddress.slice(0,12) + '...'; | |
| document.getElementById('statusNetwork').textContent = 'Network: ' + (network.name || network.chainId); | |
| document.getElementById('statusBalance').textContent = 'Balance: ' + balanceEth + ' ETH'; | |
| log('β Wallet connected: ' + userAddress, 'success'); | |
| log('Network: ' + (network.name || 'Chain ' + network.chainId), 'info'); | |
| log('Balance: ' + balanceEth + ' ETH', 'info'); | |
| }} catch (e) {{ | |
| log('β Connection failed: ' + e.message, 'error'); | |
| }} | |
| }} | |
| function compileContract() {{ | |
| log('β‘ Sending compile request to server...', 'info'); | |
| log('This will compile with Solidity 0.8.20', 'info'); | |
| // Trigger Streamlit rerun with compile flag | |
| const url = new URL(window.parent.location.href); | |
| url.searchParams.set('compile', 'true'); | |
| window.parent.location.href = url.toString(); | |
| }} | |
| async function deployContract() {{ | |
| if (!signer) {{ | |
| log('β Please connect your wallet first!', 'error'); | |
| alert('Please connect MetaMask first!'); | |
| return; | |
| }} | |
| if (!contractBytecode || contractBytecode === '') {{ | |
| log('β Contract not compiled! Click Compile first.', 'error'); | |
| alert('Please compile the contract first!'); | |
| return; | |
| }} | |
| try {{ | |
| const btn = document.getElementById('deployBtn'); | |
| btn.disabled = true; | |
| btn.innerHTML = 'β³ Deploying...'; | |
| log('π Starting deployment...', 'info'); | |
| const factory = new ethers.ContractFactory(contractABI, contractBytecode, signer); | |
| const argsInput = document.getElementById('constructorArgs').value.trim(); | |
| let args = []; | |
| if (argsInput) {{ | |
| args = argsInput.split(',').map(a => {{ | |
| const trimmed = a.trim(); | |
| if (/^\\d+$/.test(trimmed)) return trimmed; | |
| return trimmed; | |
| }}); | |
| }} | |
| log('Constructor args: [' + (args.length ? args.join(', ') : 'none') + ']', 'info'); | |
| const contract = await factory.deploy(...args); | |
| log('π€ Transaction sent: ' + contract.deployTransaction.hash, 'info'); | |
| log('β³ Waiting for confirmation...', 'info'); | |
| await contract.deployed(); | |
| log('β CONTRACT DEPLOYED!', 'success'); | |
| log('π Address: ' + contract.address, 'success'); | |
| const list = document.getElementById('contractsList'); | |
| list.innerHTML += ` | |
| <div class="contract-card"> | |
| <div class="name">${{document.getElementById('contractName').textContent}}</div> | |
| <div class="address">${{contract.address}}</div> | |
| </div> | |
| `; | |
| btn.disabled = false; | |
| btn.innerHTML = 'π Deploy Contract'; | |
| }} catch (e) {{ | |
| log('β Deployment failed: ' + e.message, 'error'); | |
| document.getElementById('deployBtn').disabled = false; | |
| document.getElementById('deployBtn').innerHTML = 'π Deploy Contract'; | |
| }} | |
| }} | |
| // Initialize | |
| updateEditor(); | |
| log('Vivara Forge v2.0.1 Ready', 'info'); | |
| log('Connect MetaMask and compile to deploy contracts', 'info'); | |
| // Auto-connect if MetaMask already authorized | |
| if (window.ethereum && window.ethereum.selectedAddress) {{ | |
| setTimeout(connectWallet, 500); | |
| }} | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # Check URL params for compile trigger | |
| query_params = st.query_params | |
| if query_params.get("compile") == "true": | |
| do_compile() | |
| # Clear the param | |
| st.query_params.clear() | |
| # Render full-screen HTML IDE | |
| components.html(html, height=850, scrolling=False) | |