Skip to Content
HyperQuote is live on HyperEVM — Start trading →
MakersEIP-712 Signing

EIP-712 Signing

Every quote submitted by a maker must be cryptographically signed using EIP-712 typed structured data . The signature is verified both by the relay (off-chain) and by the OptionsEngine contract (on-chain) using ECDSA.recover.

This page is the technical reference for the EIP-712 domain, type definitions, signing process, and verification.

EIP-712 Domain

The domain separator must match the on-chain OptionsEngine constructor, which initializes EIP712("HyperQuote Options", "1"):

import { TypedDataDomain } from "ethers"; const domain: TypedDataDomain = { name: "HyperQuote Options", version: "1", chainId: 31337, // your chain ID verifyingContract: "0x5FbDB...", // OptionsEngine contract address };

The SDK provides a helper to build this:

import { buildDomain } from "@hyperquote/sdk-maker"; const domain = buildDomain(31337, "0x5FbDB2315678afecb367f032d93F642f64180aa3");

The chainId and verifyingContract must exactly match the deployed OptionsEngine. A mismatch will produce a different domain separator, and the on-chain signature verification will fail with InvalidSignature().

Quote Type Definition

The QUOTE_TYPEHASH in Solidity is:

Quote(address maker,address taker,address underlying,address collateral,bool isCall,bool isMakerSeller,uint256 strike,uint256 quantity,uint256 premium,uint256 expiry,uint256 deadline,uint256 nonce)

The TypeScript equivalent used by the SDK:

import { TypedDataField } from "ethers"; const QUOTE_TYPES: Record<string, TypedDataField[]> = { Quote: [ { name: "maker", type: "address" }, { name: "taker", type: "address" }, { name: "underlying", type: "address" }, { name: "collateral", type: "address" }, { name: "isCall", type: "bool" }, { name: "isMakerSeller", type: "bool" }, { name: "strike", type: "uint256" }, { name: "quantity", type: "uint256" }, { name: "premium", type: "uint256" }, { name: "expiry", type: "uint256" }, { name: "deadline", type: "uint256" }, { name: "nonce", type: "uint256" }, ], };

Field order matters. The fields must appear in exactly the order shown above, matching the Solidity QuoteLib.QUOTE_TYPEHASH string. Reordering fields produces a different typehash and will cause all signatures to fail.

Signing a Quote with ethers v6

The SDK’s signQuote function uses wallet.signTypedData() from ethers v6:

import { Wallet } from "ethers"; import { signQuote } from "@hyperquote/sdk-maker"; const wallet = new Wallet("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); const quote = { maker: wallet.address, taker: "0x0000000000000000000000000000000000000000", underlying: "0x0000000000000000000000000000000000000001", collateral: "0x0000000000000000000000000000000000000002", isCall: false, isMakerSeller: false, strike: 25000000000000000000n, // $25 in 1e18 quantity: 1000000000000000000n, // 1 WHYPE premium: 1000000n, // $1 in 1e6 USDC expiry: 1700121600n, deadline: 1700035200n, nonce: 0n, }; const chainId = 31337; const engineAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; const signature = await signQuote(wallet, quote, chainId, engineAddress); // signature: "0x..." (132 hex chars = 0x + 65 bytes)

Under the Hood

The signQuote function internally does:

import { buildDomain, QUOTE_TYPES, quoteToTypedDataValue } from "@hyperquote/sdk-maker"; const domain = buildDomain(chainId, engineAddress); const value = quoteToTypedDataValue(quote); // converts Quote to plain object // ethers v6 signTypedData const signature = await wallet.signTypedData(domain, QUOTE_TYPES, value);

The quoteToTypedDataValue function converts the Quote object into the value record expected by TypedDataEncoder. Bigint fields are passed directly — ethers v6 handles them natively.

Signature Format

The signature is a 65-byte hex string (with 0x prefix, totaling 132 characters):

0x[r: 32 bytes][s: 32 bytes][v: 1 byte]
  • r (bytes 0-31): The x-coordinate of the ephemeral public key point.
  • s (bytes 32-63): The signature proof scalar.
  • v (byte 64): Recovery identifier, either 27 or 28.

The on-chain contract uses OpenZeppelin’s ECDSA.recover(digest, signature) to extract the signer address from these components.

Verification

Off-chain Verification (TypeScript)

import { verifyQuoteSignature, recoverQuoteSigner } from "@hyperquote/sdk-maker"; // Verify signature matches the maker address const isValid = verifyQuoteSignature(quote, signature, chainId, engineAddress); // isValid: true // Recover the signer address const signer = recoverQuoteSigner(quote, signature, chainId, engineAddress); // signer: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"

The verification function internally uses verifyTypedData from ethers:

import { verifyTypedData } from "ethers"; const recovered = verifyTypedData(domain, QUOTE_TYPES, value, signature); const isValid = recovered.toLowerCase() === quote.maker.toLowerCase();

On-chain Verification (Solidity)

The OptionsEngine._validateAndVerifyQuote function computes the EIP-712 digest and recovers the signer:

bytes32 digest = _hashTypedDataV4(QuoteLib.hash(quote)); address signer = ECDSA.recover(digest, signature); if (signer != quote.maker) revert InvalidSignature();

Computing Hashes Directly

The SDK also exports functions for computing intermediate hash values, useful for debugging:

import { hashQuoteStruct, hashQuoteTypedData, buildDomain, } from "@hyperquote/sdk-maker"; // Struct hash (without domain) const structHash = hashQuoteStruct(quote); // Full EIP-712 hash (the digest that gets signed) const domain = buildDomain(chainId, engineAddress); const digest = hashQuoteTypedData(domain, quote);

Cross-Verification Test Vectors

The SDK includes test vectors that are verified against Foundry/Solidity output to ensure exact cross-language compatibility:

ValueExpected Hash
QUOTE_TYPEHASH0xa658686ca3f902cf1315142c0a4df619d29c35f788c2a46c2c1dbcc66319d88a
Struct hash (test quote)0xf586aeaa8533a62ea9b68dfba2d8e28330a14e4e2e5a395f4d82f35332dda319
Domain separator (chain 31337)0x64f51da9639c393a7b73866db9f4e32fb43540402f8114e15b5416230171896e
Full digest (test quote)0x5393cf8faedae66ca0225391e477819c8a913b6af6a121d2b5ba5bf4308a71ba

These vectors are tested in sdk-maker/test/eip712.test.ts and match the Solidity EIP712CrossCheck forge test exactly.

Last updated on