A browser extension Ethereum smart wallet built for two underserved use cases: first-class ENS identity management and agent ergonomics -- a system where humans grant bounded on-chain permissions to AI agents and automated processes.
Blonk uses ERC-4337 account abstraction (ZeroDev Kernel) so that agent permissions are enforced both off-chain by the backend and on-chain by smart contract policy validators. ENS is a primary feature, not an afterthought -- registration, renewal, record editing, subname management, and ENS-specific activity history are all built in.
- Features
- Architecture
- Tech Stack
- Project Structure
- Setup
- Development
- Extension Usage
- Agent API
- Database Schema
- API Reference
- Configuration
- Security Model
- ERC-4337 account abstraction via ZeroDev Kernel v3.1
- Password-encrypted key storage (PBKDF2 600K iterations + AES-256-GCM)
- Lock/unlock state machine with session-based key management
- Multi-chain support (Ethereum mainnet + Sepolia testnet)
- Counterfactual smart account addresses
- Name registration with commit-reveal anti-frontrunning (60-second wait, alarm-based timing that survives service worker restarts)
- Name renewal with duration picker and price calculator
- Record editor for text records (twitter, github, discord, url, avatar, description, email), ETH address, and content hash
- Subname manager -- create subnames like
agent.yourname.ethfor free under names you own - ENS activity history -- filtered transaction view showing only ENS-related operations
- Expiry warnings on the dashboard for names expiring within 90 days
- Session key generation -- create bounded ECDSA keypairs for agents with:
- Per-token spending limits (daily and total)
- Contract interaction whitelists (address + optional function selector restrictions)
- Rate limiting (max operations per time window)
- Expiration timestamps
- Challenge-response authentication -- agents prove identity by signing a nonce with their session key, receive a JWT
- Double validation -- every agent transaction is checked off-chain (backend: daily spending, rate limits, whitelists) AND on-chain (Kernel permission validator policies)
- Transaction relay -- agents submit transactions to the backend API, which validates and relays to the bundler
- License dashboard with spending progress bars, transaction history, and one-click revocation
- Alerts for approaching spending limits and expiring licenses
- EIP-1193 provider injected as
window.ethereumfor browser dApp compatibility - EIP-6963 announcement for multi-wallet discovery
- WalletConnect v2 -- paste a WC URI to connect to mobile/cross-platform dApps
- Approval flow -- connect, sign, and transaction requests all surface as modal overlays for user confirmation
- Connected sites manager with per-site disconnect
- personal_sign / eth_sign for message signing
- EIP-712 eth_signTypedData_v4 with structured type display
- eth_sendTransaction with decoded calldata display, value formatting, and gas estimation
- Token balances fetched via Alchemy (ETH + all ERC-20s) with USD price display
- Transaction history with event decoding (Transfer, Approval, ENS events)
- Token approval checker -- check and revoke ERC-20 allowances for any spender
┌──────────────────────────────────────────────────────┐
│ Browser Extension │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ Popup │ │ Content │ │ Service Worker │ │
│ │ (React) │ │ Script │ │ (Background) │ │
│ │ │◄─┤ (Relay) │◄─┤ │ │
│ │ Wallet │ │ │ │ WalletController│ │
│ │ ENS │ └────────────┘ │ TxController │ │
│ │ Agents │ │ EnsController │ │
│ │ Settings│ ┌────────────┐ │ SessionManager │ │
│ └──────────┘ │ Inpage │ │ RpcHandler │ │
│ │ (Provider) │ │ ApprovalManager │ │
│ │ window. │ │ WCManager │ │
│ │ ethereum │ │ ConnectionMgr │ │
│ └────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────┘
│
REST API
│
┌──────────▼──────────┐
│ Fastify Backend │
│ │
│ Wallet routes │
│ Agent auth (JWT) │
│ License CRUD │
│ Tx validation │
│ ENS indexer │
│ Alchemy service │
│ Tx decoder │
└─────────┬───────────┘
│
┌───────▼───────┐
│ PostgreSQL │
│ 13 tables │
└───────────────┘
dApp page Content Script Service Worker Popup UI
(window.ethereum) (message relay) (RPC handler) (approval modals)
│ │ │ │
│ request() │ │ │
├──────────────────►│ │ │
│ │ sendMessage() │ │
│ ├───────────────────►│ │
│ │ │ (needs approval?) │
│ │ ├──────────────────►│
│ │ │ │ user confirms
│ │ │◄──────────────────┤
│ │ response │ │
│ │◄───────────────────┤ │
│ result │ │ │
│◄──────────────────┤ │ │
| Layer | Technology |
|---|---|
| Monorepo | pnpm workspaces |
| Extension build | Vite + CRXJS (Manifest V3) |
| Extension UI | React 18 + Tailwind CSS + React Router |
| Blockchain | viem, permissionless.js |
| Smart wallet | ZeroDev SDK (Kernel v3.1, ERC-4337) |
| ENS | @ensdomains/ensjs, direct contract calls |
| WalletConnect | @walletconnect/web3wallet v2 |
| Backend | Fastify + TypeScript |
| Database | PostgreSQL (raw SQL via pg driver) |
| Auth | JWT (jsonwebtoken), EIP-712 signatures |
| Validation | Zod |
| Data provider | Alchemy (Token API, Transfer API) |
blonk-wallet/
├── package.json # pnpm workspace root
├── pnpm-workspace.yaml
├── tsconfig.base.json # shared TypeScript config
├── .env.example # environment variable template
│
├── packages/
│ ├── shared/ # @blonk/shared - types, constants, utilities
│ │ └── src/
│ │ ├── types/ # wallet, agent, ens, transaction, api types
│ │ ├── constants/ # chain configs, ENS contracts, known tokens
│ │ └── utils/ # zod schemas, formatting helpers
│ │
│ ├── extension/ # @blonk/extension - Chrome extension
│ │ ├── vite.config.ts # Vite + CRXJS build config
│ │ ├── tailwind.config.js
│ │ └── src/
│ │ ├── background/ # Service worker (13 controllers)
│ │ │ ├── index.ts # entry, wires everything together
│ │ │ ├── wallet-controller.ts # key gen, encryption, smart account
│ │ │ ├── lock-controller.ts # lock/unlock state machine
│ │ │ ├── chain-manager.ts # viem clients per chain
│ │ │ ├── connection-manager.ts # dApp origin tracking
│ │ │ ├── approval-manager.ts # pending request queue
│ │ │ ├── approval-controller.ts# ERC-20 allowance checks
│ │ │ ├── tx-controller.ts # signing + tx sending
│ │ │ ├── ens-controller.ts # ENS operations
│ │ │ ├── session-manager.ts # agent session keys
│ │ │ ├── walletconnect-manager.ts # WC v2
│ │ │ ├── rpc-handler.ts # EIP-1193 RPC methods
│ │ │ ├── message-router.ts # message dispatch
│ │ │ └── storage.ts # chrome.storage wrapper
│ │ ├── content/ # content script (message relay)
│ │ ├── inpage/ # injected provider (window.ethereum)
│ │ ├── popup/ # React UI
│ │ │ ├── App.tsx
│ │ │ ├── contexts/ # WalletContext
│ │ │ ├── hooks/ # useTokens, useTransactions, useENS,
│ │ │ │ # useLicenses, useApprovals, useConnections
│ │ │ ├── lib/ # api-client.ts (backend communication)
│ │ │ └── components/ # 28 components organized by feature
│ │ │ ├── auth/ # CreateWallet, UnlockScreen
│ │ │ ├── wallet/ # BalanceCard, TokenList, ActivityList,
│ │ │ │ # TokenApprovals, WalletHome
│ │ │ ├── ens/ # ENSDashboard, RegisterFlow, RenewFlow,
│ │ │ │ # RecordEditor, SubnameManager, ENSActivityList
│ │ │ ├── agents/ # LicenseList, LicenseDetail, CreateLicense
│ │ │ ├── connections/ # ConnectedDApps, WalletConnectScan, WCSessions
│ │ │ ├── signing/ # ApprovalOverlay, ConnectRequest,
│ │ │ │ # SignRequest, TxConfirm
│ │ │ ├── layout/ # AppShell, Header, BottomNav, ErrorBoundary
│ │ │ └── settings/ # SettingsPage
│ │ └── types/ # message type definitions
│ │
│ └── backend/ # @blonk/backend - Fastify API
│ └── src/
│ ├── index.ts # server bootstrap
│ ├── config.ts # env validation (Zod)
│ ├── server.ts # Fastify setup + plugin registration
│ ├── db/
│ │ ├── pool.ts # pg connection pool
│ │ ├── migrate.ts # SQL migration runner
│ │ ├── migrations/ # sequential .sql files
│ │ └── queries/ # typed SQL query functions
│ ├── routes/ # 7 route modules, 22 endpoints
│ ├── middleware/ # wallet-auth, agent-auth, rate-limit, errors
│ └── services/ # alchemy, ens-indexer, tx-decoder,
│ # validation, chain-service
- Node.js >= 18
- pnpm >= 8 (
npm install -g pnpm) - PostgreSQL >= 14
- Chrome (or Chromium-based browser)
git clone <repo-url> blonk-wallet
cd blonk-wallet
pnpm installcp .env.example .envEdit .env with your credentials:
# Required
DATABASE_URL=postgresql://localhost:5432/blonk_wallet
JWT_SECRET=your-secret-at-least-16-chars
# Required for balance/tx fetching
ALCHEMY_API_KEY=your_alchemy_api_key
# Optional (for full smart wallet deployment)
ZERODEV_PROJECT_ID=your_zerodev_project_id
# Optional
PORT=3001Getting API keys:
- Alchemy: Sign up at alchemy.com and create an app for Ethereum mainnet + Sepolia
- ZeroDev: Sign up at zerodev.app and create a project (needed for full Kernel smart wallet deployment)
createdb blonk_wallet
pnpm --filter @blonk/backend migratepnpm --filter @blonk/shared buildIn two terminals:
# Terminal 1: Backend
pnpm dev:backend
# Terminal 2: Extension
pnpm dev:extension- Open
chrome://extensions/ - Enable Developer mode (top right toggle)
- Click Load unpacked
- Select the
packages/extension/distdirectory - The Blonk Wallet icon appears in your toolbar
# Build all packages
pnpm build
# Typecheck all packages
pnpm typecheck
# Build shared types (run after changing shared/)
pnpm build:shared
# Dev mode (hot reload)
pnpm dev:extension # extension with HMR
pnpm dev:backend # backend with watch mode
# Database
pnpm --filter @blonk/backend migrate # run migrations- The service worker is the brain of the extension. All wallet logic, signing, ENS operations, and session key management run here. The popup UI communicates with it via
chrome.runtime.sendMessage. - The content script runs in an isolated world on every page. It injects the inpage script and relays messages between the page and service worker.
- The inpage script runs in the page's main world and creates
window.ethereum(the EIP-1193 provider). dApps interact with this provider. - Chrome's Manifest V3 terminates service workers after ~30 seconds of inactivity. All state is persisted in
chrome.storageand operations usechrome.alarmsfor timing (e.g., ENS commit-reveal). - The backend is a Fastify server that caches blockchain data (balances, transactions, ENS names) in PostgreSQL and hosts the agent authentication/relay API.
- Click the Blonk extension icon
- Enter a password (minimum 8 characters) and confirm
- Your smart wallet is created with an encrypted key vault stored locally
- You're automatically unlocked and see the main wallet view
- Balance card shows ETH balance with USD value
- Token list displays all ERC-20 tokens with balances
- Activity feed shows decoded transaction history
- Click Refresh to fetch latest balances from Alchemy
- Click Sync to pull transaction history from the chain
- Register: Search for available
.ethnames, select duration (1-5 years), see the price, then go through the 2-step commit-reveal process (60-second mandatory wait between steps) - Renew: Select names approaching expiry and extend them
- Records: Edit text records (social links, avatar, description), set the ETH address, or update the content hash
- Subnames: Create subnames under your owned names (e.g.,
bot.yourname.eth) -- useful for giving agents their own ENS identity - ENS Activity: Filtered transaction history showing only ENS operations
- Create License: Multi-step wizard:
- Set expiration (1/7/30/90 days) and rate limit (ops per day)
- Add whitelisted contracts (address + label)
- Set per-token spending limits (daily + total)
- Review and confirm
- Copy the session key and approval bundle to give to your agent
- License List: View all licenses with status (active/expired), contract and limit counts
- License Detail: Full policy view, spending progress bars, revoke button
- View account addresses (smart account + EOA owner)
- Manage connected dApp sites (view and disconnect)
- WalletConnect: paste a WC URI to connect to any WC-compatible dApp
- Token Approvals: check and revoke ERC-20 allowances
- Lock wallet
When a dApp calls eth_requestAccounts, a connect request modal appears showing the site and requested permissions. Approve or reject. Once connected, signing and transaction requests surface as overlay modals with full data display.
Agents interact with the Blonk backend API using session keys issued by the human wallet owner.
1. Agent requests a challenge:
POST /api/agents/challenge
Body: { "agentAddress": "0x..." }
Response: { "challenge": "blonk-auth:...", "expiresAt": 1234567890 }
2. Agent signs the challenge with their session private key (EIP-191 personal_sign)
3. Agent submits the signed challenge:
POST /api/agents/authenticate
Body: { "agentAddress": "0x...", "challenge": "blonk-auth:...", "signature": "0x..." }
Response: { "token": "eyJ...", "expiresAt": ..., "licenses": [...] }
4. Agent includes JWT in all subsequent requests:
Authorization: Bearer eyJ...
POST /api/agents/transactions/relay
Authorization: Bearer <jwt>
{
"licenseId": "uuid",
"chainId": 1,
"calls": [
{
"to": "0x...token...",
"value": "0",
"data": "0xa9059cbb..."
}
]
}
The backend validates:
- License exists, is active, and not expired
- Target contracts are in the whitelist
- Function selectors are allowed (if restricted)
- Token spending is within daily and total limits
- Rate limit is not exceeded
If all checks pass, the transaction is logged and spending is tracked.
GET /api/agents/me
Authorization: Bearer <jwt>
Response: {
"agentAddress": "0x...",
"licenses": [{ id, agent_name, chain_id, status, token_limits, contract_whitelist, ... }]
}
13 tables organized by domain:
| Domain | Tables | Purpose |
|---|---|---|
| Wallet | wallets, token_balances |
Account registration, cached balances |
| Transactions | transactions, transaction_events |
Cached tx history with decoded events |
| ENS | ens_names, ens_records |
Cached ENS name ownership and records |
| Agents | licenses, spending_tracking, transaction_logs, auth_challenges, agent_sessions, alerts |
License policies, spending tracking, auth, audit trail |
Run migrations:
pnpm --filter @blonk/backend migrate| Method | Path | Description |
|---|---|---|
| POST | /api/wallets/register |
Register a smart wallet |
| GET | /api/wallets/:address |
Get wallet info |
| GET | /api/wallets/:address/balances?chainId=&refresh= |
Token balances |
| Method | Path | Description |
|---|---|---|
| GET | /api/wallets/:address/transactions?chainId=&page=&limit= |
Paginated tx history |
| GET | /api/wallets/:address/transactions/ens |
ENS-only tx history |
| POST | /api/wallets/:address/transactions/sync |
Sync from Alchemy |
| Method | Path | Description |
|---|---|---|
| GET | /api/wallets/:address/ens/names |
Owned ENS names + records |
| GET | /api/ens/:name/records |
Get records for a name |
| POST | /api/wallets/:address/ens/sync |
Sync ENS data from chain |
| Method | Path | Description |
|---|---|---|
| POST | /api/wallets/:address/licenses |
Create a license |
| GET | /api/wallets/:address/licenses |
List all licenses |
| GET | /api/wallets/:address/licenses/:id |
License detail + spending |
| PATCH | /api/wallets/:address/licenses/:id/revoke |
Revoke a license |
| GET | /api/wallets/:address/alerts |
Get alerts |
| Method | Path | Description |
|---|---|---|
| POST | /api/agents/challenge |
Request auth challenge |
| POST | /api/agents/authenticate |
Sign challenge, get JWT |
| GET | /api/agents/me |
Agent context + licenses |
| POST | /api/agents/transactions/relay |
Validate + relay transaction |
| GET | /api/agents/transactions?licenseId= |
Agent tx history |
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Database connectivity check |
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
JWT_SECRET |
Yes | Secret for signing agent JWTs (min 16 chars) |
ALCHEMY_API_KEY |
Yes | Alchemy API key for balance/tx fetching |
ZERODEV_PROJECT_ID |
No | ZeroDev project ID for Kernel deployment |
PORT |
No | Backend port (default: 3001) |
MAINNET_RPC_URL |
No | Override mainnet RPC URL |
SEPOLIA_RPC_URL |
No | Override Sepolia RPC URL |
| Chain | ID | ENS | Status |
|---|---|---|---|
| Ethereum Mainnet | 1 | Yes | Production |
| Sepolia Testnet | 11155111 | Yes (testnet contracts) | Development |
The architecture supports adding new chains by extending SUPPORTED_CHAINS in packages/shared/src/constants/chains.ts.
- Private keys are encrypted at rest with AES-256-GCM using a key derived from the user's password via PBKDF2 (600,000 iterations)
- Encrypted vault stored in
chrome.storage.local(persistent) - Decrypted key held in
chrome.storage.sessiononly while unlocked (volatile, clears on browser restart)
- Off-chain (backend): Daily/total spending limits, rate limiting, contract whitelist, function selector checks -- prevents invalid transactions from wasting gas
- On-chain (Kernel policies): Per-call value limits, argument constraints, expiration, rate limits -- enforces bounds even if the backend is bypassed
- If the backend is compromised: on-chain policies still enforce bounds; attacker cannot forge session key signatures
- If an agent key is compromised: human can revoke on-chain + backend; on-chain limits cap the damage window
- Manifest V3 with no
eval()or inline scripts (CSP enforced) - Service worker uses Web Crypto API for all cryptographic operations
- Content script runs in an isolated world; inpage script cannot access extension storage
- Connected dApp origins are tracked and can be revoked individually
MIT