Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add web3js paymaster dapp tutorial #106

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 307 additions & 0 deletions content/tutorials/web3js-paymaster-tutorial/10.index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
---
title: Building a dApp with Stablecoin Gas Payments using Web3.js
description: Learn how to build a decentralized application that enables users to pay transaction gas fees using stablecoins instead of ETH on ZKSync.
---

Build a decentralized application that allows users to pay transaction gas fees using stablecoins instead of ETH on ZKSync.

## Prerequisites

- Node.js v14 or later
- Basic knowledge of React and TypeScript
- Understanding of Web3 concepts
- MetaMask wallet
- Basic understanding of smart contracts

## Project Setup

1. Create a new React TypeScript project:

```bash{copy}
npx create-react-app zksync-paymaster-demo --template typescript
cd zksync-paymaster-demo
```

2. Install required dependencies:

```bash{copy}
npm install web3 web3-plugin-zksync zksync-web3-contract-paymaster-plugin
```

## Smart Contract Setup

::callout{icon="i-ph-info-bold"}
First, let's set up our contract addresses and ABIs in a constants file. You'll need these for interacting with the smart contracts.
::

Store your contract addresses and ABIs in a constants file:

```typescript{copy}
export const MOCK_USDT_ADDRESS = "0xff68f7561562C1F24A317d939B46741F76c4Ef55";
export const MOCK_USDT_PAYMASTER_ADDRESS = "0xfA7Adc05E56893df8ecE1F63E4dB1db7767146f4";
export const STORE_CONTRACT_ADDRESS = "0xEc969112DB5440c954CB60B4Bbd1159673eeE4C3";

export const MOCK_USDT_ADDRESS = "0xff68f7561562C1F24A317d939B46741F76c4Ef55";
export const MOCK_USDT_PAYMASTER_ADDRESS = "0xfA7Adc05E56893df8ecE1F63E4dB1db7767146f4";
export const STORE_CONTRACT_ADDRESS = "0xEc969112DB5440c954CB60B4Bbd1159673eeE4C3";

// Store Contract ABI
export const STORE_CONTRACT_ABI = [
{
"inputs": [
{
"internalType": "uint256",
"name": "_productId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quantity",
"type": "uint256"
}
],
"name": "purchaseProduct",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},

];

// Stablecoin (MOCK USDT) ABI
export const STABLECOIN_ABI = [

{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},

];
```

## Paymaster Integration

### Understanding Paymasters in ZKSync

::callout{icon="i-ph-lightbulb-bold"}
Before diving into the implementation, it's important to understand what a Paymaster is and how it works in ZKSync.
::

A Paymaster is a smart contract in ZKSync that enables gas abstraction - allowing users to pay transaction fees with tokens other than ETH.
This is a powerful feature that improves user experience by:

- Letting users pay gas fees in ERC20 tokens
- Enabling sponsored transactions where a third party pays the gas
- Supporting more complex gas payment scenarios

In this tutorial we are using a custom made paymaster which accepts a custom token as referenced above:
`MOCK_USDT_PAYMASTER_ADDRESS` and `MOCK_USDT_ADDRESS`. The paymaster contract address must be supplied to the `zksync-web3-contract-paymaster-plugin`.
A comprehensive guide on creating a custom ERC20 paymaster can be found in the [ZKSync ERC20 Paymaster Tutorial](https://code.ZKSync.io/tutorials/erc20-paymaster).

1. Create a custom hook for Paymaster interactions
2. Set up Web3 and plugin initialization
3. Implement contract interaction methods

### Implementation

1. Create a custom hook for Paymaster interactions:

```typescript{copy}
import { useState, useEffect, useCallback } from 'react';
import Web3 from 'web3';
import { getPaymasterParams, types, Web3ZKSyncL2, ZKSyncPlugin } from 'web3-plugin-zksync';
import ZKSyncContractPaymasterPlugin from "zksync-web3-contract-paymaster-plugin";
import { MOCK_USDT_ADDRESS } from './constants';

const usePaymasterAsync = (contractAddress: string, contractAbi: any, _paymasterAddress?: string) => {
// Note: _paymasterAddress is optional - if not provided, the plugin will use testnet paymaster
const [web3, setWeb3] = useState<Web3>();
const [plugin, setPlugin] = useState<ZKSyncContractPaymasterPlugin>();

// Initialize Web3 and register the required plugins
useEffect(() => {
const initializeWeb3 = async () => {
if (typeof window.ethereum !== 'undefined') {
// Create Web3 instance
const web3 = new Web3(window.ethereum);

// Initialize ZKSync L2 provider for Sepolia testnet
const l2 = Web3ZKSyncL2.initWithDefaultProvider(types.Network.Sepolia);

// Create instance of the Paymaster plugin
const plugin = new ZKSyncContractPaymasterPlugin(window.ethereum);

// Register both plugins with Web3
web3.registerPlugin(plugin);
web3.registerPlugin(new ZKSyncPlugin(l2));

setPlugin(plugin);
setWeb3(web3);
}
};

initializeWeb3();
}, []);

// This function handles contract interactions with Paymaster integration
const writeContractWithPaymaster = useCallback(async (
{ functionName, args }: { functionName: string; args: any[] }
) => {
if (!web3 || !plugin) return;

return await plugin.write(contractAddress, contractAbi, {
methodName: functionName,
args: args,
from: await web3.eth.getAccounts().then(a => a[0]),
customData: {
// Gas per pubdata is a ZKSync-specific parameter
gasPerPubdata: 50000,
// Configure Paymaster parameters for gas abstraction
paymasterParams: getPaymasterParams(_paymasterAddress, {
type: "ApprovalBased",
minimalAllowance: 10,
token: MOCK_USDT_ADDRESS,
innerInput: new Uint8Array(),
})
}
});
}, [web3, plugin, contractAddress]);

return { writeContractWithPaymaster };
};
```

The `usePaymasterAsync` hook does several important things:

• Initializes Web3 with ZKSync plugins

• Provides a function to interact with any smart contract while using the Paymaster

• Handles the configuration of Paymaster parameters for gas fee abstraction

2. Implement the purchase function with Paymaster support:

This example shows how to use the Paymaster hook with any smart contract.
In this case, we're using a marketplace contract that takes a
product ID and quantity, but you can adapt this pattern for any contract function:

```typescript{copy}
const buyProductWithPayMaster = async (product: Product, itemQty: number) => {
try {
// IMPORTANT: First approve the spending of tokens
// This is required for any ERC20 token interaction
const approvalTx = await approveWithPaymasterAsync(
{
functionName: 'approve',
args: [STORE_CONTRACT_ADDRESS, product.price]
}
);

// Wait for approval transaction to be confirmed
await approvalTx.wait();

// Then make the actual contract call
// This could be any function on any contract
const tx = await buyWithPaymasterAsync(
{
functionName: 'purchaseProduct', // Your contract function name
args: [product.id, itemQty] // Your function arguments
}
);

return { tx, product };
} catch (error) {
console.error('Error buying product with Paymaster', error);
throw error;
}
};

// Example of how this is used in a component
const Products = ({ account, web3 }) => {
// Initialize the Paymaster hooks for both token and marketplace contracts
const { writeContractWithPaymaster: approveWithPaymasterAsync } = usePaymasterAsync(
MOCK_USDT_ADDRESS, // Token contract
STABLECOIN_ABI,
MOCK_USDT_PAYMASTER_ADDRESS
);

const { writeContractWithPaymaster: buyWithPaymasterAsync } = usePaymasterAsync(
STORE_CONTRACT_ADDRESS, // Your contract
STORE_CONTRACT_ABI,
MOCK_USDT_PAYMASTER_ADDRESS
);

// Function to handle the purchase
async function buy(product: Product, quantity: number) {
if (!web3 || !account) {
toast.error('Wallet not connected');
return;
}

const id = toast.loading(`Purchasing ${product.name}... and paying gas fees with MOCK_USDT`);
try {
const val = await buyProductWithPayMaster(product, quantity);
if (!val) return;

const { tx } = val;
toast.success(`Purchase successful. Gas paid with MOCK_USDT`, { id });
} catch (error) {
toast.error('Error purchasing product: ' + error, { id });
}
}

return (
// Your UI components
);
};
```

::callout{icon="i-ph-list-checks-bold"}
Here are the key points to remember when implementing Paymaster functionality:
::

1. **Token Approval**: Before any ERC20 token transaction, you must approve the spending amount. This is standard for all ERC20 tokens.

2. **Contract Interaction**: The example shows a purchase function, but you can use this pattern with any smart contract function. Just modify:
- The contract address
- The contract ABI
- The function name
- The function arguments

3. **Gas Payment**: The Paymaster handles converting your token payment into gas fees automatically.

## Conclusion

This tutorial demonstrated how to build a dApp that leverages ZKSync's Paymaster feature to enable stablecoin gas payments. The key components are:

- Integration with ZKSync's L2 network using the Paymaster plugin
- Smart contract interactions for token approvals and purchases
- User interface for seamless interaction

::callout{icon="i-ph-arrow-right-bold"}
You can view a live demo of this implementation at [Web3js ZKSync Plugin Demo](https://web3js-ZKSync-plugin.vercel.app/).
::

::alert{type="success"}
Congratulations! You've built a dApp that uses ZKSync's Paymaster feature to enable stablecoin gas payments.
::
32 changes: 32 additions & 0 deletions content/tutorials/web3js-paymaster-tutorial/_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
title: Building a ZKSync dApp with Stablecoin Gas Payments using Web3.js
authors:
- name: Jovells
image: https://avatars.githubusercontent.com/u/100000000?v=4
twitter: https://twitter.com/jovells
github: https://github.com/jovells
tags:
- web3.js
- paymaster
- gas-abstraction
- stablecoin
- react
- typescript
level: intermediate
duration: 45 minutes
github_repo: https://github.com/Jovells/ZKSync-web3js-paymaster-demo
summary: |
Learn how to build a dApp that allows users to pay transaction gas fees using stablecoins instead of ETH on ZKSync.
This tutorial uses Web3.js and ZKSync's Paymaster feature to implement gas abstraction.
description: |
This comprehensive guide walks through creating a decentralized application that leverages ZKSync's Paymaster feature
for stablecoin gas payments. You'll learn how to integrate Web3.js with ZKSync plugins, implement smart contract
interactions, and create a user interface for seamless gas abstraction.
what_you_will_learn:
- How to integrate Web3.js with ZKSync plugins
- How to implement smart contract interactions
- How to create a user interface for seamless gas abstraction
updated: 2024-12-21
tools:
- Web3.js
- React
- MetaMask
Loading