Bastion
An ERC-4337 smart account system with scoped session keys and a Go event indexer that survives chain reorgs, built with Foundry, Svelte 5, and PostgreSQL, deployed and verified on Sepolia.

Overview
Bastion is an ERC-4337 smart account system with session key support, paired with a Go indexer that reads account activity back off-chain. Contracts are Solidity on Foundry, the indexer runs on the Go standard library, and one SvelteKit frontend serves both halves. All four contracts are deployed and verified on Sepolia, with gas sponsored by a Pimlico paymaster so the demo runs without testnet ETH.
The Problem
Account abstraction moves signature validation out of the protocol and into the account itself. That makes scoped delegation possible: a temporary key that can call one function, on one contract, inside one time window. Session keys are what make smart accounts worth the added complexity, and they're usually the part that gets skipped, or demoed without revocation.
Reading the history back is a separate problem. Once accounts emit UserOperationEvent, something has to index those logs and survive reorgs without double-counting or serving partial state. Most reference implementations stop at the contract.
Design
- Account:
SmartAccountvalidates the owner via ECDSA, with a separate path for session keys, exposingexecuteandexecuteBatch. Accounts sit behind ERC1967 proxies deployed by a CREATE2 factory, so an address derives from(owner, salt)and is usable before deployment.initCodeon the first UserOp deploys the proxy in the same transaction that validates it. - Session keys: Scope lives in
_validateSessionKeyon the account. Each key binds to a target address, a 4-byte selector, and avalidAfter/validUntilwindow. Compromising one key grants nothing on any other account that authorizes the same public key. - Indexer: Go on
net/httpwith no framework and no ORM.eth_getLogspolling is the authoritative range scanner and the only path that handles reorgs. Aneth_subscribefeed onnewHeadswakes the loop early, and if the socket drops, polling continues unchanged. - Reorg handling: Only blocks below
latest - confirmationsget indexed, and each pass rewinds a configurable window before rescanning. Delete-above, insert, and cursor update commit as one Postgres transaction, so readers never see a half-applied range. - Frontend: Svelte 5 with viem and permissionless.js. Session keypairs are generated in browser memory and never persisted. The
SessionKeyAddedandSessionKeyRevokedevents are the source of truth.
Highlights
- Session key lifecycle runs end to end: generate, register, execute from a second tab signing locally, then revoke and watch the same call fail validation at the EntryPoint
- Killing the indexer mid-run and restarting it resumes from the persisted cursor, rewinds, and catches up without duplicating rows
- Roughly one line of Go test per line of source across decoding, RPC retry, safe-head, and database layers, with Foundry tests on all four contracts
- Documented limitations covering the absent upgrade path, lack of social recovery, selector-level-only scoping, and the session key fee surface