Skip to content
Alchemy Logo

Sign typed data

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.

signTypedData.ts
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); // true
Using @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:

client.ts (v4.x.x)
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 (from or account). 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/infra instead of viem/chains.
  • Numeric values use hex strings: value: "0x0" instead of value: BigInt(0).
  • In v4.x.x, the paymaster capability on prepareCalls or sendCalls is called paymasterService instead of paymaster, or you can set the policyId directly on the client.
  • Signers use LocalAccountSigner / WalletClientSigner from @aa-sdk/core. In v5.x.x, a viem LocalAccount or WalletClient is 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:

Was this page helpful?