This guide will teach you how to sign EIP-712 typed data with your Smart Wallet. Typed data signing provides a more structured and secure way to sign complex data compared to plain text messages.
- API key from your dashboard
- A Smart Wallet with an associated signer
When using EIP-7702, your account must be delegated before signatures will be valid. Send at least one transaction to delegate your account first.
EIP-712 typed data signing allows users to:
- Sign structured data with clear type definitions
- Improve user experience with readable signature requests
- Enhance security through type safety and domain separation
Typed data follows the EIP-712 standard, which provides a way to encode structured data for signing.
@alchemy/wallet-apis (v5.x.x) is currently in beta but is the recommended replacement for @account-kit/wallet-client (v4.x.x). If you run into any issues, please reach out.See the signTypedData SDK reference for full parameter descriptions.
import { createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { client } from "./client";
const signerAddress = client.account.address;
// Sign typed data
const typedData = {
domain: {
name: "MyDApp",
version: "1",
chainId: 1,
verifyingContract: "0x...",
},
types: {
Auth: [
{ name: "user", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "timestamp", type: "uint256" },
],
},
primaryType: "Auth",
message: {
user: signerAddress,
nonce: 12345,
timestamp: Date.now(),
},
} as const;
const signature = await client.signTypedData({
...typedData,
});
console.log("Signature:", signature);
// Verify the signature
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
});
const isValid = await publicClient.verifyTypedData({
address: client.account.address,
...typedData,
signature,
});
console.log("Valid:", isValid); // trueUsing @account-kit/wallet-client (v4.x.x)?
The examples on this page use @alchemy/wallet-apis (v5.x.x). If you're using @account-kit/wallet-client (v4.x.x), the client setup looks like this:
import { LocalAccountSigner } from "@aa-sdk/core";
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { alchemy, sepolia } from "@account-kit/infra";
const signer = LocalAccountSigner.privateKeyToAccountSigner("0xYOUR_PRIVATE_KEY" as const);
export const client = createSmartWalletClient({
transport: alchemy({ apiKey: "YOUR_API_KEY" }),
chain: sepolia,
signer,
account: signer.address, // can also be passed per action as `from` or `account`
// Optional: sponsor gas for your users (see "Sponsor gas" guide)
policyId: "YOUR_POLICY_ID",
});Key v4.x.x differences:
- Account address must be specified on the client or per action (
fromoraccount). In v5.x.x, the client automatically uses the signer's address as the account address via EIP-7702. - Chain imports come directly from
@account-kit/infrainstead ofviem/chains. - Numeric values use hex strings:
value: "0x0"instead ofvalue: BigInt(0). - In v4.x.x, the paymaster capability on
prepareCallsorsendCallsis calledpaymasterServiceinstead ofpaymaster, or you can set thepolicyIddirectly on the client. - Signers use
LocalAccountSigner/WalletClientSignerfrom@aa-sdk/core. In v5.x.x, a viemLocalAccountorWalletClientis used directly.
See the full migration guide for a complete cheat sheet.
EIP-712 typed data consists of four main components:
The domain provides context and prevents signature reuse across different dApps:
const domain = {
name: "Example DApp",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
};Define the structure of your data:
const types = {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person" },
{ name: "contents", type: "string" },
],
};Specify which type is the main type being signed:
const primaryType = "Mail";The actual data to sign:
const message = {
from: {
name: "Alice",
wallet: "0xAaAaAaAaAaAaAaAaAaAAAAAAAAaaaAaAaAaaAaAa",
},
to: {
name: "Bob",
wallet: "0xBbBbBbBbBbBbBbBbBbBBBBBBBBbbBbBbBbbBbBb",
},
contents: "Hello, Bob!",
};Build more:
Troubleshooting: