WalletYeet

WalletYeet — AI agents reorg your wallet to multiple destinations in one EIP-7702 signature.

WalletYeet

Created At

Open Agents

Project Description

WalletYeet is a tool for reorganizing an Ethereum wallet in a single signature. The reason I built it is pretty simple: anyone who's held an EOA for more than a few months knows what happens. You accumulate half-forgotten NFTs, dust tokens worth pennies, ENS subnames you registered years ago and never used, and a small pile of native ETH you keep meaning to sweep. Migrating that mess manually means signing dozens of transactions, missing assets, and inevitably routing things to the wrong wallet. I wanted that to be one signature instead. Three AI agents handle the work for you. The first is Scout, the discovery agent. It queries Alchemy and reads the chain directly to find every token in the wallet — both the curated tokens we recognize and anything else, auto-resolved via Alchemy's metadata API. It pulls every NFT through Alchemy's NFT v3 endpoint, finds every ENS subname (including the unwrapped ones that the deprecated ENS subgraph silently misses), and reads the native ETH balance. The native ETH gets a dynamically sized gas reserve attached based on the live gas price, so the migration never accidentally leaves the source wallet stranded without enough ETH to do anything else.

The second agent is the Auditor. Its job is risk and dust. It scores each asset SAFE, SUSPICIOUS, or DANGEROUS using deterministic heuristics, identifies sub-$1 dust tokens that are eligible for auto-conversion, and flags items that can't be migrated at all (POAPs, soulbound NFTs). The risk levels and dust flags are computed in code; GPT-4o-mini only refines the human-readable explanation text afterwards. That separation is intentional — risk levels are stable across runs and the LLM can't hallucinate a different threat model on you.

The third agent is the Planner. It builds the actual operation list — what to do, in what order, with what parameters.The Planner also probes Uniswap V3 across every fee tier (100, 500, 3000, and 10000 basis points) and against every USDC candidate — Circle's official Sepolia USDC and a seeded mock — to find pools with real liquidity. When a pool exists, the swap and the transfer get encoded inline so they happen atomically. When no pool exists, the operation gracefully downgrades to a plain transfer — a 12-asset migration shouldn't abort because one swap couldn't find liquidity. Once the agents are done, the user gets a customize step. Every asset has a checkbox to exclude it from the migration and a destination dropdown to override the default. Up to five destination wallets are supported, which means you can finally split things sensibly: NFTs go to a display wallet, stablecoins go to a savings wallet, your ENS identity goes to its own wallet. Toggle dust auto-swap on and any sub-$1 token with on-chain liquidity gets converted to USDC inside the same migration transaction, with the output routed straight to whatever destination you picked for that asset. There's a separate toggle for swapping unknown tokens (off by default — didn't want to surprise-swap something valuable that just doesn't have a price oracle yet).

Execution is where the EIP-7702 part actually matters. Under the hood I use wallet_sendCalls (EIP-5792), which MetaMask routes via EIP-7702 — the user's EOA temporarily delegates to a tiny Batcher contract for the duration of one transaction. Inside that single tx every sub-call runs with msg.sender set to the user's own address, so plain transfer, safeTransferFrom, and setOwner all just work without per-asset approvals. That means one signature for the entire batch, including the Uniswap swap and any ENS handovers. There's a legacy multi-signature fallback flow for wallets that don't support 7702 yet, with a one-click toggle to switch over.

ENS subnames are first-class migrating assets in this flow, not an afterthought. Wrapped names (NameWrapper, which is ERC-1155 under the hood) reuse the TRANSFER_ERC1155 opcode and ride the same atomic batch as your tokens. Unwrapped names go through the registry's setOwner. Both kinds get a readable name displayed in the UI thanks to an on-chain NameWrapper.names() lookup that fires when Alchemy's response leaves the name field empty.

While the agents run, the UI streams their actual progress live via Server-Sent Events. Each agent also has its own ENS subname under walletyeet-demo.eth with on-chain text records (description, ai.role, ai.model, url, com.github), and the dApp resolves these on every run. A green ✓ verified badge on the card means the on-chain lookup succeeded; that "ENS for AI agents" angle is real, not a logo on a slide. Each card also shows real wall-clock duration and the model identifier (gpt-4o-mini@azure) so the provenance is verifiable.

After the migration confirms, the execute screen shows per-sub-call results — green checks for the ones that succeeded, red ✕ for any that reverted, with a contextual hint next to each failure (something like "Reverted on Uniswap router — no V3 pool for this token pair"). A live gas-savings panel compares the batch's actual gas usage to what the equivalent unbatched flow would have cost, which on a typical 12-asset migration shows around 36% in savings. Every operation runs inside a try/catch so one revert never aborts the batch — successful ops still move real assets, and the user can verify everything on Etherscan.

Some thoughts on the Future Scope - There's a lot I'd ship next if this project kept going. The biggest is multi-chain. Right now WalletYeet is Sepolia-only on purpose, so judges can verify everything on a single explorer, but v2 would add a chain dropdown for Mainnet, Base, Arbitrum, Optimism, and Monad — same pipeline, run per network. After that, cross-chain reorg via LayerZero or Hyperlane so "reorg my entire EVM footprint to this new Base address" becomes one signature per source chain.

For users still on wallets that don't support EIP-7702 yet, EIP-2612 permit() would replace on-chain approvals in the legacy fallback path with off-chain typed-data signatures, for tokens that support it (real USDC, DAI, plenty of others). It's strictly a fallback-path improvement — the primary path doesn't need approvals at all.

On the Uniswap side, I'd add real slippage protection. Right now amountOutMinimum: 0 is passed because Sepolia mock liquidity is too thin to quote meaningful minimums, but mainnet would compute the minimum from a frontend quote with a 1–3% tolerance window and route through Flashbots Protect to avoid getting sandwiched. Auto-conversion of dust also only makes economic sense when gas cost is less than the dust value (a $0.40 swap costing $20 in gas just loses money), so we'd add a gas-aware dust threshold with a "only swap if dust value > $X" input.

Discovery would generalize too. Today Scout reads a curated (token, spender) pair list to find approvals; the production-grade version would do a getAssetTransfers query filtered to Approval events for the wallet's full history, then re-check current allowance per spender. That generalizes beyond our curated list to literally any approval the user has ever set. Auto-discovered tokens would also get real prices via Alchemy Token Prices or Coingecko's simple/price endpoint instead of showing as "—" in the value column.

How it's Made

The smart contracts are written in Solidity and built with Foundry. Two contracts ship with the project: MigrationVault, the legacy multi-signature path with multicall plus a try/catch wrapped around each operation so a single failed swap doesn't abort the whole batch, and a tiny Batcher contract that the user's EOA delegates to under EIP-7702. For the duration of one transaction, the EOA temporarily is the Batcher's code, which means every sub-call inside runs with msg.sender set to the user's own address. That removes the need for per-asset approvals — plain transfer, safeTransferFrom, and setOwner all just work.

The frontend is a Next.js 14 application using TypeScript, wagmi 2.19, viem 2.48, RainbowKit, and Tailwind. The three agents — Scout, Auditor, and Planner — run as a streamed Server-Sent Events pipeline served from a single Next.js API route. Each agent reasons over live chain data — queried via the Alchemy SDK and direct JSON-RPC, with multi-fee-tier Uniswap V3 pool probing and on-chain ENS Registry reads — and uses GPT-4o-mini through Microsoft Foundry's Azure OpenAI deployment to do its analysis. Strict JSON-schema prompts and a response validator keep the agents' outputs structured and consistent across runs, with a deterministic fallback path so the pipeline keeps working when the model misbehaves or the API rate-limits. For the Uniswap integration, the Planner probes the V3 Factory across every fee tier (100, 500, 3000, and 10000 basis points) crossed against multiple USDC candidates — Circle's official Sepolia USDC plus a seeded mock — to discover real pools with liquidity. When a pool is found, exactInputSingle calls get encoded inline so dust tokens swap to USDC and route to the destination atomically. Real Sepolia LINK to USDC trades execute in the demo through the actual on-chain pool, not a mock. The ENS integration runs a little deeper than naming. Each of the three agents has its own subname registered under walletyeet-demo.eth, with on-chain text records like description, ai.role, ai.model, url, and com.github. The dApp resolves those on every run, which makes the "ENS for AI agents" angle real rather than cosmetic. User-owned subnames migrate as first-class assets in the same atomic batch as tokens and NFTs — wrapped names (NameWrapper, which is ERC-1155 under the hood) reuse the existing TRANSFER_ERC1155 opcode while unwrapped names go through the registry's setOwner.

A few things turned out hackier than expected and are worth flagging.

The ENS subgraph silently deprecated mid-2024, and Alchemy's NFT API doesn't index unwrapped subnames because they aren't NFTs — they live as raw (node, owner) tuples in the Registry. Scout therefore brute-force probes the Registry directly, walking a curated list of common labels (alice, vault, cold, hot, wallet, main, and so on) and calling getOwner(namehash) per candidate. Most users don't even remember they own these subnames, and the agent doing the gross work for them is one of the most "this is a real product" details in the build.

MetaMask 13.x has a quirk where passing forceAtomic: true together with per-call gas hints in wallet_sendCalls causes it to give up on its own gas estimation and fall back to a 21-million-gas block-limit safe default, which then trips its own "gas limit too high" hard-block error. Most of a day went into debugging that one because the error message points at gas when the actual cause is failed estimation. The fix turned out to be stripping both flags and letting MetaMask estimate the whole 7702 transaction natively. MetaMask also hard-caps wallet_sendCalls at 10 sub-calls per request, so larger plans transparently chunk into N batches of ten or fewer ops each. The user signs N times instead of once, which is still way fewer signatures than the legacy one-per-op path.

NFT transfers use transferFrom rather than safeTransferFrom, because under EIP-7702 the EOA temporarily is a contract, and some receivers' onERC721Received pre-flight checks revert in MetaMask's simulator. The unsafe variant skips the receiver check and works fine for both EOA and contract destinations as long as they can hold the NFT.

viem 2.48 strict-rejects data: "0x" — the underlying regex requires at least one nibble after 0x — so the data field is conditionally omitted for native ETH transfers in the sendCalls payload. The EIP-5792 spec treats data as optional and MetaMask handles its absence cleanly.

EIP-5792 receipt accounting turned out to be ambiguous in practice. Atomic 7702 batches return one receipt for the whole transaction (so all sub-calls share the success status), but sequential batches return one receipt per call. The wallet doesn't tell you which mode it picked, so the dApp infers: if receipts.length is 1 for a multi-call batch and that single receipt is success, every sub-call is marked succeeded. Without that heuristic the UI was reporting "1/12 succeeded" on perfectly good migrations.

Sepolia LINK has a deliberately fudged price of $0.001 per token in the curated list, specifically so 20 LINK from the faucet reads as sub-$1 dust. That way Scout flags it as dust, the Planner finds a real Uniswap pool against USDC, and the demo executes a real on-chain swap with real liquidity and a real receipt — totally honest result, just calibrated to fit within faucet-sized amounts.

Approval revocations were originally part of the feature set. Scout discovered risky approvals via on-chain reads against a SUSPICIOUS_ADDRESSES list, the Auditor scored them DANGEROUS or SUSPICIOUS, the Planner emitted REVOKE_ERC20 ops, and the contract handled them. But MetaMask's phishing-detection list hard-blocks any transaction whose approve target matches a known-bad-looking pattern, and the demo seed deliberately sets approvals to obviously-flagged addresses like 0x12345… and 0xDeadBeef…. Migrations that included revokes simply refused to send. Rather than fight MetaMask's heuristics, the entire approval narrative was ripped out of the codebase — types, contracts, UI, documentation — and the project pivoted. The tradeoff is honest: users still need to revoke separately if they want to, but the migration itself never gets blocked.

The demo mocks deserve a mention because I personally don't have real assets to test against and spent quite a lot of time writing the mock tokens. A demo wallet is seeded via script/SeedDemoWallet.s.sol with five mock ERC-20s — Mock USDC at 6 decimals, SHIB-PEPE and GOV at 18, plus DUST-A and DUST-B at 18 with sub-$1 prices — alongside two mock ERC-721 collections (CryptoPunks-style and Art Gallery). The script deploys, mints to the demo wallet, and logs the addresses, so the state can be reset cleanly.

Brand assets are programmatic (since I'm not a designer and I didn't want to use AI generated art.) The mascot is a 16×16 pixel-art astronaut with a black MJ-style fedora and a sparkly white glove on the raised hand (small tribute), rendered as inline SVG. The favicon, the social-share OG image at /opengraph-image, and the submission logo at /logo.png all use Vercel's next/og ImageResponse, rendering the same pixel grid at different scales — Satori (the engine behind ImageResponse) doesn't render raw SVG, so the mascot is laid out as absolute-positioned divs at the requested pixel size. Three pixel-art agent animals — a puppy for Scout, an owl for the Auditor, and a fox for the Planner — live in components/AgentAnimal.tsx, with a subtle wiggle animation on the active agent card so he user can see which one is currently working.

background image mobile

Join the mailing list

Get the latest news and updates