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.
JavaScript
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 type Address = `0x${string}`Address,
type type Authorization<uint32 = number, signed extends boolean = false> = {
address: Address;
chainId: uint32;
nonce: uint32;
} & (signed extends true ? Signature<uint32> : ExactPartial<Signature<uint32>>)Authorization,
function createWalletClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined>(parameters: WalletClientConfig<transport, chain, accountOrAddress, rpcSchema>): WalletClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema>Creates a Wallet Client with a given Transport configured for a Chain.
Docs: https://viem.sh/docs/clients/wallet
A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.
The Wallet Client supports signing over:
JSON-RPC Accounts (e.g. Browser Extension Wallets, WalletConnect, etc).
Local Accounts (e.g. private key/mnemonic wallets).
createWalletClient,
function custom<provider extends EthereumProvider>(provider: provider, config?: CustomTransportConfig): CustomTransportcustom,
type type EIP1193Provider = {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}EIP1193Provider,
} from "viem";
import { type type AuthorizationRequest<uint32 = number> = OneOf<{
address: Address;
} | {
contractAddress: Address;
}> & {
chainId: uint32;
nonce: uint32;
}AuthorizationRequest, class WalletClientSignerRepresents a wallet client signer for smart accounts, providing methods to get the address, sign messages, sign typed data, and sign 7702 authorizations.
WalletClientSigner } from "@aa-sdk/core";
import { const baseSepolia: ChainbaseSepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import {
function createSmartWalletClient<TAccount extends Address | undefined = undefined>(params: SmartWalletClientParams<TAccount>): SmartWalletClient<TAccount>Creates a smart wallet client that can be used to interact with a smart account.
createSmartWalletClient,
type type SmartWalletClient<TAccount extends Address | undefined = `0x${string}` | undefined> = Client_Base<Transport, Chain, JsonRpcAccount<`0x${string}`> | undefined, WalletServerViemRpcSchema> & {
policyIds?: string[];
} & SmartWalletActions<TAccount> & {
extend: <const client extends {
[x: string]: unknown;
account?: undefined;
batch?: undefined;
cacheTime?: undefined;
ccipRead?: undefined;
chain?: undefined;
experimental_blockTag?: undefined;
key?: undefined;
name?: undefined;
pollingInterval?: undefined;
request?: undefined;
transport?: undefined;
type?: undefined;
uid?: undefined;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}SmartWalletClient,
} from "@account-kit/wallet-client";
export type type EmbeddedWallet = {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
}EmbeddedWallet = {
address: stringaddress: string;
getEthereumProvider: () => Promise<EIP1193Provider>getEthereumProvider: () => interface Promise<T>Represents the completion of an asynchronous operation
Promise<type EIP1193Provider = {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}EIP1193Provider>;
};
export type type SignAuthorizationFn = (req: AuthorizationRequest<number> & {
contractAddress?: string;
}) => Promise<{
r: string;
s: string;
yParity: number;
}>SignAuthorizationFn = (
req: AuthorizationRequest<number> & {
contractAddress?: string;
}req: type AuthorizationRequest<uint32 = number> = OneOf<{
address: Address;
} | {
contractAddress: Address;
}> & {
chainId: uint32;
nonce: uint32;
}AuthorizationRequest<number> & { contractAddress?: string | undefinedcontractAddress?: string },
) => interface Promise<T>Represents the completion of an asynchronous operation
Promise<{ r: stringr: string; s: strings: string; yParity: numberyParity: number }>;
export type type CreateSmartWalletParams = {
embeddedWallet: EmbeddedWallet;
signAuthorization: SignAuthorizationFn;
apiKey: string;
policyId?: string;
}CreateSmartWalletParams = {
embeddedWallet: EmbeddedWalletembeddedWallet: type EmbeddedWallet = {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
}EmbeddedWallet;
signAuthorization: SignAuthorizationFnsignAuthorization: type SignAuthorizationFn = (req: AuthorizationRequest<number> & {
contractAddress?: string;
}) => Promise<{
r: string;
s: string;
yParity: number;
}>SignAuthorizationFn;
apiKey: stringapiKey: string;
policyId?: string | undefinedpolicyId?: string;
};
/**
* Creates an Alchemy Smart Wallet client for a generic embedded wallet using EIP-7702.
*/
export async function function createSmartEmbeddedWalletClient({ embeddedWallet, signAuthorization, apiKey, policyId, }: CreateSmartWalletParams): Promise<SmartWalletClient>Creates an Alchemy Smart Wallet client for a generic embedded wallet using EIP-7702.
createSmartEmbeddedWalletClient({
embeddedWallet: EmbeddedWalletembeddedWallet,
signAuthorization: SignAuthorizationFnsignAuthorization,
apiKey: stringapiKey,
policyId: string | undefinedpolicyId,
}: type CreateSmartWalletParams = {
embeddedWallet: EmbeddedWallet;
signAuthorization: SignAuthorizationFn;
apiKey: string;
policyId?: string;
}CreateSmartWalletParams): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type SmartWalletClient<TAccount extends Address | undefined = `0x${string}` | undefined> = Client_Base<Transport, Chain, JsonRpcAccount<`0x${string}`> | undefined, WalletServerViemRpcSchema> & {
policyIds?: string[];
} & SmartWalletActions<TAccount> & {
extend: <const client extends {
[x: string]: unknown;
account?: undefined;
batch?: undefined;
cacheTime?: undefined;
ccipRead?: undefined;
chain?: undefined;
experimental_blockTag?: undefined;
key?: undefined;
name?: undefined;
pollingInterval?: undefined;
request?: undefined;
transport?: undefined;
type?: undefined;
uid?: undefined;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}SmartWalletClient> {
if (!apiKey: stringapiKey) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Missing Alchemy API key");
}
const const provider: {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}provider = await embeddedWallet: EmbeddedWalletembeddedWallet.getEthereumProvider: () => Promise<EIP1193Provider>getEthereumProvider();
const const baseSigner: WalletClientSignerbaseSigner = new new WalletClientSigner(client: WalletClient, signerType: string): WalletClientSignerInitializes a signer with a given wallet client and signer type.
WalletClientSigner(
createWalletClient<CustomTransport, Chain, `0x${string}`, []>(parameters: {
account?: `0x${string}` | Account | undefined;
cacheTime?: number | undefined | undefined;
ccipRead?: {
request?: (parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>;
} | false | undefined | undefined;
... 5 more ...;
transport: CustomTransport;
}): {
...;
}Creates a Wallet Client with a given Transport configured for a Chain.
Docs: https://viem.sh/docs/clients/wallet
A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.
The Wallet Client supports signing over:
JSON-RPC Accounts (e.g. Browser Extension Wallets, WalletConnect, etc).
Local Accounts (e.g. private key/mnemonic wallets).
createWalletClient({
account?: `0x${string}` | Account | undefinedThe Account to use for the Client. This will be used for Actions that require an account as an argument.
account: embeddedWallet: EmbeddedWalletembeddedWallet.address: stringaddress as type Address = `0x${string}`Address,
chain?: Chain | undefinedChain for the client.
chain: const baseSepolia: ChainbaseSepolia,
transport: CustomTransportThe RPC transport
transport: custom<{
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}>(provider: {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}, config?: CustomTransportConfig): CustomTransportcustom(const provider: {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}provider),
}),
"custom",
);
const const signer: {
signAuthorization(unsignedAuth: AuthorizationRequest<number>): Promise<Authorization<number, true>>;
signerType: string;
inner: WalletClient;
getAddress: () => Promise<`0x${string}`>;
signMessage: (message: SignableMessage) => Promise<`0x${string}`>;
signTypedData: <const TTypedData extends TypedData | Record<string, unknown>, TPrimaryType extends keyof TTypedData | "EIP712Domain" | string = string>(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) => Promise<Hex>;
}signer = {
...const baseSigner: WalletClientSignerbaseSigner,
async function signAuthorization(unsignedAuth: AuthorizationRequest<number>): Promise<Authorization<number, true>>signAuthorization(
unsignedAuth: AuthorizationRequest<number>unsignedAuth: type AuthorizationRequest<uint32 = number> = OneOf<{
address: Address;
} | {
contractAddress: Address;
}> & {
chainId: uint32;
nonce: uint32;
}AuthorizationRequest<number>,
): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Authorization<uint32 = number, signed extends boolean = false> = {
address: Address;
chainId: uint32;
nonce: uint32;
} & (signed extends true ? Signature<uint32> : ExactPartial<Signature<uint32>>)Authorization<number, true>> {
const { const address: `0x${string}` | undefinedaddress, ...const rest: {
contractAddress?: undefined;
chainId: number;
nonce: number;
} | {
contractAddress: Address;
chainId: number;
nonce: number;
}rest } = unsignedAuth: AuthorizationRequest<number>unsignedAuth;
const const signature: {
r: string;
s: string;
yParity: number;
}signature = await signAuthorization: (req: AuthorizationRequest<number> & {
contractAddress?: string;
}) => Promise<{
r: string;
s: string;
yParity: number;
}>signAuthorization({
...const rest: {
contractAddress?: undefined;
chainId: number;
nonce: number;
} | {
contractAddress: Address;
chainId: number;
nonce: number;
}rest,
contractAddress: `0x${string}`contractAddress: (const address: `0x${string}` | undefinedaddress ?? const rest: {
contractAddress?: undefined;
chainId: number;
nonce: number;
} | {
contractAddress: Address;
chainId: number;
nonce: number;
}rest.contractAddress?: `0x${string}` | undefinedcontractAddress) as `0x${string}`,
});
return {
...unsignedAuth: AuthorizationRequest<number>unsignedAuth,
...const signature: {
r: string;
s: string;
yParity: number;
}signature,
} as type Authorization<uint32 = number, signed extends boolean = false> = {
address: Address;
chainId: uint32;
nonce: uint32;
} & (signed extends true ? Signature<uint32> : ExactPartial<Signature<uint32>>)Authorization<number, true>;
},
};
return createSmartWalletClient<`0x${string}` | undefined>(params: SmartWalletClientParams<`0x${string}` | undefined>): SmartWalletClient<`0x${string}` | undefined>Creates a smart wallet client that can be used to interact with a smart account.
createSmartWalletClient({
chain: Chainchain: const baseSepolia: ChainbaseSepolia,
transport: AlchemyTransporttransport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey }),
signer: SmartWalletSignersigner,
policyId?: string | undefinedpolicyId,
});
}When using the SmartWalletClient you must set the eip7702Auth capability to true:
import type { type Address = `0x${string}`Address, type EIP1193Provider = {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}EIP1193Provider } from "viem";
import {
import createSmartEmbeddedWalletClientcreateSmartEmbeddedWalletClient,
type import SignAuthorizationFnSignAuthorizationFn,
} from "./getClient";
async function function main(): Promise<void>main() {
const const embeddedWallet: {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
}embeddedWallet =
/* your embedded wallet instance */ null as unknown as {
address: stringaddress: string;
getEthereumProvider: () => Promise<EIP1193Provider>getEthereumProvider: () => interface Promise<T>Represents the completion of an asynchronous operation
Promise<type EIP1193Provider = {
on: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
removeListener: <event extends keyof EIP1193EventMap>(event: event, listener: EIP1193EventMap[event]) => void;
request: EIP1193RequestFn<EIP1474Methods>;
}EIP1193Provider>;
};
const const signAuthorization: SignAuthorizationFnsignAuthorization: import SignAuthorizationFnSignAuthorizationFn =
/* your EIP-7702 sign function */ async () => {
return { r: stringr: "0x", s: strings: "0x", yParity: numberyParity: 1 };
};
const const client: anyclient = await import createSmartEmbeddedWalletClientcreateSmartEmbeddedWalletClient({
embeddedWallet: {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
}embeddedWallet,
signAuthorization: SignAuthorizationFnsignAuthorization,
apiKey: stringapiKey: "YOUR_ALCHEMY_API_KEY",
policyId: stringpolicyId: "YOUR_GAS_POLICY_ID", // Optional: for gas sponsorship
});
// Send transaction with EIP-7702 and optional gas sponsorship
const const result: anyresult = await const client: anyclient.anysendCalls({
from: `0x${string}`from: const embeddedWallet: {
address: string;
getEthereumProvider: () => Promise<EIP1193Provider>;
}embeddedWallet.address: stringaddress as type Address = `0x${string}`Address,
calls: {
to: string;
value: string;
data: string;
}[]calls: [
{
to: stringto: "0x0000000000000000000000000000000000000000",
value: stringvalue: "0x0",
data: stringdata: "0x",
},
],
capabilities: {
eip7702Auth: boolean;
paymasterService: {
policyId: string;
};
}capabilities: {
eip7702Auth: booleaneip7702Auth: true,
paymasterService: {
policyId: string;
}paymasterService: {
policyId: stringpolicyId: "YOUR_GAS_POLICY_ID",
},
},
});
if (!const result: anyresult.anypreparedCallIds?.anylength) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("No prepared call IDs returned");
}
// Wait for transaction confirmation
const const txStatus: anytxStatus = await const client: anyclient.anywaitForCallsStatus({
id: anyid: const result: anyresult.anypreparedCallIds[0],
timeout: numbertimeout: 60_000,
});
const const txnHash: anytxnHash = const txStatus: anytxStatus.anyreceipts?.[0]?.anytransactionHash;
if (!const txnHash: anytxnHash) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Transaction hash not found in receipt");
}
var console: ConsoleThe console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream. * A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.
Example using the global console:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format() for more information.
log("Transaction confirmed:", const txnHash: anytxnHash);
}
function main(): Promise<void>main();