_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 previous integration flow diagram):
// Example code// ethers v5import {BigNumber, Contract, Wallet} from"zksync-ethers";exportasyncfunctiongetSignature( from: string, to: string, expirationTime: BigNumber, maxNonce: BigNumber, maxFeePerGas: BigNumber, gasLimit: BigNumber, paymaster: Contract
){constsigner=newWallet(process.env.SIGNER_PRIVATE_KEY, provider);// EIP-712 domain from the paymasterconsteip712Domain=awaitpaymaster.eip712Domain();constdomain= { name: eip712Domain[1], version: eip712Domain[2], chainId: eip712Domain[3], verifyingContract: eip712Domain[4], }consttypes= { 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 --------------------constvalues= { 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 codereturn [(awaitsigner._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 custom data to the user transaction as below(point 5 as per previous integration diagram) :
// This is example code. Direct copy/paste won't workimport {utils, provider, Contract, BigNumber} from"zksync-ethers";constpaymasterAddress="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);"];
constpaymasterContract=newContract(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.constmaxNonce=awaitprovider.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. constexpirationTime=BigNumber.from((awaitprovider.getBlock).timestamp +60);// Get the current gas price.constmaxFeePerGas=awaitprovider.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.constgasLimit=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.constinnerInput=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-ethersconstpaymasterParams=utils.getPaymasterParams( paymasterAddress,// Paymaster address { type:"General", innerInput: innerInputs });// Send the transaction with paymaster data. // Users will get transaction signature pop-upconsttx=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.
Miscellaneous
_maxNonce allows flexibility to Dapps by allowing signature replay in a secure constrained way. Signer should ensure maxNonce is not too big from the current nonce of the user and _expirationTime is not too far from the current timestamp. If _maxNonce is set to current nonce of the user, then signature cannot be replayed at all.
// Validate that the transaction generated by the API is not expired
if (block.timestamp > expirationTime)
revert Errors.PM_SignatureExpired();
// Validate that the nonce is not higher than the maximum allowed
if (_transaction.nonce > maxNonce) revert Errors.PM_InvalidNonce();
This paymaster has gas overhead of 51K-60K gas, which is quite nominal compare to other paymaster gas overhead. Signer should ensure to add this overhead i.e. 60K in the _gasLimit, if there are setting it close to the actual required gas.
Do not worry to set the _gasLimit high. All extra ETH spent from the manager's gas funds are refunded back to the manager.
Markup charge
Zyfi Dao will have the ability to set the markup fee percent similar to Uniswap fee switch. Currently, markup is set to 0 percent.
Once activated, a markup percent will be charged on each transaction on the required ETH (gasPrice*gasLimit) of the transaction and will be deducted from the respective manager's balance.
This markup will incentivise the DAO to promote paymaster evolution.
FAQs
1. 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.
2. 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.
3. 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 leaked signer address will be at risk. Rest all the manager funds will be safe.
4. 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 the respective manager's balance.