Introducing account abstraction for zkSync enabled chains. This guide will walk you through the setup of zkSync account abstraction with comprehensive method references and transaction examples.
Dashboard Setup
Enable zkSync Chain
Navigate to the EVM section of your Dynamic Dashboard and toggle on a zkSync enabled chain, then click Save.
When using zkSync with global wallets, ensure that the zkSync enabled chain is the only one selected.
Configure Account Abstraction
Go to the Account Abstraction section and:
- Enter the optional
factoryAddress
, paymasterAddress
and sessionKeyAddress
(if available)
- Save the settings
- Enable the toggle next to the ZkSync section
Make sure to test your configuration on testnet before deploying to mainnet.
Configure Smart Contract Wallet Distribution
Choose your SCW distribution strategy:
Wallet Level Configuration:
- All Wallets: Issue SCWs to all connected wallets (requires custom UI handling)
- Embedded Wallets Only: Issue SCWs only to Dynamic’s embedded wallets
User Level Configuration:
- All Users: Issue SCWs to all users (existing users get SCWs on next login)
- New Users Only: Issue SCWs only to newly registered users
For most applications, we recommend starting with Embedded Wallets Only and New Users Only to ensure a smooth rollout.
Configure Wallet Display Options
Choose how users interact with their wallets:
- Show Smart Wallet Only: Users interact directly with the smart wallet
- Show Smart Wallet & Signer: Users can switch between the smart wallet and signer
Installation & Setup
Install the required packages:
npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethereum-aa-zksync
npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethereum-aa-zksync
yarn add @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethereum-aa-zksync
pnpm add @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethereum-aa-zksync
bun add @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/ethereum-aa-zksync
Configure your application:
import { DynamicContextProvider, DynamicWidget } from "@dynamic-labs/sdk-react-core";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { ZkSyncSmartWalletConnectors } from "@dynamic-labs/ethereum-aa-zksync";
const App = () => (
<DynamicContextProvider
settings={{
environmentId: "YOUR_ENVIRONMENT_ID",
walletConnectors: [
EthereumWalletConnectors,
ZkSyncSmartWalletConnectors
],
}}
>
<DynamicWidget />
</DynamicContextProvider>
);
export default App;
Make sure to grab your Dynamic environment ID from the Dynamic Dashboard under Developer > SDK & API Keys, and replace it in the environmentId
setting.
ZKsync Connector Methods
The ZKsync connector provides several key methods for interacting with smart accounts:
getAccountAbstractionProvider
Returns a smart account client or session client based on the provided parameters. This method is the primary way to interact with zkSync smart accounts.
import { isZKsyncConnector } from '@dynamic-labs/ethereum-aa-zksync';
// Get the smart account provider
const provider = wallet.connector.getAccountAbstractionProvider();
// Get a session-based provider
const sessionProvider = wallet.connector.getAccountAbstractionProvider({
sessionId: 'your-session-id',
origin: 'https://your-app.com'
});
getWalletClient
Creates a wallet client with custom transport for RPC methods.
const walletClient = await wallet.getWalletClient(chainId.toString());
Transaction Examples
Basic Transaction
Send a simple transaction using the account abstraction provider:
import { isZKsyncConnector } from '@dynamic-labs/ethereum-aa-zksync';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { parseEther } from 'viem';
const sendBasicTransaction = async () => {
const { primaryWallet } = useDynamicContext();
if (!primaryWallet?.connector || !isZKsyncConnector(primaryWallet.connector)) {
throw new Error('ZKsync connector not found');
}
// Get the account abstraction provider
const client = primaryWallet.connector.getAccountAbstractionProvider();
if (!client) {
throw new Error('Account abstraction client not found');
}
try {
const hash = await client.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: parseEther('0.001'),
data: '0x'
});
console.log('Transaction hash:', hash);
return hash;
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
};
Create a session
Create a session in order to make transactions on the users behalf
import { isZKsyncConnector } from '@dynamic-labs/ethereum-aa-zksync';
const generateSessionConfig = () => {
const currentTime = BigInt(Math.floor(Date.now() / 1000));
const oneDayPeriod = BigInt(86400);
const defaultExpiryTime = (currentTime + oneDayPeriod).toString();
return {
// expiration for the whole session. If not specified, defaults to 24hrs
expiresAt: defaultExpiryTime,
callPolicies: [],
feeLimit: {
limit: parseEther('1'), // maximum fees allowed to be covered during this session
limitType: 2, // 0 = UNLIMITED, 1 = LIFETIME, 2 = ALLOWANCE
period: defaultExpiryTime // time before the feeLimit resets
},
transferPolicies: [
{
maxValuePerUse: parseEther('10'), // maximum amount of ETH that can be sent in a single transaction
target: '0x....', // to address for the transfer requests
valueLimit: parseEther('10'), // maximum amount of ETH that can be sent during the period defined below
limitType: 2, // 0 = UNLIMITED, 1 = LIFETIME, 2 = ALLOWANCE
period: defaultExpiryTime // time before the valueLimit resets
}
]
}
}
const createSession = async () => {
if (!primaryWallet?.connector || !isZKsyncConnector(primaryWallet.connector)) {
throw new Error('ZKsync connector not found');
}
const {
sessionId, // session hash
expiresAt,
session: {
sessionConfiguration, // session configuration w/ bigints as string
sessionKey, // registered session private key
sessionKeyValidator // session key validator contract address
}
} = await primaryWallet.connector.createSession({
sessionConfig: generateSessionConfig(),
});
...
}
Session-Based Transaction
Send a transaction using the session client from getAccountAbstractionProvider:
import { isZKsyncConnector } from '@dynamic-labs/ethereum-aa-zksync';
import { parseEther } from 'viem';
const sendSessionTransaction = async (sessionId, origin) => {
if (!primaryWallet?.connector || !isZKsyncConnector(primaryWallet.connector)) {
throw new Error('ZKsync connector not found');
}
// Get session-based client
const client = primaryWallet.connector.getAccountAbstractionProvider({
sessionId,
origin
});
if (!client) {
throw new Error('Session client not found');
}
try {
const hash = await client.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: parseEther('0.001'),
data: '0x'
});
return hash;
} catch (error) {
console.error('Session transaction failed:', error);
throw error;
}
};
Send a transaction using the session client (w/o getAccountAbstractionProvider)
import { isZKsyncConnector } from '@dynamic-labs/ethereum-aa-zksync';
import { parseEther } from 'viem';
const sendSessionTransaction = async (sessionPrivateKey, sessionConfiguration) => {
if (!primaryWallet?.connector || !isZKsyncConnector(primaryWallet.connector)) {
throw new Error('ZKsync connector not found');
}
// Get session-based client
const client = await primaryWallet.connector.createSessionClient(sessionPrivateKey, sessionConfiguration);
if (!client) {
throw new Error('Session client not found');
}
try {
const hash = await client.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: parseEther('0.001'),
data: '0x'
});
return hash;
} catch (error) {
console.error('Session transaction failed:', error);
throw error;
}
};
Message Signing
Sign messages using the smart account:
const signMessage = async (message) => {
if (!primaryWallet?.connector || !isZKsyncConnector(primaryWallet.connector)) {
throw new Error('ZKsync connector not found');
}
const client = primaryWallet.connector.getAccountAbstractionProvider();
if (!client) {
throw new Error('Account abstraction client not found');
}
try {
const signature = await client.signMessage({
message: message
});
console.log('Message signature:', signature);
return signature;
} catch (error) {
console.error('Message signing failed:', error);
throw error;
}
};
Next Steps