-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add web3js paymaster dapp tutorial
- Loading branch information
Showing
2 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
307 changes: 307 additions & 0 deletions
307
content/tutorials/web3js-paymaster-tutorial/10.index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
:: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |