zerogochi

an 8-bit pet that lives in your Telegram and knows you've been bad to it

zerogochi

Created At

Open Agents

Project Description

Zerogochi is an 8-bit Tamagotchi that lives inside Telegram. Each pet is an iNFT on 0G mainnet with an encrypted personality, and the pet reads its own on-chain history to remember exactly how you've treated it.

The hook is that the pet is genuinely opinionated about you. When you tap "JUDGE" in the chat, the pet pulls every Born, Fed, Played, Spoke event from the chain, builds a chronicle of your behavior, and asks DeepSeek (running in a TEE on 0G Compute) to render an honest verdict. It cites specific moments — "you minted me two hours ago and only fed me twice" — because those moments really are on chain. The reply is generated inside a sealed environment, signed by the TEE-attested provider, and delivered through 0G's broker.

Mechanically: you open the bot in Telegram, tap "Open Zerogochi", and a real iNFT mints to a wallet that lives in your Telegram CloudStorage. The personality is generated client-side from an 8-axis vector (anxious, dramatic, affectionate, talkative, cynical, curious, vain, loyal), AES-GCM encrypted with a key derived from your wallet, and uploaded to 0G Storage. Its hash is committed on-chain through an ERC-7857 verifier. You feed it (button A), play with it (B), or chat with it (C). Every action is a real on-chain transaction; gas is sponsored by a relayer wallet via EIP-2771 meta-transactions, so the user never sees a wallet popup.

Beyond JUDGE, there's a DREAM button (the pet narrates a surreal in-character dream that distorts its real history), autonomous thoughts (every 60-120 seconds the pet says something to itself — real DeepSeek call, real on-chain inference settlement), inheritance (the pet knows when it's been transferred to a new owner and behaves accordingly), and life stages (child to adult to elder, with sprite scaling). Death is permanent — if the pet stays at zero hunger for over 24 hours the contract marks it dead, and there's no revive.

The whole thing is shipped on 0G mainnet (chain id 16661). Four contracts, all verified on chainscan. The repo is at github.com/LevanIlashvili/zerogochi and the live bot is t.me/zerogochiBot.

How it's Made

Zerogochi is three layers stitched together: a Next.js Telegram Mini App, a NestJS backend, and four Solidity contracts deployed on 0G mainnet.

The mini-app is built with Next.js 14 (App Router) and ethers v6. The aesthetic is a strict Game Boy DMG palette (only four greens), Press Start 2P + Silkscreen typefaces, and a sprite system where each pet's body is composed from 24x24 pixel layers (3 bodies x 4 eyes x 3 ear styles x 4 patterns = 144 visual identities, all derived from a single uint8 visualSeed stored on chain). The wallet is generated entirely in the browser using ethers' HDNodeWallet. Keys live in Telegram CloudStorage with a localStorage mirror as fallback (Telegram's CloudStorage callbacks are unreliable, so we wrap them in a 2.5s timeout that gracefully falls back). The personality is encrypted client-side with AES-GCM using a key derived from a personal_sign over a fixed message — deterministic per wallet, so the user can re-derive the key on later visits without storing any secret.

The backend is NestJS with grammy for the Telegram bot. It exposes /api/relay, /api/talk, /api/storage/upload, /api/pet/mine, and /api/register. Every authenticated route is gated by a TgInitDataGuard that does the official Telegram HMAC-SHA256 verification of initData using the bot token. On top of that, a sliding-window rate limiter keys requests by Telegram user id (not IP, not wallet — Telegram is the strongest identity we trust at that layer). The /api/relay endpoint is hardened with a destination-and-selector whitelist: even though the user signs every forward request, the relayer only pays gas when the call is to the Zerogochi contract and the function selector matches mintPet, feed, play, or logSpoke. The nag cron runs every 30 minutes via @nestjs/schedule, scans registered users, queries each pet's stats from chain, and sends an in-character "I miss you" line through the bot if a stat is below 30 — that nag line is itself generated by a DeepSeek call.

The contracts are written in Solidity 0.8.24 with Foundry. The core is Zerogochi.sol, which inherits from a vendored AgentNFT (the still-draft ERC-7857 reference from 0glabs/0g-agent-nft, eip-7857-draft branch). Pet state lives in its own ERC-7201 namespaced storage slot ("zerogochi.storage.Pets") to avoid colliding with AgentNFT's storage on upgrade. We added ERC-2771 meta-transaction support directly into the vendored AgentNFT (replacing every msg.sender with _msgSender(), except in initialize where admin grants must come from the proxy deployer), wired through OpenZeppelin's audited ERC2771Forwarder. There's also a MockVerifier that accepts hash-only proofs (the trivial path the upstream Verifier already supports) — the real TEE-attestation oracle isn't deployed on 0G mainnet yet. The proxy is a standard ERC1967Proxy initialized in the same deploy script. 14 Foundry tests cover mint, feed/play, decay, starvation+death, the full meta-tx round-trip, and personality hash round-trip through dataHashesOf. All four contracts are verified on chainscan.0g.ai via Foundry's verify-contract command pointed at https://chainscan.0g.ai/open/api with --verifier custom (the docs say "no constructor args supported" via the manual form, but the API path happily accepts them).

Three things were genuinely hacky and worth flagging: First, the @0glabs/0g-* npm packages are deprecated and ship testnet contract addresses + outdated ABIs. We had to migrate to @0gfoundation/0g-compute-ts-sdk and @0gfoundation/0g-storage-ts-sdk, which auto-detect mainnet from the signer's chain id and use the new Submission struct shape (selector 0xbc8c11f8 instead of the old 0xef3e12dc). The mainnet inference + storage contracts were not findable in any docs page when we started — we had to read the new SDK's constants.ts to find the real addresses (Ledger 0x2dE54c..., Inference 0x47340d..., Flow 0x62D4144d...). Second, the older 0G iNFT spec is still draft and the AgentNFT base ships with _disableInitializers() in the constructor, blocking direct instantiation — we deploy through ERC1967Proxy and call initialize() through the proxy. Third, decorator metadata for NestJS DI under tsx + bundler module resolution isn't always emitted, so we explicitly @Inject() every constructor parameter rather than relying on implicit type-based injection.

For sealed inference, every pet reply travels: prompt + on-chain history + decrypted personality (sent client-side just-in-time) -> 0G Compute broker -> DeepSeek v3 provider 0x1B3AAef3...85EB0 inside a TEE. The broker handles billing on-chain (each call is a settlement tx from a pre-funded ledger account; we addLedger(3) once at deploy time). DeepSeek's reply comes back signed by the TEE-attested provider key. The broker exposes processResponse() to verify the signature — we surface verified=true/false but currently the verification step throws due to a known broker billing edge, so we hide the badge while keeping the underlying capability.

Storage works similarly. The encrypted personality blob (around 600 bytes, AES-GCM ciphertext base64-encoded) flows from the browser to /api/storage/upload, which uses @0gfoundation/0g-storage-ts-sdk's Indexer.upload() to push the bytes to 0G Storage. The SDK builds a Merkle tree, queries pricePerSector, attaches the right msg.value, submits to the Flow contract, and waits for storage nodes to sync. We get back a content-addressed root hash that the frontend then includes in the iNFT mint as the personality dataHash.

Deployment is on Easypanel via Docker. The frontend is a multi-stage build (deps, then Next.js standalone build with NEXT_PUBLIC_* baked in as build args, then a slim runtime). The backend is a Node 20 alpine image running tsx directly with a persistent volume mount at /app/data for the users.json registry. CORS is allowlisted to the production frontend origin.

The whole thing burns real OG on every interaction. Mints, feeds, plays, talks, and even autonomous thoughts each cost gas + storage + inference. The relayer wallet is funded once and pays for the user. From the user's perspective, they tap a button in Telegram and a thing happens. From 0G's perspective, every action is an honest on-chain event.

background image mobile

Join the mailing list

Get the latest news and updates