Introducing — A “one-for-all” permission-less multi-signer paymaster
TLDR;
Dear Dapps on ZKsync,
- No need to deploy a custom paymaster for gas sponsorship now.- Zyfi is introducing a new permission-less signature-based paymaster.
1. Deposit gas funds -> 2. Add a signer -> you are done!- Only users with correct signature can utilise a Dapp’s gas funds based on respective business logic. Maintain off-chain signature signing with merely 35 lines of code & sponsor gas for your users.
- You can always delegate the signature signing part to Zyfi’s API.
- Thus one paymaster for all…
Current Scenario:
Dapps interested in sponsoring gas for their users with specific conditions — such as offering free gas to certain NFT holders, supporting large swap transactions over $1,000, or recognizing whitelisted users — can enhance user engagement. They might also want to sponsor events like a Dapp’s anniversary, achieving significant milestones, or special promotions like ‘Gas Free Weekends.’
Currently, Zyfi offers compelling solutions that facilitate these goals to a great extent. However, the process for a Dapp to set up this kind of sponsorship independently can be quite cumbersome:
- Develop their own paymaster with custom logic for gas sponsorship.
- Get it audited by audit firms to ensure the bug free deployment.
- Deploy the paymaster.
- Deposit funds on this new paymaster.
- Maintain the funds on paymaster to ensure error free user experience.
- Still not fully flexible as per Dapp’s requirement.
Zyfi’s insight in the space:
Thanks to ZKsync Native AA support, Zyfi becomes the first paymaster solution to cross 1M+ transactions, with major Dapps collaborating with Zyfi, with 120K paymaster users; we have some insights to share based on our experience:
- Users are more than happy to use paymaster, especially with the good UX it provides. It is also seen that paymaster supported Dapps are re-used by users more.
2. Dapps have no problem sponsoring gas for users up to some extent (due to lower L2 fees), but do not have enough resources to make it a priority. Dapps see gas sponsorship as a “good add-on” rather than “priority UX” especially due to flexibility issues.
The gap is visible and Dapps clearly require easy solution for quick adoption.
Zyfi intends to bridge this gap.
Introducing a new Permission-less Multi-signer Paymaster
Zyfi is proud to announce a new common good paymaster :
“A singular permission-less multi-signer paymaster allowing multiple Dapps to seamlessly sponsor gas for their users through signature verification.”
Github : https://github.com/ondefy/permissionless-multisigner-paymaster/
Paymaster Address : 0x68b4F1146a863d0E7cF1e134857411C8a941C86d
Audit: https://www.zyfi.org/report-permissionless-paymaster-zyfi.pdf
Dapps can start using this paymaster by simply depositing funds and adding a signer address. Once done, it can be interacted as if it’s Dapp’s own custom paymaster; thus removing the need to deploy paymaster at all.
- Permission-less : Multiple Dapps can use this paymaster without any restrictions. Deposit gas funds, sponsor gas, withdraw at any time.
- Multi-signer : It allows Dapps to add multiple signers whose signature will be verified before gas sponsorship for the users. Ensuring gas funds are only accessed if Dapp approves off-chain.
How it works?
- Simple 1-Step activation: Dapps deposit gas funds and add a signer address in this paymaster ( can be done in 1 transaction).
1 step activation of your account in paymaster
Done. Ready. All Set. Completed. Voilà. Paymaster activated ✅ 🫡
The depositor address of the Dapp is- “Manager” and the added
signer address is the trusted “Signer” whose signature will be verified for sponsorship in the paymaster to use the manager’s gas funds for the user.
- “Manager” address — Managed fully by Dapp. Responsible for depositing/ withdrawing gas funds, adding and removing “Signers” addresses.
- “Signer” address — Can be managed by Dapp or trusted 3rd party like Zyfi’s API. Signer’s signature required to access gas funds by the Dapp’s user.
The Dapp’s “Manager” can add or remove multiple “Signers” as per requirement.
- Dapps can have any business logic to check which user should have the gas sponsorship on their side, and simply signs upon
user's address
and other data by the Signer. - This extra data & signature is passed as paymaster data along with transaction of the user on ZKsync.
- The paymaster will verify this signature, ensure it’s the correct signer, deducts the required funds from the manager(Dapp’s address) and sponsor the gas for the Dapp’s user.
- Dapps should ensure signature signing part occurs on a secure backend/API server. They can also delegate the signing part to Zyfi API.
- Below diagram explains how easy it becomes to integrate paymaster to your Dapp :
- A manager can have multiple signers.
- All added signers will have access to the gas funds deposited by the manager.
- Infact, a manager can be a signer address as well.
To ensure a seamless experience for Dapps and projects looking to use this common good, Zyfi will provide a dashboard for easy integration.
Use-cases
This paymaster is highly flexible for Dapps, following are few of thousand use-cases:
- Address whitelisting : Maintain list of addresses on frontend. Only sign for those addresses and sponsor gas for them. No need for on-chain transaction.
- Contract based sponsorship : Sponsor all approve transactions of a token or any interaction with a new type of product on your frontend.
Again, no need for any on-chain transaction, update your signing logic likewise. - Exclusive events : Dapp’s anniversary, token launch party, real-life events or even “office-hours” gas sponsorship. Every sort of custom-logic can be created on Dapp side and signer solely need to sign based on this logic.
Technical Details
Dapps can integrate this paymaster within 35 lines of code. This only concerns those who want to manage their own signer. If you choose to call the Zyfi API, you just need to make the call and it will manage all the following steps for you.
- What paymaster data would the signer key sign on?
- Paymaster validates upon EIP-712 type signatures.
(_domainSeparator +
hash(
SIGNATURE_TYPEHASH,
_from,
_to,
_expirationTime,
_maxNonce,
_maxFeePerGas,
_gasLimit
))
_from : The user address the signer wants to sponsor.
_to: The target contract user address is interacting i.e. Dapp’s contract.
_expirationTime : Timestamp post which the signature expires.
_maxNonce : Nonce of the user(_from) post which signature cannot be replayed.
_maxFeePerGas : Current gas price returned by the provider.
_gasLimit : Gas limit required by the transaction. Paymaster cost 60K gas overhead. Hence, should be added while setting close gasLimit.
- Following code represents what the exact values the are required to be signed by the signer(point 3 & 4 as per above integration flow diagram):
// Example code
// ethers v5
import {BigNumber, Contract, Wallet} from "zksync-ethers";
export async function getSignature(
from: string, to: string, expirationTime: BigNumber, maxNonce: BigNumber, maxFeePerGas: BigNumber, gasLimit: BigNumber, paymaster: Contract
){
const signer = new Wallet(process.env.SIGNER_PRIVATE_KEY, provider);
// EIP-712 domain from the paymaster
const eip712Domain = await paymaster.eip712Domain();
const domain = {
name: eip712Domain[1],
version: eip712Domain[2],
chainId: eip712Domain[3],
verifyingContract: eip712Domain[4],
}
const types = {
PermissionLessPaymaster: [
{ name: "from", type: "address"},
{ name: "to", type: "address"},
{ name: "expirationTime", type: "uint256"},
{ name: "maxNonce", type: "uint256"},
{ name: "maxFeePerGas", type: "uint256"},
{ name: "gasLimit", type: "uint256"}
]
};
// -------------------- IMPORTANT --------------------
const values = {
from, // User address
to, // Your dapp contract address which the user will interact
expirationTime, // Expiration time post which the signature expires
maxNonce, // Max nonce of user after which signature becomes invalid
maxFeePerGas, // Current max gas price
gasLimit // Max gas limit you want to allow to your user. Ensure to add 60K gas for paymaster overhead.
}
// Note: MaxNonce allows the signature to be replayed.
// For eg: If currentNonce of user is 5, maxNonce is set to 10. Signature will allowed to replayed for nonce 6,7,8,9,10 on the same `to` address by the same user.
// This is to provide flexibility to Dapps to ensure signature works if users have multiple transactions running.
// Important: Signers are recommended to set maxNonce as current nonce of the user or as close as possible to ensure safety of gas funds.
// Important : Signers should set expirationTime is close enough to ensure safety of funds.
// Signer wallet will already defined in the code
return [(await signer._signTypedData(domain, types, values)), signer.address];
}
2. What extra data will be send with transaction for paymaster data?
- Once you get the signature, you simply need to add below custom data to the user transaction as below(point 5 as per above integration diagram) :
// This is example code. Direct copy/paste won't work
import {utils, provider, Contract, BigNumber} from "zksync-ethers";
const paymasterAddress = "0x1fc6AAd6FFc4b26229a29432FbC4b65d5A5e462b";
const paymasterAbi = ["function eip712Domain() external view returns (bytes1 fields,string memory name,string memory version,uint256 chainId,address verifyingContract,bytes32 salt,uint256[] memory extensions);"];
const paymasterContract = new Contract(paymasterAddress, paymasterAbi, provider);
// Below part can be managed in getSignature() as well.
// ------------------------------------------------------------------------------------
// Note: Do not set maxNonce too high than current to avoid unwanted signature replay.
// Consider maxNonce is as replayLimit. And setting maxNonce to currentNonce means 0 replay.
// Get the maxNonce allowed to user. Here we ensure it's currentNonce.
const maxNonce = await provider.getNonce(userAddress);
// You can also check for min Nonce from the NonceHolder System contract to fully ensure as ZKsync support arbitrary nonce.
// -----------------
// const nonceHolderAddress = "0x0000000000000000000000000000000000008003";
// const nonceHolderAbi = ["function getMinNonce(address _address) external view returns (uint256)"];
// const nonceHolderContract = new Contract(nonceHolderAddress, nonceHolderAbi, provider);
// const maxNonce = await nonceHolderContract.callStatic.getMinNonce(userAddress);
// -----------------
// Get the expiration time. Here signature will be valid upto 60 sec.
const expirationTime = BigNumber.from((await provider.getBlock).timestamp + 60);
// Get the current gas price.
const maxFeePerGas = await provider.getGasPrice();
// Set the gasLimit. Here, Dapp would know range of gas a function could cost and add 60K top up for paymaster overhead..
// Setting 215K (For eg: 150K function gas cost + 65K paymaster overhead)
// It will refunded anyways, so not an issue if Dapps set more.
const gasLimit = 215_000;
// ------------------------------------------------------------------------------------
const [signature, signerAddress] = await getSignature(userAddress,DappContract.address,expirationTime, maxNonce, maxFeePerGas, gasLimit, paymasterContract);
// We encode the extra data to be sent to paymaster
// Notice how it's not required to provide from, to, maxFeePerGas and gasLimit as per signature above.
// That's because paymaster will get it from the transaction struct directly to ensure it's the correct user.
const innerInput = ethers.utils.arrayify(
abiCoder.encode(
["uint256","uint256","address","bytes"],
[expirationTime, // As used in above signature
maxNonce, // As used in above signature
signerAddress, // The signer address
signature]), // Signature created in the above snippet. get from API server
);
// getPaymasterParams function is available in zksync-ethers
const paymasterParams = utils.getPaymasterParams(
paymasterAddress, // Paymaster address
{
type: "General",
innerInput: innerInputs
});
// Send the transaction with paymaster data.
// Users will get transaction signature pop-up
const tx = await DappContract.<function>([args..],{
maxFeePerGas, // Ensure it's same as used for signature
gasLimit, // Ensure it's same as used for signature
customData:{
paymasterParams, // Paymaster address + paymaster data with signature.
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
},
});
Further documentation on this paymaster will be available soon here & on https://code.zksync.io .
Zyfi is also working on an SDK to reduces the above code into 5 lines of code.
FAQs :
1. As a Dapp, how to manage the private key of a signer?
— We strongly recommend that signature signing part happens on closed API server of Dapp, rather than on front-end.
2. We do not have enough resources to manage API Server. We just have front-end…
— Zyfi at the rescue; you can always delegate signing part to Zyfi API server; and call Zyfi API as per requirement. You can also check different products we offer like gas payment in any token, sponsorship ratio etc.
3. Are there any fees for interacting with this paymaster other than potential markup fee set by the DAO?
— No, this paymaster is meant for common good. No fees on deposit, withdrawal, adding signers.
4. What if the private key of our signer gets leaked?
— You will need to quickly remove/replace the leaked signer address from the paymaster. A leaked signer private key can drain gas funds of the related manager’s balance.
5. As a manager(Dapp), are my funds at risk if private key of un-related signer address is leaked?
— Only the manager’s gas funds related to the signer address will be at risk. Rest all the manager funds will be safe.
6. ZKsync processes refunds for extra gas fees paid in each transaction. As a manager, would I be receiving those refunds that is ideally deducted from my balance?
— Yes, this paymaster solves the refund issue innovatively. Each manager’s balance will be updated with exact refund amount during the next paymaster interaction. Hence, all refunds are added back to manager balances.
7. Is the paymaster audited?
— Yes, audited by Cantina, report can be found here.
Closing thoughts
ZKsync native AA support brings a drastic UX change for users.
We, at Zyfi thrive to work towards mainstreaming paymasters everywhere. This new paymaster is an attempt to bridge the gap for Dapps to integrate paymaster functionality while simultaneously being permission-less.
This new paymaster aims to be common good singular paymaster on ZKsync where each Dapp can interact freely.