Custom Integration

If you have an existing custom signer or another third-party embedded wallet provider, you can upgrade your embedded EOAs to smart wallets by connecting your existing signer. This will allow you to use EIP-7702 to get features like gas sponsorship, batching, and more.

Requirement: Your signer or embedded wallet provider must support signing EIP-7702 authorizations in order to delegate to a smart account.

To bring your own signer, you create a SmartAccountSigner that implements signAuthorization. See the details for the interface requirements here.

For example, you can upgrade an existing embedded EOA by extending a viem WalletClient to use your provider’s EIP-7702 authorization signing.

The bulk of the logic happens in a function that returns a client. Your embedded wallet is wrapped in a WalletClientSigner that supports signAuthorization, then passed to createSmartWalletClient to construct a client for sending transactions.

Steps:

  1. Wrap your embedded wallet with WalletClientSigner that implements signAuthorization.
  2. Create a SmartWalletClient with your signer and Alchemy API key (optionally a gas policyId).
  3. Send calls with eip7702Auth: true (and optional paymasterService.policyId).
create-smart-embedded-wallet-client.ts
1import {
2 type Address,
3 type Authorization,
4 createWalletClient,
5 custom,
6 type EIP1193Provider,
7} from "viem";
8import { type AuthorizationRequest, WalletClientSigner } from "@aa-sdk/core";
9import { baseSepolia, alchemy } from "@account-kit/infra";
10import {
11 createSmartWalletClient,
12 type SmartWalletClient,
13} from "@account-kit/wallet-client";
14
15export type EmbeddedWallet = {
16 address: string;
17 getEthereumProvider: () => Promise<EIP1193Provider>;
18};
19
20export type SignAuthorizationFn = (
21 req: AuthorizationRequest<number> & { contractAddress?: string },
22) => Promise<{ r: string; s: string; yParity: number }>;
23
24export type CreateSmartWalletParams = {
25 embeddedWallet: EmbeddedWallet;
26 signAuthorization: SignAuthorizationFn;
27 apiKey: string;
28 policyId?: string;
29};
30
31/**
32 * Creates an Alchemy Smart Wallet client for a generic embedded wallet using EIP-7702.
33 */
34export async function createSmartEmbeddedWalletClient({
35 embeddedWallet,
36 signAuthorization,
37 apiKey,
38 policyId,
39}: CreateSmartWalletParams): Promise<SmartWalletClient> {
40 if (!apiKey) {
41 throw new Error("Missing Alchemy API key");
42 }
43 const provider = await embeddedWallet.getEthereumProvider();
44
45 const baseSigner = new WalletClientSigner(
46 createWalletClient({
47 account: embeddedWallet.address as Address,
48 chain: baseSepolia,
49 transport: custom(provider),
50 }),
51 "custom",
52 );
53
54 const signer = {
55 ...baseSigner,
56 async signAuthorization(
57 unsignedAuth: AuthorizationRequest<number>,
58 ): Promise<Authorization<number, true>> {
59 const { address, ...rest } = unsignedAuth;
60 const signature = await signAuthorization({
61 ...rest,
62 contractAddress: (address ?? rest.contractAddress) as `0x${string}`,
63 });
64
65 return {
66 ...unsignedAuth,
67 ...signature,
68 } as Authorization<number, true>;
69 },
70 };
71
72 return createSmartWalletClient({
73 chain: baseSepolia,
74 transport: alchemy({ apiKey }),
75 signer,
76 policyId,
77 });
78}

When using the SmartWalletClient you must set the eip7702Auth capability to true:

send-with-eip7702.ts
1import type { Address, EIP1193Provider } from "viem";
2import {
3 createSmartEmbeddedWalletClient,
4 type SignAuthorizationFn,
5} from "./getClient";
6
7async function main() {
8 const embeddedWallet =
9 /* your embedded wallet instance */ null as unknown as {
10 address: string;
11 getEthereumProvider: () => Promise<EIP1193Provider>;
12 };
13 const signAuthorization: SignAuthorizationFn =
14 /* your EIP-7702 sign function */ async () => {
15 return { r: "0x", s: "0x", yParity: 1 };
16 };
17
18 const client = await createSmartEmbeddedWalletClient({
19 embeddedWallet,
20 signAuthorization,
21 apiKey: "YOUR_ALCHEMY_API_KEY",
22 policyId: "YOUR_GAS_POLICY_ID", // Optional: for gas sponsorship
23 });
24
25 // Send transaction with EIP-7702 and optional gas sponsorship
26 const result = await client.sendCalls({
27 from: embeddedWallet.address as Address,
28 calls: [
29 {
30 to: "0x0000000000000000000000000000000000000000",
31 value: "0x0",
32 data: "0x",
33 },
34 ],
35 capabilities: {
36 eip7702Auth: true,
37 paymasterService: {
38 policyId: "YOUR_GAS_POLICY_ID",
39 },
40 },
41 });
42
43 if (!result.preparedCallIds?.length) {
44 throw new Error("No prepared call IDs returned");
45 }
46
47 // Wait for transaction confirmation
48 const txStatus = await client.waitForCallsStatus({
49 id: result.preparedCallIds[0],
50 timeout: 60_000,
51 });
52
53 const txnHash = txStatus.receipts?.[0]?.transactionHash;
54 if (!txnHash) {
55 throw new Error("Transaction hash not found in receipt");
56 }
57
58 console.log("Transaction confirmed:", txnHash);
59}
60
61main();