Skip to content
Alchemy Logo

Bring your own signer

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

Any signer that provides a viem LocalAccount or WalletClient works with createSmartWalletClient. This guide covers the requirements and how to set up each type.

Your signer must implement:

PropertyRequiredPurpose
addressYesThe signer's Ethereum address
signMessageYesSigns plaintext and raw messages (EIP-191)
signTypedDataYesSigns structured data (EIP-712)
signAuthorizationYes (for EIP-7702)Signs the EIP-7702 delegation authorization

signAuthorization is required for EIP-7702, which is the default mode. If your signer doesn't support it, you can use non-7702 mode instead.

Use a LocalAccount when your signer gives you direct access to a private key or a signing function. This is the recommended approach because it provides the signAuthorization interface needed for EIP-7702 delegation.

import { toAccount } from "viem/accounts";
import { arbitrumSepolia } from "viem/chains";
import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";
 
const account = toAccount({
  address: "0xYOUR_SIGNER_ADDRESS",
 
  async signMessage({ message }) {
    // delegate to your signer
  },
 
  async signTypedData(typedData) {
    // delegate to your signer
  },
 
  async signTransaction(transaction) {
    // unused by the Smart Wallet client
    throw new Error("signTransaction is not implemented");
  },
 
  async signAuthorization(unsignedAuth) {
    // required for EIP-7702 delegation
    // delegate to your signer and return { ...unsignedAuth, r, s, yParity }
  },
});
 
const client = createSmartWalletClient({
  signer: account,
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: arbitrumSepolia,
});

For a private key signer, you can use viem's built-in helper which implements all methods automatically:

import { privateKeyToAccount } from "viem/accounts";
 
const client = createSmartWalletClient({
  signer: privateKeyToAccount("0x..."),
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: arbitrumSepolia,
});

Use a WalletClient when your signer is a browser wallet or embedded wallet that exposes an EIP-1193 provider (e.g. window.ethereum).

import { createWalletClient, custom } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";
 
// Create a WalletClient from the browser wallet
const [address] = await window.ethereum.request({ method: "eth_requestAccounts" });
 
const walletClient = createWalletClient({
  account: address,
  chain: arbitrumSepolia,
  transport: custom(window.ethereum),
});
 
// Pass the WalletClient as the signer
const client = createSmartWalletClient({
  signer: walletClient,
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: arbitrumSepolia,
  paymaster: { policyId: "YOUR_GAS_POLICY_ID" }, // optional
});

EIP-7702 authorization signing requires a LocalAccount with signAuthorization implemented. If your wallet doesn't support this, you may need to use non-7702 mode instead.

Once you have a client, usage is the same regardless of signer type:

const result = await client.sendCalls({
  calls: [
    {
      to: "0x0000000000000000000000000000000000000000",
      value: BigInt(0),
      data: "0x",
    },
  ],
});
 
const txStatus = await client.waitForCallsStatus({
  id: result.id,
  timeout: 60_000,
});
 
console.log("Transaction confirmed:", txStatus.receipts?.[0]?.transactionHash);

See full integration guides for supported providers:

Was this page helpful?