A paymaster is a contract that allows for the payment of fees in an ERC-4337 transaction. This functionality brings about new possibilities in decentralized applications (dapps), similar to how web2 companies pay Visa/Mastercard to process transactions. With Paymasters, users can enjoy blockchain transactions without needing to have Ethereum in their wallet, just as they are accustomed to in web2.
Additionally, paymasters enable users to pay fees using ERC20 tokens such as USDC. By utilizing decentralized oracles, paymasters can fetch the USDC to ETH exchange rate and automatically convert the user's USDC into ETH, allowing for fee payment in a single transaction. This eliminates the need for users to make any extra efforts, enabling them to start transacting on the blockchain without ever owning any ETH.
The paymaster functionality in ERC-4337 works as follows:
When a userOperation
is sent to the global singleton Entrypoint
contract, the first step is to verify the signature from the user's wallet. This ensures that the operation is authenticated.
After the signature verification, if the userOperation
specifies a paymaster that should be used, the Entrypoint
checks whether that paymaster has sufficient deposited funds to sponsor the gas fees for the operation. If the funds are not enough, the operation is rejected. However, if the funds are sufficient, the userOperation
is forwarded to the Paymaster contract for further verification.
The Paymaster contract has the opportunity to review the userOperation
and decide whether it wants to sponsor that specific operation or not. For example, in the case of a dapp paymaster, it may check if the userOperation
interacts with the dapp's contracts and decide to sponsor it only in such cases. It allows paymasters to have control over which operations they choose to sponsor.
It's important to note that a paymaster is required to stake at the Entrypoint
. This stake is locked and serves the purpose of preventing "Sybil attacks" on the blockchain. It makes launching a Denial-of-Service (DoS) attack very expensive.
Once the paymaster has agreed to pay the gas fees, the wallet contract is called to execute the transaction. The execution is completed, and afterwards, the postOp function of the Paymaster contract is called, providing information about the exact gas usage. This allows the paymaster to perform any necessary accounting or further actions if needed.
Trampoline is a lightweight framework used for building Smart Contract Wallets based on Chrome extensions. In this article series, we will explain the process of attaching a paymaster to a Smart Contract Wallet built using the Trampoline. We have divided this series into two articles, and in this part, we will explore an example where we attach Pimlico's paymaster. This paymaster will enable your Smart Contract Wallet users to pay gas fees using the USDC token.
By integrating a paymaster into your Smart Contract Wallet, users can conveniently handle gas fees using USDC tokens. This offers a seamless and user-friendly experience when transacting on the blockchain. Doesn’t that sound great? Let’s review the step-by-step process of incorporating Pimlico's paymaster into your Trampoline-based Smart Contract Wallet!
To better comprehend the code that follows, it's essential to grasp the basics of Trampoline's architecture. This understanding will enhance your comprehension of the subsequent code snippets.
The transaction process flow within Trampoline can be summarized as follows:
Dapp Initiates Transaction: The dapp triggers a transaction by calling eth_sendTransaction
.
Pre-Transaction Confirmation: The PreTransactionConfirmationComponent is loaded. It has the ability to display relevant transaction information or paymaster details and can also return a context.
Unsigned User Operation Creation: The context obtained from the previous step is passed to the background account-api function called createUnsignedUserOpWithContext
. This function generates the unsigned user operation, which includes the paymaster and data. If the developer requires specific parameters for the paymaster, they can be included in the context obtained from Step 2, which is then passed to createUnsignedUserOpWithContext
.
Transaction Confirmation: The unsigned user operation created in the last step is passed to the TransactionConfirmationComponent. This component serves as the default transaction confirmation screen, but developers have the flexibility to modify it since it is part of the account-api
component. The TransactionConfirmationComponent also receives the context from Step 2 and can return a new context if necessary.
Post-Transaction Confirmation: The PostTransactionConfirmationComponent is mounted using the context obtained from Step 4. At this stage, developers can request external signatures after the transaction has been confirmed by the user. For instance, in a two-owner setup, a rainbow wallet’s signature is needed. This component also returns a context, which will be passed to account-api
.
User Operation Signature: After Step 5, the account-api
is called, and the function signUserOpWithContext is executed, passing the context obtained from Step 5.
User Operation Dispatch
By following this transaction process flow, Trampoline ensures the smooth and secure handling of transactions within the Smart Contract Wallet.
To make the USDC paymaster by Pimlico work, there are certain steps that need to be followed. While you can refer to Pimlico's documentation for more detailed information about their paymasters and how they function, we will focus on the necessary steps to make the USDC paymaster work with Trampoline.
One crucial requirement is that the wallet must approve access to USDC for Pimlico's Paymaster before it can start sponsoring gas. This step is essential because if you're using SimpleAccount by Eth-infinitism, you need to deploy the wallet and then send a transaction to approve USDC, as outlined in Pimlico's documentation. However, this process requires the user to have access to the native token (ETH) to perform these actions. To overcome this limitation, we will create a new Smart Contract Wallet (SCW) that extends SimpleAccount. In the initialization phase of the SCW, we will approve USDC, allowing us to sponsor Account creation with USDC.
You can find the contract I have written for this purpose at the following GitHub link.
In the provided code, you'll notice that we allow a single transaction to be included in the initialize function call. Here, we will encode the transaction data for approving USDC, which will enable the approval to occur during the creation of the SCW.
We also need to modify the SimpleAccountFactory
so that we can call the initialize function with the correct parameters, you can check the code for the factory.
In the factory, as you can see, we now also accept the transaction details that we expect in our new SimpleAccountWithPaymaster
.
We must also deploy the account factory before we move forward. To deploy the factory you must make changes to deploy/deploy.ts
. You can check out those changes here.
Once you have made those changes you can deploy the Factory using npx hardhat deploy –network mumbai
. Alternatively, we have already deployed such a factory on the Mumbai network, so you can instead just use 0xb6f4fB799a085ef048c796F22a74B5c646BF77d4
as the factory address in your exconfig. For more info check out the last section about how to test.
IMPORTANT NOTE:
When we approve USDC, the USDC contract accesses global storage to check if USDC is paused or not. This is a problematic limitation because according to the ERC-4337 bundler rules, bundlers cannot allow access to global storage when a SCW is created. To address this, a proposed solution is for well-known bundler providers such as Stackup, Candide, Alchemy, etc., to create a separate mempool that allows ONLY USDC to access global storage. However, for testing purposes, we will be running the bundler locally in an unsafe mode to overcome this limitation, as the alternate mempool is not active yet. You can also choose to use Candide’s Bundler or Pimlico’s Bundler as they have already whitelisted a USDC paymaster.
Since we have developed our custom Smart Contract Wallet (SCW), we also need to modify the way we generate the initialization code for the account. Referring to the previous section, our SCW's initialize function takes transaction information, and we mentioned that we need to encode the USDC approval transaction information and send it during initialization.
First, we need to initialize the erc20Paymaster
provided by Pimlico's SDK.
To modify the generation of the account init code, we will make changes to the getAccountInitCode
function in account-api
.
In this code snippet, you can observe the following steps:
We retrieve the usdcTokenAddress
from Pimlico's erc20Paymaster
.
With the usdcTokenAddress
, we connect to the ERC20 implementation provided in Pimlico's documentation.
The next step involves obtaining the encoded transaction data for approving the USDC token. In this code, we approve the maximum amount of USDC tokens, but you can adjust this based on your specific use case.
Finally, we wrap this transaction data with our Account's Factory code and return the account init code data.
By incorporating these changes, we ensure that the initialization code for the account includes the necessary steps to approve the USDC token for the needed amount. This enables the proper functioning of the SCW with the integrated USDC paymaster.
To continue with the implementation, now that our Smart Contract Wallet (SCW) is ready and we have approved USDC during the creation phase, we move on to the next step. Referring to Trampoline's structure mentioned earlier, we need to return PaymasterAndData
in the createUnsignedUserOpWithContext
function call that occurs in step 3.
To follow along with the code, you can visit Github here.
In the code snippet, we can observe the following steps:
First, we create an unsigned user operation by calling super.createUnsignedUserOp(info)
, as we are extending BaseAccountAPI
.
Once the unsigned user operation is created, we call adjustGasParameters
to handle a small bug in BaseAccountApi
where proper gas calculations are not performed.
After adjusting the gas parameters, we invoke erc20Paymaster.generatePaymasterAndData
to obtain PaymasterAndData
.
Once we have PaymasterAndData
, we append it to our user operation and return the unsigned user operation.
With these implementations, our USDC token paymaster code is complete. This enables the integration of USDC as a paymaster within the Trampoline-based Smart Contract Wallet.
To test the functionality of the SCW with the integrated USDC paymaster, we need to run the bundler on our local machine, since approving USDC during SCW creation is not supported by current bundler providers in the main mempool. Alternatively, you can use Candide’s Bundler or Pimlico’s Bundler as they have already whitelisted a USDC paymaster.
Here are the steps to follow:
Clone the bundler repository: git clone https://github.com/eth-infinitism/bundler
Install the dependencies by running yarn install
.
Compile all local dependencies with yarn preprocess
.
Edit bundler.config.json
located at packages/bundler/localconfig
:
Set the network to your local Hardhat node.
Modify the entryPoint
address with the latest address.
Ensure your mnemonic
and beneficiary
are correctly set up with native tokens in the address corresponding to your mnemonic
to forward transactions to the blockchain.
Start the bundler by running yarn bundler --unsafe --auto
.
Once the bundler is up and running, you need to make changes to src/exconfig.ts
by updating the bundler URL and the factory address for the newly deployed factory. You can find the changes made to exconfig.ts
in this link.
In the modified config, the network has been changed to Polygon, the bundler URL is set to Candide’s bundler, and the factory_address
is updated with the deployed factory address.
Before proceeding with testing, make sure you obtain some USDC on the Mumbai testnet. If you're using MetaMask, switch your chain to Mumbai and visit this page to swap testnet MATIC to USDC on Uniswap.
Once you have USDC, completed the configuration changes, and have the bundler running, load the Trampoline extension in your Chrome browser and create a new wallet. Transfer at least 10 USDC to the newly created wallet on the Mumbai chain to perform test transactions.
To deploy the account, you can execute any test transaction. For example, in this test, we will call the setGreet
function of a greeter contract deployed on the Mumbai network. You can find the contract at https://mumbai.polygonscan.com/address/0xe418F56098e065A15458871F38E136aCd5C55785#code
To perform the test transaction:
Visit PolygonScan and navigate to the "Write Contract" section of the greeter contract.
Click on "Connect to Web3" and grant permission for Trampoline to connect.
Once permission is granted, enter any value in the setGreeting
input field and click the button that says "Write".
With that, you will be able to deploy and test a transaction from your SCW using USDC.
By following these steps, you can thoroughly test the SCW with the integrated USDC paymaster, ensuring smooth transaction functionality.
Another type of paymaster which can allow you to pay for gas based on off-chain events is Verying Paymaster. Check out Paymasters with Trampoline - Part II: Verifying Paymasters to know more about it and how to use it with Trampoline.