Skip to content

Code review hardening: paymaster client, broadcast panic, eslint, owner rotation - #68

Open
flwrenn wants to merge 6 commits into
devfrom
fix/review-findings
Open

Code review hardening: paymaster client, broadcast panic, eslint, owner rotation#68
flwrenn wants to merge 6 commits into
devfrom
fix/review-findings

Conversation

@flwrenn

@flwrenn flwrenn commented Jul 20, 2026

Copy link
Copy Markdown
Owner

What

Post-review hardening across all three components, plus owner rotation:

  • fix(frontend): use Pimlico paymaster client with fee estimation — switches from ERC-7677 pm_* methods (paid tier) to pimlico_* methods via createPimlicoClient, with a shared bundler-client helper deduplicating deploy() and sendUserOp setup
  • fix(indexer): prevent broadcast panic on slow WebSocket clients — break in the select only exited the select, so the next iteration sent on a closed channel and killed the whole process; now a labeled continue, with a multi-message regression test
  • chore(frontend): restore .gitignore required by eslint config — includeIgnoreFile crashed on ENOENT, so lint had been silently broken
  • fix(frontend): resolve eslint errors across app — 23 accumulated errors down to 0
  • feat(contracts): add owner rotation via transferOwnership — mirrors the existing onlyOwnerOrEntryPoint auth, emits OwnershipTransferred, 7 new tests including a session-key self-call escalation negative
  • docs: document account limitations (upgradeability, recovery, session key fee surface, selector-level scoping)

Why

A full code review found a process-killing crash in the indexer hub, a paymaster integration on the paid-tier API path, silently broken lint tooling, and no way to rotate a compromised owner key. This PR fixes all four and documents the remaining known gaps.

Scope

  • Contracts
  • Backend
  • Frontend
  • Tooling / CI
  • Documentation
  • Test

How to verify

  • cd contracts && forge test -vvv — 100 passed (was 93)
  • cd indexer && go test ./... — all pass; the new TestBroadcastMultipleMessagesDropsSlowClientOnce panics against the pre-fix hub
  • cd frontend && pnpm lint && pnpm build — 0 errors, clean build
  • Manual: deploy flow on Sepolia lands a sponsored UserOp via the Pimlico free tier

Related issues

None closed. #65, #66, #67 were filed from the same review as deferred low-severity follow-ups.

Copilot AI review requested due to automatic review settings July 20, 2026 19:41
@flwrenn flwrenn self-assigned this Jul 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR applies post-code-review hardening across contracts, indexer, and frontend tooling/UI. It fixes a crash in WebSocket broadcasting, updates the frontend’s paymaster integration to Pimlico’s free-tier-compatible client, restores/strengthens ESLint behavior, and adds owner rotation to the SmartAccount contract alongside updated documentation.

Changes:

  • Indexer: fix Hub broadcast loop to avoid sending on a closed channel; add a regression test covering multi-message batches.
  • Frontend: switch paymaster integration to Pimlico client + fee estimation and restore/resolve ESLint configuration and lint errors.
  • Contracts + docs: add transferOwnership + tests and document key account limitations/trade-offs.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Documents additional account limitations and trade-offs (with new ownership/session-key implications).
indexer/internal/api/hub.go Prevents process-killing panic by correctly skipping remaining sends for a dropped slow client.
indexer/internal/api/hub_test.go Adds a regression test ensuring multi-message broadcasts drop a slow client safely and deliver to healthy clients.
frontend/src/routes/session/+page.svelte Adds targeted ESLint suppression for external Etherscan navigation.
frontend/src/routes/indexer/+page.svelte Adds targeted ESLint suppression for control-regex usage and external navigation.
frontend/src/routes/+page.svelte Adds targeted ESLint suppression for external Etherscan navigation.
frontend/src/routes/+layout.svelte Uses SvelteKit resolve() for internal navigation links.
frontend/src/lib/userOp.ts Introduces shared bundler/paymaster client helper using Pimlico client + fee estimation.
frontend/src/lib/components/StatsPanel.svelte Minor rendering tweak (em dash literal).
frontend/src/lib/components/SessionKeyManager.svelte Fixes reactivity/ESLint concerns and adds keyed each-blocks.
frontend/src/lib/components/FaucetTokenCard.svelte Adjusts reactivity dependency tracking; adds ESLint suppression for external navigation.
frontend/src/lib/components/CounterCard.svelte Adjusts reactivity dependency tracking; adds ESLint suppression for external navigation.
frontend/src/lib/account.svelte.ts Deduplicates bundler/paymaster setup by using the shared helper for deploy flow.
frontend/eslint.config.js Enforces consistent unused-var handling (underscore-prefixed ignores).
frontend/.gitignore Restores ignore file needed by ESLint config’s ignore-file inclusion.
contracts/test/SmartAccount.t.sol Adds tests for ownership transfer and signature behavior after rotation.
contracts/src/SmartAccount.sol Adds transferOwnership, event/error wiring, and documentation comments for ownership rotation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +190 to +204
/// @notice Transfer ownership of this account to a new EOA. Callable by the
/// current owner directly or via the EntryPoint (owner-signed UserOp).
/// Session keys cannot reach this function — same rationale as
/// registerSessionKey: a self-call via execute has msg.sender =
/// address(this), which fails the modifier.
/// @dev Note: SmartAccountFactory.getAddress(owner, salt) derives the
/// counterfactual address from the INITIAL owner — after a transfer,
/// the factory mapping no longer corresponds to the current owner.
/// @param newOwner The EOA that will own this account. Must not be address(0).
function transferOwnership(address newOwner) external onlyOwnerOrEntryPoint {
if (newOwner == address(0)) revert InvalidNewOwner();
address previousOwner = owner;
owner = newOwner;
emit OwnershipTransferred(previousOwner, newOwner);
}
Comment thread README.md
Comment on lines +251 to +254
- **No upgrade path.** Accounts sit behind ERC1967 proxies, but no UUPS upgrade authorization is exposed — the implementation address is fixed at deploy time. A bug in the implementation cannot be patched for existing accounts.
- **No recovery mechanism.** `transferOwnership` requires the current owner's authorization, so a lost owner key still bricks the account — there is no social recovery or guardian scheme. A compromised-but-not-lost key, however, can be rotated out via `transferOwnership`.
- **Session key fee surface.** Session keys don't constrain UserOp gas fields or paymaster choice. An in-scope compromised session key can grief a self-funded account's EntryPoint deposit by submitting ops with inflated gas parameters — the prefund is drawn from the account's deposit.
- **Selector-level scoping only.** Session key scope stops at the 4-byte selector — there are no argument-level constraints. A key scoped to `transfer(address,uint256)` on a token can send the full balance to any address.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants