Skip to content

AI integration

aichannode edited this page Jun 1, 2026 · 3 revisions

AI Integration β€” Design Decision: "Living Pets"

Decision doc for adding AI to CryptoPets. Status: proposed. Author prompt: "AI Pets with Memory β€” chat with your pet, it remembers, its personality evolves, on-chain traits affect behavior."


TL;DR β€” my verdict

Build it. One feature, done deeply. But reframe it.

The idea as written ("chat + memory + evolving personality") is good, but if you build it literally you'll end up with a generic LLM chatbot wearing an NFT skin β€” nothing about it is Web3, and it could be cloned in a weekend by anyone with an OpenAI key. The part that is genuinely yours, that cannot be copied without your contracts, is this:

A pet's personality is deterministically seeded by its on-chain DNA and shaped by its real combat/breeding history. Two pets with the same DNA behave the same way; a pet that has lost 40 battles is bitter; a freshly bred legendary is arrogant. The chain is the source of the soul.

So the headline isn't "chat with your pet." It's "your pet is alive, and its life is on-chain." Chat and memory are how you experience that β€” not the product itself.

I'd name it Living Pets (or "Soulbound Personalities") rather than "AI chat."


On-chain vs off-chain β€” the definitive answer

This is the question that matters most, so here it is with no hand-waving.

The one rule

For any piece of data, ask: "Does someone need to verify this without trusting my server, AND is it small?"

  • Yes β†’ on-chain. (identity, ownership, achievements)
  • No β†’ off-chain. (conversations, summaries, anything big/private/chatty)

The headline you need to internalize

The MVP adds ZERO new on-chain data. Everything the AI reads from the chain already exists in your contracts today. The only new storage you build is the off-chain database. On-chain writes only appear in the optional Phase 3 (anchoring a memory hash β€” never the content).

So "on-chain traits affect behavior" does not mean "write personality to the chain." It means read the traits that are already on-chain and feed them to the AI. The chain is the input, not a new place to store AI stuff.

Per-field classification (every piece of data in this feature)

Data Where it lives New? Notes
dna On-chain already exists Seeds innate personality. Backend reads it.
rarity On-chain already exists Archetype/tone.
level On-chain already exists Maturity/vocabulary.
winCount / lossCount On-chain already exists Confidence vs bitterness.
name On-chain already exists Used in the persona prompt.
owner On-chain already exists The auth check: only owner may chat.
Personality trait vector (curiosity, aggression…) Nowhere β€” computed n/a Pure function of dna+rarity. Deterministic, so it is recomputed on demand, never stored. This is the trick: it's "on-chain-derived" without being on-chain-stored.
Lived modifiers (confidence from win rate…) Nowhere β€” computed n/a Pure function of level/winCount/lossCount. Recomputed each request.
Conversation messages (full transcript) Off-chain DB NEW Large, private, written every turn. Never on-chain.
Episodic memory summary Off-chain DB NEW The "it remembers" text. Regenerated periodically.
Trait snapshots over time (for "evolution" history) Off-chain DB NEW Optional; lets you show "how the pet changed."
Rate-limit counters (msgs/day per wallet) Off-chain DB NEW Operational data, never on-chain.
LLM system prompt Nowhere β€” assembled per request n/a Built from on-chain reads + DB summary at call time.
Memory hash (tamper-evidence) On-chain (Phase 3 only) NEW, optional A single 32-byte hash + tx, NOT the content. The only new on-chain write in the whole design, and only if you want the "provably remembers" badge.

Why each category is where it is

  • On-chain (the existing pet fields): these define identity and achievement. They must be trustless (you can't fake your win count), portable (the pet's nature travels with the NFT to any wallet/marketplace), and they're tiny. That's textbook on-chain data β€” and you already built it.
  • Computed (personality): because personality is a pure, deterministic function of on-chain data, storing it would be redundant and would risk drift. Anyone can recompute it and get the identical result β€” that is the proof. Recompute, don't store.
  • Off-chain DB (chat + memory): transcripts are large, grow every message, are private to the owner, and nobody needs to cryptographically verify them. Putting them on-chain would be expensive, slow, and a privacy disaster. A normal database is the correct tool.
  • On-chain hash (Phase 3, optional): the one place the two worlds meet. You hash the off-chain memory and store only the 32-byte fingerprint on-chain. Now you can prove memory wasn't tampered with, without putting private chat on a public ledger.

Picture it

        ON-CHAIN (already exists, READ-ONLY for this feature)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  dna Β· rarity Β· level Β· winCount Β· lossCount   β”‚
        β”‚  name Β· owner                                  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚ backend reads (ethers / anchor)
                        β–Ό
        COMPUTED IN MEMORY (stored nowhere, deterministic)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  innatePersonality(dna, rarity)                β”‚
        β”‚  livedModifiers(level, winCount, lossCount)    β”‚
        β”‚  β†’ assembled into the LLM system prompt        β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚ + recent turns & summary from DB
                        β–Ό
        OFF-CHAIN DB (the ONLY new storage in the MVP)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  conversations Β· episodic summary              β”‚
        β”‚  trait snapshots Β· rate-limit counters         β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚ (Phase 3, optional)
                        β–Ό
        ON-CHAIN (the ONLY new write β€” a hash, never content)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  memory_hash (32 bytes) + anchoring tx         β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why this is the right single bet

Criterion Verdict
Differentiated βœ… Tied to YOUR contracts (DNA/stats), not a generic LLM wrapper
Uses what you already have βœ… dna, rarity, level, winCount, lossCount already exist in shared/src/types/pet.ts
Demoable in 30 seconds βœ… "Watch the same prompt produce different answers from two different pets"
Reuses your auth βœ… Wallet β†’ JWT already done in backend/ + @shared/core
Reasonable scope ⚠️ Needs a DB and an LLM proxy β€” you have neither yet (backend is in-memory)
On-chain integrity ⚠️ The hard part. See the "honesty problem" below.

Compared to scattering 5 features (AI-generated art, price prediction, auto-battle bots, NFT recommendations, etc.), this is the only one that makes a reviewer go "wait, how does it know that?" β€” because the answer is your chain.


The honesty problem (and how we solve it)

The pitch says the pet "remembers" and its "personality evolves." If that memory lives only in Postgres, the claim is marketing, not engineering. A skeptical reviewer (the kind you want to impress) will ask: "so it's just a chatbot with a database?" You need a real answer.

Solution: a three-layer soul, each layer honest about where it lives.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Layer 1 β€” INNATE (on-chain, immutable)                          β”‚
β”‚   Derived purely from dna + rarity. Deterministic.              β”‚
β”‚   Same DNA β†’ same base personality, forever, for anyone.        β”‚
β”‚   "This is provably the pet's nature. I didn't make it up."     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 2 β€” LIVED (on-chain facts, off-chain interpretation)      β”‚
β”‚   level, winCount, lossCount, breeding lineage, age.            β”‚
β”‚   These ARE on-chain. The AI just narrates them.                β”‚
β”‚   "It's confident because it has 30 wins β€” verify on-chain."    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 3 β€” MEMORY (off-chain, optionally anchored)               β”‚
β”‚   Conversation history + episodic summaries in a DB.            β”‚
β”‚   To make "it remembers" provable: periodically hash the        β”‚
β”‚   memory state and anchor the hash on-chain (cheap) or sign it  β”‚
β”‚   with the backend key. "Tamper-evident memory."                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Layers 1 and 2 give you the unfakeable Web3 story. Layer 3 is where the LLM and DB do their work, and the anchoring keeps it from being "just a database."

For an MVP, Layers 1–2 plus a plain DB (Layer 3 without anchoring) is enough to ship and demo. Anchoring is the v2 flourish that wins the "is this really Web3?" argument.


How personality is derived (the core algorithm)

This is the part to get right, because it's the whole differentiator. It must be deterministic (same inputs β†’ same personality) and explainable (you can point at the trait and say where it came from).

Step 1 β€” DNA β†’ trait vector (deterministic, on-chain-grounded)

Your dna is a bigint (EVM uint256 / Solana u64). Slice it into fields, the same way your existing utils/pets/cosmetics.ts already turns DNA into appearance. Reuse that pattern:

// pseudo β€” lives in shared/src/utils/pets/personality.ts
interface Personality {
  curiosity: number;    // 0–100, from dna bits [0..7]
  aggression: number;   // from dna bits [8..15]
  loyalty: number;      // from dna bits [16..23]
  humor: number;        // from dna bits [24..31]
  archetype: string;    // bucketed from rarity ("trickster", "stoic", "regal"...)
}

function innatePersonality(pet: Pet): Personality { /* pure function of dna+rarity */ }

Because it's a pure function of on-chain data, anyone can recompute it and get the same answer. That's the "provable" part.

Step 2 β€” lived history β†’ modifiers (on-chain facts)

winRate = winCount / (winCount + lossCount)
  high winRate  β†’ +confidence, +arrogance
  high lossCount β†’ +caution or +bitterness
level           β†’ +maturity / vocabulary
rarity          β†’ tone (legendary = regal, common = scrappy)
freshly bred    β†’ naive / energetic

Step 3 β€” compose the system prompt

The backend assembles a system prompt from Layers 1–2 + a running summary of Layer 3, then calls the LLM. The pet "is" its prompt. Example skeleton:

You are {name}, a {archetype} CryptoPet (rarity {rarity}, level {level}).
Core nature: curiosity {curiosity}, aggression {aggression}, loyalty {loyalty}.
You have won {winCount} battles and lost {lossCount}; you are {confident|wary}.
You remember: {episodic_summary}.
Stay in character. Never break the fourth wall about being an AI.

Architecture (mapped onto what you already have)

You already have wallet→JWT auth and a chain-agnostic Pet model. The new pieces are a persistence layer, an LLM proxy, and one shared hook.

Frontend / Mobile
  └─ useChatWithPet()  ← NEW shared hook in @shared/core (chain-agnostic)
        β”‚  POST /api/pets/:petId/chat   (Bearer JWT, the auth you already have)
        β–Ό
Backend (Express)  ← extend the EXISTING service
  β”œβ”€ verify JWT + assert caller owns petId (read owner from chain via ethers/anchor)
  β”œβ”€ load Pet from chain  β†’ innatePersonality() + livedModifiers()
  β”œβ”€ load memory from DB  β†’ episodic summary
  β”œβ”€ build system prompt  β†’ call LLM (Anthropic Claude β€” you're already in that ecosystem)
  β”œβ”€ stream answer back
  └─ write turn to DB; periodically summarize + (v2) anchor hash on-chain
        β”‚
        β–Ό
  Persistence (NEW): Postgres / SQLite / Supabase
    - conversations(pet_id, owner, role, content, ts)
    - pet_memory(pet_id, summary, traits_snapshot, memory_hash, anchored_tx?)
        β”‚
        β–Ό
  LLM provider: Anthropic Claude (recommend claude-haiku-4-5 for cost, opus for "wow")

Why these choices

  • LLM = Claude. Cheapest credible quality at claude-haiku-4-5; use a bigger model only for the demo. Prompt-cache the static persona block (it's identical across a conversation) to cut cost dramatically.
  • Backend, not frontend, calls the LLM. Never ship an API key to the client, and you need server-side ownership checks + rate limiting anyway.
  • Ownership check is the security boundary. The pet's owner is on-chain (ownerOf / PetAccount.owner). Only the owner can chat as / persist memory for a pet. This reuses the address inside your JWT.
  • Persistence: your backend is currently in-memory. This feature forces a real DB. Supabase or a single Postgres on Render (where the backend already deploys) is the path of least resistance.

Memory design (so "it remembers" is real, not hand-wavy)

Don't dump full transcript into the prompt β€” it gets expensive and dumb fast. Use two-tier memory:

  1. Working memory: last N turns, verbatim.
  2. Episodic memory: a rolling LLM-written summary ("Owner named me after their dog. I lost to pet #412 and hold a grudge. I like jokes about fish."). Regenerated every K turns and stored in pet_memory.summary.

Optional v2: vector recall β€” embed each turn, retrieve the top-k relevant past memories per message. Only add this if simple summaries feel thin; it's a real cost/complexity jump.

Personality "evolves" = the trait snapshot is recomputed when on-chain stats change. After a battle (you already emit events / have watchers like vrf-fulfill-watcher.ts), recompute lived-modifiers and let the next summary reflect it: "After my 10th win I've grown cocky." The evolution is anchored in real chain events, not random drift β€” which is exactly what makes it defensible.


Phased plan

Phase 0 β€” spike (Β½ day)

Pure function innatePersonality(pet) in @shared/core + a script that prints personalities for 5 random DNAs. Prove the determinism + that different DNA feels different. No UI, no LLM yet. This de-risks the whole idea.

Phase 1 β€” MVP chat (the demoable thing)

  • Add Postgres to backend; conversations + pet_memory tables.
  • POST /api/pets/:petId/chat with JWT + ownership check + Claude call.
  • useChatWithPet() in @shared/core; a chat panel in frontend/src/components/pet/ (you already have pet-interactions/ β€” add a chat-panel sibling to battle/breed/level-up).
  • System prompt from Layers 1–2. Working memory only.
  • Ship this. It already demos "same prompt, two pets, different souls."

Phase 2 β€” real memory

  • Episodic summarization every K turns.
  • Recompute lived-modifiers on battle/breed/level-up so personality shifts.
  • Prompt caching for cost.

Phase 3 β€” provable memory (the Web3 flex)

  • Hash pet_memory state; anchor the hash on-chain (a cheap event/PDA write) or sign it with the backend key. Show a "memory verified βœ“ block #…" badge in the UI. This is the answer to "isn't this just a chatbot?"

Phase 4 (optional, high-wow) β€” pet-to-pet

Let two owned pets "talk" before a battle, or generate trash-talk based on both personalities. Cheap to add once Phase 1 exists, great for screenshots.


Cost & safety (don't skip β€” reviewers will ask)

  • Rate limit per wallet (e.g. N messages/day) β€” tied to the JWT address. Prevents your LLM bill from being a DoS vector.
  • Prompt-cache the persona block (static per conversation) β†’ big savings.
  • Content safety: keep pets in-character; add a light system-prompt guard and the provider's moderation. It's a kids-friendly pet game in tone.
  • Cost ceiling: Haiku + caching + short context β‰ˆ fractions of a cent per message. Budget is a non-issue at demo scale; the rate limit protects you at real scale.
  • Graceful degradation: if the LLM is down, fall back to a templated personality blurb derived from Layers 1–2 (which need no LLM at all).

What I would NOT do

  • ❌ Don't store personality/memory only in a DB and call it Web3.
  • ❌ Don't put chat memory on-chain directly (gas/rent insane, privacy bad) β€” anchor hashes, not content.
  • ❌ Don't let the client call the LLM (key leakage, no ownership check).
  • ❌ Don't build 5 AI features. Build this one until it's polished.
  • ❌ Don't make personality random β€” it must be deterministic from DNA, or the whole "provable soul" story collapses.

Open decisions for you

  1. DB choice: Supabase (fastest), Render Postgres (same host as backend), or SQLite (simplest, but weak for prod)? β€” I'd start Supabase or Render PG.
  2. LLM tier for the demo: Haiku everywhere, or Opus for the "wow" recording? β€” Haiku in prod, record the demo with Opus.
  3. Anchoring in scope for v1, or v2? β€” v2; ship chat first.
  4. Both chains day one, or EVM-first? The personality math is chain-agnostic; only the ownership read differs. β€” EVM-first, Solana right after, since the shared hook makes it cheap.
  5. Voice/TTS? Big "wow," but scope creep. β€” Skip for now.

Bottom line

Yes β€” make CryptoPets your flagship and make Living Pets its one strong feature. But win the argument that separates you from "ChatGPT with a JPEG": the pet's nature is computed from on-chain DNA, its growth is driven by on-chain battles, and its memory is tamper-evident. Lead with that. The chat is just the window you look through.

Start with Phase 0 (the deterministic personality function) β€” it's half a day and it tells you immediately whether the magic is there.


If you want, I can implement Phase 0 next: innatePersonality() in @shared/core plus a small script that prints souls for sample DNAs, so you can feel it before committing to the backend work.


Frontend / UX design

The feature must not feel bolted on. It slots into your existing dashboard interactions hub (pet-interactions/) as one more action β€” alongside Breeding Lab, Battle Arena, Level Up, Rename β€” and reuses your neon theme, Icon/Tones, PetContainer, and the usePet* shared-hook pattern.

Where it lives (matches your conventions)

Layer New piece Mirrors existing
Shared hook useChatWithPet() in @shared/core useBattlePets, useBreedPets
Panel frontend/src/components/pet/interactions/chat-panel/ battle-panel/, breed-panel/
Hub card a "Talk" card in pet-interactions/index.tsx Breeding Lab / Battle Arena cards
Route CHAT_PATH = '/chat' in constants/interactionRoutes.ts BATTLE_PATH, BREED_PATH
Icon/Tone a ChatIcon + e.g. Tones.Cyan or a new Tones.Emerald BattleIcon/EggIcon

1. The hub card (entry point)

A new card in the interactions grid, same shape as your Breeding Lab / Battle Arena cards:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ’¬  Talk to your Pet            β”‚   ← Icon + title, neon header
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  "Aria" is curious & loyal.     β”‚   ← one-line personality teaser
β”‚  Remembers your last 3 chats.   β”‚   ← memory hint (Phase 2+)
β”‚                                 β”‚
β”‚        [  Start chatting  ]     β”‚   ← neon-button, disabled if no pets
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. The chat panel (the main screen)

This is where the differentiator must be visible. The screen is split: the Soul Card on the left proves the personality comes from on-chain data, the conversation on the right is the experience. On mobile they stack (the soul card collapses to a header strip).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ’¬  Talk to Aria                                    [ Aria β–Ύ ]  ← pet  β”‚  pet picker
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  THE SOUL CARD         β”‚   CONVERSATION                              β”‚
β”‚  (left, ~280px)        β”‚   (right, flex)                             β”‚
β”‚                        β”‚                                             β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚   β”‚   🐾 sprite   β”‚     β”‚   β”‚ Aria: Oh, you're back. Did you     β”‚    β”‚
β”‚   β”‚   (from DNA) β”‚     β”‚   β”‚ finally beat pet #412? 😼          β”‚    β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚   Aria Β· Lv.7 Β· Rare   β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚                        β”‚              β”‚ You: not yet, training... β”‚   β”‚
β”‚   ── Innate (DNA) ──   β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚   Curiosity   β–“β–“β–“β–“β–“β–“β–“β–‘ β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚   Aggression  β–“β–“β–‘β–‘β–‘β–‘β–‘β–‘ β”‚   β”‚ Aria: typing…  ● ● ●               β”‚    β”‚
β”‚   Loyalty     β–“β–“β–“β–“β–“β–“β–‘β–‘ β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚   Humor       β–“β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β”‚                                             β”‚
β”‚                        β”‚                                             β”‚
β”‚   ── Lived (chain) ──  β”‚                                             β”‚
β”‚   βš” 12 wins / 3 losses β”‚                                             β”‚
β”‚   β†’ confident, teasing β”‚                                             β”‚
β”‚                        β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚   πŸ”— verify on-chain    β”‚   β”‚ Type a message…          [ Send β–Ά ]β”‚    β”‚
β”‚   βœ“ memory block #…(P3)β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key UX intentions:

  • Trait bars are derived live from dna (the Phase 0 function). Hovering a bar shows "from DNA bits 0–7". This is the show-don't-tell of "provable soul."
  • "Lived (chain)" block reads winCount/lossCount/level and shows the one-line interpretation that's also fed to the LLM β€” the user sees why the pet talks the way it does.
  • "πŸ”— verify on-chain" links to the explorer / a contract read, hammering the Web3 point. "βœ“ memory block #…" badge appears only in Phase 3.
  • Pet picker (top-right) switches which owned pet you're talking to β€” each has a different soul, which is the wow moment.
  • Streaming: tokens stream in; reuse your transaction-status spinner / typing affordance for async feedback.

3. States to handle (reuse StateCard)

Same state matrix as your other interactions β€” don't reinvent:

  • Wallet disconnected β†’ StateCard "Connect your wallet to talk to your pets."
  • No pets yet β†’ StateCard "Create a pet first" β†’ link to dashboard.
  • LLM/network error β†’ graceful fallback: render the templated personality blurb from Layers 1–2 (no LLM needed) + a "Aria is napping, try again" message.
  • Rate-limited β†’ "Aria needs a rest β€” N messages left today" (ties to the per-wallet limit).

4. Optional flourishes (cheap, high-impact for a demo)

  • "Compare two pets": two soul cards side by side, send the same prompt, show both replies β€” instantly proves personalities are real and distinct.
  • Pre-battle trash talk: a "Hype me up" button on the Battle Arena card that generates a one-liner in the pet's voice. Reuses the same backend endpoint.
  • Mood tint: color the chat accent by dominant trait (aggressive = magenta, curious = cyan) using your existing Tones.

Component sketch

// frontend/src/components/pet/interactions/chat-panel/index.tsx
const ChatPanel: React.FC<{ isStandaloneView: boolean }> = () => {
  const { pets } = usePetList();
  const [activeId, setActiveId] = useState(pets[0]?.id);
  const pet = pets.find(p => p.id === activeId);

  const soul = useMemo(() => innatePersonality(pet), [pet]);   // Phase 0, pure
  const { messages, send, isStreaming, error } = useChatWithPet(activeId);

  // left:  <SoulCard pet={pet} soul={soul} />   (trait bars + lived block + verify link)
  // right: <MessageList messages={messages} /> + <Composer onSend={send} disabled={isStreaming} />
};

Note innatePersonality(pet) needs no backend β€” the Soul Card renders instantly from on-chain data already loaded by usePetList. So you can build and ship the entire left half of this screen in Phase 0/1 before the LLM exists, which de-risks the UI work and gives you something visual immediately.

Clone this wiki locally