Appearance
Account Abstraction with AccountKit
TIP
Privy now allows you to natively use smart wallet for a better developer experience. Check out the docs here.
AccountKit is a toolkit by Alchemy for creating ERC-4337-compatible smart accounts for your users, using the user's EOA as the smart account's signer. This allows you to easily add Account Abstraction features into your app.
You can easily integrate AccountKit alongside Privy to create smart wallets from your user's embedded or external wallets, allowing you to enhance your app with gas sponsorship, batched transactions, and more!
Read below to learn how to configure your app to create smart wallets for all your users!
What is an EOA?
An EOA, or externally-owned account, is any Ethereum account that is controlled by a private key. Privy's embedded wallets and most external wallets (MetaMask, Coinbase Wallet, Rainbow Wallet, etc.) are EOAs.
EOAs differ from contract accounts, which are instead controlled by smart contract code and do not have their own private key. AccountKit's smart wallet is a contract account. Contract accounts have enhanced capabilities, such as gas sponsorship and batched transactions.
Since they do not have their own private key, contract accounts cannot directly produce signatures and initiate transaction flows. Instead, each contract account is generally "managed" by an EOA, which authorizes actions taken by the contract account via a signature; this EOA is called a signer.
In this integration, the user's EOA (from Privy) serves as the signer for their smart wallet (from AccountKit). The smart wallet (AccountKit) holds all assets and submits all transactions to the network, but the signer (Privy) is responsible for producing signatures and "kicking off" transaction flows.
1. Install Privy and AccountKit
Install the @privy-io/react-auth
SDK from Privy, the @alchemy/{aa-core, aa-alchemy}
SDKs from Alchemy, and viem
:
sh
npm i @privy-io/react-auth @alchemy/aa-core @alchemy/aa-alchemy @alchemy/aa-accounts viem
2. Configure your app's PrivyProvider
First, follow the instructions in the Privy Quickstart to get your app set up with Privy.
Next, set Add confirmation modals to "off" in your app's Embedded wallets page in the Privy dashboard. This will configure Privy to not show its default UIs when your user must sign messages or send transactions. Instead, we recommend you use your own custom UIs for showing users the UserOperation
s they sign.
Then, update the config.embeddedWallets.createOnLogin
property of your PrivyProvider
to 'users-without-wallets'
.This will configure Privy to create an embedded wallet for users logging in via a web2 method (email, phone, socials), ensuring that all of your users have a wallet that can be used as an EOA.
Your PrivyProvider
should then look like:
tsx
<PrivyProvider
appId='insert-your-privy-app-id'
config={{
/* Replace this with your desired login methods */
loginMethods: ['email', 'wallet'],
/* Replace this with your desired appearance configuration */
appearance: {
theme: 'light',
accentColor: '#676FFF',
logo: 'your-logo-url'
}
embeddedWallets: {
createOnLogin: 'users-without-wallets',
showWalletUIs: false
},
// Import your desired chain from `viem/chains` and pass it to `defaultChain`
defaultChain: sepolia,
}}
>
{/* Your app's components */}
</PrivyProvider>
3. Initialize your users' smart accounts
When users log into your app, Privy provisions each user an embedded wallet, which is an EOA. In order to leverage the features of Alchemy's account abstraction, each user also needs a smart account from AccountKit. You can provision smart accounts with AccountKit for each user by assigning their embedded wallet as a signer for their smart account.
To start, after a user logs in, find the user's embedded wallet from Privy's useWallets
hook, and create a viem WalletClient
for it. You can find embedded wallet by finding the only entry in the useWallets
array with a walletClientType
of 'privy'
.
tsx
import {useWallets} from '@privy-io/react-auth';
import {sepolia} from 'viem/chains';
import {createWalletClient, custom} from 'viem';
...
// Find the embedded wallet and get its EIP1193 provider
const {wallets} = useWallets();
const embeddedWallet = wallets.find((wallet) => (wallet.walletClientType === 'privy'));
const eip1193provider = await embeddedWallet.getEthereumProvider();
// Create a viem WalletClient from the embedded wallet's EIP1193 provider
const privyClient = createWalletClient({
account: embeddedWallet.address,
chain: sepolia,
transport: custom(eip1193provider)
});
Then, from the embedded wallet's WalletClient
, initialize an AccountKit SmartAccountSigner
, like below. This configures the user's embedded wallet (EOA, from Privy) to be the signer for their smart account (contract account, from AccountKit).
tsx
import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core";
...
// Create an AccountKit SmartAccountSigner from the embedded wallet
const privySigner: SmartAccountSigner = new WalletClientSigner(
privyClient,
"json-rpc"
);
Lastly, with the user's SmartAccountSigner
, initialize an instance of an AlchemySmartAccountClient
. You will later use this smart account client to send transactions from the user's smart account.
tsx
import { createLightAccountAlchemyClient } from "@alchemy/aa-alchemy";
...
// Create an Alchemy Light Account Client with the `privySigner`
export const smartAccountClient = await createLightAccountAlchemyClient({
account: {
signer: privySigner,
},
chain: sepolia,
apiKey: "YOUR_ALCHEMY_API_KEY",
});
TIP
You can also store the user's smart account address on Privy's user object. See this guide for more.
Want to see this code end-to-end?
You can find the code snippets above pasted in an end-to-end example below.tsx
/**
* This example assumes your app is wrapped with the `PrivyProvider` and
* is configured to create embedded wallets for users upon login.
*/
import {createLightAccountAlchemyClient} from '@alchemy/aa-alchemy';
import {WalletClientSigner, type SmartAccountSigner} from '@alchemy/aa-core';
import {createWalletClient, custom} from 'viem';
import {sepolia} from 'viem/chains';
import {useWallets} from '@privy-io/react-auth';
// The code below makes use of Privy's React hooks. You must paste
// or use it within a React Component or Context.
// Find the user's embedded wallet
const {wallets} = useWallets();
const embeddedWallet = wallets.find((wallet) => wallet.walletClientType === 'privy');
// Get a viem client from the embedded wallet
const eip1193provider = await embeddedWallet.getEthereumProvider();
const privyClient = createWalletClient({
account: embeddedWallet.address,
chain: sepolia,
transport: custom(eip1193provider),
});
// Create a smart account signer from the embedded wallet's viem client
const privySigner: SmartAccountSigner = new WalletClientSigner(privyClient, 'json-rpc');
// Create an Alchemy Light Account Client with the `privySigner`
export const smartAccountClient = await createLightAccountAlchemyClient({
account: {
signer: privySigner,
},
chain: sepolia,
apiKey: 'YOUR_ALCHEMY_API_KEY',
});
Note: if your app uses React, we suggest that you store the user's AccountKit SmartAccountSigner
and AlchemySmartAccountClient
in a React context that wraps your application. This allows you to easily access the smart account from your app's pages and components.
5. Send UserOperations from the smart account
Now that your users have AccountKit smart accounts, they can now send user operations from their smart account. This is the AA analog to sending a transaction.
To send a user operation from a user's smart account, use the AlchemySmartAccountClient
's sendUserOperation
method.
As a parameter to this method, you should pass an object containing a uo
field with the desired user operation as the value.
tsx
// this is an example ABI for a contract with a "mint" function
const ABI = [
{
inputs: [{internalType: 'address', name: 'recipient', type: 'address'}],
name: 'mint',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
// Using `encodeFunctionData` from `viem`
const uoCallData = encodeFunctionData({
abi: ABI,
functionName: 'mint',
args: [smartAccountClient.getAddress()],
});
const uo = await smartAccountClient.sendUserOperation({
uo: {
target: '0xTARGET_ADDRESS',
data: uoCallData,
},
});
const txHash = await smartAccountClient.waitForUserOperationTransaction(uo);
console.log(txHash);
That's it! You've configured your app to create smart wallets for all of your users, and can seamlessly add in AA features like gas sponsorship, batched transactions, and more. 🎉