🚀 NERO Chain x AKINDO WaveHack has officially started! Join now: https://app.akindo.io/wave-hacks/VwQGxPraOF0zZJkX
CookbookSending UserOperations

How to Send Operations with the NERO Chain Paymaster API

This tutorial explains how to execute transactions using the NERO Chain Paymaster API, focusing on sponsored gas transactions through Account Abstraction.

What You’ll Learn

  • What UserOperations are in the ERC-4337 Account Abstraction standard
  • How the NERO Chain Paymaster enables gasless transactions
  • How to create a function that sends transactions through AA wallets
  • How to execute your first sponsored (free) transaction
  • How to implement proper error handling and transaction monitoring

Prerequisites

  • Completed the AA Wallet Integration tutorial
  • Basic understanding of smart contract interactions with ethers.js
  • Access to an AA wallet configuration as shown in the previous tutorial
  • An API key from the NERO Chain AA Platform

Understanding Paymasters and UserOperations

What is a Paymaster?

A Paymaster is a service that sponsors transaction gas fees for users. In the Account Abstraction (AA) model, the Paymaster:

  1. Reviews a transaction before it’s executed
  2. Determines if it should sponsor the gas fees
  3. Signs the transaction to indicate its willingness to pay
  4. Can accept alternative payment methods (like ERC-20 tokens) instead of the native token

What is a UserOperation?

In the ERC-4337 Account Abstraction standard, a UserOperation replaces traditional Ethereum transactions. It contains:

  • Sender: The AA wallet address
  • CallData: The encoded function call to execute
  • Gas Parameters: Limits and prices for the execution
  • Signature: Proof that the operation is authorized by the wallet owner
  • PaymasterAndData: Optional paymaster information for gas sponsorship

Step 1: Setting Up Paymaster Configuration

First, update your configuration file to include Paymaster settings:

// src/config.ts
// Add to existing configuration from AA wallet tutorial
export const AA_PLATFORM_CONFIG = {
  bundlerRpc: "https://bundler.service.nerochain.io",
  paymasterRpc: "https://paymaster-testnet.nerochain.io",
};
 
// Your API key from the NERO Chain AA Platform
export let API_KEY: string = "your_api_key_here";
 
// Add NFT contract for testing
export const CONTRACT_ADDRESSES = {
  entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
  accountFactory: "0x9406Cc6185a346906296840746125a0E44976454",
  tokenPaymaster: "0xA919e465871871F2D1da94BccAF3acaF9609D968",
  nftContract: "0x63f1f7c6a24294a874d7c8ea289e4624f84b48cb"
};

Step 2: Creating a Basic Transaction Function

Let’s create a function that can send a transaction with sponsored gas through the Paymaster:

// src/utils/aaUtils.ts
import { ethers } from 'ethers';
import { Client, Presets } from 'userop';
import { 
  NERO_CHAIN_CONFIG, 
  AA_PLATFORM_CONFIG, 
  CONTRACT_ADDRESSES,
  API_KEY 
} from '../config';
 
// Function to execute a contract call via AA with sponsored gas
export const executeSponsoredOperation = async (
  accountSigner: ethers.Signer,
  contractAddress: string,
  contractAbi: any,
  functionName: string,
  functionParams: any[],
  options?: {
    apiKey?: string;
    gasMultiplier?: number;
  }
) => {
  try {
    // Initialize AA client
    const client = await Client.init(NERO_CHAIN_CONFIG.rpcUrl, {
      overrideBundlerRpc: AA_PLATFORM_CONFIG.bundlerRpc,
      entryPoint: CONTRACT_ADDRESSES.entryPoint,
    });
    
    // Initialize AA builder
    const builder = await Presets.Builder.SimpleAccount.init(
      accountSigner,
      NERO_CHAIN_CONFIG.rpcUrl,
      {
        overrideBundlerRpc: AA_PLATFORM_CONFIG.bundlerRpc,
        entryPoint: CONTRACT_ADDRESSES.entryPoint,
        factory: CONTRACT_ADDRESSES.accountFactory,
      }
    );
    
    // Configure gas parameters
    const gasParams = {
      callGasLimit: "0x88b8",
      verificationGasLimit: "0x33450",
      preVerificationGas: "0xc350",
      maxFeePerGas: "0x2162553062",
      maxPriorityFeePerGas: "0x40dbcf36",
    };
    
    // Set gas parameters
    builder.setCallGasLimit(gasParams.callGasLimit);
    builder.setVerificationGasLimit(gasParams.verificationGasLimit);
    builder.setPreVerificationGas(gasParams.preVerificationGas);
    builder.setMaxFeePerGas(gasParams.maxFeePerGas);
    builder.setMaxPriorityFeePerGas(gasParams.maxPriorityFeePerGas);
    
    // Configure paymaster for sponsored transactions (free)
    const paymasterOptions = {
      apikey: options?.apiKey || API_KEY,
      rpc: AA_PLATFORM_CONFIG.paymasterRpc,
      type: "0" // Type 0 = sponsored/free gas
    };
    
    // Set paymaster options
    builder.setPaymasterOptions(paymasterOptions);
    
    // Create contract instance
    const contract = new ethers.Contract(
      contractAddress,
      contractAbi,
      ethers.getDefaultProvider()
    );
    
    // Encode function call
    const callData = contract.interface.encodeFunctionData(
      functionName,
      functionParams
    );
    
    // Create the UserOperation
    const userOp = await builder.execute(contractAddress, 0, callData);
    
    console.log("Sending UserOperation to paymaster...");
    
    // Send the UserOperation
    const res = await client.sendUserOperation(userOp);
    console.log("UserOperation sent with hash:", res.userOpHash);
    
    // Wait for the transaction to be included
    const receipt = await res.wait();
    if (!receipt) {
        throw new Error("Transaction receipt is null");
    }
    console.log("Transaction mined in block:", receipt.blockNumber);
 
    return {
        userOpHash: res.userOpHash,
        transactionHash: receipt.transactionHash,
        receipt: receipt
    };
  } catch (error) {
    console.error("Error executing operation:", error);
    throw error;
  }
};

Step 3: Creating a Specific Function for NFT Minting

Let’s implement a specific function for minting an NFT using the generic operation executor:

// src/utils/aaUtils.ts
 
//Add a generic NFT ABI : This can be changed by creating abi folders, or having your own abis. 
const NFT_ABI = [
    "function mint(address to, string memory uri) external",
    "function tokenURI(uint256 tokenId) external view returns (string memory)",
    "function balanceOf(address owner) external view returns (uint256)"
];
 
export const mintNFT = async (
  accountSigner: ethers.Signer,
  recipientAddress: string,
  metadataUri: string,
  options?: {
    apiKey?: string;
    gasMultiplier?: number;
  }
) => {
  try {
    // Execute the mint function with sponsored gas
    return await executeSponsoredOperation(
      accountSigner,
      CONTRACT_ADDRESSES.nftContract,
      NFT_ABI,
      'mint',
      [recipientAddress, metadataUri],
      options
    );
  } catch (error) {
    console.error("Error minting NFT:", error);
    throw error;
  }
};

Step 4: Using the Functions in a React Component

Now, implement the function in a React component:

// Example usage in a component
import React, { useState } from 'react';
import { getSigner, mintNFT } from '../utils/aaUtils';
import { NERO_CHAIN_CONFIG } from '../config';
import { toast } from 'react-toastify';
 
const NFTMinterButton: React.FC = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [txHash, setTxHash] = useState('');
 
  const handleMint = async () => {
    try {
      setIsLoading(true);
      
      // Get signer from browser wallet
      const signer = await getSigner();
      
      // Define NFT metadata (can be fetched from form)
      const metadataUri = "ipfs://bafkreiabag3ztnhe5pg7js3cokbq3id2b3t6evbncbpzzh2c5sdioxngoe";
      
      // Execute the mint operation with sponsored gas
      const result = await mintNFT(
        signer,
        await signer.getAddress(), // Mint to the connected wallet
        metadataUri,
        { apiKey: 'your_api_key_here' }
      );
      
      setTxHash(result.transactionHash);
      toast.success("NFT minted successfully!");
    } catch (error) {
      console.error("Error minting NFT:", error);
      toast.error("Failed to mint NFT: " + error.message);
    } finally {
      setIsLoading(false);
    }
  };
 
  return (
    <div className="minter-container">
      <button 
        onClick={handleMint}
        disabled={isLoading}
      >
        {isLoading ? "Minting..." : "Mint NFT"}
      </button>
      
      {txHash && (
        <div className="tx-info">
          <p>Transaction successful!</p>
          <a 
            href={`${NERO_CHAIN_CONFIG.explorer}/tx/${txHash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            View on Explorer
          </a>
        </div>
      )}
    </div>
  );
};
 
export default NFTMinterButton;

Testing

The same as before, add the component to your App.tsx file and run it.

Understanding the Paymaster Flow

  1. Builder Creation: Initialize the SimpleAccount builder with your signer
  2. Paymaster Configuration: Set the API key and payment type (0 for free/sponsored)
  3. Operation Preparation: Create the contract call data for your function
  4. UserOp Creation: Build the UserOperation with the builder
  5. UserOp Submission: Send the operation to the bundler via the client
  6. Transaction Tracking: Wait for the receipt and track the result

Best Practices for Sponsored Transactions

  1. Error Handling: Always implement proper error handling and provide user feedback
  2. Gas Parameters: Adjust gas parameters based on your operation’s complexity
  3. API Key Security: Protect your API key by using environment variables and backend proxies in production
  4. Cost Monitoring: Track your sponsored transaction usage through the NERO Chain AA Platform dashboard
  5. User Feedback: Show loading states and transaction results clearly to improve user experience

Key Considerations for Paymaster Usage

  1. API Key Security: Never expose your Paymaster API key in client-side code in production. Consider using a backend proxy.
  2. Gas Limits: The gas parameters should be adjusted based on the complexity of your transactions.
  3. Error Handling: Implement robust error handling for Paymaster-related failures.
  4. Rate Limiting: Be aware that sponsored transactions may have usage limits based on your account.

Next Steps

Now that you’ve learned how to send sponsored UserOperations with your AA wallet, you might want to explore alternative payment methods. Continue to the Payment Methods tutorial to learn how to implement prepaid and postpaid transactions using ERC20 tokens.