From ac6617d26edc1ffe9c1b40929ea40a5fafc63c3f Mon Sep 17 00:00:00 2001 From: Albiona Date: Fri, 23 Aug 2024 18:35:16 +0100 Subject: [PATCH 1/2] fix(docs): fix format and copy --- .../permissionless-paymaster/10.index.md | 155 +++++++++--------- 1 file changed, 81 insertions(+), 74 deletions(-) diff --git a/content/tutorials/permissionless-paymaster/10.index.md b/content/tutorials/permissionless-paymaster/10.index.md index 795e6ef0..22358faa 100644 --- a/content/tutorials/permissionless-paymaster/10.index.md +++ b/content/tutorials/permissionless-paymaster/10.index.md @@ -55,21 +55,21 @@ This paymaster allows the manager to set multiple signers through which users ca ![manager-signer-relation-diagram](/images/permissionless-paymaster/manager-signer.jpg) ## Integration -Below the diagram provides the flow of the integration: +The diagram below provides the flow of the integration: -1. Dapp decides on custom logic for each user. Let's assume that Dapp decides to sponsor gas for every approve transaction. +1. Dapp decides on custom logic for each user. Let's assume that Dapp decides to sponsor gas for every approved transaction. 2. Dapp calls the backend server or Zyfi API with relevant data to get the signer's signature. - - It is recommended that the signer's signing part is done on a secure backend server of the Dapp. + For the utmost security, it is recommended that the signer's signature be sent to Dapp's secure backend server. 3. The signer's key signs this paymaster data and returns the signature and signer address to the Dapp's frontend. 4. Paymaster address and required data with signature are added to the transaction blob in the frontend. -5. User gets transaction signature request pop-up on their wallet. **User only signs the transaction** and the transaction is sent on-chain. +5. The user receives the transaction signature request pop-up on their wallet. **The User only signs the transaction**, and it is sent on-chain. 6. The paymaster validates the signature, identifies the manager related to the signer, -deducts gas fees from the manager's balance, and pays for the user's transaction +deducts gas fees from the manager's balance and pays for the user's transaction. ![flow](/images/permissionless-paymaster/flowDiagram.jpg) @@ -176,42 +176,54 @@ import dotenv from "dotenv"; dotenv.config(); export async function getSignature( - from: string, to: string, expirationTime: BigNumber, maxNonce: BigNumber, maxFeePerGas: BigNumber, gasLimit: BigNumber -){ - const rpcUrl = process.env.ZKSYNC_RPC_URL ?? 'https://sepolia.era.zksync.dev'; - const provider = new Provider(rpcUrl); - const signer = new Wallet(process.env.NEXT_PUBLIC_SIGNER_PRIVATE_KEY || "", provider); - -// Paymaster Sepolia Testnet address -const paymasterAddress = "0xc1B0E2edC4cCaB51A764D7Dd8121CBf58C4D9E40"; -const paymasterAbi = [ - "function eip712Domain() public 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 -); -// EIP-712 domain from the paymaster - const eip712Domain = await paymasterContract.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 -------------------- + from: string, + to: string, + expirationTime: BigNumber, m + axNonce: BigNumber, + maxFeePerGas: BigNumber, + gasLimit: BigNumber) { + const rpcUrl = process.env.ZKSYNC_RPC_URL ?? 'https://sepolia.era.zksync.dev'; + const provider = new Provider(rpcUrl); + const signer = new Wallet(process.env.NEXT_PUBLIC_SIGNER_PRIVATE_KEY || "", provider); + + // Paymaster Sepolia Testnet address + const paymasterAddress = "0xc1B0E2edC4cCaB51A764D7Dd8121CBf58C4D9E40"; + const paymasterAbi = [ + "function eip712Domain() public 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 + ); + + // EIP-712 domain from the paymaster + const eip712Domain = await paymasterContract.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"} + ] + }; + +/** + * Note: MaxNonce allows signature replay within the specified range. + * Important: Set maxNonce close to the current nonce for gas funds safety. + * Important: Set expirationTime close to the current time for safety. + */ + const values = { from, // User address to, // Your dapp contract address which the user will interact @@ -220,11 +232,6 @@ const paymasterContract = new Contract( 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 the currentNonce of user is 5, maxNonce is set to 10. The signature will allowed to be 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 the safety of gas funds. -// Important: Signers should set expirationTime close enough to ensure safety of funds. return [paymasterAddress,(await signer._signTypedData(domain, types, values)), signer.address]; } @@ -270,26 +277,27 @@ const preparePaymasterParam = async (account:any, estimateGas: BigNumber) =>{ // Get the maxNonce allowed to user. Here we ensure it's currentNonce. const maxNonce = BigNumber.from( await provider.getTransactionCount(account.address || "") - ); + ); + // 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 maxNonce2 = await nonceHolderContract.callStatic.getMinNonce( account.address || "" - ); + ); console.log(maxNonce2.toString()); // ----------------- // Get the expiration time. Here signature will be valid up to 120 sec. - const currentTimestamp = BigNumber.from( - (await provider.getBlock("latest")).timestamp - ); + const currentTimestamp = BigNumber.from((await provider.getBlock("latest")).timestamp); const expirationTime = currentTimestamp.add(120); // Get the current gas price. const maxFeePerGas = await provider.getGasPrice(); @@ -303,30 +311,29 @@ const preparePaymasterParam = async (account:any, estimateGas: BigNumber) =>{ maxNonce, maxFeePerGas, gasLimit - ); + ); console.log("Signer: " + signerAddress); // 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 the 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"], - [ + abiCoder.encode(["uint256", "uint256", "address", "bytes"], + [ expirationTime, // As used in the above signature maxNonce, // As used in the above signature signerAddress, // The signer address signature, - ] - ) // Signature created in the above snippet. get from API server - ); + ] + ) // 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: innerInput, - } - ); + paymasterAddress, // Paymaster address + { + type: "General", + innerInput: innerInput, + } + ); // Returns paymaster params, gas fee, gas limit return [paymasterParams, maxFeePerGas, gasLimit]; }; @@ -334,17 +341,17 @@ const preparePaymasterParam = async (account:any, estimateGas: BigNumber) =>{ ##### **5.2** Estimate gas and call the above function -- In the `WriteContract` component, we will estimate the gas and call the above created `preparePaymasterParam` function - before the contract call for approve transaction. +In the `WriteContract` component, right before the `const tx = await contract.approve(spender, amount);` line, we will: + +1. Estimate gas +2. Call the `preparePaymasterParam` function we created above + +with the snippet below: ```javascript [src/components/WriteContract.tsx] - // ------------- Add above approve function call // Estimate gas const estimateGas = await contract.estimateGas.approve(spender,amount); - const [paymasterParams, maxFeePerGas, gasLimit] = await preparePaymasterParam(account, estimateGas); - // -------------- - const tx = await contract.approve(spender, amount); - + const [account, paymasterParams, maxFeePerGas, gasLimit] = await preparePaymasterParam(account, estimateGas); ``` ## 6. Add paymaster data to the transaction @@ -357,7 +364,7 @@ const preparePaymasterParam = async (account:any, estimateGas: BigNumber) =>{ // Estimate gas const estimateGas = await contract.estimateGas.approve(spender,amount); - const [paymasterParams, maxFeePerGas, gasLimit] = await preparePaymasterParam(account, estimateGas); + const [account, paymasterParams, maxFeePerGas, gasLimit] = await preparePaymasterParam(account, estimateGas); const tx = await contract.approve(spender, amount,{ maxFeePerGas, From 8cc4e8cf2d4e22eeb4701efb959f4fb52fdc476c Mon Sep 17 00:00:00 2001 From: Albiona Date: Fri, 23 Aug 2024 18:39:01 +0100 Subject: [PATCH 2/2] fix(docs): fix copy l1tol2 --- content/tutorials/how-to-send-l1-l2-transaction/_dir.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/content/tutorials/how-to-send-l1-l2-transaction/_dir.yml b/content/tutorials/how-to-send-l1-l2-transaction/_dir.yml index 5d1dd230..c636ced5 100644 --- a/content/tutorials/how-to-send-l1-l2-transaction/_dir.yml +++ b/content/tutorials/how-to-send-l1-l2-transaction/_dir.yml @@ -8,9 +8,8 @@ tags: - smart contracts - how-to - L2-L1 communication -summary: Learn how to send a transaction from Ethereum that to ZKsync -description: - This how-to guide explains how to send a transaction from Ethereum that interacts with a contract deployed on ZKsync. +summary: Learn how to send a transaction from Ethereum to ZKsync +description: This how-to guide shows how to send a transaction from Ethereum to a contract on ZKsync. what_you_will_learn: - How L1-L2 communication works - How to send a transaction from ZKsync Ethereum to ZKsync.