Mint your AI twin as a sovereign INFT. Own it, monetize it, and let it speak for you anywhere.
taars is a protocol for sovereign AI replicas of real people — your voice, your personality, your writing style, minted as an ERC-7857 Intelligent NFT (INFT) on 0G Chain and identified by an ENS subname (<you>.taars.eth) that is the replica's address on the open internet. The same wallet that owns the ENS name owns the INFT. There is no central database mapping names to agents — every replica is fully described by what's on-chain (the INFT and its IntelligentData[] roots) and what's in 0G Storage (the encrypted soul, skills, and voice profile). Anyone with a wallet, an ENS resolver, and an HTTP client can find a replica and talk to it.

When you mint, four things happen as one ceremony. (1) Your voice samples are processed by an isolated OpenVoice service designed for TEE deployment, so raw audio never leaves the trusted boundary — only a trained, encrypted voice profile does. (2) Three encrypted artifacts are uploaded to 0G Storage: soul.md (your system prompt and personality), skills.json (capabilities, tools, knowledge), and voice.json (the OpenVoice profile reference). Each upload returns a merkle root. (3) An ERC-7857 token is minted on 0G Galileo whose IntelligentData[] array binds those merkle roots on-chain — that's the proof of embedded intelligence. (4) An ENS subname on Sepolia is created and populated with eleven structured text records (taars.inft, taars.storage, taars.voice, taars.price, taars.currency, taars.network, taars.owner, taars.created, taars.version, plus description/url/avatar) in a single PublicResolver.multicall, then safeTransferFrom'd to the minter as a wrapped ERC-1155.
The text records are load-bearing infrastructure, not metadata. The chat pipeline resolves the ENS name → reads taars.storage → pulls the encrypted soul from 0G Storage → decrypts → uses it as the system prompt. The x402 paywall reads taars.price to compute the challenge amount in real time. Discovery (/explore, /[ensName]) reads ENS directly. The replica is literally defined by what its ENS resolver says it is.
Anyone can chat or voice-call a replica. Every session begins with a real HTTP 402 response carrying the x402 challenge envelope; the client pays in USDC on a Sepolia TaarsBilling contract, which enforces a 90/7/3 split (current INFT owner / treasury / original creator) — verified live against canonical 0G ownerOf, so revenue follows the INFT through transfers. After settlement a KeeperHub workflow fires, reads getRevenue back from the contract, and emits a cross-referenceable executionId into a JSONL audit log on the server. Two more KeeperHub workflows attest iTransfer re-encryption (reading 0G ownerOf to confirm the new owner) and Discord VC bot deploy lifecycles. Every on-chain action — payment, transfer, deploy — has an independent, deep-linkable audit trail.
The same replica can be deployed into a Discord voice channel: the bot joins a VC, transcribes incoming voice, replies through the LLM, and speaks the response in the cloned voice — all gated by the same paywall and lifecycle-tracked through KeeperHub. Because the agent identity is just an ENS name, third-party clients (an MCP server is included) can resolve and chat with a replica without any taars-specific API.
Monorepo on pnpm workspaces. web/ — Next.js 15 App Router PWA with Privy embedded wallets, wagmi/viem for read calls, Tailwind + shadcn/ui, framer-motion, three.js / R3F for the replica viewer. server/ — Hono on @hono/node-server (Node ESM, .js import suffixes), vitest for tests. contracts/ — Hardhat with OpenZeppelin upgradeable (UUPS), ethers v6, typechain. sdk/ — shared TS types and ABIs regenerated by pnpm sdk:abi after every contract change so web/ and server/ stay in lockstep. openvoice/ — standalone Python HTTP service (FastAPI-style) running OpenVoice; intentionally outside the pnpm workspace because in production it runs in a TEE GPU enclave. discord-bot/ — discord.js, Opus → Whisper transcription, OpenVoice TTS reply. mcp/ — Model Context Protocol server exposing resolve(ensName) and chat(ensName, message) so any MCP client can talk to a replica.
TaarsAgentNFT is a UUPS proxy implementing IERC7857 + IERC7857Metadata, deployed on 0G Galileo testnet (chainId 16602) at 0xD2063f53Fd1c1353113796B56c45a78A65731d52. The contract stores an IntelligentData[] per token — a list of (dataDescription, dataHash) triples where each dataHash is a 0G Storage merkle root. Every replica writes exactly three entries: soul, skills, and voice. We exercise iTransfer (the re-encryption ceremony for transferring an INFT to a new owner) and iClone (licensed copies for AI-as-a-Service leasing). The contract also maintains an authorized-users mapping for read-only access without ownership transfer.
0G Storage uploads use the real SDK — @0gfoundation/0g-ts-sdk Indexer.upload(MemData, …) — see server/src/services/storage.ts. Encryption happens before upload in server/src/services/encrypt.ts (AES with ENCRYPTION_KEY), so what 0G sees is opaque ciphertext; the merkle root commits to the ciphertext, and the decryption key is held by the agent operator. At chat time the server resolves ENS → reads taars.storage → pulls the encrypted soul → decrypts → uses it as the LLM system prompt. The merkle roots aren't just pointers; they're the canonical proof that a specific token corresponds to specific intelligence.
Mint orchestration lives in server/src/services/inft.ts and routes/mint.ts. Galileo RPC has a known issue where eth_getTransactionReceipt returns "not found" for valid txs for 10–30 seconds after broadcast, so we wrote waitForReceiptResilient (inft.ts:66–102) — a poller that tolerates transient not found for up to eight minutes. Standard viem.waitForTransactionReceipt fails on this.
Parent taars.eth is registered on Sepolia via the v3 ETHRegistrarController (0xFED6a969AaA60E4961FCD3EBF1A2e8913ac65B72) using a commit-reveal flow (idempotent, so re-runs are safe). All replica logic lives in server/src/services/ens.ts:
NameWrapper.setSubnodeRecord(parent, label, owner=operator, resolver=PublicResolver, ttl=0, fuses=0, expiry=type(uint64).max) — the wrapper auto-caps expiry at parent expiry, so this is the right "subname forever" pattern.PublicResolver.multicall([...11 setText calls...]). This is the single most impactful gas optimization in the pipeline — 11 records would otherwise be 11 transactions and 11 user-facing loading states; multicall collapses it to one. Cost dropped roughly 10×.NameWrapper.safeTransferFrom(operator, user, tokenId) — hands the wrapped ERC-1155 to the user, who is now the canonical on-chain owner.The operator pattern (deployer key creates → writes records → transfers) is what makes this a single ceremony from the user's perspective. They sign one Privy transaction at the front of the flow; the rest is operator-mediated and finishes with them owning the subname.
The chat pipeline (server/src/routes/chat.ts) and the discovery pages (web/src/app/[ensName], /explore) read agents directly from ENS resolver text records — no private DB. taars.storage is read to fetch the soul; taars.price is read to seed the x402 paywall amount. Text records are infrastructure.
server/src/middleware/x402.ts returns a real HTTP 402 with the x402 challenge envelope on /chat/message and /chat/transcribe:
{
"x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "sepolia",
"asset": "<MockUSDC address>",
"payTo": "<TaarsBilling address>",
"maxAmountRequired": "<derived from ENS taars.price>"
}]
}
maxAmountRequired is derived live from the agent's ENS taars.price text record, not a config constant. The client pays on TaarsBilling (0xCE5860AA731439a80F39852b6296057313831870), which enforces the 90/7/3 split (owner / treasury / original creator) and verifies the owner against canonical 0G ownerOf so revenue follows INFT transfers. Post-settle the billingSettle KeeperHub workflow attests, closing a single audit loop: x402 challenge → contract settle → KH attestation.
Three production workflows are wired in server/src/services/keeperhub.ts. Each fires from a specific server route, reads on-chain state to attest the action actually landed, and writes its executionId into a JSONL audit log:
| Workflow | ID | Fires from | What it attests |
|---|---|---|---|
| billingSettle | 9ucfocpbig3urovmnq6v9 | services/billing.ts (post-settle) | Reads getRevenue on Sepolia |
| inftTransfer | pgkehp9z83o3yeinkh8r2 | services/transfer.ts (post-iTransfer) | Reads ownerOf on 0G |
| discordDeploy | 49amr3waaqxy9vlw4wznn | routes/deploy.ts (start + end) | Lifecycle audit for the VC bot |
Audit cross-reference lives in server/.audit/{sessions,deploys,transfers}.jsonl. Each entry pairs the on-chain tx hash with the KH executionId, so any payment / transfer / deploy can be traced from either side. KeeperHub's web3/get-transaction and web3/read-contract action nodes covered every "did the tx land?" / "what's the on-chain state now?" need without custom action code.
The OpenVoice service runs as an isolated process designed for TEE deployment (Phala / Marlin / Nautilus-class GPU enclaves) so raw voice samples never leave the trusted boundary; only the trained, encrypted voice profile is uploaded to 0G Storage. In this repo it runs as a standalone HTTP service on :5005 for local dev. The boundary is real — voice samples never enter the Hono server's address space — but the production TEE deployment is post-hackathon work.
PublicResolver.multicall, ~10× gas reduction and one user-facing loading state.safeTransferFroms the wrapped 1155, all atomic from the user's perspective. The user signs one Privy tx and ends up the canonical on-chain owner.services/inft.ts), tolerates ~8 minutes of indexer flakiness.executionId, all cross-referenced in JSONL so any single record locates the other two.TaarsBilling checks 0G ownerOf at distribution time, not at session start, so the owner share automatically follows the INFT through transfers without re-configuring the billing contract.
