How to create a Smart Contract Account with AccountKit and ZAN

Overview

This guide will show you how to create a smart contract account and send a User Operation with Account Kit and ZAN Bundler API.

Account Kit consists of a number of SDK packages that you can leverage to interact with ERC-4337 infrastructure. ZAN provides ERC-4337 compliant APIs that integrate effortlessly with widely-used Account Abstraction SDKs.

Prerequisites

Before creating a SCA with this guide, you should possess skills and knowledge about the topics we are going over. We assume that you already have an understanding/experience with the following:

  • The development of smart contracts and their role in Ethereum
  • Metamask or similar crypto wallet usage
  • We are creating the SCA on the Sepolia testnet, so you won’t need to use real ETH for this guide. However, you will need SepoliaETH to transact on the testnet. You can get test ETH from Alchemy's Sepolia faucet.
  • TypeScript
  • Account Abstraction (ERC-4337) and how it works, conceptually.

Quickstart


1. Setup a project

add the following dependencies:

yarn add viem
yarn add @alchemy/aa-accounts @alchemy/aa-core 
yarn add @zannodeservice/aa-zan
yarn add dotenv

viem is a lightweight interface for Ethereum.

Two Alchemy packages @alchemy/aa-accountsand @alchemy/aa-core come from the Alchemy Account Kit, and will provide the key building blocks.

The aa-zan SDK @zannodeservice/aa-zan enables better interaction with the ZAN AA infrastructure APIs, including the ERC-4337 standard interface, ZAN paymaster service, and ZAN's unique data query interfaces.

dotenv is an npm package that automatically loads environment variables from a .env file into process.env, making it easier to manage configuration options for your applications.

2. Loading Modules and Environment Parameters

import {
    LocalAccountSigner,
    type SmartAccountSigner,
} from "@alchemy/aa-core";
import {
    createLightAccount,
} from "@alchemy/aa-accounts";
import { createZanSmartAccountClient } from "@zannodeservice/aa-zan";
import * as dotenv from "dotenv";
import {http} from "viem";
import { sepolia } from "viem/chains";

Get Constants from environment parameters.

dotenv.config()

const rpcUrl = process.env.RPC_URL;

const private_key = process.env.PRIVATE_KEY;

The .env file must look like follows:

RPC_URL = https://api.zan.top/node/v1/eth/sepolia/{apiKey}
PRIVATE_KEY = 0x123abc...

3. Create a Signer

Our smart contract account needs to have an "owner". An "owner" refers to the entity that has control over a smart contract, including the capability to execute specific privileged actions, such as updating the contract or invoking certain functions that are restricted to the owner. In our case, the owner of the smart contract account is designated by a private key.

PRIVATE_KEY environment variable is read and stored in the private_key constant.

Creating an Account Signer instance based on the private key.

const eoaSigner: SmartAccountSigner = LocalAccountSigner.privateKeyToAccountSigner(private_key); 


4. Create a client

const chain = sepolia;

export const zanSmartAccountClient = createZanSmartAccountClient({
    rpcUrl,
    chain,
    account: await createLightAccount({
        transport: http(rpcUrl),
        chain,
        signer: eoaSigner,
    })
});

5. Send a UserOperation

Build an Eth transfer.

const targetAddress = "0x136aF0A9155d89CD428E8f292F79D74a69B38E0f"; // Replace with the desired target address
const userOpData = {
  target: targetAddress, 
  data: "0x0", 
  value: 0n,
};

Build and then send the UserOperation by calling sendUserOperation method.

then waitForUserOperationTransaction method of smartAccountClient is called, which waits and polls for the user operation to be mined and included into a block, identified by txHash (the transaction hash).

const { hash: batchedUoHash } = await zanSmartAccountClient.sendUserOperation({
    uo:  [userOpData]
  });
// Send a user operation from your smart contract account
console.log("Resulting UserOperation: ", batchedUoHash); // Log the user operation hash
console.log(`Checkout https://jiffyscan.xyz/userOpHash/${batchedUoHash}?network=${chain.name}`)

const txHash = await zanSmartAccountClient.waitForUserOperationTransaction({
  hash: batchedUoHash
});
console.log(`Transaction Hash: ${txHash}`);

you can also use a website called "jiffyscan.xyz for tracking its processing.