Skip to main content

Getting Started

In this tutorial, you will use ZeroDev to send gasless transactions and bundle transactions.

Create a project

Go to the ZeroDev dashboard and create a project for Polygon Mumbai.

Note that your project has an ID. We will be using this ID in one of the later steps.

Set up gas policies

While we are at the dashboard, let's set up "Gas Policies" -- rules that determine which transactions we will sponsor gas for.

Go to the "Gas Policies" section of your dashboard and create a new "Project Policy":

Here we are saying that we will sponsor up to 100 transactions per minute.

Install dependencies


For Node.js we recommend v18 or above. Lower Node.js versions have not been tested.

Create an empty working directory and initialize it with npm:

mkdir zerodev-tutorial
cd zerodev-tutorial
npm init -y

Then install the ZeroDev SDK:

npm i @zerodev/sdk

Send gasless transactions

We will now implement a simple script that:

  1. Creates an AA wallet from a private key
  2. Mints an NFT without paying gas

To make things easier, we already deployed an NFT contract on Polygon Mumbai that allows anyone to mint NFTs.

Create a file app.js with the following content:

const { ECDSAProvider } = require('@zerodev/sdk')
const { LocalAccountSigner } = require("@alchemy/aa-core")
const { encodeFunctionData, parseAbi, createPublicClient, http } = require('viem')
const { polygonMumbai } = require('viem/chains')

// ZeroDev Project ID
const projectId = process.env.PROJECT_ID

// The "owner" of the AA wallet, which in this case is a private key
const owner = LocalAccountSigner.privateKeyToAccountSigner(process.env.PRIVATE_KEY)

// The NFT contract we will be interacting with
const contractAddress = '0x34bE7f35132E97915633BC1fc020364EA5134863'
const contractABI = parseAbi([
'function mint(address _to) public',
'function balanceOf(address owner) external view returns (uint256 balance)'
const publicClient = createPublicClient({
chain: polygonMumbai,
// the API is rate limited and for demo purposes only
// in production, replace this with your own node provider (e.g. Infura/Alchemy)
transport: http(''),

const main = async () => {
// Create the AA wallet
const ecdsaProvider = await ECDSAProvider.init({
const address = await ecdsaProvider.getAddress()
console.log('My address:', address)

// Mint the NFT
const { hash } = await ecdsaProvider.sendUserOperation({
target: contractAddress,
data: encodeFunctionData({
abi: contractABI,
functionName: 'mint',
args: [address],
await ecdsaProvider.waitForUserOperationTransaction(hash)

// Check how many NFTs we have
const balanceOf = await publicClient.readContract({
address: contractAddress,
abi: contractABI,
functionName: 'balanceOf',
args: [address],
console.log(`NFT balance: ${balanceOf}`)

main().then(() => process.exit(0))

Feel free to read the script and see what it's doing. It should be fairly straightforward. Note that:

  • We are using the ZeroDev SDK with Viem in this example, but you can use it with Ethers as well.
  • A transaction from an AA wallet is also known as a "user operation," which is why we use the function sendUserOperation to send the transaction.

The script requires that we set a project ID and a private key. We can generate a random private key with this command:

node -e "console.log('0x' + require('crypto').randomBytes(32).toString('hex'))"

Then export the variables:

export PROJECT_ID="your project ID"
export PRIVATE_KEY="your private key"

Make sure to replace the values with your actual project ID and the private key you just generated.

Now run this script:

node app.js

If everything goes well, you should see output like:

My address:  0xdc25579151367F44a99E9e92D1E4237200A32Cba
NFT balance: 1

Boom! You just sent a your first gasless AA transaction. You can go to the block explorer and search for your address, and you should see a transaction under the ERC-721 Token Txns section, even though your wallet has no gas. Magical!


You might wonder why you don't see any transactions in the block explorer. This is because a ERC-4337 transaction is not a regular transaction. To see your transactions, use a ERC-4337 explorer such as JiffyScan.

Note how our account is identified as a "contract" by the block explorer. This is because in account abstraction, all accounts are smart contract accounts.

Feel free to run the script a few more times to mint more NFTs. It's free after all :)

Bundle transactions

Minting one NFT at a time is cool, but what if we wanna mint two at a time? With a traditional wallet, you'd have to send two transactions. With AA, we can bundle multiple transactions and send them as one -- saving the user time and gas.

To mint two NFTs at a time, simply replace this block:

  const { hash } = await ecdsaProvider.sendUserOperation([{
target: contractAddress,
data: encodeFunctionData({
abi: contractABI,
functionName: 'mint',
args: [address],

With this block (passing two UserOperations as an array):

  const userOp = {
target: contractAddress,
data: encodeFunctionData({
abi: contractABI,
functionName: 'mint',
args: [address],
const { hash } = await ecdsaProvider.sendUserOperation([userOp, userOp])

Now, run node app.js again. You should see that your NFT balance is now increasing two at a time!

Next Steps

Now that you have got a taste of ZeroDev, it's time to dive deep into the docs!