תארו לעצמכם שאפליקציית React שלכם משתמשת ב-ethers.js כדי לתקשר עם חוזים. כשהמשתמש מחליף רשת, הנתונים לא מתעדכנים, ובקשות N+1 ל-RPC חונקות את האפליקציה. נתקלנו בזה פעמים רבות — חוסר התאמה בהידרציה ב-Next.js, דליפות זיכרון ממצב משתנה של ethers.js. הפתרון הוא שילוב wagmi + viem. הניסיון שלנו בפיתוח Web3 עולה על 5 שנים, ויישמנו פתרון זה ביותר מ-10 פרויקטי DeFi. המעבר ל-wagmi/viem מפחית את נפח הקוד פי 2–3, מוריד את ה-TTFB ב-40%, ומפחית את עלויות בקשות ה-RPC הודות למטמון אוטומטי.
למה ethers.js מאט את הביצועים
ethers.js היא ספרייה מונחית מחלקות עם מצב משתנה. כל useReadContract יוצר אובייקט המקושר לספק. ב-React זה בעייתי: כשהרשת או החשבון משתנים, יש ליצור את האובייקט מחדש, אחרת הנתונים לא מתעדכנים. Viem בנוי על פונקציות וקליינטים בלתי משתנים. Wagmi מנהל אוטומטית את מחזור החיים של הקליינט — ה-hooks מתעדכנים בכל שינוי בארנק. לפי הסטטיסטיקות שלנו, המעבר ל-wagmi/viem מפחית את נפח הקוד פי 2–3 ומוריד את ה-TTFB ב-40%.
השוואה: ethers.js לעומת wagmi/viem
| קריטריון | ethers.js | wagmi + viem |
|---|---|---|
| מצב | משתנה, אובייקטים חיים בקונטקסט | בלתי משתנה, אין צורך ביצירה מחדש |
| אינטגרציית React | ידנית באמצעות useState/useEffect | hooks מובנים עם מטמון |
| עדכון בעת החלפת רשת | צריך להירשם לאירועים | אחזור אוטומטי באמצעות watch |
| טיפוסים | חלש, המרות ידניות | טיפוסים מלאים באמצעות @wagmi/cli |
| ביצועים | אחזור גבוה יותר עקב מחלקות | TTFB נמוך יותר עקב קריאות עצלות |
איך wagmi/viem פותרים את הבעיות של ethers.js
ה-hooks של Wagmi נרשמים אוטומטית לאירועי ארנק (chainChanged, accountsChanged). זה מבטל בעיות של נתונים מיושנים. הקליינטים של Viem הם בלתי משתנים — כל קריאה מחזירה תוצאה חדשה. יחד הם מספקים ראקטיביות ללא דליפות זיכרון. לדוגמה, useWatchContractEvent מאחזר נתונים אוטומטית בעת החלפת רשת, ו-useReadErc20BalanceOf מסנן אירועים מבלי ליצור הרשמות חדשות בכל רינדור מחדש. אנו משתמשים בגישה זו בפרויקטים בייצור, ומפחיתים את עומס ה-RPC ומשפרים את חוויית המשתמש. כפי שצוין ב-התיעוד הרשמי של viem, "viem נועד להיות מודולרי, ניתן ל-tree-shaking, ובטוח בטיפוסים כברירת מחדל".
מה מביאה יצירת טיפוסי ABI
ללא יצירת טיפוסים, כל קריאה לחוזה דורשת ציון ידני של ה-ABI והמרת תוצאות. Wagmi CLI יוצר טיפוסים מקבצי ה-ABI שלכם בפקודה אחת. לאחר היצירה, מקבלים hooks עם טיפוסים מלאים לארגומנטים ולערכי החזרה. זה מבטל שגיאות בכתובות, בארגומנטים, ומאפשר לעורך להציע ערכים אפשריים. בפשטות, מייבאים את npm i -D @wagmi/cli npx wagmi generate ומעבירים את הכתובת הנדרשת — הכל נבדק בזמן הקומפילציה.
npm i -D @wagmi/cli npx wagmi generate איך אנו מיישמים wagmi/viem: שלב אחר שלב
הגדרת תצורה
// lib/wagmi.ts
import { createConfig, http } from 'wagmi';
import { mainnet, arbitrum, base } from 'wagmi/chains';
import { injected, walletConnect } from 'wagmi/connectors';
export const config = createConfig({
chains: [mainnet, arbitrum, base],
connectors: [
injected(),
walletConnect({
projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
}),
],
transports: {
[mainnet.id]: http(process.env.ETH_RPC_URL!),
[arbitrum.id]: http(process.env.ARBITRUM_RPC_URL!),
[base.id]: http(process.env.BASE_RPC_URL!),
},
});
קריאת נתונים מחוזה
// hooks/useTokenData.ts
import { useReadContracts } from 'wagmi';
import { erc20Abi, formatUnits } from 'viem';
export function useTokenData(
tokenAddress: `0x${string}`,
userAddress?: `0x${string}`
) {
const { data, isLoading } = useReadContracts({
contracts: [
{
address: tokenAddress,
abi: erc20Abi,
functionName: 'name',
},
{
address: tokenAddress,
abi: erc20Abi,
functionName: 'symbol',
},
{
address: tokenAddress,
abi: erc20Abi,
functionName: 'decimals',
},
{
address: tokenAddress,
abi: erc20Abi,
functionName: 'totalSupply',
},
...(userAddress
? [
{
address: tokenAddress,
abi: erc20Abi,
functionName: 'balanceOf' as const,
args: [userAddress] as [`0x${string}`],
},
]
: []),
],
query: {
refetchInterval: 30_000,
staleTime: 10_000,
},
});
const decimals = (data?.[2].result as number) ?? 18;
return {
isLoading,
name: data?.[0].result as string | undefined,
symbol: data?.[1].result as string | undefined,
decimals,
totalSupply: data?.[3].result
? formatUnits(data[3].result as bigint, decimals)
: undefined,
userBalance:
userAddress && data?.[4]?.result
? formatUnits(data[4].result as bigint, decimals)
: undefined,
};
}
כתיבה לחוזה עם סימולציה
// hooks/useTokenTransfer.ts
import { useWriteContract, useWaitForTransactionReceipt, useSimulateContract } from 'wagmi';
import { erc20Abi, parseUnits } from 'viem';
import { useState } from 'react';
export function useTokenTransfer(tokenAddress: `0x${string}`, decimals: number) {
const [recipient, setRecipient] = useState<`0x${string}` | undefined>();
const [amount, setAmount] = useState('');
const amountWei = amount ? parseUnits(amount, decimals) : 0n;
const { error: simError } = useSimulateContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'transfer',
args: [recipient!, amountWei],
query: { enabled: !!recipient && amountWei > 0n },
});
const { writeContract, data: txHash, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash: txHash });
const transfer = () => {
if (!recipient || amountWei === 0n) return;
writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'transfer',
args: [recipient, amountWei],
});
};
return {
recipient,
setRecipient,
amount,
setAmount,
simError,
transfer,
txHash,
isPending,
isConfirming,
isSuccess,
};
} קליינט viem בצד השרת
// lib/publicClient.ts
import { createPublicClient, http, createWalletClient } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
export const publicClient = createPublicClient({
chain: mainnet,
transport: http(process.env.ETH_RPC_URL!),
});
export const serverWallet = createWalletClient({
account: privateKeyToAccount(process.env.RELAYER_PRIVATE_KEY as `0x${string}`),
chain: mainnet,
transport: http(process.env.ETH_RPC_URL!),
});
יצירת טיפוסים מ-ABI
npm i -D @wagmi/cli npx wagmi generate לאחר היצירה, מופיעים hooks כמו // lib/wagmi.ts import { createConfig, http } from 'wagmi'; import { mainnet, arbitrum, base } from 'wagmi/chains'; import { injected, walletConnect } from 'wagmi/connectors'; export const config = createConfig({ chains: [mainnet, arbitrum, base], connectors: [ injected(), walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID! }), ], transports: { [mainnet.id]: http(process.env.ETH_RPC_URL!), [arbitrum.id]: http(process.env.ARBITRUM_RPC_URL!), [base.id]: http(process.env.BASE_RPC_URL!), }, }); עם טיפוסי ארגומנטים מלאים.
דוגמת שימוש ב-Next.js App Router
ברכיבי שרת, השתמשו ב-// hooks/useTokenData.ts import { useReadContracts } from 'wagmi'; import { erc20Abi, formatUnits } from 'viem'; export function useTokenData(tokenAddress: `0x${string}`, userAddress?: `0x${string}`) { const { data, isLoading } = useReadContracts({ contracts: [ { address: tokenAddress, abi: erc20Abi, functionName: 'name' }, { address: tokenAddress, abi: erc20Abi, functionName: 'symbol' }, { address: tokenAddress, abi: erc20Abi, functionName: 'decimals' }, { address: tokenAddress, abi: erc20Abi, functionName: 'totalSupply' }, ...(userAddress ? [{ address: tokenAddress, abi: erc20Abi, functionName: 'balanceOf' as const, args: [userAddress] as [`0x${string}`], }] : []), ], query: { refetchInterval: 30_000, staleTime: 10_000, }, }); const decimals = (data?.[2].result as number) ?? 18; return { isLoading, name: data?.[0].result as string | undefined, symbol: data?.[1].result as string | undefined, decimals, totalSupply: data?.[3].result ? formatUnits(data[3].result as bigint, decimals) : undefined, userBalance: userAddress && data?.[4]?.result ? formatUnits(data[4].result as bigint, decimals) : undefined, }; } ישירות מכיוון ש-// hooks/useTokenTransfer.ts import { useWriteContract, useWaitForTransactionReceipt, useSimulateContract } from 'wagmi'; import { erc20Abi, parseUnits } from 'viem'; import { useState } from 'react'; export function useTokenTransfer(tokenAddress: `0x${string}`, decimals: number) { const [recipient, setRecipient] = useState<`0x${string}` | undefined>(); const [amount, setAmount] = useState(''); const amountWei = amount ? parseUnits(amount, decimals) : 0n; const { error: simError } = useSimulateContract({ address: tokenAddress, abi: erc20Abi, functionName: 'transfer', args: [recipient!, amountWei], query: { enabled: !!recipient && amountWei > 0n }, }); const { writeContract, data: txHash, isPending } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash: txHash }); const transfer = () => { if (!recipient || amountWei === 0n) return; writeContract({ address: tokenAddress, abi: erc20Abi, functionName: 'transfer', args: [recipient, amountWei], }); }; return { recipient, setRecipient, amount, setAmount, simError, transfer, txHash, isPending, isConfirming, isSuccess, }; } עובד רק בצד הלקוח. צרו // lib/publicClient.ts import { createPublicClient, http, createWalletClient } from 'viem'; import { mainnet } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; export const publicClient = createPublicClient({ chain: mainnet, transport: http(process.env.ETH_RPC_URL!), }); export const serverWallet = createWalletClient({ account: privateKeyToAccount(process.env.RELAYER_PRIVATE_KEY as `0x${string}`), chain: mainnet, transport: http(process.env.ETH_RPC_URL!), }); והשתמשו בו לקריאות נתונים. לדוגמה, ב-npm i -D @wagmi/cli npx wagmi generate , ייבאו את useReadErc20BalanceOf(...) מ-viem וקראו למתודות ללא hooks של React.
תהליך העבודה שלנו
- ביקורת — ניתוח היישום הנוכחי, זיהוי בעיות (דליפות זיכרון, נתונים מיושנים).
- עיצוב — עיצוב שכבת האינטראקציה: תצורות, hooks, קליינטים לשרת.
- יישום — כתיבת קוד עם יצירת טיפוסים, כיסוי מקרי קצה.
- בדיקות — סימולציות, בדיקות רשת, בדיקות ידניות עם ארנקים שונים.
- פריסה — פריסה, ניטור, תיעוד.
מה כלול
- קוד אינטגרציה עם wagmi/viem: hooks עם טיפוסים.
- תיעוד לשימוש ולהמשך פיתוח.
- הגדרת CI/CD לעדכוני ABI אוטומטיים.
- הדרכת צוות (1–2 מפגשים).
- אחריות תאימות עם MetaMask, WalletConnect, Coinbase Wallet.
לוחות זמנים
אינטגרציה בסיסית (קריאה/כתיבה עבור 1–2 חוזים) — 1–2 ימים. שכבה מלאה עם יצירת טיפוסים, קליינטים לשרת והרשמות לאירועים — 2–3 ימים. לוחות הזמנים מותאמים לאחר ביקורת בפרויקט שלכם.
הזמינו ביקורת על האינטגרציה שלכם — נמצא צווארי בקבוק תוך יום אחד
השוואת גישות: ethers.js לעומת wagmi/viem
| פרמטר | ethers.js | wagmi/viem |
|---|---|---|
| קוד תבניתי | גבוה (ניהול ספק ידני) | נמוך (תצורה אוטומטית) |
| עדכון נתונים | ידני באמצעות useEffect | אוטומטי באמצעות watch |
| בטיחות טיפוסים | לא | מלאה (באמצעות @wagmi/cli) |
| ביצועים | ~2 אלפיות שנייה לקריאה | <1 אלפית שנייה לקריאה (הערכה עצלה) |
| תמיכה בצד השרת | מורכבת (דורש ethers.providers) | פשוטה (viem createPublicClient) |
צרו קשר — נדון בפרטי הפרויקט שלכם ונבחר את הארכיטקטורה האופטימלית.







