Skip to content
Alchemy Logo

Sign messages

This guide will teach you how to sign messages using your Smart Wallet. Message signing is a key feature that allows users to authenticate and prove ownership of their wallet without spending gas.

Smart Wallets will generate signatures that can be validated using ERC-1271. If the wallet is an undeployed smart contract account (also known as a counterfactual address), then the signature will be wrapped according to ERC-6492.

  • 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.

Message signing allows users to:

  • Authenticate without spending gas
  • Prove ownership of their wallet
  • Sign arbitrary data for offchain verification
  • Interact with dApps that require signature-based authentication

Smart Wallets support EIP-191 message signing, which is the standard for Ethereum message signing. You may also see EIP-191 referred to as the personal_sign message format.

@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 signMessage SDK reference for full parameter descriptions.

signTextMessage.ts
import { createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { client } from "./client";
 
// Sign a simple text message
const message = "Hello, world!";
const signature = await client.signMessage({ message });
 
console.log("Signature:", signature);
 
// Verify the signature
const publicClient = createPublicClient({
  chain: arbitrumSepolia,
  transport: http(),
});
 
const isValid = await publicClient.verifyMessage({
  address: client.account.address,
  message,
  signature,
});
 
console.log("Valid:", isValid); // true

@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 signMessage SDK reference for full parameter descriptions.

signRawMessage.ts
import { createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { client } from "./client";
 
// Sign a raw hex message
const message = {
  raw: "0x48656c6c6f2c20776f726c6421" as const, // "Hello, world!" in hex
};
const signature = await client.signMessage({ message });
 
console.log("Signature:", signature);
 
// Verify the signature
const publicClient = createPublicClient({
  chain: arbitrumSepolia,
  transport: http(),
});
 
const isValid = await publicClient.verifyMessage({
  address: client.account.address,
  message,
  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.

Build more:

Troubleshooting:

Was this page helpful?