<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Waleed</title>
    <description>The latest articles on DEV Community by Waleed (@waleed_7c).</description>
    <link>https://dev.to/waleed_7c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838229%2F861f4a76-93b1-49ef-bf53-713cc7e6a0ed.png</url>
      <title>DEV Community: Waleed</title>
      <link>https://dev.to/waleed_7c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waleed_7c"/>
    <language>en</language>
    <item>
      <title>I Automated 85% of the wagmi v1-v2 Migration Using Codemods. Here's How.</title>
      <dc:creator>Waleed</dc:creator>
      <pubDate>Sat, 02 May 2026 09:46:37 +0000</pubDate>
      <link>https://dev.to/waleed_7c/i-automated-85-of-the-wagmi-v1-v2-migration-using-codemods-heres-how-5dj7</link>
      <guid>https://dev.to/waleed_7c/i-automated-85-of-the-wagmi-v1-v2-migration-using-codemods-heres-how-5dj7</guid>
      <description>&lt;p&gt;wagmi v2 broke everything. If you've tried migrating a real DeFi frontend from wagmi v1 to v2, you know exactly what I mean.&lt;br&gt;
Every hook was renamed. The provider component changed. Connectors went from classes to factory functions. TanStack Query params moved into a nested &lt;code&gt;query:{}&lt;/code&gt; object. &lt;code&gt;configureChains&lt;/code&gt; was removed entirely. The official migration guide is 4,000+ words long.&lt;br&gt;
I built a codemod that automates 85% of this migration deterministically — with zero false positives — and leaves clear AI-ready TODO comments for the remaining 15%. Here's exactly how I did it, what worked, what didn't, and the real numbers from running it against the official wagmi v1 examples repo.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;wagmi v2 is a ground-up rewrite built around two new primitives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viem&lt;/strong&gt; instead of ethers.js&lt;br&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt; instead of internal state management&lt;/p&gt;

&lt;p&gt;This meant every single hook had a breaking change. Not just renames — the entire API contract changed. &lt;code&gt;useContractWrite&lt;/code&gt; in v1 accepted the contract config at the hook level. In v2 (&lt;code&gt;useWriteContract&lt;/code&gt;), the contract config moves to the &lt;code&gt;writeContract()&lt;/code&gt; call site. &lt;code&gt;useNetwork()&lt;/code&gt; was removed entirely — replaced by &lt;code&gt;useChainId()&lt;/code&gt; (returns a number, not a Chain object) and &lt;code&gt;useChains()&lt;/code&gt;.&lt;br&gt;
For a team with 50+ components using wagmi, this is a multi-day migration. For a team with 200+, it's a week of careful, error-prone manual work.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Approach: Deterministic First, AI Second&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The core principle: &lt;strong&gt;anything mechanical goes in the codemod, anything semantic goes to AI&lt;/strong&gt;.&lt;br&gt;
A rename is mechanical. Moving &lt;code&gt;enabled: true&lt;/code&gt; into &lt;code&gt;query: { enabled: true }&lt;/code&gt; is mechanical. But figuring out what Viem transport to use as a replacement for &lt;code&gt;alchemyProvider({ apiKey })&lt;/code&gt; — that requires understanding the developer's infrastructure. That's AI territory.&lt;br&gt;
This gave us a clean split:&lt;br&gt;
Deterministic (codemod handles): ~85% of patterns&lt;br&gt;
AI/manual (TODO comments): ~15% of patterns&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Tool: Codemod + JSSG&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I used the Codemod platform with their JSSG engine (ast-grep based). JSSG uses Tree-sitter under the hood — it parses TypeScript/TSX into a concrete syntax tree and lets you write transforms that match and replace code patterns.&lt;br&gt;
The key advantage over regex: it understands code structure. It knows the difference between &lt;code&gt;useContractRead&lt;/code&gt; in an import statement, a function call, and a string literal. Regex doesn't.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The 8 Transforms&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Transform 01: Hook Renames&lt;/strong&gt;&lt;br&gt;
The largest transform — 11 hook renames in one pass.&lt;br&gt;
useContractRead        → useReadContract&lt;br&gt;
useContractWrite       → useWriteContract&lt;br&gt;
useContractEvent       → useWatchContractEvent&lt;br&gt;
useContractReads       → useReadContracts&lt;br&gt;
useContractInfiniteReads → useInfiniteReadContracts&lt;br&gt;
useWaitForTransaction  → useWaitForTransactionReceipt&lt;br&gt;
useSwitchNetwork       → useSwitchChain&lt;br&gt;
useSigner              → useWalletClient&lt;br&gt;
useProvider            → usePublicClient&lt;br&gt;
useWebSocketProvider   → usePublicClient&lt;br&gt;
useFeeData             → useEstimateFeesPerGas&lt;br&gt;
&lt;strong&gt;The tricky part:&lt;/strong&gt; import source verification. If a developer has their own hook named &lt;code&gt;useProvider&lt;/code&gt; in a file that also imports from wagmi, we must NOT rename their hook. The transform confirms each hook is actually imported from &lt;code&gt;'wagmi'&lt;/code&gt; before renaming anything.&lt;br&gt;
&lt;strong&gt;Another tricky part:&lt;/strong&gt; position detection. The pattern &lt;code&gt;import { $$$A, useSwitchNetwork, $$$B } from 'wagmi'&lt;/code&gt; fails when &lt;code&gt;useSwitchNetwork&lt;/code&gt; is the last specifier (because &lt;code&gt;$$$B&lt;/code&gt; requires at least one node). Solution: use text-based import detection with a regex word-boundary check instead.&lt;br&gt;
For hooks where the return shape also changed, we add a TODO comment at the call site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="c1"&gt;// TODO(wagmi-codemod): useWriteContract API changed significantly.&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Rename: write → writeContract, writeAsync → writeContractAsync&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Contract config moves from hook args to writeContract() call site.&lt;/span&gt;
&lt;span class="c1"&gt;//    Before: const { write } = useContractWrite({ address, abi, functionName })&lt;/span&gt;
&lt;span class="c1"&gt;//    After:  const { writeContract } = useWriteContract()&lt;/span&gt;
&lt;span class="c1"&gt;//            writeContract({ address, abi, functionName, args })&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWriteContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transform 02: WagmiConfig → WagmiProvider&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WagmiConfig&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WagmiProvider&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handles both the import specifier rename and all JSX opening/closing tag occurrences.&lt;br&gt;
&lt;strong&gt;Transform 03: Prepare Hooks&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePrepareContractWrite&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContractWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// After (automated part)&lt;/span&gt;
&lt;span class="c1"&gt;// TODO(wagmi-codemod): Rename 'config' → 'data', pass data.request to writeContract()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSimulateContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rename is automated. The destructuring fix (&lt;code&gt;config&lt;/code&gt; → &lt;code&gt;data&lt;/code&gt;) and the &lt;code&gt;data.request&lt;/code&gt; passing is flagged for AI.&lt;br&gt;
&lt;strong&gt;Transform 04: TanStack Query Params&lt;/strong&gt;&lt;br&gt;
In v2, all TanStack Query params must live inside a &lt;code&gt;query: {}&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="nf"&gt;useReadContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;balanceOf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="nf"&gt;useReadContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;balanceOf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hardest part of this transform: correctly splitting object properties by comma. A naive split breaks on &lt;code&gt;functionName: 'transfer,all'&lt;/code&gt; (comma inside a string). The solution: a string-aware comma splitter that tracks string literal boundaries and only splits at depth-0 commas outside strings.&lt;br&gt;
We also had to avoid tracking &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt; as depth characters — that would break on JSX inside hook args.&lt;br&gt;
&lt;strong&gt;Transform 05: Watch Prop Removal&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;watch: true&lt;/code&gt; was removed from most hooks in v2. But — critical safety detail — watch: true is still valid on &lt;code&gt;useBlockNumber&lt;/code&gt; and &lt;code&gt;useBlock&lt;/code&gt;. The transform explicitly excludes those two hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBalance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// After (automated)&lt;/span&gt;
&lt;span class="c1"&gt;// TODO(wagmi-codemod): 'watch' prop removed. Use useBlockNumber + useEffect to refetch:&lt;/span&gt;
&lt;span class="c1"&gt;//   const { data: blockNumber } = useBlockNumber({ watch: true })&lt;/span&gt;
&lt;span class="c1"&gt;//   useEffect(() =&amp;gt; { refetch() }, [blockNumber])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBalance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;Transform 06: Connector Renames&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetaMaskConnector&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wagmi/connectors/metaMask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WalletConnectConnector&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wagmi/connectors/walletConnect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MetaMaskConnector&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;chains&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WalletConnectConnector&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;chains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;metaMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;walletConnect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wagmi/connectors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;metaMask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;walletConnect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things automated: import path consolidation, &lt;code&gt;new ClassName()&lt;/code&gt; → &lt;code&gt;factoryFn()&lt;/code&gt; conversion, and chains property removal (no longer needed in v2 — &lt;code&gt;chains&lt;/code&gt; are set in &lt;code&gt;createConfig&lt;/code&gt;).&lt;br&gt;
Also handles &lt;code&gt;@wagmi/core/connectors/*&lt;/code&gt; paths for projects using the core package directly.&lt;br&gt;
&lt;strong&gt;Transform 07: Config API&lt;/strong&gt;&lt;br&gt;
Three separate operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;createClient&lt;/code&gt; → &lt;code&gt;createConfig&lt;/code&gt; (import + all call sites)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;configureChains&lt;/code&gt; → TODO comment with Viem transport example&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useNetwork()&lt;/code&gt; → &lt;code&gt;useChainId()&lt;/code&gt; / &lt;code&gt;useChains()&lt;/code&gt; based on what's destructured&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For useNetwork, we analyze the destructuring pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;const { chain } = useNetwork() → const chainId = useChainId()&lt;/code&gt; + TODO about type change&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;const { chains } = useNetwork() → const chains = useChains()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Aliased: &lt;code&gt;const { chain: currentChain } = useNetwork()&lt;/code&gt; → fallback TODO&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transform 08: Import Cleanup&lt;/strong&gt;&lt;br&gt;
After all previous transforms have run, some import specifiers become stale. This transform rebuilds the wagmi import with only the remaining valid specifiers.&lt;br&gt;
&lt;strong&gt;Critical design decision:&lt;/strong&gt; this transform's STALE list only includes hooks that were completely removed (useNetwork, configureChains) or renamed by another transform that also fixes the import (WagmiConfig → renamed by transform 02). It does NOT include hooks like &lt;code&gt;useSwitchNetwork&lt;/code&gt; that transform 01 already renames in-place in the import specifier. Adding those would cause a bug where the code says &lt;code&gt;useSwitchChain()&lt;/code&gt; but the import has no &lt;code&gt;useSwitchChain&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The AI Step&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;workflow.yaml&lt;/code&gt; includes an AI step that runs after all 8 deterministic transforms. It only fires on files containing &lt;code&gt;TODO(wagmi-codemod)&lt;/code&gt; comments, and includes specific instructions for each pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;yaml- name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;resolve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;remaining&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;TODO(wagmi-codemod)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;patterns"&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Fix each TODO(wagmi-codemod) comment:&lt;/span&gt;

      &lt;span class="s"&gt;1. configureChains removed → replace with Viem http transports&lt;/span&gt;
      &lt;span class="s"&gt;2. watch:true → useBlockNumber + useEffect pattern  &lt;/span&gt;
      &lt;span class="s"&gt;3. prepare hook destructuring → config → data.request&lt;/span&gt;
      &lt;span class="s"&gt;4. useSwitchChain result → switchNetwork → switchChain&lt;/span&gt;
      &lt;span class="s"&gt;5. useWalletClient → WalletClient not ethers Signer&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key: TODOs are placed at the exact call site, not at the top of the file. This gives the AI maximum context for each fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Real-World Results: wevm/&lt;a href="mailto:wagmi@1.x"&gt;wagmi@1.x&lt;/a&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I ran the full codemod against the official wagmi v1 examples repo (the 1.x branch of wevm/wagmi on GitHub — the wagmi team's own reference implementations).&lt;br&gt;
Files scanned:           27&lt;br&gt;
Files changed:           10&lt;br&gt;
Lines changed:          +62 / -60&lt;br&gt;
TODO comments added:      7&lt;br&gt;
False positives:          0&lt;br&gt;
Here's what each changed file got:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;File: &lt;code&gt;ReadContract.tsx&lt;/code&gt; / What changed: &lt;code&gt;useContractRead → useReadContract, enabled → query:{enabled}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;ReadContracts.tsx&lt;/code&gt; / What changed: &lt;code&gt;useContractReads → useReadContracts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;ReadContractsInfinite.tsx&lt;/code&gt; / What changed: &lt;code&gt;useContractInfiniteReads → useInfiniteReadContracts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;WriteContract.tsx&lt;/code&gt; / What changed: &lt;code&gt;useContractWrite → useWriteContract&lt;/code&gt; + TODO&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;WriteContractPrepared.tsx&lt;/code&gt; / What changed: &lt;code&gt;usePrepareContractWrite → useSimulateContract&lt;/code&gt; + TODO&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;WatchContractEvents.tsx&lt;/code&gt; / What changed: &lt;code&gt;useContractEvent → useWatchContractEvent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;SendTransactionPrepared.tsx&lt;/code&gt; / What changed: &lt;code&gt;usePrepareContractWrite → useSimulateContract&lt;/code&gt; + TODO&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;NetworkSwitcher.tsx&lt;/code&gt; / What changed: &lt;code&gt;useNetwork → useChainId, useSwitchNetwork → useSwitchChain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;Balance.tsx&lt;/code&gt; / What changed: &lt;code&gt;watch: true&lt;/code&gt; removed + TODO&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;_app.tsx&lt;/code&gt; / What changed: &lt;code&gt;WagmiConfig → WagmiProvider, createClient → createConfig&lt;/code&gt;, connectors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero false positives. Every change is correct. The 7 TODO comments are all legitimate patterns that require semantic understanding.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Automation vs Manual Effort&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Category: Hook renames (11 hooks) / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Provider rename / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Import cleanup / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Connector migration / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Config API rename / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Query param restructure / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: Watch prop removal / Effort: 100% automated&lt;/li&gt;
&lt;li&gt;Category: configureChains → Viem transports / Effort: AI/manual (provider-specific)&lt;/li&gt;
&lt;li&gt;Category: write → writeContract + args migration / Effort: AI/manual (call-site-specific)&lt;/li&gt;
&lt;li&gt;Category: chain → chainId type change / Effort: AI/manual (usage-specific)&lt;/li&gt;
&lt;li&gt;Category: useSigner → WalletClient usage / Effort: AI/manual (type-specific)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;~85% fully automated. ~15% flagged for AI with exact context.&lt;br&gt;
On a typical 50-file DeFi frontend, this turns a 2-day manual migration into:&lt;/p&gt;

&lt;p&gt;30 seconds to run the codemod&lt;br&gt;
1-2 hours to resolve the 15% TODOs with AI assistance&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Key Lessons&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Import position matters more than you think.&lt;/strong&gt;
Pattern matching with &lt;code&gt;$$$A, hookName, $$$B&lt;/code&gt; fails when the hook is the first or last specifier. Text-based import detection with word boundaries is more reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The STALE set in cleanup transforms must be carefully curated.&lt;/strong&gt;
We initially included all v1 hook names in the cleanup transform's STALE set. This caused &lt;code&gt;useSwitchChain&lt;/code&gt; to disappear from imports because the cleanup ran after the rename and removed the already-renamed specifier. Only hooks that are genuinely removed (not renamed) belong in the STALE set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String-aware parsing is non-negotiable.&lt;/strong&gt;
A naive comma split on object properties breaks on any string value containing a comma. Worth the extra 20 lines to implement correctly.&lt;/li&gt;
&lt;li&gt;*&lt;em&gt;watch: true is not uniformly removed.
*&lt;/em&gt;&lt;code&gt;useBlockNumber&lt;/code&gt; and &lt;code&gt;useBlock&lt;/code&gt; still accept &lt;code&gt;watch: true&lt;/code&gt; in v2. An overzealous transform that removes it everywhere would introduce bugs, not fix them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TODOs at the call site, not the file top.&lt;/strong&gt;
The AI step is much more effective when TODO comments appear immediately before the code that needs changing, with the full context of the surrounding logic visible.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Try It&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bashnpx codemod waleedbhattiii-wagmi-v1-to-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or run individual transforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bashnpx codemod jssg run &lt;span class="nt"&gt;--language&lt;/span&gt; tsx ./scripts/01-rename-hooks.ts &lt;span class="nt"&gt;--target&lt;/span&gt; ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Waleedbhattiii/wagmi-v1-to-v2" rel="noopener noreferrer"&gt;https://github.com/Waleedbhattiii/wagmi-v1-to-v2&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Registry:&lt;/strong&gt; &lt;a href="https://app.codemod.com/registry/waleedbhattiii-wagmi-v1-to-v2" rel="noopener noreferrer"&gt;https://app.codemod.com/registry/waleedbhattiii-wagmi-v1-to-v2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's Next&lt;/strong&gt;&lt;br&gt;
The next step is getting this referenced in the official wagmi migration guide. If the wagmi team adopts it, developers upgrading wagmi won't have to find this manually — it'll be the first thing they see when they open the migration docs.&lt;br&gt;
If you maintain a project that's stuck on wagmi v1, try it and open an issue if anything doesn't work. Every real-world repo has edge cases the test suite didn't cover.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the Codemod Hackathon. If this helped you, consider starring the repo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wagmi</category>
      <category>typescript</category>
      <category>web3</category>
      <category>codemod</category>
    </item>
    <item>
      <title>I built a multiplayer crypto price prediction game using Pyth Network oracles</title>
      <dc:creator>Waleed</dc:creator>
      <pubDate>Sun, 22 Mar 2026 10:45:37 +0000</pubDate>
      <link>https://dev.to/waleed_7c/i-built-a-multiplayer-crypto-price-prediction-game-using-pyth-network-oracles-84c</link>
      <guid>https://dev.to/waleed_7c/i-built-a-multiplayer-crypto-price-prediction-game-using-pyth-network-oracles-84c</guid>
      <description>&lt;p&gt;A few weeks ago I started wondering, what if you could turn live crypto price data into a competitive multiplayer game? No wallets, no tokens, no risk. Just raw price feeds and your instincts.&lt;br&gt;
That became Price Royale.&lt;br&gt;
&lt;strong&gt;What it is&lt;/strong&gt;&lt;br&gt;
Price Royale is a free-to-play PvP game where players predict whether ETH, BTC, or SOL will go UP or DOWN within a configurable time window (15–90 seconds). Multiple players join the same room, everyone locks in their prediction, and a live Pyth oracle price feed settles the outcome when time runs out.&lt;br&gt;
The thing I'm most proud of: your entry price is recorded at the exact moment you click,`` not the round start price. So if you wait 20 seconds before committing, you're betting from a different baseline than someone who clicked immediately. It creates real strategy around timing.&lt;br&gt;
&lt;strong&gt;Why Pyth specifically&lt;/strong&gt;&lt;br&gt;
I needed price feeds that were:&lt;/p&gt;

&lt;p&gt;Fast enough to update during a 60-second game round&lt;br&gt;
Accurate enough that players would trust the settlement&lt;br&gt;
Easy to integrate without blockchain complexity&lt;/p&gt;

&lt;p&gt;Pyth's Hermes REST API fit perfectly. Three feeds, one endpoint, polling every 3 seconds:&lt;br&gt;
&lt;code&gt;jsconst res = await axios.get(&lt;br&gt;
  &lt;/code&gt;&lt;a href="https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=$%7BfeedId%7D%60%7B%" rel="noopener noreferrer"&gt;https://hermes.pyth.network/v2/updates/price/latest?ids[]=${feedId}`{%&lt;/a&gt; endraw %}&lt;br&gt;
);&lt;br&gt;
const price = res.data.parsed[0].price.price * Math.pow(10, expo);{% raw %}&lt;code&gt;&lt;br&gt;
But the feature I didn't expect to love: the confidence interval. Every Pyth price comes with a conf value representing the spread of oracle reports. I use this as a score multiplier — when the CI is wide (market is uncertain/volatile), a correct prediction earns up to 1.5× points. Players who time their commits during high-volatility moments get rewarded more.&lt;br&gt;
&lt;/code&gt;jsconst confBps = (conf / price) * 10000;&lt;br&gt;
let ciMultiplier = 1.0;&lt;br&gt;
if (confBps &amp;gt;= 50)      ciMultiplier = 1.5;&lt;br&gt;
else if (confBps &amp;gt;= 25) ciMultiplier = 1.25;&lt;br&gt;
else if (confBps &amp;gt;= 10) ciMultiplier = 1.1;`&lt;br&gt;
It's a small touch but it makes the game feel genuinely connected to real market conditions.&lt;br&gt;
&lt;strong&gt;The architecture&lt;/strong&gt;&lt;br&gt;
Backend is Node.js + Express + Socket.io. Frontend is Vite + React. No blockchain, no wallet connection required — just username/password or Discord OAuth.&lt;br&gt;
The real-time game loop runs entirely over WebSockets:&lt;/p&gt;

&lt;p&gt;Host creates a room, shares the 6-letter code&lt;br&gt;
Players join and see the live price ticker&lt;br&gt;
Host starts, a 30-second commit window opens&lt;br&gt;
Each player picks UP or DOWN (their personal entry price is snapped at that moment)&lt;br&gt;
Chart keeps running after commit window closes&lt;br&gt;
At round end, Pyth settles, result screen shows who was right, with CI/speed/streak bonuses&lt;/p&gt;

&lt;p&gt;Three game modes: custom rooms, Quick Royale (auto-start when 2+ join), and Tournament (bracket elimination, 4–32 players).&lt;br&gt;
&lt;strong&gt;Hardest bug I hit&lt;/strong&gt;&lt;br&gt;
The scariest moment was discovering that socket.join() in Socket.io v4 is async, it returns a Promise. I wasn't awaiting it. So when a player's socket reconnected and I transferred their room membership to the new socket before disconnecting the old one, the join hadn't actually completed yet. The game:starting broadcast fired, but the new socket wasn't in the room channel. Players would click Start and just... sit there. Spent way too long on this one.&lt;br&gt;
Fix was two lines:&lt;br&gt;
&lt;code&gt;jsawait socket.join(prevSocket.currentRoomId);&lt;/code&gt;&lt;br&gt;
*&lt;em&gt;Stack&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Frontend: React 18 (Vite), Socket.io-client, TradingView Lightweight Charts&lt;br&gt;
Backend: Node.js, Express, Socket.io&lt;br&gt;
Database: MongoDB Atlas&lt;br&gt;
Oracle: Pyth Network Hermes REST API&lt;br&gt;
Auth: JWT + Discord OAuth2&lt;br&gt;
Deployed: Railway (backend), Vercel (frontend)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;br&gt;
Live: &lt;a href="https://price-royale.vercel.app" rel="noopener noreferrer"&gt;https://price-royale.vercel.app&lt;/a&gt;&lt;br&gt;
Code: &lt;a href="https://github.com/waleedbhattiii/price-royale" rel="noopener noreferrer"&gt;https://github.com/waleedbhattiii/price-royale&lt;/a&gt;&lt;br&gt;
Would love feedback, especially if you find edge cases in the tournament bracket logic. That part was its own adventure.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>cryptocurrency</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
