Technical Details

DApps can integrate this paymaster within 35 lines of code. This only concerns for Dapps who want to manage signer themselves.

1. 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,
    _markupPercent
))

_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.

_markupPercent : Optional markup charge on the total gas funds required(_gasLimit * _maxFeePerGas). For Zyfi Api use only. - Dapps should ensure it's set to 0, or else will be considered donation.

  • 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):

// This is example code. Direct copy/paste won't work
// ethers v5 or v6
export async function getSignature(
  from: string, to: string, expirationTime: BigNumber, maxNonce: BigNumber, maxFeePerGas: BigNumber, gasLimit: BigNumber, markupPercent: BigNumber, paymaster: Contract
){
// 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"},
      { name: "markupPercent", 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.
    markupPercent // This is optional and only for Zyfi API delegation. 
  }
// 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 and. 
// Note: Markup percent is meant for Zyfi API only. Markup percent is top-up charge on the total gas required and sent to Zyfi treasury.
// As a Dapp, you can set it to 0(recommended). 
// Dapps should ensure markupPercent is 0, otherwise it's considered donation to Zyfi.


// Signer wallet will already defined in the code
  return (await signer._signTypedData(domain, types, values));

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) :

// ethers v5 or v6
// This is example code. Direct copy/paste won't work
import {utils, provider, Contract, BigNumber} from "zksync-ethers";

// ----------This part can be managed on API Server too--------------------------------

// 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);
// Get the expiration time. Here signature will be valid upto 2 min. 
const expirationTime = BigNumber.from((await provider.getBlock).timestamp + 120);
// Get the signature as per above code snippet.
const maxFeePerGas = await provider.getGasPrice();
// Set the gasLimit. Here, Dapp would know range of gas a function could cost and add 60K topup for paymaster overhead.. 
// Setting 210K (For eg: 150K function gas cost + 60K paymaster overhead)
// It will refunded anyways, so not an issue if Dapps set more.
const gasLimit = 210_000;
// Set markup to 0
const markupPercent = 0;
// ------------------------------------------------------------------------------------

const signature = API.getSignature(<userAddress>,<DappContract>,expirationTime, maxNonce, maxFeePerGas, gasLimit, markupPercent, 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","uint256","address","bytes"], 
[expirationTime, // As used in above signature 
 maxNonce, // As used in above signature
 markupPercent, // As used in above signature Ideally set to 0
 signerAddress, // The signer address
 signature]), // Signature created in the above snippet. get from API server
    );
   // getPaymasterParams function is avaiable in zksync-ethers
const paymasterParams = utils.getPaymasterParams(
            paymaster.address.toString(), // 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,
    },
  });
// Thus paymaster is successfully integrated.

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

  1. _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.

    Check here:

    PermissionlessPaymaster.sol
            // 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();

  2. ZKsync might allow arbitary nonce ordering in future. To ensure surety over nonce of a user, you can add one more check by calling getMinNonce on the NonceHolder system contract of ZKsync. For more details, check docs here & here.

  3. This paymaster has gas overhead of 50K-60K gas, which is quite nominal compare to other paymaster gas overhead. Signer should ensure to add this overhead in the _gasLimit, if there are setting it close to the actual required gas.

  4. Do not worry to set the _gasLimit high. All extra ETH spent from the manager's gas funds are refunded back to the manager.

FAQs

1. Are there any fees for interacting with this paymaster? No, this paymaster is meant for public good. No fees on deposit, withdrawal, adding signers or paymaster usage especially if a Dapp is managing signer itself.

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 manager balances.

Last updated

#68: Permissionless Paymaster

Change request updated