Skip to content

Instantly share code, notes, and snippets.

@roninjin10
Created February 2, 2026 23:10
Show Gist options
  • Select an option

  • Save roninjin10/fbdf3046ba4c78402856bd28f76f958a to your computer and use it in GitHub Desktop.

Select an option

Save roninjin10/fbdf3046ba4c78402856bd28f76f958a to your computer and use it in GitHub Desktop.
Misty Engineering doc

Misty Browser — Engineering Design Document

Status: Draft (implementation-ready) Last updated: 2026-02-02 Audience: Core engineering (Bun backend, Astro/Solid main view, provider bridge, wallet/security) Scope: Desktop macOS (Electrobun + CEF), Base-first

Related: PRD, Design, Infra, Testing


1. Architecture overview

Processes

  • Main View (Astro + Solid): trusted UI (chrome, agent, wallet panel, explorer pages)
  • CEF Webviews: untrusted dApp content
  • Bun Backend: trusted services (wallet keys, signing, network, indexing clients, simulation)

Trust boundary

  • Webviews cannot access wallet keys, backend, or browser chrome directly
  • Provider requests flow: webview → main view bridge → backend → main view confirm → backend signs → RPC broadcast

RPC proxy

All JSON-RPC requests go through our Cloudflare Worker proxy. This gives us:

  • Caching control
  • Rate limiting
  • Request analytics
  • Ability to failover between RPC providers
  • No direct exposure of RPC provider API keys to client

Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                             MAIN VIEW                               │
│  Astro/Solid UI                                                     │
│  - Home (Agent UI)                                                  │
│  - Explorer routes                                                  │
│  - Wallet overlays (confirmations)                                  │
│  - ProviderBridge (webview → trusted UI)                            │
│                                                                     │
│         Electrobun RPC (typed requests/responses)                   │
└───────────────────────────────────────────────────┬─────────────────┘
                                                    │
                                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│                           BUN BACKEND (Effect)                      │
│  Existing: WalletManager, PermissionsService, NetworkService        │
│  New:                                                               │
│   - AgentService (LLM provider + tools)                             │
│   - SimulationService (tevm fork execution)                         │
│   - ExplorerService (RPC + index + enrichment)                      │
│   - IndexSupplyService (remote Base indexing fallback)              │
│   - IndexRouterService (Local/Remote routing)                       │
│   - PersistenceService (SQLite + encryption optional)               │
│   - ExtensionBridgeService (WebSocket server for companion ext)     │
└─────────────────────────────────────────────────────────────────────┘

2. Coinbase Smart Wallet (flagship feature)

Misty's wallet is built on Coinbase Smart Wallet — an ERC-4337 smart contract wallet that enables gasless transactions, passkey authentication, and account abstraction while maintaining self-custody.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Misty Wallet                              │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                    SmartWalletService                       ││
│  │  - Manages Smart Wallet ↔ owner mapping                     ││
│  │  - Builds UserOperations (ERC-4337)                         ││
│  │  - Wraps signatures for Smart Wallet                        ││
│  └──────────────────────────┬──────────────────────────────────┘│
│                             │                                    │
│         ┌───────────────────┼───────────────────┐               │
│         ▼                   ▼                   ▼               │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │ PasskeySigner│    │  HotSigner  │    │ HardwareSigner│        │
│  │  (P-256)    │    │ (secp256k1) │    │  (future)   │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
└─────────────────────────────────────────────────────────────────┘
                              │
                    Sign userOpHash
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Coinbase Bundler + Paymaster                  │
│                    (gasless transaction submission)              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Base Network                                  │
│  ┌─────────────────┐    ┌─────────────────────────────────────┐│
│  │   EntryPoint    │───▶│    Coinbase Smart Wallet Contract   ││
│  │   (ERC-4337)    │    │    Owner(s): passkey / EOA / both   ││
│  └─────────────────┘    └─────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘

SmartWalletService

class SmartWalletService extends Context.Tag("SmartWalletService")<
  SmartWalletService,
  {
    // Create new Smart Wallet with passkey owner
    createWithPasskey: () => Effect.Effect<{ address: Address; credentialId: string }, SmartWalletError>

    // Create Smart Wallet with existing EOA as owner
    createWithEOA: (eoaAddress: Address) => Effect.Effect<Address, SmartWalletError>

    // Add owner (passkey or EOA) to existing Smart Wallet
    addOwner: (smartWallet: Address, owner: PasskeyOwner | EOAOwner) => Effect.Effect<void, SmartWalletError>

    // Get Smart Wallet address (deterministic, works before deployment)
    getAddress: (owners: Owner[], nonce: bigint) => Effect.Effect<Address>

    // Build UserOperation from transaction request
    buildUserOp: (smartWallet: Address, tx: TransactionRequest) => Effect.Effect<UserOperation>

    // Sign UserOperation with appropriate owner
    signUserOp: (smartWallet: Address, userOp: UserOperation) => Effect.Effect<SignedUserOperation>

    // Submit via bundler (with optional paymaster for gasless)
    sendUserOp: (userOp: SignedUserOperation, gasless?: boolean) => Effect.Effect<Hash>
  }
>() {}

PasskeySigner (WebAuthn)

class PasskeySigner extends Context.Tag("PasskeySigner")<
  PasskeySigner,
  {
    // Create new passkey credential
    create: (rpId: string, userName: string) => Effect.Effect<PasskeyCredential, PasskeyError>

    // Sign with existing passkey
    sign: (credentialId: string, challenge: Uint8Array) => Effect.Effect<PasskeySignature, PasskeyError>

    // List stored credentials
    list: () => Effect.Effect<PasskeyCredential[]>
  }
>() {}

// WebAuthn wrapped in Effect
const createPasskeyCredential = (challenge: Uint8Array, userId: Uint8Array, userName: string) =>
  Effect.tryPromise({
    try: () => navigator.credentials.create({
      publicKey: {
        challenge,
        rp: { id: 'misty.app', name: 'Misty' },
        user: { id: userId, name: userName, displayName: userName },
        pubKeyCredParams: [{ alg: -7, type: 'public-key' }], // ES256 (P-256)
        authenticatorSelection: { userVerification: 'required' }
      }
    }),
    catch: (error) => new PasskeyError({ cause: error })
  })

Signature Wrapping

Smart Wallet expects signatures wrapped with owner index:

const wrapSignature = (ownerIndex: bigint, signatureData: Hex): Hex =>
  encodeAbiParameters(
    [{ type: 'uint256' }, { type: 'bytes' }],
    [ownerIndex, signatureData]
  )

// For EOA owner (secp256k1): 65 bytes packed (r, s, v)
// For passkey owner (P-256): WebAuthn assertion response

Factory Addresses (same on all chains)

Version Factory Address
v1.1 0xBA5ED110eFDBa3D005bfC882d75358ACBbB85842
v1.0 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a

Future: Recovery options

  • iCloud backup — encrypted backup enabling wallet recovery
  • Additional recovery methods TBD

3. ENS + Basenames resolution

NameService

class NameService extends Context.Tag("NameService")<
  NameService,
  {
    // Forward resolution: name → address
    resolve: (name: string) => Effect.Effect<Address | null, NameResolutionError>

    // Reverse resolution: address → primary name
    reverse: (address: Address) => Effect.Effect<string | null, NameResolutionError>

    // Get all records for a name
    getRecords: (name: string) => Effect.Effect<NameRecords, NameResolutionError>

    // Batch resolve (for UI lists)
    resolveMany: (names: string[]) => Effect.Effect<Map<string, Address | null>>
    reverseMany: (addresses: Address[]) => Effect.Effect<Map<Address, string | null>>
  }
>() {}

interface NameRecords {
  address: Address | null           // ETH address (coinType 60)
  addressBase: Address | null       // Base address (coinType 2147492101)
  url: string | null                // Website URL
  avatar: string | null             // Avatar URL/URI
  description: string | null        // Profile description
  twitter: string | null            // Twitter handle
  github: string | null             // GitHub username
  email: string | null              // Email address
}

Resolution strategy

const resolve = (name: string) => Effect.gen(function* () {
  // Detect name type
  if (name.endsWith('.base.eth')) {
    return yield* resolveBasename(name)
  } else if (name.endsWith('.eth')) {
    return yield* resolveENS(name)
  }
  return null
})

// ENS: query Ethereum mainnet
const resolveENS = (name: string) => Effect.gen(function* () {
  const provider = yield* EthereumMainnetProvider
  // 1. Get resolver from ENS Registry
  // 2. Query resolver for addr(node)
  // 3. Cache result
})

// Basenames: CCIP-Read (ENSIP-10)
const resolveBasename = (name: string) => Effect.gen(function* () {
  // 1. Query L1 resolver → reverts with OffchainLookup
  // 2. Extract gateway URL from revert data
  // 3. Call CCIP gateway (Base infrastructure)
  // 4. Verify response and cache
})

Multi-chain address support

ENS supports chain-specific addresses via ENSIP-9 coin types:

Chain Coin Type How to query
Ethereum 60 addr(node)
Base 2147492101 addr(node, 2147492101)

When displaying addresses in Misty (Base-first):

  1. Check for Base-specific address first (coinType 2147492101)
  2. Fall back to ETH address (coinType 60)

Caching

interface NameCache {
  // Forward: name → resolution
  forward: Map<string, { address: Address | null; expiresAt: number }>

  // Reverse: address → name
  reverse: Map<Address, { name: string | null; expiresAt: number }>
}

// TTL: 5 minutes for positive, 1 minute for negative
const CACHE_TTL_POSITIVE = 5 * 60 * 1000
const CACHE_TTL_NEGATIVE = 1 * 60 * 1000

4. Other Coinbase integrations

Misty prioritizes Coinbase developer products for Base-native functionality.

Coinbase Onramp / Offramp

Zero-fee fiat ↔ USDC conversion on Base.

import { initOnRamp } from '@coinbase/cbpay-js'

// Wrap callback-based API in Effect
const openOnRamp = (userAddress: Address) =>
  Effect.async<void, OnRampError>((resume) => {
    initOnRamp({
      appId: 'MISTY_APP_ID',
      widgetParameters: {
        destinationWallets: [{
          address: userAddress,
          blockchains: ['base']
        }]
      },
      onSuccess: () => resume(Effect.succeed(undefined)),
      onExit: () => resume(Effect.fail(new OnRampError({ reason: 'user_exit' })))
    })
  })

Coinbase Paymaster (gasless transactions)

Sponsor gas fees for new users via ERC-4337 Paymaster.

// Bundler & Paymaster API wrapped in Effect
const sendUserOperation = (userOp: UserOperation, entryPoint: Address) =>
  Effect.gen(function* () {
    const config = yield* CoinbaseConfig

    const response = yield* Effect.tryPromise({
      try: () => fetch('https://api.developer.coinbase.com/rpc/v1/base', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${config.cdpApiKey}` },
        body: JSON.stringify({
          jsonrpc: '2.0',
          method: 'eth_sendUserOperation',
          params: [userOp, entryPoint],
          id: 1
        })
      }),
      catch: (error) => new BundlerError({ cause: error })
    })

    return yield* Effect.tryPromise({
      try: () => response.json(),
      catch: (error) => new BundlerError({ cause: error })
    })
  })
  • Free tier: 0.25 ETH in gas credits
  • Base Gasless Campaign: up to $15K credits

Coinbase Smart Wallet support

Passkey-based wallet with no seed phrases. Misty can connect to existing Smart Wallets.

import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk'

// Wrap SDK initialization in Effect Layer
const CoinbaseWalletLive = Layer.sync(CoinbaseWallet, () => {
  const sdk = new CoinbaseWalletSDK({
    appName: 'Misty',
    appChainIds: [8453] // Base
  })
  return sdk.makeWeb3Provider({ preference: 'smartWalletOnly' })
})

Coinbase Verifications

Onchain KYC attestations without revealing private data.

  • Verified Account — user has Coinbase trading account
  • Verified Country — user's country of residence

Query via Ethereum Attestation Service (EAS).

CDP Swap API

Backend-driven token swaps with 7M+ tokens via 0x aggregation.


5. Base-first configuration

Default network

  • chainId = 8453 (Base mainnet)
  • Default RPC list includes at least one Base endpoint; user may add custom endpoints
  • Wallet/network UI defaults to Base and returns to Base on fresh install

Naming

  • Resolve .eth and .base.eth in:
    • URL bar
    • address display
    • search resolution pipeline

6. Data sources

Primary: TrueBlocks (local indexing)

Misty uses TrueBlocks for local-first, private blockchain indexing:

  • All queries stay local — no third party sees what addresses you look up
  • Complete history including internal transactions and traces
  • Base is not pre-indexed, so the local index builds over time (TrueBlocks)

Fallback: Index Supply

While the local TrueBlocks index is building, Misty falls back to Index Supply:

  • Base (8453) indexed from block 1 — provides instant data while local syncs (Index Supply)
  • SQL queries on blocks, transactions, logs, and events
  • Live SSE streaming for real-time updates

Once TrueBlocks catches up, queries switch to local automatically.


7. Core services to implement (Bun backend, Effect.ts)

7.1 IndexingService interface

Both IndexSupplyService and TrueBlocksLocalService implement the same interface:

interface IndexingService {
  queryTransfers: (address: Address, range?: BlockRange) => Effect.Effect<Transfer[], IndexError>
  queryApprovals: (address: Address, range?: BlockRange) => Effect.Effect<Approval[], IndexError>
  queryTransactions: (address: Address, range?: BlockRange) => Effect.Effect<Transaction[], IndexError>
  queryLogs: (filter: LogFilter) => Effect.Effect<Log[], IndexError>
  getStatus: () => Effect.Effect<IndexStatus>
}

7.2 IndexSupplyService

Responsibility: typed wrapper around Index Supply HTTP + SSE.

Key requirements:

  • Support GET /v2/query (single) and POST /v2/query (batch)
  • Support GET /v2/query-live (SSE streaming)
  • Manage cursor per chain/query and persist it (cursor encodes chain-blocknum…). (Index Supply)
  • Handle reorg semantics:
    • If new cursor implies a lower block height than last seen, discard derived state and restart (crash-only). (Index Supply)

Implementation notes:

  • Prefer direct HTTP usage via Bun fetch to avoid pulling in disallowed Ethereum libs.
  • Add structured retry/backoff (network errors, 429s).
  • Add internal rate limiting + dedupe:
    • many UI components will ask for overlapping history.

Proposed Effect API

class IndexSupplyService extends Context.Tag("IndexSupplyService")<
  IndexSupplyService,
  {
    query: (request: { sql: string; signatures?: string[]; cursor?: string }) => Effect.Effect<Response, IndexSupplyError>
    batch: (requests: Request[]) => Effect.Effect<Response[], IndexSupplyError>
    live: (request: LiveRequest) => Stream.Stream<Response, IndexSupplyError>
  }
>() {}

7.3 TrueBlocksLocalService (primary indexer)

Responsibility: manage local indexing and queries using TrueBlocks tools.

Constraints from TrueBlocks docs:

  • Multi-chain works if user provides requisite RPC endpoints; public endpoints can rate limit. (TrueBlocks)
  • Unchained Index data isn't provided for all chains. (TrueBlocks)

Design:

  • Bundle chifra binary (or run via a local container) and manage lifecycle
  • Provide:
    • startScraper(chain)
    • getStatus(chain) (latest/finalized/staging/unripe)
    • queryAppearances(address, range)
    • queryTransfers(address, range) (may require follow-up RPC fetch)

Persist:

  • local chain config (including Base RPC endpoint)
  • local index location + disk usage stats
  • last known sync status

7.4 IndexRouterService

Responsibility: unify "history/explorer query" APIs across sources (Index Supply remote, TrueBlocks local, RPC fallback).

Routing rules:

  • If local TrueBlocks index covers the requested block range → use local
  • If local index is still syncing → use Index Supply for uncovered ranges
  • Once local catches up → all queries go through TrueBlocks
  • Private mode: local-only even if incomplete (no remote fallback)

Router must return:

  • data
  • provenance (Remote/Local/RPC)
  • coverage (block range covered, if partial)

7.5 ExplorerService

Responsibility: implement explorer pages' query needs.

Rather than building a full explorer indexer, v1 uses Index Supply queries plus RPC enrichment:

  • Index Supply returns rows for events/txs/logs
  • RPC fetch (eth_getTransactionByHash, eth_getTransactionReceipt) provides full tx payload when needed
  • Simulation engine provides decoded/trace-like experience for "show what happened"

7.6 SimulationService (tevm-powered)

Responsibility: simulate pending txs and show diffs.

Inputs:

  • tx request (to, data, value, from, chain)
  • fork block tag (latest)

Outputs:

  • status (success/revert)
  • state diffs (token transfers, approvals, ETH delta)
  • call trace summary
  • warnings

Effect interface:

class SimulationService extends Context.Tag("SimulationService")<
  SimulationService,
  {
    simulateTx: (input: SimulateTxInput) => Effect.Effect<TxSimulation, SimulationError>
  }
>() {}

Integration points:

  • invoked from:
    • tx confirmation overlay
    • agent "preview this"
    • explorer tx page (optional "simulate now")

7.7 AgentService + ToolRegistry

Responsibility: LLM orchestration + safe tool-calling.

Key design: agent cannot execute arbitrary code; it can only call typed tools.

Tools include:

  • wallet.getAccounts, wallet.getBalance, wallet.sendTx
  • explorer.search, explorer.getTx, explorer.getAddress
  • simulate.transaction
  • index.queryTransfers, index.queryApprovals
  • browser.openTab, browser.navigate

Agent rendering contract

  • LLM response must be either:
    • TextMessage
    • ComponentMessage[] (typed components only)
    • ToolCall[] (validated)
  • No HTML.

8. Index Supply query design (Base)

8.1 Canonical queries we need

  1. Address token transfers

    • Use Transfer(address indexed from, address indexed to, uint tokens) virtual table
    • Query both directions: where "from" = $addr or "to" = $addr
    • Add where chain = 8453
  2. Approvals

    • Use Approval(address indexed owner, address indexed spender, uint value) signature
    • Provide "unlimited approvals" detection at UI layer
  3. Transaction inputs (function calls)

    • Use function signatures and query txs table (Index Supply supports function signature mode). (Index Supply)
  4. Blocks/txs/logs base tables

    • For generic explorer views, query base tables directly (blocks, txs, logs) when no ABI signature is known. (Index Supply)

8.2 Performance and caching (critical)

Index Supply rate limits heavily and queries can be slow. Requirements:

Benchmarking:

  • Every Index Supply query must be benchmarked before shipping
  • Set performance budgets (e.g., p95 < 500ms for common queries)
  • Monitor query latency in production

Caching strategy (Cloudflare Worker):

  • All Index Supply requests go through our Cloudflare Worker proxy
  • Cache responses by query hash + cursor
  • Cache TTLs:
    • Historical data (old blocks): long TTL (hours/days)
    • Recent data (last ~100 blocks): short TTL (seconds)
    • Live queries: no cache, pass through
  • Deduplicate concurrent identical requests (coalesce)

Rate limit handling:

  • Track rate limit headers from Index Supply
  • Queue and batch requests when approaching limits
  • Graceful degradation: show cached/stale data with indicator rather than failing

8.3 Reorg strategy (required)

Index Supply states: if reorg occurs, clients may receive a lower block height and should discard state and restart. (Index Supply)

Implementation:

  • For each live feed:
    • track lastCursorBlock per chain
    • if incoming < lastCursorBlock: emit ReorgDetected event
    • downstream UI clears list + replays from a stable checkpoint (or from 0 for small datasets)

9. Agent architecture

9.1 Where the agent runs

Recommendation: Agent planning + tool execution in the Bun backend, UI rendering in the main view.

Reasons:

  • Tool calls already live in the backend's service graph (wallet, network, permissions, indexing).
  • Keeps sensitive context away from the webview surface.
  • Centralizes guardrails and audit logging.

9.2 LLM configuration

Default: Cloud model for best quality.

Optional local mode: User clicks "Use local model" button which downloads and installs the model for full privacy.

// AgentService configuration
interface AgentConfig {
  provider: 'cloud' | 'ollama'
  model: string
  baseUrl?: string  // For Ollama: http://localhost:11434
}

Local model (when user opts in):

  • Qwen3-8B recommended (F1: 0.933 on tool calling)
  • Downloaded via Ollama on Apple Silicon Macs
// Ollama integration wrapped in Effect
const ollamaChat = (messages: Message[], tools: Tool[]) =>
  Effect.gen(function* () {
    const config = yield* AgentConfig

    const response = yield* Effect.tryPromise({
      try: () => fetch(`${config.baseUrl}/api/chat`, {
        method: 'POST',
        body: JSON.stringify({
          model: config.model,
          messages,
          tools
        })
      }),
      catch: (error) => new AgentError({ cause: error })
    })

    return yield* Effect.tryPromise({
      try: () => response.json(),
      catch: (error) => new AgentError({ cause: error })
    })
  })

9.3 LabelingService (AI-powered)

Background service that researches and labels unknown addresses.

class LabelingService extends Context.Tag("LabelingService")<
  LabelingService,
  {
    // Get label for address (returns cached or triggers research)
    getLabel: (address: Address) => Effect.Effect<AddressLabel | null>

    // Queue address for background research
    queueResearch: (address: Address) => Effect.Effect<void>

    // User override
    setUserLabel: (address: Address, label: string) => Effect.Effect<void>

    // Bulk lookup
    getLabels: (addresses: Address[]) => Effect.Effect<Map<Address, AddressLabel>>
  }
>() {}

interface AddressLabel {
  address: Address
  name: string                          // "Uniswap V3: Router"
  type: 'protocol' | 'token' | 'nft' | 'wallet' | 'multisig' | 'unknown'
  confidence: 'verified' | 'ai' | 'user'
  source?: string                       // "Basescan verified" | "ENS" | "AI research"
  tags?: string[]                       // ["defi", "dex", "router"]
  updatedAt: number
}

Research pipeline (runs in background):

  1. Check verified sources first:

    • Basescan verified contract name
    • ENS reverse lookup
    • Basenames reverse lookup
    • Known protocol registry (Aerodrome, Uniswap, etc.)
  2. If unverified, queue for AI research:

    • Analyze contract bytecode patterns
    • Check transaction patterns (is it a token? NFT? multisig?)
    • Cross-reference with known deployers
    • Search for social mentions
  3. Store results locally (SQLite)

Label priority:

  1. User override (always wins)
  2. Verified onchain (ENS, verified contract)
  3. AI-generated (with confidence score)

9.4 Agent response schema

type AgentResponse = {
  conversationId: string
  messageId: string
  createdAt: number
  text: string

  components: Array<AgentComponent>
  intents: Array<ActionIntent>

  context: {
    chainId: number
    account?: `0x${string}`
    origin?: string
    dataProvenance: Array<{
      source: "rpc" | "indexsupply" | "trueblocks_local" | "cache"
      at: number
      chainId: number
    }>
  }
}

9.5 Component types (initial set)

  • BalanceCard
  • TokenList
  • SwapQuoteCard
  • TxSimulationPanel
  • ApprovalList
  • AddressSummary
  • TxSummary

9.6 Intent types (must be confirm-gated)

  • ProposeSendTransaction
  • ProposeSignMessage
  • ProposeSwitchNetwork
  • OpenTab
  • AddTokenWatchAsset

10. Transaction confirmation: unified pipeline

10.1 Shared TxReview primitive

type TxReview = {
  request: {
    chainId: number
    from: `0x${string}`
    to?: `0x${string}`
    value?: string
    data?: `0x${string}`
  }
  decoding: {
    functionSignature?: string
    humanSummary: string
    decodedParams?: unknown
    knownProtocol?: string
  }
  simulation?: {
    success: boolean
    gasUsed?: bigint
    stateDiff: {
      ethDeltas: Array<{ address: string; deltaWei: bigint }>
      tokenTransfers: Array<TokenTransfer>
      approvals: Array<ApprovalChange>
    }
    callTrace?: unknown
  }
  warnings: Array<Warning>
}

10.2 Gating logic

  • If simulation fails: require explicit "simulation unavailable" acknowledgment
  • If High severity warning: require checkbox-style acknowledgment

11. Backend API surface (Electrobun RPC)

11.1 Agent handlers

  • agent.createConversation()
  • agent.sendMessage(conversationId, text)
  • agent.listConversations()
  • agent.searchConversations(query)

11.2 Simulation handlers

  • simulation.simulateTx(txRequest)
  • simulation.simulateCalldata({ to, data, from, value })

11.3 Explorer handlers

  • explorer.search(term)
  • explorer.getTx(hash)
  • explorer.getAddress(id)
  • explorer.getContract(address)
  • explorer.getLogs(filter)

11.4 Indexing handlers

  • index.getStatus()
  • index.setMode(mode)
  • index.startLocalSync()
  • index.stopLocalSync()

12. Persistence layer

Principle: All data should be in SQLite when possible. SQLite is the single source of truth for local state.

SQLite stores:

  • conversations
  • bookmarks/history
  • permissions per origin
  • cached index queries
  • last known cursors for Index Supply feeds
  • local indexing config/status
  • address labels (user + AI-generated)
  • wallet metadata (not keys — keys use OS keychain)

13. Error handling

Principle: Effect.ts provides strongly typed errors. Every error must be handled explicitly — no unhandled errors, no dangling loading spinners.

Requirements

  • Every error condition must have designed UX (not just console.log)
  • Every loading state must have a corresponding error state
  • Tests must cover error paths, not just happy paths
  • No catchAll that swallows errors silently

Effect-native error taxonomy

  • SimulationError (fork unavailable, execution error, rpc error)
  • ExplorerError (not found, rpc error, decode error)
  • IndexError (local binary missing, remote unreachable)
  • AgentError (model unavailable, tool validation failure)
  • WalletError (signing failed, locked, invalid passkey)
  • NetworkError (RPC unreachable, timeout, rate limited)
  • NameResolutionError (ENS/Basename not found, resolver error)

Fallback rules

  • If simulation fails: UI must surface "simulation unavailable" + extra confirmation gate
  • If local TrueBlocks fails: in Fast mode fallback to remote; in Private mode show degraded state
  • If RPC fails: retry with fallback endpoints, then show clear error with retry button
  • If agent fails: show error message with option to retry or rephrase

14. Tech stack reference

Runtime & Framework

Layer Technology Purpose URL
Runtime Bun JavaScript runtime for backend https://bun.sh
Desktop Framework Electrobun Native desktop app shell (CEF + Bun) https://electrobun.dev
Meta-framework Astro Static site generation, routing https://astro.build
UI Framework SolidJS Reactive UI components https://solidjs.com
Component Library Kobalte Accessible SolidJS components https://kobalte.dev
Styling CSS Plain CSS (no Tailwind) -
Patterns Effect.ts Typed errors, services, async https://effect.website

Ethereum & Web3

Layer Technology Purpose URL
Ethereum Core voltaire-effect RPC, encoding, types (Effect-native) https://voltaire-effect.tevm.sh
Simulation Tevm EVM fork simulation https://tevm.sh
Smart Wallet @coinbase/wallet-sdk Coinbase Smart Wallet integration https://github.com/coinbase/coinbase-wallet-sdk
Onramp @coinbase/cbpay-js Fiat on/off ramp https://github.com/coinbase/cbpay-js
Bundler/Paymaster Coinbase CDP API ERC-4337 bundler + gas sponsorship https://docs.cdp.coinbase.com

Data & Indexing

Layer Technology Purpose URL
Remote Indexing Index Supply Base blockchain indexing (fallback) https://indexsupply.com
Local Indexing TrueBlocks Local-first private indexing https://trueblocks.io
Database SQLite Local persistence (via Bun) https://bun.sh/docs/api/sqlite
Caching/Proxy Cloudflare Workers RPC proxy, Index Supply cache https://workers.cloudflare.com

Agent & LLM

Layer Technology Purpose URL
Cloud LLM Anthropic Claude Default cloud model with tool calling https://anthropic.com
Local LLM Ollama Local model runtime https://ollama.ai
Local Model Qwen3-8B Recommended local model (F1: 0.933) https://ollama.ai/library/qwen3

Testing

Layer Technology Purpose URL
Unit Tests Bun test Built-in test runner https://bun.sh/docs/cli/test
Effect Tests @effect/vitest Effect-native test utilities https://github.com/Effect-TS/effect
E2E Tests macOS Accessibility Native UI automation -

Not Using

Technology Reason
viem Use voltaire-effect instead
ethers.js Use voltaire-effect instead
Tailwind Using plain CSS
React Using SolidJS
Electron Using Electrobun

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment