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).
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): CustomTransport
custom
,
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 WalletClientSigner

Represents 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: Chain
baseSepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
address
: 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 | undefined
contractAddress
?: string },
) =>
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<{
r: string
r
: string;
s: string
s
: string;
yParity: number
yParity
: number }>;
export type
type CreateSmartWalletParams = { embeddedWallet: EmbeddedWallet; signAuthorization: SignAuthorizationFn; apiKey: string; policyId?: string; }
CreateSmartWalletParams
= {
embeddedWallet: EmbeddedWallet
embeddedWallet
:
type EmbeddedWallet = { address: string; getEthereumProvider: () => Promise<EIP1193Provider>; }
EmbeddedWallet
;
signAuthorization: SignAuthorizationFn
signAuthorization
:
type SignAuthorizationFn = (req: AuthorizationRequest<number> & { contractAddress?: string; }) => Promise<{ r: string; s: string; yParity: number; }>
SignAuthorizationFn
;
apiKey: string
apiKey
: string;
policyId?: string | undefined
policyId
?: 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: EmbeddedWallet
embeddedWallet
,
signAuthorization: SignAuthorizationFn
signAuthorization
,
apiKey: string
apiKey
,
policyId: string | undefined
policyId
,
}:
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: string
apiKey
) {
throw new
var Error: ErrorConstructor new (message?: string) => Error
Error
("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: EmbeddedWallet
embeddedWallet
.
getEthereumProvider: () => Promise<EIP1193Provider>
getEthereumProvider
();
const
const baseSigner: WalletClientSigner
baseSigner
= new
new WalletClientSigner(client: WalletClient, signerType: string): WalletClientSigner

Initializes 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 | undefined

The Account to use for the Client. This will be used for Actions that require an account as an argument.

account
:
embeddedWallet: EmbeddedWallet
embeddedWallet
.
address: string
address
as
type Address = `0x${string}`
Address
,
chain?: Chain | undefined

Chain for the client.

chain
:
const baseSepolia: Chain
baseSepolia
,
transport: CustomTransport

The 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): CustomTransport
custom
(
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: WalletClientSigner
baseSigner
,
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}` | undefined
address
, ...
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}` | undefined
address
??
const rest: { contractAddress?: undefined; chainId: number; nonce: number; } | { contractAddress: Address; chainId: number; nonce: number; }
rest
.
contractAddress?: `0x${string}` | undefined
contractAddress
) 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: Chain
chain
:
const baseSepolia: Chain
baseSepolia
,
transport: AlchemyTransport
transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
}),
signer: SmartWalletSigner
signer
,
policyId?: string | undefined
policyId
,
}); }

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 createSmartEmbeddedWalletClient
createSmartEmbeddedWalletClient
,
type
import SignAuthorizationFn
SignAuthorizationFn
,
} 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: string
address
: 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: SignAuthorizationFn
signAuthorization
:
import SignAuthorizationFn
SignAuthorizationFn
=
/* your EIP-7702 sign function */ async () => { return {
r: string
r
: "0x",
s: string
s
: "0x",
yParity: number
yParity
: 1 };
}; const
const client: any
client
= await
import createSmartEmbeddedWalletClient
createSmartEmbeddedWalletClient
({
embeddedWallet: { address: string; getEthereumProvider: () => Promise<EIP1193Provider>; }
embeddedWallet
,
signAuthorization: SignAuthorizationFn
signAuthorization
,
apiKey: string
apiKey
: "YOUR_ALCHEMY_API_KEY",
policyId: string
policyId
: "YOUR_GAS_POLICY_ID", // Optional: for gas sponsorship
}); // Send transaction with EIP-7702 and optional gas sponsorship const
const result: any
result
= await
const client: any
client
.
any
sendCalls
({
from: `0x${string}`
from
:
const embeddedWallet: { address: string; getEthereumProvider: () => Promise<EIP1193Provider>; }
embeddedWallet
.
address: string
address
as
type Address = `0x${string}`
Address
,
calls: { to: string; value: string; data: string; }[]
calls
: [
{
to: string
to
: "0x0000000000000000000000000000000000000000",
value: string
value
: "0x0",
data: string
data
: "0x",
}, ],
capabilities: { eip7702Auth: boolean; paymasterService: { policyId: string; }; }
capabilities
: {
eip7702Auth: boolean
eip7702Auth
: true,
paymasterService: { policyId: string; }
paymasterService
: {
policyId: string
policyId
: "YOUR_GAS_POLICY_ID",
}, }, }); if (!
const result: any
result
.
any
preparedCallIds
?.
any
length
) {
throw new
var Error: ErrorConstructor new (message?: string) => Error
Error
("No prepared call IDs returned");
} // Wait for transaction confirmation const
const txStatus: any
txStatus
= await
const client: any
client
.
any
waitForCallsStatus
({
id: any
id
:
const result: any
result
.
any
preparedCallIds
[0],
timeout: number
timeout
: 60_000,
}); const
const txnHash: any
txnHash
=
const txStatus: any
txStatus
.
any
receipts
?.[0]?.
any
transactionHash
;
if (!
const txnHash: any
txnHash
) {
throw new
var Error: ErrorConstructor new (message?: string) => Error
Error
("Transaction hash not found in receipt");
}
var console: Console

The 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: any
txnHash
);
}
function main(): Promise<void>
main
();