Gucci / app.py
luguog's picture
Update app.py
d794679 verified
#!/usr/bin/env python3
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import uvicorn
app = FastAPI()
HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>LLM Remix IDE — Hardened Alpha</title>
<!-- Pinned CDNs -->
<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>
<script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.5.1"></script>
<!-- Editor -->
<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>
<style>
:root { --bg:#0a0a0a; --panel:#111; --accent:#ff6700; --ok:#19c37d; --err:#ff4d4f; }
body { background:var(--bg); color:#f8f8f8; font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin:0; }
header { padding:0.8rem 1rem; background:var(--panel); color:var(--accent); font-weight:600; display:flex; gap:.75rem; align-items:center; justify-content:space-between; }
.row { display:flex; gap:12px; flex-wrap:wrap; padding:12px; }
button, select, input { background:#141414; color:var(--accent); padding:8px 12px; border:none; border-radius:8px; cursor:pointer; }
button:hover { background:var(--accent); color:#000; }
select, input[type="text"] { color:#f8f8f8; }
.pane { background:var(--panel); border-radius:10px; padding:12px; margin:12px; }
#status { font-weight:700; padding:8px 12px; }
#output { white-space:pre-wrap; background:#0e0e0e; padding:12px; border-radius:8px; max-height:220px; overflow:auto; }
#editor { height:360px; border:1px solid #333; border-radius:8px; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:12px; }
.fn { background:#0e0e0e; padding:10px; border-radius:8px; }
.tag { font-size:.85rem; color:#bbb; margin-left:.5rem; }
.warn { color:var(--err); }
.ok { color:var(--ok); }
.small { font-size:.85rem; color:#bbb; }
.field { display:flex; gap:8px; align-items:center; margin:6px 0; }
.field input { width:100%; }
</style>
</head>
<body>
<header>
<div>🤖 LLM Remix IDE · safer alpha</div>
<div class="row">
<button id="connectBtn">Connect Wallet</button>
<button id="switchSepoliaBtn" title="Switch to Sepolia 11155111">Switch → Sepolia</button>
<button id="generateBtn">Generate</button>
<button id="compileBtn">Compile</button>
<button id="deployBtn" title="Deploy to current network">Deploy</button>
</div>
</header>
<div id="status" class="pane">Status: idle</div>
<div class="row">
<div class="pane" style="flex:1 1 420px;">
<h3 style="margin-top:0">Prompt</h3>
<div class="field">
<input id="prompt" type="text" placeholder="Describe the contract"
value="ERC20 token with daily rebase and transfer fee" />
<select id="modelPreset">
<option value="code">code preset</option>
<option value="vanilla">vanilla</option>
</select>
</div>
<div class="small">LLM runs in browser via Xenova. Generation quality is limited. Review code before deploy.</div>
</div>
<div class="pane" style="flex:2 1 640px;">
<h3 style="margin-top:0">Solidity Source</h3>
<div id="editor"></div>
<div class="small">Edits auto-save to localStorage.</div>
</div>
</div>
<div class="row">
<div class="pane" style="flex:1 1 420px;">
<h3 style="margin-top:0">Compile Log</h3>
<div id="output">Output will appear here…</div>
</div>
<div class="pane" style="flex:1 1 420px;">
<h3 style="margin-top:0">Deployment</h3>
<div class="field">
<label class="small">Payable value (wei):</label>
<input id="deployValue" type="text" placeholder="0" value="0" />
</div>
<div id="ctorFields"></div>
<div class="small">Mainnet is blocked by default. You can override with an explicit confirm.</div>
<div>Address: <span id="contractAddr">-</span></div>
</div>
</div>
<div class="pane">
<h3 style="margin-top:0;">Contract Functions <span class="tag small" id="fnSummary"></span></h3>
<div id="contractUI" class="grid"></div>
</div>
<script>
let provider, signer, currentChainId, contract, abi, bytecode, generator, editor;
// --------- Utils ---------
const byId = id => document.getElementById(id);
const setStatus = (t, cls="") => { const el = byId("status"); el.textContent = t; el.className = "pane " + cls; };
const log = (msg) => { const out = byId("output"); out.textContent = String(msg); };
const save = (k,v)=>localStorage.setItem(k,v);
const load = (k,d="")=>localStorage.getItem(k) ?? d;
function parseArg(type, val) {
if (val === null || val === undefined) return val;
if (type.startsWith("uint") || type.startsWith("int")) return BigInt(val);
if (type === "bool") return val === true || val === "true" || val === "1";
if (type === "address") {
if (!/^0x[0-9a-fA-F]{40}$/.test(val)) throw new Error("Invalid address: " + val);
return val;
}
if (type.endsWith("[]")) {
const inner = type.slice(0, -2);
const parts = String(val).split(",").map(s => s.trim()).filter(Boolean);
return parts.map(p => parseArg(inner, p));
}
return val;
}
function pickFirstArtifact(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];
}
// --------- Editor ---------
(function setupEditor(){
editor = CodeMirror(byId("editor"), {
value: load("src", `// Example\npragma solidity ^0.8.24;\ncontract 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()));
byId("prompt").value = load("prompt", byId("prompt").value);
byId("prompt").addEventListener("change", e=> save("prompt", e.target.value));
})();
// --------- Wallet ---------
byId("connectBtn").onclick = async () => {
if (!window.ethereum) return alert("No wallet provider found");
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
currentChainId = (await provider.getNetwork()).chainId; // BigInt
setStatus(`Wallet: ${await signer.getAddress()} · chainId ${currentChainId}`);
};
byId("switchSepoliaBtn").onclick = async () => {
if (!window.ethereum) return;
try {
await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: "0xaa36a7" }] }); // 11155111
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
currentChainId = (await provider.getNetwork()).chainId;
setStatus(`Switched. chainId ${currentChainId}`, "ok");
} catch (e) {
setStatus("Switch failed: " + (e?.message || e), "warn");
}
};
// --------- LLM ---------
(async () => {
setStatus("Loading LLM…");
generator = await window.transformers.pipeline("text-generation", "Xenova/distilgpt2");
setStatus("LLM ready");
})();
function systemPrompt(preset, user) {
const base = [
"Write ONLY valid Solidity source. No commentary.",
"Target pragma ^0.8.24.",
"Avoid unsafe patterns. No tx.origin. No inline assembly unless needed.",
"Prefer OpenZeppelin interfaces if applicable.",
].join("\n");
if (preset === "code") {
return `${base}\nTask: ${user}\nReturn one file with a single primary contract.`;
}
return `Write a Solidity contract: ${user}`;
}
byId("generateBtn").onclick = async () => {
const user = byId("prompt").value;
const preset = byId("modelPreset").value;
setStatus("Generating…");
let attempt = 0, code = "";
while (attempt < 3) {
attempt++;
const out = await generator(systemPrompt(preset, user), { max_new_tokens: 400 });
const txt = out?.[0]?.generated_text || "";
// try to strip junk
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}`);
};
// --------- Compile ---------
byId("compileBtn").onclick = async () => {
const source = editor.getValue();
setStatus("Compiling…");
const input = {
language: "Solidity",
sources: { "Contract.sol": { content: source } },
settings: { outputSelection: { "*": { "*": [ "abi", "evm.bytecode", "evm.deployedBytecode" ] } } }
};
try {
const compiled = JSON.parse(solc.compile(JSON.stringify(input)));
if (compiled.errors?.length) {
const lines = compiled.errors.map(e => `${e.severity.toUpperCase()}: ${e.formattedMessage.trim()}`).join("\n");
log(lines);
const fatal = compiled.errors.some(e => e.severity === "error");
if (fatal) { setStatus("Compile errors", "warn"); return; }
}
const file = compiled.contracts["Contract.sol"];
const [name, art] = pickFirstArtifact(file);
if (!name) throw new Error("No contract bytecode found");
abi = art.abi;
bytecode = art.evm.bytecode.object;
contract = { name, abi, bytecode };
log(`✅ Compiled ${name}\nFunctions: ${abi.filter(x=>x.type==="function").length}`);
buildCtorUI(abi);
buildUI(null, abi); // Prebuild shells
setStatus("Compiled", "ok");
} catch (e) {
log("❌ Compile failed:\n" + (e?.message || e));
setStatus("Compile failed", "warn");
}
};
// --------- Deploy ---------
byId("deployBtn").onclick = async () => {
try {
if (!signer) return alert("Connect wallet first");
if (!abi || !bytecode) return alert("Compile first");
const net = await provider.getNetwork();
const isMainnet = net.chainId === 1n;
if (isMainnet) {
const ok = confirm("Mainnet detected. This is dangerous. Continue?");
if (!ok) return;
}
const ctor = abi.find(x => x.type === "constructor") || { inputs: [] };
const args = [];
for (const inp of ctor.inputs) {
const el = byId(`ctor_${inp.name || "arg"}`);
const parsed = parseArg(inp.type, el?.value ?? "");
args.push(parsed);
}
const valueWei = byId("deployValue").value?.trim() || "0";
const overrides = valueWei && valueWei !== "0" ? { value: BigInt(valueWei) } : {};
const factory = new ethers.ContractFactory(abi, bytecode, signer);
const est = await provider.estimateGas(factory.getDeployTransaction(...args, overrides));
log(`Gas estimate: ${est.toString()}`);
const instance = await factory.deploy(...args, overrides);
setStatus("Deploying…");
await instance.waitForDeployment();
const addr = await instance.getAddress();
byId("contractAddr").textContent = addr;
setStatus("Deployed at " + addr, "ok");
buildUI(instance, abi);
} catch (e) {
setStatus("Deploy failed: " + (e?.message || e), "warn");
}
};
// --------- Dynamic UI ---------
function buildCtorUI(abi) {
const box = byId("ctorFields");
box.innerHTML = "";
const ctor = abi.find(x => x.type === "constructor");
if (!ctor || !ctor.inputs?.length) { box.innerHTML = "<div class='small'>No constructor args</div>"; return; }
for (const inp of ctor.inputs) {
const row = document.createElement("div"); row.className = "field";
const label = document.createElement("label"); label.className = "small";
label.textContent = `${inp.name || "arg"} (${inp.type})`;
const input = document.createElement("input"); input.type = "text"; input.id = `ctor_${inp.name || "arg"}`;
row.appendChild(label); row.appendChild(input); box.appendChild(row);
}
}
function buildUI(instance, abi) {
const div = byId("contractUI"); div.innerHTML = "";
const fns = abi.filter(x => x.type === "function");
byId("fnSummary").textContent = `${fns.length} functions`;
for (const fn of fns) {
const card = document.createElement("div"); card.className = "fn";
const title = document.createElement("div");
const readonly = fn.stateMutability === "view" || fn.stateMutability === "pure";
title.innerHTML = `<b>${fn.name}</b> <span class="tag">${readonly ? "read" : "write"} • ${fn.stateMutability}</span>`;
card.appendChild(title);
// inputs
const argsInputs = [];
for (const inp of fn.inputs) {
const row = document.createElement("div"); row.className = "field";
const label = document.createElement("label"); label.className = "small";
label.textContent = `${inp.name || "arg"} (${inp.type})`;
const input = document.createElement("input"); input.type = "text";
row.appendChild(label); row.appendChild(input); card.appendChild(row);
argsInputs.push({ def: inp, el: input });
}
// payable override
let valueEl = null;
if (!readonly && fn.stateMutability === "payable") {
const row = document.createElement("div"); row.className = "field";
const label = document.createElement("label"); label.className = "small"; label.textContent = "value (wei)";
const input = document.createElement("input"); input.type = "text"; input.placeholder = "0";
row.appendChild(label); row.appendChild(input); card.appendChild(row);
valueEl = input;
}
const btn = document.createElement("button"); btn.textContent = "Run";
btn.onclick = async () => {
try {
if (!instance) throw new Error("Deploy or attach first");
const args = argsInputs.map(a => parseArg(a.def.type, a.el.value));
if (readonly) {
const res = await instance[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 instance[fn.name](...args, ov);
log(`Sent: ${tx.hash}`);
const rc = await tx.wait();
log(`Mined in block ${rc.blockNumber}`);
}
} catch (e) {
log("Error: " + (e?.message || e));
}
};
card.appendChild(btn);
div.appendChild(card);
}
}
</script>
</body>
</html>
"""
@app.get("/", response_class=HTMLResponse)
async def root():
return HTML
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=7860)