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

1

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.

2

Configure Account Abstraction

Go to the Account Abstraction section and:

  1. Enter the optional factoryAddress, paymasterAddress and sessionKeyAddress (if available)
  2. Save the settings
  3. Enable the toggle next to the ZkSync section

Make sure to test your configuration on testnet before deploying to mainnet.

3

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.

4

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

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