You're going to need @alchemy/wallet-apis and viem.
npm install @alchemy/wallet-apis viem
This quickstart uses a local private key as an example. You can use any viem-compatible signer, including providers like Privy and Turnkey.
import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";import { arbitrumSepolia } from "viem/chains";import { privateKeyToAccount } from "viem/accounts";const client = createSmartWalletClient({ transport: alchemyWalletTransport({ apiKey: "YOUR_API_KEY", }), chain: arbitrumSepolia, signer: privateKeyToAccount("0xYOUR_PRIVATE_KEY" as const), paymaster: { policyId: "YOUR_POLICY_ID", },});
The client defaults to EIP-7702, so your EOA will be delegated to a smart wallet to enable gas sponsorship, batching, and more. The SDK handles delegation automatically on the first transaction.
import { zeroAddress } from "viem";const { id } = await client.sendCalls({ calls: [{ to: zeroAddress, value: BigInt(0) }],});
const status = await client.waitForCallsStatus({ id });const txHash = status.receipts?.[0]?.transactionHash;console.log(`Explorer: https://sepolia.arbiscan.io/tx/${txHash}`);
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 owner'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.
Owners use LocalAccountSigner / WalletClientSigner from @aa-sdk/core. In v5.x.x, a viem LocalAccount or WalletClient is used directly.
Sign the returned signature request(s) using your signer key.
If the account isn't delegated yet (array response): Sign both the EIP-7702 authorization and the transaction. The authorization only needs to be signed once to delegate your account.
Once delegated: Sign only the transaction.
If using a Smart Wallets authentication provider, learn how to stamp the request on the frontend here. When using this authentication provider, sign the signatureRequest.rawPayload directly.
If you are using a library such as Viem that supports the personal_sign method, use that to sign the transaction hash (since the signatureRequest.type is "personal_sign").
With the signature(s) from step 2 and the data from step 1, you're good to send the call!
If the account isn't delegated yet (array response):
Call wallet_getCallsStatus to check the status of the calls. A “pending” state (1xx status codes) is expected for some time before the transition to “confirmed,” so poll this endpoint while the status is pending. If the call does not progress, refer to the retry guide.