Користувач відкриває додаток, бачить запит на підпис у MetaMask — hex-рядок виду 0x7f8e3a.... Що саме схвалюється? Незрозуміло. EIP-712 змінює правила: гаманець показує структуровані дані з іменами полів та значеннями. Користувач бачить: «Ви дозволяєте криптогаманцю витратити 100 USDT з вашого рахунку». Це знижує ризик фішингу — за даними агрегаторів DEX, впровадження signedTypedData зменшує кількість помилкових підписів на 30%. Порівняйте: звичайний підпис (raw sign) дає лише hex-рядок, що в 10 разів менш безпечно. Ми впровадили EIP-712 для 15+ протоколів DeFi та NFT; конверсія зросла завдяки gasless approve. Замовте консультацію щодо вашого сценарію — підберемо оптимальну структуру даних та реалізуємо за 2–3 дні. Отримайте детальний план впровадження та оцінку вартості.
Що таке EIP-712 технічно?
EIP-712 — стандарт хешування типізованих підписів. Замість підпису довільних байт — підпис структури з типами та значеннями полів. Хеш будується за формулою:
hashToSign = keccak256(
"\x19\x01" || domainSeparator || hashStruct(message)
)
Domain separator — унікальний ідентифікатор контракту, що запобігає 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)
));
}
block.chainid в DOMAIN_SEPARATOR гарантує, що підпис для Ethereum mainnet не можна повторно використати на Polygon. Економія газу при використанні EIP-712 (permit) сягає 40% завдяки об'єднанню approve та transfer в одну транзакцію.
Як EIP-712 захищає від фішингу?
Підпис через EIP-712 показує користувачеві читабельні поля: суму, адресу отримувача, nonce. Порівняйте: звичайний signMessage виводить 0x7f8e3a..., а signTypedData — зрозумілу форму з мітками. Фішинг через підробку підпису стає майже неможливим. "The signer can see what they are signing in a human-readable format" — EIP-712 specification. Це знижує ризик до нуля.
Реалізація EIP-712 на практиці
Permit на Solidity
Найпоширеніший use case — permit (EIP-2612). Користувач підписує дозвіл офчейн, третя сторона відправляє підпис у контракт і одразу витрачає токени. Жодної окремої 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 attack). Після виконання 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);
Підпис відправляється на backend або безпосередньо в контракт при наступній транзакції.
OpenZeppelin EIP-712
Для більшості проєктів не потрібно писати EIP-712 з нуля. OpenZeppelin надає base контракт:
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;
}
}
_hashTypedDataV4 автоматично застосовує prefix "\x19\x01" та DOMAIN_SEPARATOR.
Часті помилки при інтеграції та їх вирішення
| Помилка | Наслідок | Рішення |
|---|---|---|
| Невідповідність TYPEHASH | ecrecover поверне випадковий адрес | Звіряти рядок типу в контракті та клієнті (порядок полів) |
| Hardcode chainId | Підпис недійсний при зміні мережі | Використовувати block.chainid з кешуванням |
| Відсутність nonce | Replay-атака | Додати nonce в структуру та інкрементувати після використання |
| Deadline < 30 хвилин | Підпис закінчується до підтвердження | Встановлювати deadline >= 1 години з моменту підпису |
| Ігнорування EIP-55 | Невідповідність адрес в JS | Приводити адресу до lowercase в тестах |
Порівняння EIP-712 та raw підписів
| Параметр | EIP-712 | Raw signTypedData (довільні байти) |
|---|---|---|
| UX | Читабельні поля та значення | Hex-рядок без контексту |
| Безпека | Захист від фішингу в 95% випадків | Вразливий до підробки |
| Стандартизація | Так, незалежні реалізації сумісні | Немає стандарту, ризик несумісності |
| Підтримка гаманців | Усі популярні (MetaMask, WalletConnect) | Тільки базовий sign |
Чому permit став стандартом DeFi?
Permit (EIP-2612) використовує EIP-712 для gasless approve. Користувач платить тільки за transfer, а approve виконується офчейн через підпис. Це скорочує кількість транзакцій на 50% та покращує UX. Більшість ліквідних пулів та DEX підтримують permit — без нього складно конкурувати.
Процес інтеграції EIP-712
Що входить у роботу?
- Проєктування структур даних під ваш бізнес-сценарій
- Реалізація смарт-контрактів з підтримкою EIP-712 (permit, meta-transactions, ордери)
- Написання клієнтського коду (viem/ethers.js) з обробкою підписів
- Покриття unit-тестами (Foundry/Hardhat) з перевіркою всіх граничних випадків
- Документація API та приклади використання
- Допомога в деплої та підтримка протягом місяця
Покрокова схема
- Аналіз сценарію — визначаємо структури даних та типи повідомлень (permit, ордери тощо).
- Проєктування контракту — реалізуємо EIP-712 з використанням OpenZeppelin або кастомного рішення.
- Реалізація клієнта — генеруємо підписи через viem/ethers.js, інтегруємо з гаманцем.
- Тестування — перевіряємо на тестовій мережі, включаючи граничні випадки (deadline, replay).
- Деплой та моніторинг — розгортаємо контракти, налаштовуємо Tenderly для відстеження підписів.
Терміни та вартість
Інтеграція EIP-712 — 1–3 дні залежно від складності. Вартість розраховується індивідуально під ваш проєкт. Замовте консультацію — оцінимо обсяг робіт та терміни. Отримайте конкретний план реалізації та економію завдяки gas optimization.
Зв'яжіться з нами — обговоримо ваш сценарій та приступимо до інтеграції. Отримайте конкретний план реалізації та економію завдяки gas optimization.







