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
- 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)
- 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
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
┌─────────────────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────────────────┘
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.
┌─────────────────────────────────────────────────────────────────┐
│ 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 ││
│ └─────────────────┘ └─────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
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>
}
>() {}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 })
})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| Version | Factory Address |
|---|---|
| v1.1 | 0xBA5ED110eFDBa3D005bfC882d75358ACBbB85842 |
| v1.0 | 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a |
- iCloud backup — encrypted backup enabling wallet recovery
- Additional recovery methods TBD
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
}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
})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):
- Check for Base-specific address first (coinType 2147492101)
- Fall back to ETH address (coinType 60)
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 * 1000Misty prioritizes Coinbase developer products for Base-native functionality.
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' })))
})
})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
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' })
})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).
Backend-driven token swaps with 7M+ tokens via 0x aggregation.
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
- Resolve
.ethand.base.ethin:- URL bar
- address display
- search resolution pipeline
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)
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.
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>
}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
fetchto 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>
}
>() {}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
chifrabinary (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
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:
dataprovenance(Remote/Local/RPC)coverage(block range covered, if partial)
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"
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")
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.sendTxexplorer.search,explorer.getTx,explorer.getAddresssimulate.transactionindex.queryTransfers,index.queryApprovalsbrowser.openTab,browser.navigate
Agent rendering contract
- LLM response must be either:
TextMessageComponentMessage[](typed components only)ToolCall[](validated)
- No HTML.
-
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
- Use
-
Approvals
- Use
Approval(address indexed owner, address indexed spender, uint value)signature - Provide "unlimited approvals" detection at UI layer
- Use
-
Transaction inputs (function calls)
- Use
functionsignatures and querytxstable (Index Supply supports function signature mode). (Index Supply)
- Use
-
Blocks/txs/logs base tables
- For generic explorer views, query base tables directly (
blocks,txs,logs) when no ABI signature is known. (Index Supply)
- For generic explorer views, query base tables directly (
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
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
lastCursorBlockper chain - if incoming
< lastCursorBlock: emitReorgDetectedevent - downstream UI clears list + replays from a stable checkpoint (or from 0 for small datasets)
- track
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.
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 })
})
})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):
-
Check verified sources first:
- Basescan verified contract name
- ENS reverse lookup
- Basenames reverse lookup
- Known protocol registry (Aerodrome, Uniswap, etc.)
-
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
-
Store results locally (SQLite)
Label priority:
- User override (always wins)
- Verified onchain (ENS, verified contract)
- AI-generated (with confidence score)
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
}>
}
}BalanceCardTokenListSwapQuoteCardTxSimulationPanelApprovalListAddressSummaryTxSummary
ProposeSendTransactionProposeSignMessageProposeSwitchNetworkOpenTabAddTokenWatchAsset
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>
}- If simulation fails: require explicit "simulation unavailable" acknowledgment
- If
Highseverity warning: require checkbox-style acknowledgment
agent.createConversation()agent.sendMessage(conversationId, text)agent.listConversations()agent.searchConversations(query)
simulation.simulateTx(txRequest)simulation.simulateCalldata({ to, data, from, value })
explorer.search(term)explorer.getTx(hash)explorer.getAddress(id)explorer.getContract(address)explorer.getLogs(filter)
index.getStatus()index.setMode(mode)index.startLocalSync()index.stopLocalSync()
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)
Principle: Effect.ts provides strongly typed errors. Every error must be handled explicitly — no unhandled errors, no dangling loading spinners.
- 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
catchAllthat swallows errors silently
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)
- 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
| 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 |
| 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 |
| 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 |
| 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 |
| 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 | - |
| Technology | Reason |
|---|---|
| viem | Use voltaire-effect instead |
| ethers.js | Use voltaire-effect instead |
| Tailwind | Using plain CSS |
| React | Using SolidJS |
| Electron | Using Electrobun |