🧑‍🍳 Examples

This page provides examples of how to use the Paymaster class and its associated functionality for interacting with Paymaster contracts on the ZKsync network.

The examples cover various scenarios and use cases, demonstrating the different methods and types available in the library.

The Paymaster class is a utility class that simplifies the process of sending transactions through Paymaster contracts, either ERC20Paymaster or SponsoredPaymaster. It abstracts away the complexities of constructing and formatting transactions, handling gas estimation, and interacting with the Paymaster contracts.

In addition to the core functionality of sending transactions, the Paymaster class also provides methods for managing restrictions, which are external smart contracts that can enforce specific conditions or rules for transactions to be eligible for payment by the Paymaster.

The examples in this document will cover the following aspects:

  • Initializing and setting up the Paymaster instance

  • Sending transactions through the Paymaster

  • Estimating gas requirements for transactions

  • Adding, retrieving, and removing restrictions

  • Calculating the minimal allowance required for ERC20 Paymasters

Each example will be accompanied by a brief description, code snippets, and explanations of the relevant methods and parameters used. These examples can serve as a starting point for understanding and integrating the Paymaster library into your applications or projects.

Note that these examples assume familiarity with the ZKsync network, Ethereum development, and the overall context in which the Paymaster library operates.

Preparation

Before you can start using the Paymaster class, you need to create a walletClient and publicClient instances. In the context of a web application, you can use the createWalletClient and createPublicClient from the viem library to get a wallet and public provider from the user's Web3 provider (e.g., MetaMask). Also, please extend the wallet client with eip712WalletActions, like showed in the following code.

import { getPaymaster, createExtension } from "@txfusion/txsync-viem";
import { createWalletClient, custom } from 'viem';
import { zkSync } from 'viem/chains';
import { eip712WalletActions } from 'viem/zksync';

const [account] = await window.ethereum!.request({ method: "eth_requestAccounts" });

const walletClient = createWalletClient({
  account,
  chain: zkSync,
  transport: custom(window.ethereum),
}).extend(eip712WalletActions());


const publicClient = createPublicClient({
  chain: zkSync,
  transport: http(),
});

Obtaining the Paymaster Instance

To create a new instance of the Paymaster class, you can use the getPaymaster helper function provided by the package. This function takes the address of the Paymaster contract and the signer instance (obtained in the previous step) as input parameters.

const paymaster = await getPaymaster(
  paymasterAddress,
  walletClient,
  publicClient
);

console.log(`PaymasterType: ${paymaster.paymasterType}`);
console.log(`Token Address: ${paymaster.token}`);

As you can see, the getPaymaster method automatically infers the type of the Paymaster contract (ERC20Paymaster or SponsoredPaymaster) based on the provided Paymaster address. Additionally, if the Paymaster is an ERC20Paymaster, it also retrieves the address of the associated ERC20 token.

Sending a Transaction through the Paymaster

To send a transaction through the Paymaster, you can use the sendPaymasterTransaction method provided by the Paymaster class. This method takes the contract address, the function to call interface (check docs), an optional array of arguments as input parameters, and optional overrides for the transaction.

const greeterAddress = "0x..."; // Replace with the actual contract address
const functionToCallSignature = "function setGreeting(string memory _greeting)";
const args = ["Hello from paymaster"];

const txResponse = await Paymaster.sendPaymasterTransaction(greeterAddress, functionToCallSignature, args);

To simplify the process of passing arguments to the sendPaymasterTransaction method, the package provides a type called PaymasterParams. This type is a tuple that defines the order and types of the parameters, making it easier to pass the arguments. Here's an example of using PaymasterParams:

import { ERC20Token } from "@txfusion/txsync-viem/src/abi/ERC20Token";

const tokenAddress = '0x...'; // Replace with the actual token address on L2
const args = ['0x...', parseEther(amount)]; // Recipient address and amount to send

const txHash = await paymaster.sendPaymasterTransaction(
  tokenAddress as Address, // Contract address
  ERC20Token, // ABI
  "transfer", // Function to call
  args,
  {
    gasLimit: gasLimit, // Override values
  },
);

Estimating Gas for Paymaster Transactions

The estimateGas method provided by the Paymaster class allows you to estimate the gas required for a transaction that will be sent through the Paymaster. While this method can be used to estimate gas for any transaction, it is specifically tailored to handle the nuances of Paymaster Paymasters.

const estimatedGas = paymaster.estimateGas(
  data.tokenAmount.token.l2Address as Address,
  ERC20Token,
  "transfer",
  [data.recipientAddress, parseEther(data.amount)],
  {
    gasLimit: data.fee.gasLimit,
  },
);

Creating a Extension

The package provides a createExtension function that allows you to create various types of extensions for your Paymaster contract. Extensions are external smart contracts that enforce specific conditions or rules for transactions to be eligible for payment by the Paymaster.

import {createExtension} from '@txfusion/txsync-viem';
import {RestrictionFactory} from '@txfusion/txsync-viem/src/abi/RestrictionFactory';

const userExtensionAddress = await createExtension(
  'USER_EXTENSION', // Name of the extension
  ExtensionMethod.USER, // Extension type
  walletClient, // WalletClient
  publicClient, // PublicClient
  [address], // In this specific case (when user extension is created), only this address will be available to use Paymaster
  RestrictionFactory, // Optional. RestrictionFactory ABI.
  extensionFactoryAddress // Optional. This is prefilled based on chain ID (TODO: Ask for Factory Addresses on testnet and mainnet).
);

Handling Extensions

After creating a extension contract, you can handle it by updating the arguments/values, removing or adding them.

const extensionHash = await handleExtension(walletClient, publicClient, {
  extensionContractAddress: <EXTENSION_ADDRESS>,
  type: ExtensionMethod.CONTRACT,
  args: [<USER_ADDRESS>, true], // When second argument is 'true', the address will be added. If it's 'false', it will be removed.
});

Managing Extensions on Paymaster

After creating a extension contract, you can add it to your Paymaster using the addExtension method provided by the Paymaster instance. Additionally, you can retrieve a list of all added extension and remove a specific extension if needed.

await (await Paymaster.addExtension(userExtensionAddress)).wait();

console.log(await Paymaster.getExtensions());

// Remove restriction at index 0
await (
  await Paymaster.removeExtensions((await Paymaster.getExtensions())[0])
).wait();

Last updated