Skip to main content

Sign a typed-data message

EIP-712 typed data is the standard for signatures a contract will verify later — permits, orders, votes. Unlike a raw message, Core renders every field on the approval screen, so the user sees amount: 1000 and deadline: …, not a hash.

This guide signs an ERC-20 permit (EIP-2612) — the most common typed-data message in production.

1. Build the typed payload

const domain = {
name: 'USD Coin', // must match the token contract
version: '2',
chainId: 43114,
verifyingContract: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // USDC
};

const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
};

const message = {
owner: account,
spender: spenderAddress,
value: '1000000', // 1 USDC (6 decimals)
nonce: await usdc.read.nonces([account]), // from the token
deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour
};

The domain binds the signature to one contract on one chain; the nonce makes it single-use. Get the nonce from the token contract itself.

2. Request the signature

const signature = await provider.request({
method: 'eth_signTypedData_v4',
params: [
account,
JSON.stringify({
domain,
message,
primaryType: 'Permit',
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
...types,
},
}),
],
});

Note the shape: params are [address, jsonString], and the JSON must include EIP712Domain in types even though the provider hashes it for you.

3. Use the signature

Split it and call the contract:

const r = signature.slice(0, 66);
const s = '0x' + signature.slice(66, 130);
const v = parseInt(signature.slice(130, 132), 16);

await usdc.write.permit([account, spenderAddress, value, deadline, v, r, s]);

The spender can now transferFrom up to value until deadline — one signature instead of an approve transaction.

Handle the decline

A 4001 rejection means the user said no on the approval screen. Treat it as a cancelled action, not a failure — see error codes.