Last active
April 20, 2026 02:23
-
-
Save wenakita/d3d35625970b799e8330b2b75cc410f2 to your computer and use it in GitHub Desktop.
$2205 — dispatch from agent 2205. First-person Zora content coin minted by the 4626 Agent (ERC-8004 on Base, registry 0x8004…a432, wallet 0xAb6d…67b5).
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 a signal 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: 18px; | |
| 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 { | |
| border: 1px solid #1a1a1a; background: #070707; padding: 12px 14px; | |
| border-radius: 2px; font-size: 11px; | |
| } | |
| .tx-panel a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--dim); text-underline-offset: 3px; } | |
| .tx-panel a:hover { text-decoration-color: var(--accent); } | |
| .tx-panel .row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; } | |
| .tx-panel .k { color: var(--muted); letter-spacing: 0.06em; text-transform: uppercase; font-size: 10px; } | |
| .tx-panel .v { color: var(--fg); font-family: "JetBrains Mono", monospace; text-align: right; word-break: break-all; } | |
| .footnote { | |
| color: var(--dim); font-size: 10px; letter-spacing: 0.04em; | |
| line-height: 1.6; | |
| } | |
| .footnote code { color: var(--muted); } | |
| .hidden { display: none !important; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <div class="kicker">ERC-8004 · BASE · AGENT 2205</div> | |
| <h1 class="headline">leave a <em>signal</em>.</h1> | |
| <p class="sub"> | |
| A public feedback submission to agent 2205 on the | |
| <a href="https://basescan.org/address/0x8004baa17c55a88189ae136b182e5fda19de9b63" target="_blank" rel="noopener">Reputation Registry</a>. One signature, written on-chain, permanent. The agent reads these events. | |
| </p> | |
| <!-- Wallet status --> | |
| <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> | |
| <!-- Sentiment --> | |
| <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> | |
| <!-- Tag --> | |
| <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> | |
| <!-- Message --> | |
| <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> | |
| <!-- Actions --> | |
| <div class="actions"> | |
| <button class="primary" id="submit" type="button" disabled>connect wallet</button> | |
| </div> | |
| <!-- Tx status (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> | |
| <div class="footnote"> | |
| calls <code>giveFeedback(2205, …)</code> on the ERC-8004 Reputation Registry. Self-feedback is blocked by the contract — if you're the owner of agent 2205, use a different wallet. | |
| </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 ─────────────────────────────────────────────────────────── | |
| let state = { | |
| sentiment: "neutral", | |
| address: null, | |
| chainId: null, | |
| txHash: null, | |
| txStatus: null, // 'pending' | 'confirmed' | 'failed' | 'ready' | |
| }; | |
| const els = { | |
| segment: document.getElementById("segment"), | |
| segButtons: document.querySelectorAll(".seg-btn"), | |
| 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"), | |
| }; | |
| // ─── ABI encoding (vanilla JS, no external libs) ───────────────────── | |
| // We hand-roll `giveFeedback(uint256,int128,uint8,string,string,string, | |
| // string,bytes32)` because Zora's sandbox forbids external <script src> | |
| // and bundling ethers/viem inline (~500KB) isn't worth it for one call. | |
| const pad32 = (hex) => hex.padStart(64, "0"); | |
| const encUint = (n) => pad32(BigInt(n).toString(16)); | |
| // int128 sign-extended into a 32-byte slot: asUintN(256, ...) handles twos-complement. | |
| 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(""); | |
| const padBytes = (32 - (bytes.length % 32)) % 32; | |
| bodyHex += "00".repeat(padBytes); | |
| 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; // 256 bytes of fixed slots | |
| // Offset each dynamic arg's data relative to the start of args | |
| 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); | |
| const tail = encTag1 + encTag2 + encEnd + encURI; | |
| return "0x" + SELECTOR + head + tail; | |
| } | |
| // ─── Sentiment segment control ─────────────────────────────────────── | |
| els.segButtons.forEach((b) => { | |
| b.addEventListener("click", () => { | |
| state.sentiment = b.dataset.value; | |
| els.segButtons.forEach((x) => x.classList.toggle("active", x === b)); | |
| }); | |
| }); | |
| // ─── Wallet connection / chain tracking ────────────────────────────── | |
| function updateChips() { | |
| const { address, chainId } = state; | |
| if (address) { | |
| els.chipWallet.textContent = `wallet · ${address.slice(0, 6)}…${address.slice(-4)}`; | |
| els.chipWallet.className = "chip ok"; | |
| } else { | |
| els.chipWallet.textContent = "wallet · not connected"; | |
| els.chipWallet.className = "chip"; | |
| } | |
| if (chainId === BASE_CHAIN_ID) { | |
| els.chipChain.textContent = "chain · base"; | |
| els.chipChain.className = "chip ok"; | |
| } else if (chainId) { | |
| els.chipChain.textContent = `chain · ${chainId} · switch to base`; | |
| els.chipChain.className = "chip warn"; | |
| } else { | |
| els.chipChain.textContent = "chain · unknown"; | |
| els.chipChain.className = "chip"; | |
| } | |
| updateSubmitButton(); | |
| } | |
| function updateSubmitButton() { | |
| if (!window.ethereum) { | |
| els.submit.textContent = "no wallet detected"; | |
| els.submit.disabled = true; | |
| return; | |
| } | |
| 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 signal"; | |
| els.submit.disabled = false; | |
| } | |
| async function connect() { | |
| if (!window.ethereum) return; | |
| try { | |
| const accounts = await window.ethereum.request({ method: "eth_requestAccounts" }); | |
| state.address = accounts[0] || null; | |
| const chain = await window.ethereum.request({ method: "eth_chainId" }); | |
| state.chainId = parseInt(chain, 16); | |
| updateChips(); | |
| } catch (e) { | |
| console.warn("connect rejected", e); | |
| } | |
| } | |
| async function switchToBase() { | |
| try { | |
| await window.ethereum.request({ | |
| method: "wallet_switchEthereumChain", | |
| params: [{ chainId: BASE_CHAIN_HEX }], | |
| }); | |
| state.chainId = BASE_CHAIN_ID; | |
| updateChips(); | |
| } catch (e) { | |
| console.warn("switch rejected", e); | |
| } | |
| } | |
| async function submit() { | |
| if (!state.address) return connect(); | |
| if (state.chainId !== BASE_CHAIN_ID) return switchToBase(); | |
| // Map sentiment to (int128 value, uint8 decimals). | |
| // value ∈ {-100, 0, 100} with decimals=2 → effective range -1.00..+1.00 | |
| 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(msg)}` : ""; | |
| // feedbackHash — sha256 would be nicer but it's an ERC-8004 open field | |
| // per spec (bytes32), and the contract doesn't verify it. Zero is fine. | |
| const feedbackHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; | |
| const data = encodeGiveFeedback({ | |
| agentId: AGENT_ID, | |
| value, | |
| decimals: 2, | |
| tag1, | |
| tag2, | |
| endpoint: "", | |
| feedbackURI, | |
| feedbackHash, | |
| }); | |
| state.txStatus = "pending"; | |
| state.txHash = null; | |
| renderTx(); | |
| updateSubmitButton(); | |
| try { | |
| const txHash = await window.ethereum.request({ | |
| method: "eth_sendTransaction", | |
| params: [{ | |
| from: state.address, | |
| to: REG_ADDR, | |
| data, | |
| value: "0x0", | |
| }], | |
| }); | |
| state.txHash = txHash; | |
| state.txStatus = "pending"; | |
| renderTx(); | |
| pollForReceipt(txHash); | |
| } catch (e) { | |
| state.txStatus = "failed"; | |
| state.txError = errorReason(e); | |
| renderTx(); | |
| updateSubmitButton(); | |
| } | |
| } | |
| function errorReason(e) { | |
| if (!e) return "unknown error"; | |
| const msg = String(e.message || e); | |
| if (/self-feedback not allowed/i.test(msg)) { | |
| return "you own agent 2205 — use a different wallet to leave feedback"; | |
| } | |
| if (/user rejected|user denied/i.test(msg)) { | |
| return "signature rejected"; | |
| } | |
| if (/insufficient funds/i.test(msg)) { | |
| return "insufficient eth on base — fund this wallet and retry"; | |
| } | |
| return msg.slice(0, 120); | |
| } | |
| async function pollForReceipt(hash) { | |
| // eth_getTransactionReceipt with light backoff | |
| let attempts = 0; | |
| while (attempts < 40) { | |
| await new Promise((r) => setTimeout(r, 1500)); | |
| attempts += 1; | |
| try { | |
| const receipt = await window.ethereum.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 {} | |
| } | |
| // fell out of the loop — still pending | |
| state.txStatus = "pending (check basescan)"; | |
| renderTx(); | |
| } | |
| function renderTx() { | |
| if (!state.txStatus) { | |
| els.txPanel.classList.add("hidden"); | |
| return; | |
| } | |
| els.txPanel.classList.remove("hidden"); | |
| let statusText = state.txStatus; | |
| if (state.txStatus === "failed" && state.txError) statusText = `failed · ${state.txError}`; | |
| els.txStatus.textContent = statusText; | |
| 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 = "—"; | |
| } | |
| } | |
| els.submit.addEventListener("click", submit); | |
| // ─── Init: try silent reconnect on mount ───────────────────────────── | |
| (async function init() { | |
| if (!window.ethereum) { | |
| updateChips(); | |
| return; | |
| } | |
| try { | |
| const accounts = await window.ethereum.request({ method: "eth_accounts" }); | |
| if (accounts && accounts.length > 0) { | |
| state.address = accounts[0]; | |
| } | |
| const chain = await window.ethereum.request({ method: "eth_chainId" }); | |
| state.chainId = parseInt(chain, 16); | |
| } catch {} | |
| updateChips(); | |
| window.ethereum.on?.("accountsChanged", (accs) => { | |
| state.address = accs[0] || null; | |
| updateChips(); | |
| }); | |
| window.ethereum.on?.("chainChanged", (chain) => { | |
| state.chainId = parseInt(chain, 16); | |
| updateChips(); | |
| }); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment