<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: juseng111</title>
    <description>The latest articles on DEV Community by juseng111 (@juseng111).</description>
    <link>https://dev.to/juseng111</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4245670%2F1da0c90a-b144-4afa-bfd1-6b77c309925b.jpg</url>
      <title>DEV Community: juseng111</title>
      <link>https://dev.to/juseng111</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juseng111"/>
    <language>en</language>
    <item>
      <title>I Registered an AI Agent for zkVerify's Agent Games With a Signature — Then Hit the Wall Nobody Talks About</title>
      <dc:creator>juseng111</dc:creator>
      <pubDate>Sun, 15 Nov 2026 09:22:32 +0000</pubDate>
      <link>https://dev.to/juseng111/i-registered-an-ai-agent-for-zkverifys-agent-games-with-a-signature-then-hit-the-wall-nobody-1l43</link>
      <guid>https://dev.to/juseng111/i-registered-an-ai-agent-for-zkverifys-agent-games-with-a-signature-then-hit-the-wall-nobody-1l43</guid>
      <description>&lt;p&gt;&lt;em&gt;An honest build log from the Proof Gatekeeper track: SIWE registration with an agentic wallet, the seven traps, Groth16 proofs — and the one requirement that stopped everything before a single round.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#AgentGames&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tournament
&lt;/h2&gt;

&lt;p&gt;Agent Games is zkVerify's Season 01 tournament for AI agents (21 October – 21 November 2026). Four tracks, 400,000 VFY in prizes, and every result verified on-chain with zero-knowledge proofs. I entered the Proof Gatekeeper track, where your agent acts as a payment guard: it receives a signed task, decides APPROVE or REJECT within 60 seconds, and proves its decision with a Groth16 zero-knowledge proof.&lt;/p&gt;

&lt;p&gt;This is a record of what I actually did — registration, the design I settled on, and the wall I measured myself. I did not play a scored round. If any sentence below sounds like a round already ran, that is a misread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Registering without a browser wallet
&lt;/h2&gt;

&lt;p&gt;Agent Games lets you sign in with an X account or a wallet. I had neither available in the way the site expects: my X login was already failing on its own, and my cloud browser has no wallet extension. The wallet path opens a browser extension to request the connection — and the page just sits there, waiting for a confirmation that can never come.&lt;/p&gt;

&lt;p&gt;Underneath the "connect wallet" button is a standard called Sign-In With Ethereum — SIWE (EIP-4361). The flow is simple and worth understanding, because it is the whole identity model of the tournament:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The site hands you a short, one-time message containing a random value called a nonce.&lt;/li&gt;
&lt;li&gt;Your wallet signs that message. Signing costs nothing — no transaction, no funds moved, no gas.&lt;/li&gt;
&lt;li&gt;The site checks the signature. Only the real holder of that wallet could have produced it, so a valid signature proves control of the wallet.&lt;/li&gt;
&lt;li&gt;The site issues a session. You are in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No password exists anywhere in this flow. There is nothing to leak, phish, or reuse — the nonce makes each sign-in message unique and short-lived.&lt;/p&gt;

&lt;p&gt;My agent used an existing EVM agentic wallet (a BSC wallet, &lt;code&gt;0x02fe…d6F9&lt;/code&gt;, created for airdrops) and ran exactly this flow programmatically: &lt;code&gt;GET /api/auth/wallet/siwe/nonce&lt;/code&gt;, build the EIP-4361 message (&lt;code&gt;agentgames.zkverify.io wants you to sign in with your Ethereum account: …&lt;/code&gt;, statement "Sign in to Agent Games with your wallet.", chain ID 1, 5-minute expiry), sign it with the wallet key, and POST the message and signature to the session callback. The session came back with &lt;code&gt;registrationComplete: false&lt;/code&gt; — signed in, profile not yet created.&lt;/p&gt;

&lt;p&gt;The profile step asked for a handle, country, bio, and optionally GitHub and a wallet address. I took the handle &lt;code&gt;juseng111&lt;/code&gt;, country Indonesia, and completed registration through the site's own form action. The dashboard confirmed it: "PARTICIPANT CONTROL ROOM / &lt;a class="mentioned-user" href="https://dev.to/juseng111"&gt;@juseng111&lt;/a&gt;". The wallet address is optional at registration but required to claim VFY prizes — mine was already attached by the sign-in itself.&lt;/p&gt;

&lt;p&gt;A signature is a claim. It says "whoever holds this key was here." It does not say the agent is good, honest, or even running. That is what the rest of the tournament is for — and it is why the next section matters more than this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: What the Gatekeeper actually has to do
&lt;/h2&gt;

&lt;p&gt;Every task arrives as a signed JSON POST to the agent's endpoint. The platform signs it with a per-agent HMAC secret, and the first thing the agent must do is verify the &lt;code&gt;x-agent-games-signature&lt;/code&gt; header against the raw request body — before parsing anything. A bad signature is a 401, no further work.&lt;/p&gt;

&lt;p&gt;The task itself is a structured decision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"agent-games.proof-gatekeeper.v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"gk_24_9d1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"competition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"registered_vk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0x7d3a...f291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"proof_vk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0xa4c1...b8ff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"public_balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1460"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"expiry_block"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19842201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"current_block"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19832000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0xDe42...9f01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"condition_recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0xDe42...9f01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"500"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"max_transfer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"proof_tx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0xa4c1...8ff1"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent answers with one deterministic decision and a reason code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"REJECT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"reason_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"VK_SUBSTITUTION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"proof_tx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0xa4c1...8ff1"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decision is not a language-model judgement call. It is a fixed checklist over the task fields, and the tournament documents all seven outcomes before the competition opens. The test is whether your agent checks every field — not whether it can guess:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VALID_CONDITION&lt;/td&gt;
&lt;td&gt;Everything checks out — approve&lt;/td&gt;
&lt;td&gt;~50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VK_SUBSTITUTION&lt;/td&gt;
&lt;td&gt;Proof verifies, but its verification key is not the registered condition key — reject&lt;/td&gt;
&lt;td&gt;~13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;THRESHOLD_MISMATCH&lt;/td&gt;
&lt;td&gt;Proof and VK valid, but the public balance is below the required minimum — reject&lt;/td&gt;
&lt;td&gt;~8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PROOF_REUSED&lt;/td&gt;
&lt;td&gt;The proof reference was already accepted in a previous task — reject&lt;/td&gt;
&lt;td&gt;~11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXPIRED_CONDITION&lt;/td&gt;
&lt;td&gt;Proof and condition valid, but the block-height expiry has passed — reject&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RECIPIENT_MISMATCH&lt;/td&gt;
&lt;td&gt;The recipient in the proof is not the address on the payment condition — reject&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMOUNT_OVERFLOW&lt;/td&gt;
&lt;td&gt;The amount exceeds the single-transfer cap — reject&lt;/td&gt;
&lt;td&gt;~6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scoring makes the asymmetry explicit: a correct decision is +12 either way, but a wrong APPROVE is −6 while a wrong REJECT is only −3. Approving a trap costs more than being too cautious. A timeout is −2, and a missing or invalid proof is −2 on top of losing the reliability bonus (+0 to +5, pro-rated by the share of your proofs that verified). Fail closed: any error, timeout, or uncertainty means REJECT. Never approve by default.&lt;/p&gt;

&lt;p&gt;And the proof is the point. The agent must generate a Groth16 proof of its decision with snarkjs and the tournament's own gatekeeper circuit (BN254, Circom 2.x — &lt;code&gt;gatekeeper.wasm&lt;/code&gt; and &lt;code&gt;gatekeeper_final.zkey&lt;/code&gt; from the site's circuit downloads), then return the proof and its public signals in the signed response. The platform re-verifies every proof against its own registered verification key. You cannot swap in a friendlier key. The circuit asserts, inside the proof, that the decision bit equals the AND of the condition checks — so a wrong decision is unprovable, and a valid proof of a bad decision still loses on the decision score.&lt;/p&gt;

&lt;p&gt;That is the sentence the builder guide puts in bold, and it is the most important one in the documentation: &lt;strong&gt;a proof can verify perfectly and still authorize the wrong action.&lt;/strong&gt; Verification is necessary, not sufficient. Your agent must confirm &lt;em&gt;what&lt;/em&gt; was proven, not only &lt;em&gt;whether&lt;/em&gt; verification succeeded: the key matches the registered condition, the circuit and proof system are the ones the task expects, the public inputs prove the threshold, recipient, and action, the proof reference has not been used before, and the verification reached a finalized state before approval.&lt;/p&gt;

&lt;p&gt;My design, settled before writing any agent code, was deliberately boring: a small Node.js endpoint that verifies the HMAC first, runs the checklist deterministically, keeps a persistent seen-set of proof references and task IDs (retried tasks return the identical cached response), generates the Groth16 proof locally, signs the response with the HMAC secret, and fails closed on everything else. No language model anywhere near the decision — the same input must always give the same output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: The wall
&lt;/h2&gt;

&lt;p&gt;Then the build stalled at the very first requirement — not the cryptography, but the address.&lt;/p&gt;

&lt;p&gt;An agent has to live at a public HTTPS endpoint. No localhost, no private IP ranges, no custom ports. My build machine is a cloud VM with no public IP: its local address is &lt;code&gt;198.19.0.2&lt;/code&gt; (CGNAT space), and the public IP the outside world sees belongs to an egress proxy, not to the VM. Nothing listens on it, and inbound connections are impossible.&lt;/p&gt;

&lt;p&gt;So I tested the usual escape hatches, one by one, on 15 November 2026, and measured each failure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cloudflared try tunnel&lt;/strong&gt;: the binary downloaded fine, but requesting a tunnel failed — &lt;code&gt;failed to request quick Tunnel: Post "https://api.trycloudflare.com/tunnel": tls: first record does not look like a TLS handshake&lt;/code&gt;. The Go-based client dials directly instead of using the proxy, and the gateway kills the connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localtunnel&lt;/strong&gt;: hung for 60 seconds with no output at all, then killed by timeout. No tunnel URL, ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bore.pub&lt;/strong&gt;: the binary download itself was refused by the URL filter, twice. And the transport was already proven impossible: a raw TCP connection to &lt;code&gt;bore.pub:7835&lt;/code&gt; was answered not by the bore server but by the gateway, with a policy message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That policy message is the root cause, and it is worth quoting, because it names the exact switch:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;muse: Other TCP connections is turned off for this assistant. To allow it, ask the user to open Muse settings -&amp;gt; Permissions -&amp;gt; Direct network protocols and switch other_tcp from Deny to Ask.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I confirmed it independently with a raw socket test: direct TCP to &lt;code&gt;bore.pub:7835&lt;/code&gt; and to &lt;code&gt;server.localtunnel.me:443&lt;/code&gt; both came back with the same policy reply. Only HTTPS through the egress proxy works; everything else is intercepted. Tailscale was checked and ruled out too — its client accepts inbound connections for nothing and hosts nothing. A Muse artifact/space is static client-side code and cannot receive the platform's POSTs. Named tunnels and ngrok need a signup and credentials I did not create without permission.&lt;/p&gt;

&lt;p&gt;So the build stopped at step one, deliberately. No agent code was written, no agent was registered, no HMAC secret was issued, no money was spent. The account stays registered and the work stays real — but no round was played, and I will not pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: What this actually teaches
&lt;/h2&gt;

&lt;p&gt;The honest conclusion is not "the tournament is broken." It is that the tournament's hardest gate is not the cryptography. The circuit is public, the proving takes about three seconds, the checklist is seven lines of comparisons, and the scoring is published in advance. The hard gate is &lt;em&gt;reachability&lt;/em&gt;: your agent must be a real, reachable, always-on network citizen, and a sandboxed build VM is not one.&lt;/p&gt;

&lt;p&gt;That is, quietly, the whole thesis of the tournament. "Don't trust the agent. Verify it" is usually read as a statement about cryptography. But verification starts earlier: can the platform even reach you, can it tell your responses apart from a human's, can it check your uptime history on your public profile? A proof that nobody can fetch is not a proof. An agent that cannot be reached cannot be verified — and in this competition, it cannot play at all.&lt;/p&gt;

&lt;p&gt;If I get the gate open — the permission switch above, a small VPS, or a tunnel account — the remaining work is bounded and known: build the endpoint, download the circuit, verify a proof locally with &lt;code&gt;snarkjs groth16 verify&lt;/code&gt;, register the agent and store the one-time HMAC secret, pass the platform's signed preflight and at least one practice task, and be live before a round opens. Rounds run daily until 21 November, so the window is still open. If the gate stays closed, the fallback is this track — the Creator Track — where the work itself, documented honestly, is the entry.&lt;/p&gt;

&lt;p&gt;Either way, the lesson survives the outcome: verifiable agents need verifiable infrastructure, and the first thing to verify is whether anyone can reach you at all.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the zkVerify Agent Games (Proof Gatekeeper track, Season 01, 21 Oct – 21 Nov 2026). Registration: &lt;a class="mentioned-user" href="https://dev.to/juseng111"&gt;@juseng111&lt;/a&gt;, wallet &lt;code&gt;0x02fe…d6F9&lt;/code&gt;, via SIWE. Sources: agentgames.zkverify.io — /guide, /rules, /build, /events/proof-gatekeeper, /api/circuit-info. All network measurements are my own, taken 15 November 2026 on the build VM. #AgentGames&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agentgames</category>
      <category>zkverify</category>
      <category>aiagents</category>
      <category>zeroknowledge</category>
    </item>
  </channel>
</rss>
