Asset Scooper - Clear your wallet dust in one click
Asset Scooper lets you swap multiple small token balances for a single asset in a single transaction, significantly reducing gas costs and friction.
The goal: simplify on-chain portfolio cleanup by leveraging ERC-4337 smart accounts and ParaSwap's aggregation network.
Overview
Asset Scooper batches all of it into a single on-chain transaction. Connect your wallet, select the tokens you want to offload, pick a destination asset, and confirm once. One signature. One gas fee. Done.
The magic underneath is ERC-4337 smart accounts combined with ParaSwap's DEX aggregator. The interface constructs a UserOperation that bundles multiple swap calldata into a single atomic execution — if any swap fails, the whole batch reverts, protecting the user from partial fills.
How It Works
1. Portfolio Discovery
On load, the app queries the Covalent API to fetch all token balances for the connected address across supported chains. Tokens are filtered, sorted by USD value, and rendered in a selectable list — giving the user a full picture of what they're holding.
2. Swap Route Aggregation
For each selected token, the app calls ParaSwap's @paraswap/core SDK to find the optimal swap route to the target asset. ParaSwap aggregates across Uniswap, Curve, Balancer, and dozens of other liquidity sources to minimize slippage and maximize output.
3. Batch Transaction via Smart Accounts
This is where the core innovation lives. Instead of submitting N separate transactions:
- The app uses Coinbase OnchainKit (
@coinbase/onchainkit) to interact with the user's ERC-4337 smart account (or deploy one if needed). - All swap calldata — including token approvals — are packed into a single
executeBatchUserOperation. - The UserOperation is sent to a bundler, which submits it on-chain as one atomic transaction.
- A paymaster can optionally sponsor gas, enabling a gasless UX.
The result: N swaps, 1 transaction, 1 signature.
4. Transaction Lifecycle
Real-time status is tracked via wagmi's useWaitForTransactionReceipt hook and TanStack Query for caching and refetching. The UI reflects pending, confirmed, and failed states with smooth transitions powered by Framer Motion.
Tech Stack
Frontend Framework
Next.js 14 (App Router) with full TypeScript. Pages use the app/ directory with server components for metadata and client components for wallet interactions.
UI Layer
Chakra UI v2 with Emotion for the component system. Dark mode is handled natively through Chakra's color mode context. Framer Motion drives page transitions, token selection animations, and the transaction confirmation flow.
Wallet & Chain Abstraction
- Wagmi v2 — hooks for wallet state, contract reads/writes, and transaction receipts
- Viem — low-level Ethereum type safety and ABI encoding
- Web3Modal (WalletConnect v2) — multi-wallet connection modal
@wagmi/connectors— injected, WalletConnect, and Coinbase Wallet connectors
Smart Account Layer
@coinbase/onchainkit provides the smart account abstraction. It handles:
- Detecting whether the connected address is a smart account
- Deploying a new ERC-4337 account if needed
- Constructing and signing UserOperations
- Routing UserOps through a bundler endpoint
Swap Aggregation
@paraswap/core — the ParaSwap SDK handles rate fetching, route building, and calldata construction for each individual swap. The app calls getRate() and buildTx() for each token, then collects all the calldata before batching.
Data Layer
- Covalent SDK (
@covalenthq/client-sdk) — token balances and USD pricing - Apollo Client + GraphQL — for any indexed on-chain data queries
- TanStack Query v5 — server state, caching, background refetching
- Axios — HTTP client for REST API calls
Other
localforage— IndexedDB-backed persistence for user preferenceszod— runtime schema validation on API responses- XMTP frames validator — for Farcaster Frame integration
Architecture
┌─────────────────────────────────────────────────┐
│ Next.js App │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Chakra UI │ │ Framer Motion │ │
│ │ Components │ │ Animations │ │
│ └──────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Wagmi + Viem Layer │
│ wallet state · contract calls · tx receipts │
├──────────────────┬──────────────────────────────┤
│ Smart Account │ Data Layer │
│ (OnchainKit) │ Covalent · Apollo · Query │
│ ERC-4337 UOps │ token balances · pricing │
├──────────────────┴──────────────────────────────┤
│ ParaSwap Aggregator │
│ rate quotes · route building · calldata │
├─────────────────────────────────────────────────┤
│ EVM (Base · Ethereum · etc.) │
│ Bundler → EntryPoint → SmartAccount.execute │
└─────────────────────────────────────────────────┘
Key Engineering Decisions
Why ERC-4337 instead of a multicall contract? A standard multicall contract requires each token to already be approved. ERC-4337 smart accounts can bundle approvals and swaps in the same atomic operation — no pre-approvals needed, and the user experience is identical to a normal swap.
Why ParaSwap over a single DEX? For dust tokens with low liquidity, routing through a single DEX often results in high slippage or failed swaps. ParaSwap's multi-route aggregation finds liquidity across the entire DeFi landscape.
Why Covalent for balances?
Covalent provides a unified, multi-chain token balance API with USD pricing in a single call — far simpler than querying an ERC-20 balanceOf for each token manually.
Challenges
The trickiest part was calldata packing order. ParaSwap builds calldata that expects the token to already be in the smart account (or approved). Getting the execution order right — approve → transfer → swap — required carefully sequencing the UserOperation's executeBatch calls and testing across multiple edge cases (zero-balance tokens, tokens without ParaSwap liquidity, tokens on unsupported chains).
Handling partial batch failures gracefully in the UI also took iteration. If ParaSwap can't find a route for one of five selected tokens, the app needs to surface that before sending the transaction — not after a revert.