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:
- Wrap your embedded wallet with
WalletClientSignerthat implementssignAuthorization. - Create a
SmartWalletClientwith your signer and Alchemy API key (optionally a gaspolicyId). - Send calls with
eip7702Auth: true(and optionalpaymasterService.policyId).
import {
type Address,
type Authorization,
createWalletClient,
custom,
type EIP1193Provider,
} from "viem";
import { type AuthorizationRequest, WalletClientSigner } from "@aa-sdk/core";
import { baseSepolia, alchemy } from "@account-kit/infra";
import {
createSmartWalletClient,
type SmartWalletClient,
} from "@account-kit/wallet-client";
export type EmbeddedWallet = {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
};
export type SignAuthorizationFn = (
req: AuthorizationRequest<number> & { contractAddress?: string },
) => Promise<{ r: string; s: string; yParity: number }>;
export type CreateSmartWalletParams = {
embeddedWallet: EmbeddedWallet;
signAuthorization: SignAuthorizationFn;
apiKey: string;
policyId?: string;
};
/**
* Creates an Alchemy Smart Wallet client for a generic embedded wallet using EIP-7702.
*/
export async function createSmartEmbeddedWalletClient({
embeddedWallet,
signAuthorization,
apiKey,
policyId,
}: CreateSmartWalletParams): Promise<SmartWalletClient> {
if (!apiKey) {
throw new Error("Missing Alchemy API key");
}
const provider = await embeddedWallet.getEthereumProvider();
const baseSigner = new WalletClientSigner(
createWalletClient({
account: embeddedWallet.address as Address,
chain: baseSepolia,
transport: custom(provider),
}),
"custom",
);
const signer = {
...baseSigner,
async signAuthorization(
unsignedAuth: AuthorizationRequest<number>,
): Promise<Authorization<number, true>> {
const { address, ...rest } = unsignedAuth;
const signature = await signAuthorization({
...rest,
contractAddress: (address ?? rest.contractAddress) as `0x${string}`,
});
return {
...unsignedAuth,
...signature,
} as Authorization<number, true>;
},
};
return createSmartWalletClient({
chain: baseSepolia,
transport: alchemy({ apiKey }),
signer,
policyId,
});
}When using the SmartWalletClient you must set the eip7702Auth capability to true:
import type { Address, EIP1193Provider } from "viem";
import {
createSmartEmbeddedWalletClient,
type SignAuthorizationFn,
} from "./getClient";
async function main() {
const embeddedWallet =
/* your embedded wallet instance */ null as unknown as {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
};
const signAuthorization: SignAuthorizationFn =
/* your EIP-7702 sign function */ async () => {
return { r: "0x", s: "0x", yParity: 1 };
};
const client = await createSmartEmbeddedWalletClient({
embeddedWallet,
signAuthorization,
apiKey: "YOUR_ALCHEMY_API_KEY",
policyId: "YOUR_GAS_POLICY_ID", // Optional: for gas sponsorship
});
// Send transaction with EIP-7702 and optional gas sponsorship
const result = await client.sendCalls({
from: embeddedWallet.address as Address,
calls: [
{
to: "0x0000000000000000000000000000000000000000",
value: "0x0",
data: "0x",
},
],
capabilities: {
eip7702Auth: true,
paymasterService: {
policyId: "YOUR_GAS_POLICY_ID",
},
},
});
if (!result.id) {
throw new Error("No call ID returned");
}
// Wait for transaction confirmation
const txStatus = await client.waitForCallsStatus({
id: result.id,
timeout: 60_000,
});
const txnHash = txStatus.receipts?.[0]?.transactionHash;
if (!txnHash) {
throw new Error("Transaction hash not found in receipt");
}
console.log("Transaction confirmed:", txnHash);
}
main();