Last active
April 20, 2026 02:45
-
-
Save wenakita/e530a91d53f5296f1447cfb728cd69fd to your computer and use it in GitHub Desktop.
$feedback — leave feedback for agent 2205. In-iframe ERC-8004 Reputation Registry terminal on Zora. Sign once, writes to 0x8004baa1…9b63 on Base.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>leave feedback for agent 2205</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet" /> | |
| <style> | |
| :root { | |
| --bg: #0a0a0a; | |
| --fg: #fafafa; | |
| --muted: #8b8b8b; | |
| --dim: #444; | |
| --accent: #c89a2b; | |
| --pos: #6bd392; | |
| --neg: #ee6a6a; | |
| --rule: #1a1a1a; | |
| color-scheme: dark; | |
| } | |
| html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; } | |
| body { | |
| background: var(--bg); color: var(--fg); | |
| font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; | |
| font-size: 12px; line-height: 1.5; | |
| display: flex; align-items: center; justify-content: center; | |
| background-image: | |
| radial-gradient(#141414 1px, transparent 1px), | |
| radial-gradient(#141414 1px, transparent 1px); | |
| background-size: 48px 48px; background-position: 0 0, 24px 24px; | |
| } | |
| .card { | |
| width: min(92vw, 560px); max-height: 94vh; | |
| background: linear-gradient(180deg, #0f0f0f 0%, #070707 100%); | |
| border: 1px solid #1a1a1a; border-radius: 2px; | |
| padding: 28px 32px; box-sizing: border-box; | |
| display: flex; flex-direction: column; gap: 16px; | |
| overflow-y: auto; | |
| } | |
| .kicker { color: var(--muted); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; } | |
| .headline { | |
| font-family: "Instrument Serif", ui-serif, Georgia, serif; | |
| font-size: clamp(28px, 5.5vw, 44px); | |
| font-weight: 400; line-height: 1.1; color: var(--fg); margin: 0; | |
| letter-spacing: -0.01em; | |
| } | |
| .headline em { font-style: italic; color: var(--accent); } | |
| .sub { color: var(--muted); font-size: 12px; line-height: 1.6; margin: 0; max-width: 46ch; } | |
| .sub a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--dim); text-underline-offset: 3px; } | |
| .sub a:hover { text-decoration-color: var(--accent); } | |
| .segment { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } | |
| .seg-btn { | |
| padding: 12px 10px; border: 1px solid #1a1a1a; background: #070707; | |
| color: var(--muted); cursor: pointer; border-radius: 2px; | |
| font-family: inherit; font-size: 12px; letter-spacing: 0.04em; | |
| text-transform: uppercase; transition: all 0.15s ease; | |
| } | |
| .seg-btn:hover { border-color: var(--dim); color: var(--fg); } | |
| .seg-btn.active[data-value="positive"] { border-color: var(--pos); color: var(--pos); background: #0a1a10; } | |
| .seg-btn.active[data-value="neutral"] { border-color: var(--muted); color: var(--fg); background: #121212; } | |
| .seg-btn.active[data-value="critical"] { border-color: var(--neg); color: var(--neg); background: #1a0a0a; } | |
| .field { display: flex; flex-direction: column; gap: 6px; } | |
| .label { color: var(--muted); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; } | |
| select, textarea { | |
| background: #070707; border: 1px solid #1a1a1a; color: var(--fg); | |
| font-family: inherit; font-size: 12px; padding: 10px 12px; | |
| border-radius: 2px; resize: vertical; | |
| } | |
| textarea { min-height: 56px; line-height: 1.6; } | |
| select:focus, textarea:focus { outline: 0; border-color: var(--accent); } | |
| .actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } | |
| button.primary { | |
| flex: 1; min-width: 140px; | |
| padding: 12px 16px; background: var(--fg); color: #000; | |
| border: 0; border-radius: 2px; cursor: pointer; | |
| font-family: inherit; font-size: 12px; letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| transition: background 0.15s ease, transform 0.1s ease; | |
| } | |
| button.primary:hover:not(:disabled) { background: #e5e5e5; } | |
| button.primary:active { transform: translateY(1px); } | |
| button.primary:disabled { opacity: 0.5; cursor: not-allowed; background: #333; color: #666; } | |
| .chips { display: flex; gap: 6px; flex-wrap: wrap; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; } | |
| .chip { padding: 3px 8px; border-radius: 999px; border: 1px solid #1a1a1a; color: var(--muted); } | |
| .chip.ok { color: var(--pos); border-color: #1f5b38; background: #0a1a10; } | |
| .chip.warn { color: var(--accent); border-color: #3a2d13; background: #1a1205; } | |
| .chip.err { color: var(--neg); border-color: #5c2020; background: #1a0a0a; } | |
| .tx-panel, .diag-panel { | |
| border: 1px solid #1a1a1a; background: #070707; padding: 12px 14px; | |
| border-radius: 2px; font-size: 11px; | |
| } | |
| .tx-panel a, .diag-panel a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--dim); text-underline-offset: 3px; } | |
| .tx-panel a:hover, .diag-panel a:hover { text-decoration-color: var(--accent); } | |
| .tx-panel .row, .diag-panel .row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; } | |
| .tx-panel .k, .diag-panel .k { color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase; font-size: 10px; } | |
| .tx-panel .v, .diag-panel .v { color: var(--fg); font-family: "JetBrains Mono", monospace; text-align: right; word-break: break-all; } | |
| /* Wallet picker modal */ | |
| .picker-overlay { | |
| position: fixed; inset: 0; background: rgba(0,0,0,0.7); | |
| display: none; align-items: center; justify-content: center; z-index: 100; | |
| } | |
| .picker-overlay.show { display: flex; } | |
| .picker { | |
| background: #0f0f0f; border: 1px solid #2a2a2a; border-radius: 4px; | |
| padding: 20px; width: min(90vw, 400px); | |
| display: flex; flex-direction: column; gap: 8px; | |
| } | |
| .picker .h { font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; } | |
| .picker button { | |
| display: flex; align-items: center; gap: 12px; | |
| padding: 12px 14px; background: #1a1a1a; border: 1px solid #2a2a2a; | |
| color: var(--fg); cursor: pointer; border-radius: 3px; text-align: left; | |
| font-family: inherit; font-size: 12px; | |
| } | |
| .picker button:hover { background: #242424; border-color: var(--accent); } | |
| .picker button img { width: 24px; height: 24px; border-radius: 4px; } | |
| .picker .cancel { text-align: center; background: transparent; border: 0; color: var(--muted); padding: 8px; margin-top: 4px; } | |
| .picker .cancel:hover { color: var(--fg); } | |
| .footnote { color: var(--dim); font-size: 10px; letter-spacing: 0.04em; line-height: 1.6; } | |
| .footnote code { color: var(--muted); } | |
| .hidden { display: none !important; } | |
| /* Collapsed diagnostic summary */ | |
| details.diag summary { | |
| list-style: none; cursor: pointer; color: var(--muted); | |
| font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; | |
| padding: 4px 0; | |
| } | |
| details.diag summary::marker { display: none; } | |
| details.diag summary::-webkit-details-marker { display: none; } | |
| details.diag summary:hover { color: var(--fg); } | |
| details.diag[open] summary { margin-bottom: 6px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <div class="kicker">ERC-8004 · BASE · AGENT 2205</div> | |
| <h1 class="headline">leave <em>feedback</em>.</h1> | |
| <p class="sub"> | |
| A public feedback submission to agent 2205, written to the | |
| <a href="https://basescan.org/address/0x8004baa17c55a88189ae136b182e5fda19de9b63" target="_blank" rel="noopener">Reputation Registry</a>. One signature, permanent on-chain, readable by the agent. | |
| </p> | |
| <div class="chips" id="chips"> | |
| <span class="chip" id="chip-wallet">wallet · not connected</span> | |
| <span class="chip" id="chip-chain">chain · unknown</span> | |
| </div> | |
| <div class="field"> | |
| <div class="label">signal</div> | |
| <div class="segment" id="segment"> | |
| <button class="seg-btn" data-value="positive" type="button">positive</button> | |
| <button class="seg-btn active" data-value="neutral" type="button">neutral</button> | |
| <button class="seg-btn" data-value="critical" type="button">critical</button> | |
| </div> | |
| </div> | |
| <div class="field"> | |
| <div class="label">category</div> | |
| <select id="tag"> | |
| <option value="overall">overall</option> | |
| <option value="responsiveness">responsiveness</option> | |
| <option value="accuracy">accuracy</option> | |
| <option value="reliability">reliability</option> | |
| <option value="ux">ux</option> | |
| <option value="pricing">pricing</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <div class="label">message (optional — written as feedbackURI on chain)</div> | |
| <textarea id="message" maxlength="800" placeholder="what should agent 2205 hear?"></textarea> | |
| </div> | |
| <div class="actions"> | |
| <button class="primary" id="submit" type="button">connect wallet</button> | |
| </div> | |
| <!-- Tx panel (hidden until there's something to show) --> | |
| <div class="tx-panel hidden" id="tx-panel"> | |
| <div class="row"><div class="k">status</div><div class="v" id="tx-status">—</div></div> | |
| <div class="row"><div class="k">tx hash</div><div class="v" id="tx-hash">—</div></div> | |
| <div class="row"><div class="k">registry</div> | |
| <div class="v"><a href="https://basescan.org/address/0x8004baa17c55a88189ae136b182e5fda19de9b63" target="_blank" rel="noopener">0x8004baa1…9b63</a></div></div> | |
| </div> | |
| <!-- Diagnostics (collapsed by default; expand if something's off). --> | |
| <details class="diag" id="diag-container"> | |
| <summary>⚠ diagnostics</summary> | |
| <div class="diag-panel"> | |
| <div class="row"><div class="k">window.ethereum</div><div class="v" id="diag-eth">detecting…</div></div> | |
| <div class="row"><div class="k">eip-6963 providers</div><div class="v" id="diag-6963">detecting…</div></div> | |
| <div class="row"><div class="k">selected wallet</div><div class="v" id="diag-selected">none</div></div> | |
| <div class="row"><div class="k">origin</div><div class="v" id="diag-origin">—</div></div> | |
| <div class="row"><div class="k">last error</div><div class="v" id="diag-err">—</div></div> | |
| </div> | |
| </details> | |
| <div class="footnote"> | |
| calls <code>giveFeedback(2205, …)</code> on the ERC-8004 Reputation Registry. Self-feedback is blocked by the contract — if you own agent 2205, use a different wallet. | |
| </div> | |
| </div> | |
| <!-- Wallet picker modal --> | |
| <div class="picker-overlay" id="picker-overlay"> | |
| <div class="picker"> | |
| <div class="h">choose a wallet</div> | |
| <div id="picker-list"></div> | |
| <button class="cancel" id="picker-cancel" type="button">cancel</button> | |
| </div> | |
| </div> | |
| <script> | |
| // ─── Constants ─────────────────────────────────────────────────────── | |
| const AGENT_ID = 2205n; | |
| const REG_ADDR = "0x8004baa17c55a88189ae136b182e5fda19de9b63"; | |
| const BASE_CHAIN_ID = 8453; | |
| const BASE_CHAIN_HEX = "0x2105"; | |
| // giveFeedback(uint256,int128,uint8,string,string,string,string,bytes32) | |
| const SELECTOR = "3c036a7e"; | |
| // ─── State ─────────────────────────────────────────────────────────── | |
| // `providers` is the EIP-6963 discovered wallet set. `activeProvider` | |
| // is whichever provider the user picked (or the sole one) and is what | |
| // we actually call .request() on. `window.ethereum` is only a fallback. | |
| let state = { | |
| sentiment: "neutral", | |
| address: null, | |
| chainId: null, | |
| txHash: null, | |
| txStatus: null, | |
| txError: null, | |
| providers: [], // [{info: {name, icon, rdns, uuid}, provider}] | |
| activeProvider: null, // a provider object | |
| activeInfo: null, // provider info (name, icon, rdns) | |
| lastError: null, | |
| }; | |
| const els = { | |
| tag: document.getElementById("tag"), | |
| message: document.getElementById("message"), | |
| submit: document.getElementById("submit"), | |
| chipWallet: document.getElementById("chip-wallet"), | |
| chipChain: document.getElementById("chip-chain"), | |
| txPanel: document.getElementById("tx-panel"), | |
| txStatus: document.getElementById("tx-status"), | |
| txHash: document.getElementById("tx-hash"), | |
| diagEth: document.getElementById("diag-eth"), | |
| diag6963: document.getElementById("diag-6963"), | |
| diagSelected: document.getElementById("diag-selected"), | |
| diagOrigin: document.getElementById("diag-origin"), | |
| diagErr: document.getElementById("diag-err"), | |
| diagContainer: document.getElementById("diag-container"), | |
| pickerOverlay: document.getElementById("picker-overlay"), | |
| pickerList: document.getElementById("picker-list"), | |
| pickerCancel: document.getElementById("picker-cancel"), | |
| segButtons: document.querySelectorAll(".seg-btn"), | |
| }; | |
| // ─── ABI encoding (unchanged, verified byte-for-byte against eth_abi) ─ | |
| const pad32 = (hex) => hex.padStart(64, "0"); | |
| const encUint = (n) => pad32(BigInt(n).toString(16)); | |
| const encInt = (n) => pad32(BigInt.asUintN(256, BigInt(n)).toString(16)); | |
| const encString = (s) => { | |
| const bytes = new TextEncoder().encode(s || ""); | |
| const lenHex = pad32(BigInt(bytes.length).toString(16)); | |
| let bodyHex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""); | |
| bodyHex += "00".repeat((32 - (bytes.length % 32)) % 32); | |
| return lenHex + bodyHex; | |
| }; | |
| const encBytes32 = (hex) => (hex || "").replace(/^0x/, "").padStart(64, "0"); | |
| function encodeGiveFeedback({ agentId, value, decimals, tag1, tag2, endpoint, feedbackURI, feedbackHash }) { | |
| const encTag1 = encString(tag1); | |
| const encTag2 = encString(tag2); | |
| const encEnd = encString(endpoint); | |
| const encURI = encString(feedbackURI); | |
| const headBytes = 8 * 32; | |
| const offTag1 = BigInt(headBytes); | |
| const offTag2 = offTag1 + BigInt(encTag1.length / 2); | |
| const offEnd = offTag2 + BigInt(encTag2.length / 2); | |
| const offURI = offEnd + BigInt(encEnd.length / 2); | |
| const head = encUint(agentId) + encInt(value) + encUint(decimals) | |
| + pad32(offTag1.toString(16)) + pad32(offTag2.toString(16)) | |
| + pad32(offEnd.toString(16)) + pad32(offURI.toString(16)) | |
| + encBytes32(feedbackHash); | |
| return "0x" + SELECTOR + head + encTag1 + encTag2 + encEnd + encURI; | |
| } | |
| // ─── EIP-6963 provider discovery ───────────────────────────────────── | |
| // Modern standard (https://eips.ethereum.org/EIPS/eip-6963) that | |
| // handles the "multiple wallet extensions fighting over window.ethereum" | |
| // problem cleanly. Each wallet announces itself via a message event | |
| // with full metadata (name, icon, uuid, rdns). We collect them all | |
| // and present a picker. | |
| function setupEip6963Discovery() { | |
| window.addEventListener("eip6963:announceProvider", (event) => { | |
| const detail = event.detail; | |
| if (!detail || !detail.provider) return; | |
| const existing = state.providers.find((p) => p.info.uuid === detail.info?.uuid); | |
| if (!existing) { | |
| state.providers.push({ info: detail.info, provider: detail.provider }); | |
| renderDiagnostics(); | |
| } | |
| }); | |
| window.dispatchEvent(new Event("eip6963:requestProvider")); | |
| // Some extensions announce lazily; re-poke after short delays | |
| setTimeout(() => window.dispatchEvent(new Event("eip6963:requestProvider")), 300); | |
| setTimeout(() => window.dispatchEvent(new Event("eip6963:requestProvider")), 1000); | |
| } | |
| // ─── Wallet picker modal ───────────────────────────────────────────── | |
| function showPicker() { | |
| const providers = state.providers; | |
| els.pickerList.innerHTML = ""; | |
| if (providers.length === 0) { | |
| // Fallback: show window.ethereum if no 6963 providers | |
| if (window.ethereum) { | |
| const btn = document.createElement("button"); | |
| btn.type = "button"; | |
| btn.textContent = "window.ethereum (unknown wallet)"; | |
| btn.addEventListener("click", () => { | |
| selectProvider({ info: { name: "Unknown (window.ethereum)", rdns: "unknown" }, provider: window.ethereum }); | |
| hidePicker(); | |
| }); | |
| els.pickerList.appendChild(btn); | |
| } else { | |
| const msg = document.createElement("div"); | |
| msg.style.color = "var(--muted)"; | |
| msg.style.fontSize = "11px"; | |
| msg.style.padding = "10px 0"; | |
| msg.textContent = "No wallet detected. Install Rabby, MetaMask, or Coinbase Wallet and reload."; | |
| els.pickerList.appendChild(msg); | |
| } | |
| } else { | |
| for (const { info, provider } of providers) { | |
| const btn = document.createElement("button"); | |
| btn.type = "button"; | |
| if (info.icon) { | |
| const img = document.createElement("img"); | |
| img.src = info.icon; | |
| img.alt = ""; | |
| btn.appendChild(img); | |
| } | |
| const label = document.createElement("span"); | |
| label.textContent = info.name || info.rdns || "Unknown wallet"; | |
| btn.appendChild(label); | |
| btn.addEventListener("click", () => { | |
| selectProvider({ info, provider }); | |
| hidePicker(); | |
| }); | |
| els.pickerList.appendChild(btn); | |
| } | |
| } | |
| els.pickerOverlay.classList.add("show"); | |
| } | |
| function hidePicker() { els.pickerOverlay.classList.remove("show"); } | |
| els.pickerCancel.addEventListener("click", hidePicker); | |
| els.pickerOverlay.addEventListener("click", (e) => { if (e.target === els.pickerOverlay) hidePicker(); }); | |
| async function selectProvider({ info, provider }) { | |
| state.activeProvider = provider; | |
| state.activeInfo = info; | |
| renderDiagnostics(); | |
| try { | |
| const accounts = await provider.request({ method: "eth_requestAccounts" }); | |
| state.address = Array.isArray(accounts) ? accounts[0] : null; | |
| const chain = await provider.request({ method: "eth_chainId" }); | |
| state.chainId = parseInt(chain, 16); | |
| state.lastError = null; | |
| attachProviderListeners(provider); | |
| updateUi(); | |
| } catch (err) { | |
| state.lastError = friendlyErr(err); | |
| renderDiagnostics(); | |
| updateUi(); | |
| } | |
| } | |
| function attachProviderListeners(provider) { | |
| if (provider._probe_attached) return; | |
| provider._probe_attached = true; | |
| provider.on?.("accountsChanged", (accs) => { | |
| state.address = (Array.isArray(accs) ? accs[0] : null) || null; | |
| updateUi(); | |
| }); | |
| provider.on?.("chainChanged", (hex) => { | |
| state.chainId = parseInt(hex, 16); | |
| updateUi(); | |
| }); | |
| } | |
| // ─── UI rendering ──────────────────────────────────────────────────── | |
| function renderDiagnostics() { | |
| els.diagEth.textContent = window.ethereum | |
| ? `present (${describeEthFlavor()})` | |
| : "undefined"; | |
| els.diagEth.className = "v"; | |
| els.diag6963.textContent = state.providers.length | |
| ? state.providers.map((p) => p.info.name || p.info.rdns || "?").join(" · ") | |
| : "none announced yet"; | |
| els.diagSelected.textContent = state.activeInfo | |
| ? `${state.activeInfo.name || state.activeInfo.rdns || "unknown"}${state.address ? ` · ${shortAddr(state.address)}` : ""}` | |
| : "none"; | |
| els.diagOrigin.textContent = window.location.origin || "null"; | |
| els.diagErr.textContent = state.lastError || "—"; | |
| } | |
| function describeEthFlavor() { | |
| if (!window.ethereum) return "none"; | |
| const e = window.ethereum; | |
| const flags = []; | |
| if (e.isRabby) flags.push("rabby"); | |
| if (e.isMetaMask) flags.push("metamask"); | |
| if (e.isCoinbaseWallet) flags.push("coinbase"); | |
| if (e.isBraveWallet) flags.push("brave"); | |
| if (e.isPhantom) flags.push("phantom"); | |
| if (Array.isArray(e.providers)) flags.push(`providers[${e.providers.length}]`); | |
| return flags.length ? flags.join(",") : "unknown flavor"; | |
| } | |
| function shortAddr(a) { return a ? `${a.slice(0,6)}…${a.slice(-4)}` : ""; } | |
| function updateChips() { | |
| if (state.address) { | |
| els.chipWallet.textContent = `wallet · ${shortAddr(state.address)}`; | |
| els.chipWallet.className = "chip ok"; | |
| } else { | |
| els.chipWallet.textContent = "wallet · not connected"; | |
| els.chipWallet.className = "chip"; | |
| } | |
| if (state.chainId === BASE_CHAIN_ID) { | |
| els.chipChain.textContent = "chain · base"; | |
| els.chipChain.className = "chip ok"; | |
| } else if (state.chainId) { | |
| els.chipChain.textContent = `chain · ${state.chainId} · switch to base`; | |
| els.chipChain.className = "chip warn"; | |
| } else { | |
| els.chipChain.textContent = "chain · unknown"; | |
| els.chipChain.className = "chip"; | |
| } | |
| } | |
| function updateSubmitButton() { | |
| // Button is ALWAYS clickable — we handle no-wallet state inside | |
| // the click handler by opening the picker or showing an error. | |
| if (!state.address) { els.submit.textContent = "connect wallet"; els.submit.disabled = false; return; } | |
| if (state.chainId !== BASE_CHAIN_ID) { els.submit.textContent = "switch to base"; els.submit.disabled = false; return; } | |
| if (state.txStatus === "pending") { els.submit.textContent = "submitting…"; els.submit.disabled = true; return; } | |
| els.submit.textContent = "sign + send feedback"; els.submit.disabled = false; | |
| } | |
| function updateUi() { | |
| updateChips(); | |
| updateSubmitButton(); | |
| renderDiagnostics(); | |
| } | |
| // ─── Submit / connect / switch orchestration ───────────────────────── | |
| async function onSubmitClick() { | |
| // Open picker if not yet connected. Always show it — let the user pick. | |
| if (!state.address) { | |
| showPicker(); | |
| return; | |
| } | |
| if (state.chainId !== BASE_CHAIN_ID) { | |
| await switchToBase(); | |
| return; | |
| } | |
| await sendFeedback(); | |
| } | |
| async function switchToBase() { | |
| if (!state.activeProvider) return; | |
| try { | |
| await state.activeProvider.request({ | |
| method: "wallet_switchEthereumChain", | |
| params: [{ chainId: BASE_CHAIN_HEX }], | |
| }); | |
| state.chainId = BASE_CHAIN_ID; | |
| state.lastError = null; | |
| } catch (err) { | |
| state.lastError = friendlyErr(err); | |
| } | |
| updateUi(); | |
| } | |
| async function sendFeedback() { | |
| const valueMap = { positive: 100, neutral: 0, critical: -100 }; | |
| const value = valueMap[state.sentiment] ?? 0; | |
| const tag1 = els.tag.value || "overall"; | |
| const tag2 = state.sentiment; | |
| const msg = (els.message.value || "").trim(); | |
| const feedbackURI = msg ? `data:text/plain;base64,${btoa(unescape(encodeURIComponent(msg)))}` : ""; | |
| const feedbackHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; | |
| const data = encodeGiveFeedback({ agentId: AGENT_ID, value, decimals: 2, tag1, tag2, endpoint: "", feedbackURI, feedbackHash }); | |
| state.txStatus = "pending"; state.txHash = null; state.txError = null; | |
| renderTx(); updateSubmitButton(); | |
| try { | |
| const txHash = await state.activeProvider.request({ | |
| method: "eth_sendTransaction", | |
| params: [{ from: state.address, to: REG_ADDR, data, value: "0x0" }], | |
| }); | |
| state.txHash = txHash; | |
| renderTx(); | |
| pollForReceipt(txHash); | |
| } catch (err) { | |
| state.txStatus = "failed"; | |
| state.txError = friendlyErr(err); | |
| state.lastError = state.txError; | |
| renderTx(); renderDiagnostics(); updateSubmitButton(); | |
| } | |
| } | |
| async function pollForReceipt(hash) { | |
| let attempts = 0; | |
| while (attempts < 40) { | |
| await new Promise((r) => setTimeout(r, 1500)); | |
| attempts += 1; | |
| try { | |
| const receipt = await state.activeProvider.request({ | |
| method: "eth_getTransactionReceipt", params: [hash], | |
| }); | |
| if (receipt) { | |
| state.txStatus = receipt.status === "0x1" ? "confirmed" : "failed"; | |
| if (receipt.status !== "0x1") state.txError = "reverted on-chain"; | |
| renderTx(); updateSubmitButton(); | |
| return; | |
| } | |
| } catch {} | |
| } | |
| state.txStatus = "pending (check basescan)"; renderTx(); | |
| } | |
| function renderTx() { | |
| if (!state.txStatus) { els.txPanel.classList.add("hidden"); return; } | |
| els.txPanel.classList.remove("hidden"); | |
| els.txStatus.textContent = state.txError ? `${state.txStatus} · ${state.txError}` : state.txStatus; | |
| if (state.txHash) { | |
| const short = `${state.txHash.slice(0, 10)}…${state.txHash.slice(-8)}`; | |
| els.txHash.innerHTML = `<a href="https://basescan.org/tx/${state.txHash}" target="_blank" rel="noopener">${short}</a>`; | |
| } else { | |
| els.txHash.textContent = "—"; | |
| } | |
| } | |
| function friendlyErr(e) { | |
| if (!e) return "unknown error"; | |
| const msg = String(e.message || e.toString()); | |
| if (/self-feedback not allowed/i.test(msg)) return "you own agent 2205 — use a different wallet"; | |
| if (/user (rejected|denied)/i.test(msg)) return "signature rejected"; | |
| if (/insufficient funds/i.test(msg)) return "insufficient eth on base"; | |
| if (/unrecognized chain|chain.*not.*added/i.test(msg)) return "base not added — add it and retry"; | |
| return msg.slice(0, 160); | |
| } | |
| // ─── Sentiment selector ────────────────────────────────────────────── | |
| els.segButtons.forEach((b) => { | |
| b.addEventListener("click", () => { | |
| state.sentiment = b.dataset.value; | |
| els.segButtons.forEach((x) => x.classList.toggle("active", x === b)); | |
| }); | |
| }); | |
| els.submit.addEventListener("click", onSubmitClick); | |
| // ─── Init ──────────────────────────────────────────────────────────── | |
| (function init() { | |
| setupEip6963Discovery(); | |
| renderDiagnostics(); | |
| updateUi(); | |
| // Don't auto-silently connect — picker is better UX. User clicks the button. | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment