| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <title>LLM Remix IDE — fixed alpha</title> |
| |
| <script src="https://cdn.jsdelivr.net/npm/ethers@6.13.2/dist/ethers.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/solc@0.8.24/soljson.js"></script> |
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.css"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/theme/dracula.min.css"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/codemirror.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.12/mode/clike/clike.min.js"></script> |
| |
| <script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.5.1"></script> |
| <style> |
| :root { --bg:#0a0a0a; --panel:#111; --accent:#ff6700; --ok:#19c37d; --err:#ff4d4f; } |
| body { background:var(--bg); color:#eee; font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin:0; } |
| header { background:#111; padding:10px 14px; color:var(--accent); display:flex; gap:8px; flex-wrap:wrap; align-items:center; } |
| button, input, select { background:#141414; color:#f8f8f8; border:none; border-radius:8px; padding:8px 12px; } |
| button { color:var(--accent); cursor:pointer; } |
| button:hover { background:var(--accent); color:#000; } |
| .wrap { padding:12px; display:grid; gap:12px; grid-template-columns: 1fr; } |
| .pane { background:#111; border-radius:10px; padding:12px; } |
| #status { font-weight:700; } |
| #output { white-space:pre-wrap; background:#0e0e0e; padding:12px; border-radius:8px; max-height:240px; overflow:auto; } |
| #editor { height:360px; } |
| .CodeMirror { height:360px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:13px; } |
| .row { display:flex; gap:8px; flex-wrap:wrap; align-items:center; } |
| .grid { display:grid; gap:10px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } |
| .fn { background:#0e0e0e; padding:10px; border-radius:8px; } |
| .small { font-size:.85rem; color:#bbb; } |
| .ok { color:var(--ok); } .warn { color:var(--err); } |
| label { font-size:.85rem; color:#bbb; } |
| </style> |
| </head> |
| <body> |
| <header> |
| <strong>🤖 LLM Remix IDE — fixed alpha</strong> |
| <div class="row"> |
| <button id="connectBtn">Connect Wallet</button> |
| <button id="switchSepoliaBtn" title="11155111">Switch → Sepolia</button> |
| <button id="generateBtn">Generate</button> |
| <button id="compileBtn">Compile</button> |
| <button id="deployBtn">Deploy</button> |
| </div> |
| </header> |
|
|
| <div class="wrap"> |
| <div id="status" class="pane">Status: idle</div> |
|
|
| <div class="pane"> |
| <div class="row"> |
| <input id="prompt" type="text" style="flex:1" value="ERC20 token with daily rebase and transfer fee" /> |
| <select id="preset"> |
| <option value="code">code preset</option> |
| <option value="vanilla">vanilla</option> |
| </select> |
| <button id="sampleStorageBtn">Sample: Storage</button> |
| <button id="sampleERC20Btn">Sample: ERC20</button> |
| </div> |
| <div id="editor"></div> |
| <div class="small">Edits auto-save. LLM optional. Use samples if generation fails.</div> |
| </div> |
|
|
| <div class="pane"> |
| <h3 style="margin:0 0 8px 0;">Compile</h3> |
| <div id="output">Output will appear here…</div> |
| </div> |
|
|
| <div class="pane"> |
| <h3 style="margin:0 0 8px 0;">Deploy</h3> |
| <div class="row"> |
| <label>Constructor + value (wei) set below. Mainnet prompts confirm.</label> |
| </div> |
| <div class="row"> |
| <label>Attach existing:</label> |
| <input id="attachAddr" type="text" placeholder="0x... address" style="min-width:320px" /> |
| <button id="attachBtn">Attach</button> |
| </div> |
| <div class="row"> |
| <label>Payable value:</label> |
| <input id="deployValue" type="text" value="0" style="width:180px" /> |
| </div> |
| <div id="ctorFields" class="row"></div> |
| <div>Deployed Address: <span id="contractAddr">-</span></div> |
| </div> |
|
|
| <div class="pane"> |
| <h3 style="margin:0 0 8px 0;">Contract Functions <span id="fnSummary" class="small"></span></h3> |
| <div id="contractUI" class="grid"></div> |
| </div> |
| </div> |
|
|
| <script> |
| let provider, signer, chainId, generator = null; |
| let editor, abi = null, bytecode = null, contractMeta = null, instance = null; |
| |
| |
| const el = (id)=>document.getElementById(id); |
| const setStatus = (t, cls="") => { const s=el("status"); s.textContent=t; s.className="pane "+cls; }; |
| const log = (m)=> el("output").textContent = String(m); |
| const save = (k,v)=> localStorage.setItem(k,v); |
| const load = (k,d="")=> localStorage.getItem(k) ?? d; |
| |
| function must(cond, msg){ if(!cond) throw new Error(msg); } |
| |
| function parseArg(type, val){ |
| if (val === "" || val === undefined || val === null) return val; |
| if (type.endsWith("[]")){ |
| const inner = type.slice(0,-2); |
| return String(val).split(",").map(x=>parseArg(inner, x.trim())); |
| } |
| if (type.startsWith("uint") || type.startsWith("int")) return BigInt(val); |
| if (type === "bool") return (val===true || val==="true" || val==="1"); |
| if (type === "address"){ |
| must(/^0x[0-9a-fA-F]{40}$/.test(val), "Invalid address "+val); |
| return val; |
| } |
| return val; |
| } |
| |
| function firstArtifact(contracts){ |
| const names = Object.keys(contracts||{}); |
| for(const n of names){ |
| const art = contracts[n]; |
| if (art?.evm?.bytecode?.object) return [n, art]; |
| } |
| return [null,null]; |
| } |
| |
| |
| (function setupEditor(){ |
| editor = CodeMirror(el("editor"), { |
| value: load("src", `// Example: Storage |
| pragma solidity ^0.8.24; |
| contract Storage { |
| uint public data; |
| function set(uint x) public { data = x; } |
| }`), |
| mode:"text/x-solidity", |
| theme:"dracula", |
| lineNumbers:true |
| }); |
| editor.on("change",()=>save("src", editor.getValue())); |
| el("prompt").value = load("prompt", el("prompt").value); |
| el("prompt").addEventListener("change", e=>save("prompt", e.target.value)); |
| })(); |
| |
| el("sampleStorageBtn").onclick = ()=>{ |
| editor.setValue(`pragma solidity ^0.8.24; |
| contract Storage { |
| uint public data; |
| function set(uint x) public { data = x; } |
| }`); |
| }; |
| el("sampleERC20Btn").onclick = ()=>{ |
| editor.setValue(`// Minimal ERC20-like (unsafe for production) |
| pragma solidity ^0.8.24; |
| contract MiniERC20 { |
| string public name="Mini"; string public symbol="MINI"; uint8 public decimals=18; |
| uint public totalSupply; mapping(address=>uint) public balanceOf; mapping(address=>mapping(address=>uint)) public allowance; |
| event Transfer(address indexed from,address indexed to,uint value); event Approval(address indexed owner,address indexed spender,uint value); |
| constructor(uint supply){ totalSupply=supply; balanceOf[msg.sender]=supply; emit Transfer(address(0), msg.sender, supply); } |
| function transfer(address to, uint v) public returns(bool){ balanceOf[msg.sender]-=v; balanceOf[to]+=v; emit Transfer(msg.sender,to,v); return true; } |
| function approve(address s, uint v) public returns(bool){ allowance[msg.sender][s]=v; emit Approval(msg.sender,s,v); return true; } |
| function transferFrom(address f,address t,uint v) public returns(bool){ uint a=allowance[f][msg.sender]; allowance[f][msg.sender]=a-v; balanceOf[f]-=v; balanceOf[t]+=v; emit Transfer(f,t,v); return true; } |
| }`); |
| }; |
| |
| |
| el("connectBtn").onclick = async ()=>{ |
| try{ |
| must(window.ethereum, "No wallet found"); |
| provider = new ethers.BrowserProvider(window.ethereum); |
| signer = await provider.getSigner(); |
| chainId = (await provider.getNetwork()).chainId; |
| setStatus(`Wallet ${await signer.getAddress()} · chainId ${chainId}`, "ok"); |
| }catch(e){ setStatus("Connect failed: "+(e.message||e), "warn"); } |
| }; |
| |
| el("switchSepoliaBtn").onclick = async ()=>{ |
| try{ |
| must(window.ethereum, "No wallet"); |
| await window.ethereum.request({ method:"wallet_switchEthereumChain", params:[{ chainId:"0xaa36a7" }] }); |
| provider = new ethers.BrowserProvider(window.ethereum); |
| signer = await provider.getSigner(); |
| chainId = (await provider.getNetwork()).chainId; |
| setStatus(`Switched to chainId ${chainId}`, "ok"); |
| }catch(e){ setStatus("Switch failed: "+(e.message||e), "warn"); } |
| }; |
| |
| |
| (async ()=>{ |
| try{ |
| if (!window.transformers) { setStatus("LLM unavailable. Use samples.", "warn"); return; } |
| setStatus("Loading LLM…"); |
| generator = await window.transformers.pipeline("text-generation","Xenova/distilgpt2"); |
| setStatus("LLM ready", "ok"); |
| }catch(e){ setStatus("LLM load failed. Use samples.", "warn"); generator=null; } |
| })(); |
| |
| function llmPrompt(preset, user){ |
| const base = [ |
| "Write ONLY valid Solidity. One file. No comments outside code.", |
| "Use pragma ^0.8.24.", |
| "Avoid deprecated/unsafe patterns." |
| ].join("\n"); |
| return preset==="code" ? `${base}\nTask: ${user}` : `Write a Solidity contract: ${user}`; |
| } |
| |
| el("generateBtn").onclick = async ()=>{ |
| try{ |
| must(generator, "LLM not available"); |
| const user = el("prompt").value; |
| const preset = el("preset").value; |
| setStatus("Generating…"); |
| let attempt=0, code=""; |
| while(attempt<3){ |
| attempt++; |
| const out = await generator(llmPrompt(preset, user), { max_new_tokens: 400 }); |
| const txt = out?.[0]?.generated_text || ""; |
| const m = txt.match(/pragma[\s\S]*$/); |
| code = m ? m[0] : txt; |
| if (code.includes("pragma solidity") && code.includes("contract")) break; |
| } |
| editor.setValue(code); |
| setStatus(`Generated attempt ${attempt}`, "ok"); |
| }catch(e){ setStatus("Generate failed: "+(e.message||e), "warn"); } |
| }; |
| |
| |
| el("compileBtn").onclick = async ()=>{ |
| try{ |
| must(window.solc, "solc not loaded"); |
| const source = editor.getValue(); |
| setStatus("Compiling…"); |
| const input = { |
| language:"Solidity", |
| sources:{ "Contract.sol":{ content: source } }, |
| settings:{ outputSelection:{ "*":{ "*":[ "abi","evm.bytecode","evm.deployedBytecode" ] } } } |
| }; |
| const compiled = JSON.parse(solc.compile(JSON.stringify(input))); |
| if (compiled.errors?.length){ |
| const msg = compiled.errors.map(e=>`${e.severity.toUpperCase()}: ${e.formattedMessage.trim()}`).join("\n"); |
| log(msg); |
| if (compiled.errors.some(e=>e.severity==="error")) { setStatus("Compile errors", "warn"); return; } |
| } |
| const file = compiled.contracts["Contract.sol"]; |
| const [name, art] = firstArtifact(file); |
| must(name, "No contract with bytecode found"); |
| abi = art.abi; bytecode = art.evm.bytecode.object; |
| contractMeta = { name, abi, bytecode }; |
| buildCtorUI(abi); |
| buildUI(null, abi); |
| log(`✅ Compiled ${name}\nFunctions: ${abi.filter(x=>x.type==="function").length}`); |
| setStatus("Compiled", "ok"); |
| }catch(e){ setStatus("Compile failed: "+(e.message||e), "warn"); } |
| }; |
| |
| |
| el("deployBtn").onclick = async ()=>{ |
| try{ |
| must(signer, "Connect wallet"); |
| must(abi && bytecode, "Compile first"); |
| const net = await provider.getNetwork(); |
| if (net.chainId === 1n){ |
| const ok = confirm("Mainnet detected. Continue?"); |
| if (!ok) return; |
| } |
| const ctor = abi.find(x=>x.type==="constructor")||{inputs:[]}; |
| const args = []; |
| for (const inp of ctor.inputs){ |
| const v = el(`ctor_${inp.name||"arg"}`)?.value ?? ""; |
| args.push(parseArg(inp.type, v)); |
| } |
| const valueStr = el("deployValue").value.trim() || "0"; |
| const overrides = (valueStr !== "0") ? { value: BigInt(valueStr) } : {}; |
| const factory = new ethers.ContractFactory(abi, bytecode, signer); |
| |
| try { |
| const txReq = factory.getDeployTransaction(...args, overrides); |
| const est = await provider.estimateGas(txReq); |
| log(`Gas estimate: ${est.toString()}`); |
| } catch {} |
| setStatus("Deploying…"); |
| instance = await factory.deploy(...args, overrides); |
| await instance.waitForDeployment(); |
| const addr = await instance.getAddress(); |
| el("contractAddr").textContent = addr; |
| buildUI(instance, abi); |
| setStatus("Deployed at "+addr, "ok"); |
| }catch(e){ setStatus("Deploy failed: "+(e.message||e), "warn"); } |
| }; |
| |
| el("attachBtn").onclick = async ()=>{ |
| try{ |
| must(signer, "Connect wallet"); |
| must(abi, "Compile to load ABI"); |
| const addr = el("attachAddr").value.trim(); |
| must(/^0x[0-9a-fA-F]{40}$/.test(addr), "Invalid address"); |
| instance = new ethers.Contract(addr, abi, signer); |
| el("contractAddr").textContent = addr; |
| buildUI(instance, abi); |
| setStatus("Attached "+addr, "ok"); |
| }catch(e){ setStatus("Attach failed: "+(e.message||e), "warn"); } |
| }; |
| |
| |
| function buildCtorUI(abi){ |
| const box = el("ctorFields"); box.innerHTML = ""; |
| const ctor = abi.find(x=>x.type==="constructor"); |
| if (!ctor || !ctor.inputs?.length){ box.innerHTML = "<span class='small'>No constructor args</span>"; return; } |
| for (const inp of ctor.inputs){ |
| const wrap = document.createElement("div"); wrap.className="row"; |
| const lab = document.createElement("label"); lab.textContent = `${inp.name||"arg"} (${inp.type})`; |
| const input = document.createElement("input"); input.type="text"; input.id=`ctor_${inp.name||"arg"}`; |
| wrap.appendChild(lab); wrap.appendChild(input); box.appendChild(wrap); |
| } |
| } |
| |
| function buildUI(inst, abi){ |
| const div = el("contractUI"); div.innerHTML = ""; |
| const fns = abi.filter(x=>x.type==="function"); |
| el("fnSummary").textContent = `${fns.length} functions`; |
| for (const fn of fns){ |
| const card = document.createElement("div"); card.className="fn"; |
| const readOnly = (fn.stateMutability==="view" || fn.stateMutability==="pure"); |
| const title = document.createElement("div"); |
| title.innerHTML = `<b>${fn.name}</b> <span class="small">• ${readOnly?"read":"write"} · ${fn.stateMutability}</span>`; |
| card.appendChild(title); |
| const inputs = []; |
| for (const inp of fn.inputs){ |
| const row = document.createElement("div"); row.className="row"; |
| const lab = document.createElement("label"); lab.textContent = `${inp.name||"arg"} (${inp.type})`; |
| const input = document.createElement("input"); input.type="text"; |
| row.appendChild(lab); row.appendChild(input); card.appendChild(row); |
| inputs.push({ def: inp, el: input }); |
| } |
| let valueEl = null; |
| if (!readOnly && fn.stateMutability==="payable"){ |
| const row = document.createElement("div"); row.className="row"; |
| const lab = document.createElement("label"); lab.textContent = `value (wei)`; |
| const input = document.createElement("input"); input.type="text"; input.placeholder="0"; |
| row.appendChild(lab); row.appendChild(input); card.appendChild(row); |
| valueEl = input; |
| } |
| const btn = document.createElement("button"); btn.textContent = "Run"; |
| btn.onclick = async ()=>{ |
| try{ |
| if (!inst) throw new Error("Deploy or attach first"); |
| const args = inputs.map(a=>parseArg(a.def.type, a.el.value)); |
| if (readOnly){ |
| const res = await inst[fn.name](...args); |
| log(`Result: ${res?.toString?.() ?? JSON.stringify(res)}`); |
| }else{ |
| const ov = {}; |
| if (valueEl && valueEl.value) ov.value = BigInt(valueEl.value); |
| const tx = await inst[fn.name](...args, ov); |
| log(`Tx: ${tx.hash}`); |
| const rc = await tx.wait(); |
| log(`Mined: block ${rc.blockNumber}`); |
| } |
| }catch(e){ log("Error: "+(e.message||e)); } |
| }; |
| card.appendChild(btn); |
| div.appendChild(card); |
| } |
| } |
| </script> |
| </body> |
| </html> |