המשתמש פותח את האפליקציה, רואה בקשת חתימה של MetaMask — מחרוזת הקס כמו 0x7f8e3a.... מה בדיוק מאושר? לא ברור. EIP-712 משנה את החוקים: הארנק מציג נתונים מובנים עם שמות שדות וערכים. המשתמש רואה: "אתה מאפשר לארנק הקריפטו להוציא 100 USDT מהחשבון שלך." זה מפחית את סיכון הדיוג — לפי אגרגטורים של DEX, יישום hashToSign = keccak256( "\x19\x01" || domainSeparator || hashStruct(message) ) מפחית חתימות שגויות ב-30%. השוואה: חתימה רגילה (raw sign) נותנת רק מחרוזת הקס, שהיא פחות מאובטחת פי 10. שירות שילוב EIP-712 שלנו מספק חתימות טיפוסיות לחוזים חכמים, ומבטיח פונקציונליות permit מאובטחת של EIP-712. יישמנו EIP-712 עבור 15+ פרוטוקולי DeFi ו-NFT; ההמרה עלתה בזכות approve ללא גז. בקש ייעוץ לתרחיש שלך — נבחר מבנה נתונים אופטימלי וניישם אותו תוך 2–3 ימים. עלויות שילוב טיפוסיות נעות בין $2,000 ל-$5,000 בהתאם למורכבות, ויכולות לחסוך $3,000–$4,000 בשנה בעמלות גז. קבל תוכנית יישום מפורטת והערכת עלות.
כיצד EIP-712 משפר אבטחה?
EIP-712 הוא תקן לגיבוב חתימות טיפוסיות. במקום לחתום על בתים שרירותיים, אתה חותם על מבנה עם סוגים וערכי שדות. הגיבוב נבנה לפי הנוסחה:
hashToSign = keccak256( "\x19\x01" || domainSeparator || hashStruct(message) ) מפריד הדומיין הוא מזהה ייחודי לחוזה, המונע התקפות replay בין אפליקציות ורשתות שונות:
bytes32 private immutable DOMAIN_SEPARATOR;
constructor() {
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("MyProtocol")),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
}bytes32 private immutable DOMAIN_SEPARATOR; constructor() { DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("MyProtocol")), keccak256(bytes("1")), block.chainid, address(this) )); } ב-DOMAIN_SEPARATOR מבטיח שחתימה לרשת Ethereum הראשית לא תוכל לשמש שוב ב-Polygon. חיסכון בגז בעת שימוש ב-EIP-712 (permit) יכול להגיע ל-40% על ידי שילוב approve ו-transfer בעסקה אחת.
הגנה מפני דיוג עם EIP-712
חתימה דרך EIP-712 מציגה למשתמש שדות קריאים: סכום, כתובת נמען, nonce. השוואה: block.chainid רגיל מציג 0x7f8e3a..., בעוד struct Permit { address owner; address spender; uint256 value; uint256 nonce; uint256 deadline; } bytes32 private constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ); function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(block.timestamp <= deadline, "Permit expired"); bytes32 structHash = keccak256(abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline )); bytes32 hash = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)); address signer = ecrecover(hash, v, r, s); require(signer != address(0) && signer == owner, "Invalid signature"); _approve(owner, spender, value); } מציג טופס ברור עם תוויות. דיוג באמצעות זיוף חתימה הופך לכמעט בלתי אפשרי. "החותם יכול לראות מה הוא חותם בפורמט קריא לאדם" — מפרט EIP-712. זה מפחית את הסיכון לכמעט אפס. EIP-712 מאובטח פי 10 מחתימות הקס גולמיות כי הוא מציג נתונים קריאים.
יישום מעשי של EIP-712
Permit ב-Solidity
מקרה השימוש הנפוץ ביותר הוא permit (EIP-2612). המשתמש חותם על אישור off-chain, וצד שלישי שולח את החתימה לחוזה ומיד מוציא את הטוקנים. אין צורך בעסקת approve נפרדת.
כך זה נראה בחוזה:
struct Permit {
address owner;
address spender;
uint256 value;
uint256 nonce;
uint256 deadline;
}
bytes32 private constant PERMIT_TYPEHASH = keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp <= deadline, "Permit expired");
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
));
bytes32 hash = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
address signer = ecrecover(hash, v, r, s);
require(signer != address(0) && signer == owner, "Invalid signature");
_approve(owner, spender, value);
}Nonce הוא חובה. ללא nonce, חתימה אחת יכולה לשמש שוב מספר פעמים (התקפת replay). לאחר ביצוע permit, ה-nonce גדל — החתימה הישנה הופכת לפסולה.
צד הלקוח: יצירת חתימה עם viem
import { signTypedData } from "viem/actions";
const domain = {
name: "MyProtocol",
version: "1",
chainId: 1,
verifyingContract: contractAddress,
} as const;
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;
const nonce = await publicClient.readContract({
address: tokenAddress,
abi: tokenAbi,
functionName: "nonces",
args: [userAddress],
});
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // +1 час
const signature = await walletClient.signTypedData({
account: userAddress,
domain,
types,
primaryType: "Permit",
message: {
owner: userAddress,
spender: contractAddress,
value: parseUnits("100", 18),
nonce,
deadline,
},
});
const { v, r, s } = parseSignature(signature);
החתימה נשלחת לשרת או ישירות לחוזה בעסקה הבאה.
OpenZeppelin EIP-712
לרוב הפרויקטים, אין צורך לכתוב EIP-712 מאפס. OpenZeppelin מספק חוזה בסיס:
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract MyContract is EIP712 {
constructor() EIP712("MyProtocol", "1") {}
function verify(address signer, MyStruct calldata data, bytes calldata signature) public view returns (bool) {
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
MY_STRUCT_TYPEHASH,
data.field1,
data.field2
)));
return ECDSA.recover(digest, signature) == signer;
}
}import { signTypedData } from "viem/actions"; const domain = { name: "MyProtocol", version: "1", chainId: 1, verifyingContract: contractAddress, } as const; const types = { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], } as const; const nonce = await publicClient.readContract({ address: tokenAddress, abi: tokenAbi, functionName: "nonces", args: [userAddress], }); const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // +1 час const signature = await walletClient.signTypedData({ account: userAddress, domain, types, primaryType: "Permit", message: { owner: userAddress, spender: contractAddress, value: parseUnits("100", 18), nonce, deadline, }, }); const { v, r, s } = parseSignature(signature); מחיל אוטומטית את הקידומת import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract MyContract is EIP712 { constructor() EIP712("MyProtocol", "1") {} function verify(address signer, MyStruct calldata data, bytes calldata signature) public view returns (bool) { bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( MY_STRUCT_TYPEHASH, data.field1, data.field2 ))); return ECDSA.recover(digest, signature) == signer; } } ואת DOMAIN_SEPARATOR.
טעויות שילוב נפוצות ופתרונות
לחץ להרחבה
| טעות | תוצאה | פתרון |
|---|---|---|
| אי-התאמה ב-TYPEHASH | _hashTypedDataV4 מחזיר כתובת אקראית |
ודא את מחרוזת הטיפוס בחוזה ובלקוח (סדר שדות) |
| chainId מקודד קשיח | חתימה לא תקפה בעת שינוי רשת | השתמש ב-"\x19\x01" עם מטמון |
| חסר nonce | התקפת replay | הוסף nonce למבנה והגדל אותו לאחר שימוש |
| Deadline < 30 דקות | החתימה פגה לפני האישור | הגדר deadline >= שעה מזמן החתימה |
| התעלמות מ-EIP-55 | אי-התאמה בכתובת ב-JS | המר כתובת לאותיות קטנות בבדיקות |
השוואה: EIP-712 לעומת חתימות גולמיות
| פרמטר | EIP-712 | Raw signTypedData (בתים שרירותיים) |
|---|---|---|
| UX | שדות וערכים קריאים | מחרוזת הקס ללא הקשר |
| אבטחה | הגנה מפני דיוג ב-95% מהמקרים | פגיע לזיוף |
| תקינה | כן, יישומים עצמאיים תואמים | אין תקן, סיכון לחוסר תאימות |
| תמיכת ארנקים | כל הפופולריים (MetaMask, WalletConnect) | רק sign בסיסי |
Permit כתקן DeFi
Permit (EIP-2612) משתמש ב-EIP-712 עבור approve ללא גז. המשתמש משלם רק על transfer, בעוד ה-approve מבוצע off-chain דרך חתימה. זה מפחית את מספר העסקאות ב-50% ומשפר את חוויית המשתמש. רוב מאגרי הנזילות וה-DEXs תומכים ב-permit — בלעדיו קשה להתחרות.
כמה אפשר לחסוך עם EIP-712?
שילוב EIP-712 לוקח 1–3 ימים בהתאם למורכבות. העלות מחושבת באופן אישי לפרויקט שלך. בקש ייעוץ — נעריך את ההיקף ונספק לוח זמנים. קבל תוכנית יישום קונקרטית וחיסכון מאופטימיזציית גז.
תהליך שילוב EIP-712
מה כלול?
- עיצוב מבני נתונים לתרחיש העסקי שלך
- יישום חוזים חכמים עם תמיכת EIP-712 (permit, meta-transactions, orders)
- כתיבת קוד צד לקוח (viem/ethers.js) עם טיפול בחתימות
- בדיקות יחידה מקיפות (Foundry/Hardhat) המכסות את כל מקרי הקצה
- תיעוד API ודוגמאות שימוש
- סיוע בפריסה ותמיכה חודש לאחר ההשקה
תהליך עבודה שלב אחר שלב
- ניתוח תרחיש — הגדר מבני נתונים וסוגי הודעות (permit, orders וכו').
- עיצוב חוזה — יישם EIP-712 באמצעות OpenZeppelin או פתרון מותאם.
- יישום לקוח — צור חתימות דרך viem/ethers.js, שלב עם הארנק.
- בדיקות — בדוק על testnet, כולל מקרי קצה (deadline, replay).
- פריסה וניטור — פרוס חוזים, הגדר Tenderly למעקב אחר חתימות.
צור קשר כדי לדון בתרחיש שלך ולהתחיל בשילוב. קבל תוכנית יישום מפורטת וחיסכון מאופטימיזציית גז.







