From c6fdba21ecea504d9ee234f64239f9218443ead4 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:39:23 +0530 Subject: [PATCH 01/60] Function made exportable --- 4337/src/utils/safe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4337/src/utils/safe.ts b/4337/src/utils/safe.ts index 7fcb8410..bce2f7d5 100644 --- a/4337/src/utils/safe.ts +++ b/4337/src/utils/safe.ts @@ -53,7 +53,7 @@ const calculateProxyAddress = (globalConfig: GlobalConfig, inititalizer: string, return ethers.getCreate2Address(globalConfig.proxyFactory, salt, ethers.keccak256(deploymentCode)) } -const buildInitParamsForConfig = (safeConfig: SafeConfig, globalConfig: GlobalConfig): { safeAddress: string; initCode: string } => { +export const buildInitParamsForConfig = (safeConfig: SafeConfig, globalConfig: GlobalConfig): { safeAddress: string; initCode: string } => { const initData = INTERFACES.encodeFunctionData('enableModules', [[globalConfig.erc4337module]]) const setupData = INTERFACES.encodeFunctionData('setup', [ safeConfig.signers, From 1dd751f70fd363621d9f25d094aa75f7d1a237c3 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:39:58 +0530 Subject: [PATCH 02/60] Test ERC721 Token Added --- 4337/contracts/test/TestERC721Token.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 4337/contracts/test/TestERC721Token.sol diff --git a/4337/contracts/test/TestERC721Token.sol b/4337/contracts/test/TestERC721Token.sol new file mode 100644 index 00000000..af775bd5 --- /dev/null +++ b/4337/contracts/test/TestERC721Token.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract TestERC721Token is ERC721 { + constructor() ERC721("ERC 721 Token", "ERC721") { + } + + // @dev This can be called by anyone. + function safeMint(address to, uint256 tokenId) public { + _safeMint(to, tokenId, ""); + } +} From 653aaf9f1a0278114756c5d7ebcc50b64db309c6 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:40:46 +0530 Subject: [PATCH 03/60] ERC721 Deployment --- 4337/src/deploy/token.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/4337/src/deploy/token.ts b/4337/src/deploy/token.ts index ffe53277..b8b1e9d9 100644 --- a/4337/src/deploy/token.ts +++ b/4337/src/deploy/token.ts @@ -14,6 +14,13 @@ const deploy: DeployFunction = async ({ deployments, getNamedAccounts, network } log: true, deterministicDeployment: true, }) + + await deploy('TestERC721Token', { + from: deployer, + args: [], + log: true, + deterministicDeployment: true, + }) } export default deploy From ff677b94b1737fd2e5b6c48fb2a01443163e79f2 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:41:14 +0530 Subject: [PATCH 04/60] Gas Metering Script Created --- 4337/test/gas/Gas.spec.ts | 345 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 4337/test/gas/Gas.spec.ts diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts new file mode 100644 index 00000000..8fb1b5e2 --- /dev/null +++ b/4337/test/gas/Gas.spec.ts @@ -0,0 +1,345 @@ +import { expect } from 'chai' +import { deployments, ethers } from 'hardhat' +import { getSafe4337Module, getEntryPoint, getFactory, getAddModulesLib, getSafeL2Singleton } from '../utils/setup' +import { buildSignatureBytes, logGas } from '../../src/utils/execution' +import { buildUserOperationFromSafeUserOperation, buildSafeUserOpTransaction, signSafeOp } from '../../src/utils/userOp' +import { chainId, timestamp } from '../utils/encoding' +import { SafeConfig, Safe4337, GlobalConfig, buildInitParamsForConfig } from '../../src/utils/safe' + +describe('Gas Metering', () => { + const setupTests = deployments.createFixture(async ({ deployments }) => { + await deployments.fixture() + const { HariWillibaldToken, TestERC721Token } = await deployments.run() + + const [user] = await ethers.getSigners() + const entryPoint = await getEntryPoint() + const module = await getSafe4337Module() + const proxyFactory = await getFactory() + const proxyCreationCode = await proxyFactory.proxyCreationCode() + const addModulesLib = await getAddModulesLib() + const singleton = await getSafeL2Singleton() + const erc20Token = await ethers.getContractAt('HariWillibaldToken', HariWillibaldToken.address) + const erc721Token = await ethers.getContractAt('TestERC721Token', TestERC721Token.address) + + return { + user, + entryPoint, + module, + validator: module, + proxyFactory, + proxyCreationCode, + addModulesLib, + singleton, + erc20Token, + erc721Token, + } + }) + + describe('Safe Deployment + Enabling 4337 Module', () => { + it('Safe with 4337 Module Deployment', async () => { + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, } = await setupTests() + + const safeConfig: SafeConfig = { + signers: [user.address], + threshold: 1, + nonce: 0, + } + + const globalConfig: GlobalConfig = { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + proxyCreationCode, + addModulesLib: await addModulesLib.getAddress(), + chainId: Number(await chainId()), + } + + const initParams = buildInitParamsForConfig(safeConfig, globalConfig) + + const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + safe.address, // No functions are called. + 0, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + + const signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + + const userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await logGas("Safe with 4337 Module Deployment", entryPoint.executeUserOp(userOp, 0)) + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + }) + }) + + describe('Safe Deployment + Enabling 4337 Module + Token Operations', () => { + it('Safe with 4337 Module Deployment + ERC20 Token Transfer', async () => { + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token, } = await setupTests() + + const safeConfig: SafeConfig = { + signers: [user.address], + threshold: 1, + nonce: 0, + } + + const globalConfig: GlobalConfig = { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + proxyCreationCode, + addModulesLib: await addModulesLib.getAddress(), + chainId: Number(await chainId()), + } + + const initParams = buildInitParamsForConfig(safeConfig, globalConfig) + + const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + expect(await erc20Token.balanceOf(safe.address)).to.equal(0) + await erc20Token.transfer(safe.address, ethers.parseUnits('4.2', 18)).then((tx) => tx.wait()) + expect(await erc20Token.balanceOf(safe.address)).to.equal(ethers.parseUnits('4.2', 18)) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + await erc20Token.getAddress(), + 0, + erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]), + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + + const signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + + const userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await logGas("Safe with 4337 Module Deployment + ERC20 Transfer", entryPoint.executeUserOp(userOp, 0)) + expect(await erc20Token.balanceOf(safe.address)).to.equal(0) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + }) + + it('Safe with 4337 Module Deployment + ERC721 Token Minting', async () => { + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token, } = await setupTests() + + const safeConfig: SafeConfig = { + signers: [user.address], + threshold: 1, + nonce: 0, + } + + const globalConfig: GlobalConfig = { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + proxyCreationCode, + addModulesLib: await addModulesLib.getAddress(), + chainId: Number(await chainId()), + } + + const initParams = buildInitParamsForConfig(safeConfig, globalConfig) + + const tokenID = 1; + + const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + let safeOp = buildSafeUserOpTransaction( + safe.address, + await erc721Token.getAddress(), + 0, + erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]), + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + let signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + let userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + expect(await erc721Token.balanceOf(safe.address)).to.equal(0) + await logGas("Safe with 4337 Module Deployment + ERC721 Transfer", entryPoint.executeUserOp(userOp, 0)) + expect(await erc721Token.balanceOf(safe.address)).to.equal(1) + expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + }) + }) + + describe('Token Operations Only', () => { + it('Safe with 4337 Module ERC20 Token Transfer', async () => { + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token, } = await setupTests() + + const safeConfig: SafeConfig = { + signers: [user.address], + threshold: 1, + nonce: 0, + } + + const globalConfig: GlobalConfig = { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + proxyCreationCode, + addModulesLib: await addModulesLib.getAddress(), + chainId: Number(await chainId()), + } + + const initParams = buildInitParamsForConfig(safeConfig, globalConfig) + + const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + let safeOp = buildSafeUserOpTransaction( + safe.address, + safe.address, // No functions are called. + 0, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + let signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + let userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await entryPoint.executeUserOp(userOp, 0) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + + // Now Token Transfer + expect(await erc20Token.balanceOf(safe.address)).to.equal(0) + await erc20Token.transfer(safe.address, ethers.parseUnits('4.2', 18)).then((tx) => tx.wait()) + expect(await erc20Token.balanceOf(safe.address)).to.equal(ethers.parseUnits('4.2', 18)) + + safeOp = buildSafeUserOpTransaction( + safe.address, + await erc20Token.getAddress(), + 0, + erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]), + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await logGas("Safe with 4337 Module ERC20 Transfer", entryPoint.executeUserOp(userOp, 0)) + + expect(await erc20Token.balanceOf(safe.address)).to.equal(0) + }) + + it('Safe with 4337 Module ERC721 Token Minting', async () => { + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token, } = await setupTests() + + const safeConfig: SafeConfig = { + signers: [user.address], + threshold: 1, + nonce: 0, + } + + const globalConfig: GlobalConfig = { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + proxyCreationCode, + addModulesLib: await addModulesLib.getAddress(), + chainId: Number(await chainId()), + } + + const initParams = buildInitParamsForConfig(safeConfig, globalConfig) + + const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + let safeOp = buildSafeUserOpTransaction( + safe.address, + safe.address, // No functions are called. + 0, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + let signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + let userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await entryPoint.executeUserOp(userOp, 0) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + + // Now ERC721 Token Transfer + const tokenID = 1; + + safeOp = buildSafeUserOpTransaction( + safe.address, + await erc721Token.getAddress(), + 0, + erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]), + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + signature = buildSignatureBytes( + [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], + ) + userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + expect(await erc721Token.balanceOf(safe.address)).to.equal(0) + await logGas("Safe with 4337 Module ERC721 Transfer", entryPoint.executeUserOp(userOp, 0)) + expect(await erc721Token.balanceOf(safe.address)).to.equal(1) + expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address) + }) + }) + +}) From dbaa339e312a7342f855a3360848277134f9f811 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:45:06 +0530 Subject: [PATCH 05/60] Linting issues rectified --- 4337/contracts/test/TestERC721Token.sol | 3 +- 4337/test/gas/Gas.spec.ts | 77 +++++++++++-------------- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/4337/contracts/test/TestERC721Token.sol b/4337/contracts/test/TestERC721Token.sol index af775bd5..f42bb4b2 100644 --- a/4337/contracts/test/TestERC721Token.sol +++ b/4337/contracts/test/TestERC721Token.sol @@ -4,8 +4,7 @@ pragma solidity >=0.8.0; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract TestERC721Token is ERC721 { - constructor() ERC721("ERC 721 Token", "ERC721") { - } + constructor() ERC721("ERC 721 Token", "ERC721") {} // @dev This can be called by anyone. function safeMint(address to, uint256 tokenId) public { diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index 8fb1b5e2..f38ebdda 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -37,7 +37,7 @@ describe('Gas Metering', () => { describe('Safe Deployment + Enabling 4337 Module', () => { it('Safe with 4337 Module Deployment', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, } = await setupTests() + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton } = await setupTests() const safeConfig: SafeConfig = { signers: [user.address], @@ -69,9 +69,7 @@ describe('Gas Metering', () => { await entryPoint.getAddress(), ) - const signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, @@ -79,16 +77,17 @@ describe('Gas Metering', () => { signature, initCode: safe.getInitCode(), }) - - await logGas("Safe with 4337 Module Deployment", entryPoint.executeUserOp(userOp, 0)) - + + await logGas('Safe with 4337 Module Deployment', entryPoint.executeUserOp(userOp, 0)) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) }) }) describe('Safe Deployment + Enabling 4337 Module + Token Operations', () => { it('Safe with 4337 Module Deployment + ERC20 Token Transfer', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token, } = await setupTests() + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token } = + await setupTests() const safeConfig: SafeConfig = { signers: [user.address], @@ -124,9 +123,7 @@ describe('Gas Metering', () => { await entryPoint.getAddress(), ) - const signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, @@ -134,14 +131,15 @@ describe('Gas Metering', () => { signature, initCode: safe.getInitCode(), }) - - await logGas("Safe with 4337 Module Deployment + ERC20 Transfer", entryPoint.executeUserOp(userOp, 0)) + + await logGas('Safe with 4337 Module Deployment + ERC20 Transfer', entryPoint.executeUserOp(userOp, 0)) expect(await erc20Token.balanceOf(safe.address)).to.equal(0) expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) }) it('Safe with 4337 Module Deployment + ERC721 Token Minting', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token, } = await setupTests() + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token } = + await setupTests() const safeConfig: SafeConfig = { signers: [user.address], @@ -161,7 +159,7 @@ describe('Gas Metering', () => { const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - const tokenID = 1; + const tokenID = 1 const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) @@ -175,18 +173,16 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - let signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, initCode: safe.getInitCode(), }) - + expect(await erc721Token.balanceOf(safe.address)).to.equal(0) - await logGas("Safe with 4337 Module Deployment + ERC721 Transfer", entryPoint.executeUserOp(userOp, 0)) + await logGas('Safe with 4337 Module Deployment + ERC721 Transfer', entryPoint.executeUserOp(userOp, 0)) expect(await erc721Token.balanceOf(safe.address)).to.equal(1) expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address) expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) @@ -195,7 +191,8 @@ describe('Gas Metering', () => { describe('Token Operations Only', () => { it('Safe with 4337 Module ERC20 Token Transfer', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token, } = await setupTests() + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token } = + await setupTests() const safeConfig: SafeConfig = { signers: [user.address], @@ -227,16 +224,14 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - let signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, initCode: safe.getInitCode(), }) - + await entryPoint.executeUserOp(userOp, 0) expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) @@ -253,23 +248,22 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, initCode: safe.getInitCode(), }) - - await logGas("Safe with 4337 Module ERC20 Transfer", entryPoint.executeUserOp(userOp, 0)) - - expect(await erc20Token.balanceOf(safe.address)).to.equal(0) + + await logGas('Safe with 4337 Module ERC20 Transfer', entryPoint.executeUserOp(userOp, 0)) + + expect(await erc20Token.balanceOf(safe.address)).to.equal(0) }) it('Safe with 4337 Module ERC721 Token Minting', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token, } = await setupTests() + const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token } = + await setupTests() const safeConfig: SafeConfig = { signers: [user.address], @@ -301,21 +295,19 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - let signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, initCode: safe.getInitCode(), }) - + await entryPoint.executeUserOp(userOp, 0) expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) // Now ERC721 Token Transfer - const tokenID = 1; + const tokenID = 1 safeOp = buildSafeUserOpTransaction( safe.address, @@ -325,21 +317,18 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - signature = buildSignatureBytes( - [await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())], - ) + signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, initCode: safe.getInitCode(), }) - + expect(await erc721Token.balanceOf(safe.address)).to.equal(0) - await logGas("Safe with 4337 Module ERC721 Transfer", entryPoint.executeUserOp(userOp, 0)) + await logGas('Safe with 4337 Module ERC721 Transfer', entryPoint.executeUserOp(userOp, 0)) expect(await erc721Token.balanceOf(safe.address)).to.equal(1) expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address) }) }) - }) From 148d5cbb1863a516d18546101c1c05f8fdee7979 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 29 Nov 2023 12:45:59 +0530 Subject: [PATCH 06/60] Linting issues rectified --- 4337/test/gas/Gas.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index f38ebdda..59b497e5 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -3,7 +3,7 @@ import { deployments, ethers } from 'hardhat' import { getSafe4337Module, getEntryPoint, getFactory, getAddModulesLib, getSafeL2Singleton } from '../utils/setup' import { buildSignatureBytes, logGas } from '../../src/utils/execution' import { buildUserOperationFromSafeUserOperation, buildSafeUserOpTransaction, signSafeOp } from '../../src/utils/userOp' -import { chainId, timestamp } from '../utils/encoding' +import { chainId } from '../utils/encoding' import { SafeConfig, Safe4337, GlobalConfig, buildInitParamsForConfig } from '../../src/utils/safe' describe('Gas Metering', () => { @@ -165,7 +165,7 @@ describe('Gas Metering', () => { expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) - let safeOp = buildSafeUserOpTransaction( + const safeOp = buildSafeUserOpTransaction( safe.address, await erc721Token.getAddress(), 0, @@ -173,8 +173,8 @@ describe('Gas Metering', () => { await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), ) - let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) - let userOp = buildUserOperationFromSafeUserOperation({ + const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) + const userOp = buildUserOperationFromSafeUserOperation({ safeAddress: safe.address, safeOp, signature, From 0e0dcc30192a0884599b3fe858151329d56032ac Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 30 Nov 2023 11:32:47 +0530 Subject: [PATCH 07/60] Refactored Gas Tests --- 4337/test/gas/Gas.spec.ts | 132 +++++--------------------------------- 1 file changed, 17 insertions(+), 115 deletions(-) diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index 59b497e5..8ddc7fd8 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -18,18 +18,23 @@ describe('Gas Metering', () => { const proxyCreationCode = await proxyFactory.proxyCreationCode() const addModulesLib = await getAddModulesLib() const singleton = await getSafeL2Singleton() + const safe = await Safe4337.withSigner(user.address, { + safeSingleton: await singleton.getAddress(), + entryPoint: await entryPoint.getAddress(), + erc4337module: await module.getAddress(), + proxyFactory: await proxyFactory.getAddress(), + addModulesLib: await addModulesLib.getAddress(), + proxyCreationCode, + chainId: Number(await chainId()), + }) const erc20Token = await ethers.getContractAt('HariWillibaldToken', HariWillibaldToken.address) const erc721Token = await ethers.getContractAt('TestERC721Token', TestERC721Token.address) return { user, entryPoint, - module, validator: module, - proxyFactory, - proxyCreationCode, - addModulesLib, - singleton, + safe, erc20Token, erc721Token, } @@ -37,27 +42,8 @@ describe('Gas Metering', () => { describe('Safe Deployment + Enabling 4337 Module', () => { it('Safe with 4337 Module Deployment', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton } = await setupTests() - - const safeConfig: SafeConfig = { - signers: [user.address], - threshold: 1, - nonce: 0, - } - - const globalConfig: GlobalConfig = { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - proxyCreationCode, - addModulesLib: await addModulesLib.getAddress(), - chainId: Number(await chainId()), - } - - const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - - const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + const { user, entryPoint, validator, safe } = await setupTests() + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) const safeOp = buildSafeUserOpTransaction( @@ -86,28 +72,8 @@ describe('Gas Metering', () => { describe('Safe Deployment + Enabling 4337 Module + Token Operations', () => { it('Safe with 4337 Module Deployment + ERC20 Token Transfer', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token } = - await setupTests() - - const safeConfig: SafeConfig = { - signers: [user.address], - threshold: 1, - nonce: 0, - } - - const globalConfig: GlobalConfig = { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - proxyCreationCode, - addModulesLib: await addModulesLib.getAddress(), - chainId: Number(await chainId()), - } - - const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - - const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + const { user, entryPoint, validator, safe, erc20Token } = await setupTests() + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) expect(await erc20Token.balanceOf(safe.address)).to.equal(0) @@ -138,31 +104,9 @@ describe('Gas Metering', () => { }) it('Safe with 4337 Module Deployment + ERC721 Token Minting', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token } = - await setupTests() - - const safeConfig: SafeConfig = { - signers: [user.address], - threshold: 1, - nonce: 0, - } - - const globalConfig: GlobalConfig = { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - proxyCreationCode, - addModulesLib: await addModulesLib.getAddress(), - chainId: Number(await chainId()), - } - - const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - + const { user, entryPoint, validator, safe, erc721Token } = await setupTests() const tokenID = 1 - const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) - expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) const safeOp = buildSafeUserOpTransaction( @@ -191,28 +135,7 @@ describe('Gas Metering', () => { describe('Token Operations Only', () => { it('Safe with 4337 Module ERC20 Token Transfer', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc20Token } = - await setupTests() - - const safeConfig: SafeConfig = { - signers: [user.address], - threshold: 1, - nonce: 0, - } - - const globalConfig: GlobalConfig = { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - proxyCreationCode, - addModulesLib: await addModulesLib.getAddress(), - chainId: Number(await chainId()), - } - - const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - - const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + const { user, entryPoint, validator, safe, erc20Token } = await setupTests() expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) @@ -262,28 +185,7 @@ describe('Gas Metering', () => { }) it('Safe with 4337 Module ERC721 Token Minting', async () => { - const { user, entryPoint, module, validator, proxyFactory, proxyCreationCode, addModulesLib, singleton, erc721Token } = - await setupTests() - - const safeConfig: SafeConfig = { - signers: [user.address], - threshold: 1, - nonce: 0, - } - - const globalConfig: GlobalConfig = { - safeSingleton: await singleton.getAddress(), - entryPoint: await entryPoint.getAddress(), - erc4337module: await module.getAddress(), - proxyFactory: await proxyFactory.getAddress(), - proxyCreationCode, - addModulesLib: await addModulesLib.getAddress(), - chainId: Number(await chainId()), - } - - const initParams = buildInitParamsForConfig(safeConfig, globalConfig) - - const safe = new Safe4337(initParams.safeAddress, globalConfig, safeConfig) + const { user, entryPoint, validator, safe, erc721Token } = await setupTests() expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) From 4a53e341032bb485778c824159e1c05e296aa011 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 30 Nov 2023 11:34:40 +0530 Subject: [PATCH 08/60] Linting issues rectified --- 4337/src/utils/safe.ts | 2 +- 4337/test/gas/Gas.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/4337/src/utils/safe.ts b/4337/src/utils/safe.ts index bce2f7d5..7fcb8410 100644 --- a/4337/src/utils/safe.ts +++ b/4337/src/utils/safe.ts @@ -53,7 +53,7 @@ const calculateProxyAddress = (globalConfig: GlobalConfig, inititalizer: string, return ethers.getCreate2Address(globalConfig.proxyFactory, salt, ethers.keccak256(deploymentCode)) } -export const buildInitParamsForConfig = (safeConfig: SafeConfig, globalConfig: GlobalConfig): { safeAddress: string; initCode: string } => { +const buildInitParamsForConfig = (safeConfig: SafeConfig, globalConfig: GlobalConfig): { safeAddress: string; initCode: string } => { const initData = INTERFACES.encodeFunctionData('enableModules', [[globalConfig.erc4337module]]) const setupData = INTERFACES.encodeFunctionData('setup', [ safeConfig.signers, diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index 8ddc7fd8..4b8757d7 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -4,7 +4,7 @@ import { getSafe4337Module, getEntryPoint, getFactory, getAddModulesLib, getSafe import { buildSignatureBytes, logGas } from '../../src/utils/execution' import { buildUserOperationFromSafeUserOperation, buildSafeUserOpTransaction, signSafeOp } from '../../src/utils/userOp' import { chainId } from '../utils/encoding' -import { SafeConfig, Safe4337, GlobalConfig, buildInitParamsForConfig } from '../../src/utils/safe' +import { Safe4337 } from '../../src/utils/safe' describe('Gas Metering', () => { const setupTests = deployments.createFixture(async ({ deployments }) => { From 99a85990a41a0ae698addd500b22a6df4661cd38 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 30 Nov 2023 16:06:24 +0530 Subject: [PATCH 09/60] XanderBlazeNFT introduced --- 4337/contracts/test/TestERC721Token.sol | 4 ++-- 4337/src/deploy/token.ts | 2 +- 4337/test/gas/Gas.spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/4337/contracts/test/TestERC721Token.sol b/4337/contracts/test/TestERC721Token.sol index f42bb4b2..19b2d0b1 100644 --- a/4337/contracts/test/TestERC721Token.sol +++ b/4337/contracts/test/TestERC721Token.sol @@ -3,8 +3,8 @@ pragma solidity >=0.8.0; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -contract TestERC721Token is ERC721 { - constructor() ERC721("ERC 721 Token", "ERC721") {} +contract XanderBlazeNFT is ERC721 { + constructor() ERC721("XanderBlazeNFT", "XBN") {} // @dev This can be called by anyone. function safeMint(address to, uint256 tokenId) public { diff --git a/4337/src/deploy/token.ts b/4337/src/deploy/token.ts index b8b1e9d9..3d0d6eb3 100644 --- a/4337/src/deploy/token.ts +++ b/4337/src/deploy/token.ts @@ -15,7 +15,7 @@ const deploy: DeployFunction = async ({ deployments, getNamedAccounts, network } deterministicDeployment: true, }) - await deploy('TestERC721Token', { + await deploy('XanderBlazeNFT', { from: deployer, args: [], log: true, diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index 4b8757d7..f2895c46 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -9,7 +9,7 @@ import { Safe4337 } from '../../src/utils/safe' describe('Gas Metering', () => { const setupTests = deployments.createFixture(async ({ deployments }) => { await deployments.fixture() - const { HariWillibaldToken, TestERC721Token } = await deployments.run() + const { HariWillibaldToken, XanderBlazeNFT } = await deployments.run() const [user] = await ethers.getSigners() const entryPoint = await getEntryPoint() @@ -28,7 +28,7 @@ describe('Gas Metering', () => { chainId: Number(await chainId()), }) const erc20Token = await ethers.getContractAt('HariWillibaldToken', HariWillibaldToken.address) - const erc721Token = await ethers.getContractAt('TestERC721Token', TestERC721Token.address) + const erc721Token = await ethers.getContractAt('XanderBlazeNFT', XanderBlazeNFT.address) return { user, From 0ee2d6627568ca2a559501bcd9112d2f625fcafb Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:14:22 +0530 Subject: [PATCH 10/60] NPM init --- paymaster-analysis/package.json | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 paymaster-analysis/package.json diff --git a/paymaster-analysis/package.json b/paymaster-analysis/package.json new file mode 100644 index 00000000..08cd3fd8 --- /dev/null +++ b/paymaster-analysis/package.json @@ -0,0 +1,43 @@ +{ + "name": "@safe-global/paymaster-analysis", + "version": "1.0.0", + "description": "Account Abstraction Paymaster Analysis for Safe with 4337 Module", + "homepage": "https://github.com/safe-global/safe-modules/paymaster-analysis", + "license": "GPL-3.0", + "type": "module", + "scripts": { + "alchemy": "tsx ./alchemy/alchemy.ts", + "pimlico:account": "tsx ./pimlico/pimlico-account.ts", + "pimlico:erc20": "tsx ./pimlico/pimlico-erc20.ts", + "pimlico:erc721": "tsx ./pimlico/pimlico-erc721.ts", + "alchemy:account:bundler": "tsx ./alchemy/alchemy-account-bundler.ts", + "alchemy:account": "tsx ./alchemy/alchemy-account.ts", + "alchemy:erc20": "tsx ./alchemy/alchemy-erc20.ts", + "alchemy:erc721": "tsx ./alchemy/alchemy-erc721.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/safe-global/safe-modules.git" + }, + "keywords": [ + "Ethereum", + "Wallet", + "Safe" + ], + "author": "@safe-global", + "bugs": { + "url": "https://github.com/safe-global/safe-modules/issues" + }, + "dependencies": { + "@alchemy/aa-accounts": "^1.2.0", + "@alchemy/aa-alchemy": "^1.2.0", + "@alchemy/aa-core": "^1.2.0", + "dotenv": "^16.3.1", + "permissionless": "^0.0.11", + "viem": "^1.19.11" + }, + "devDependencies": { + "@types/node": "^20.10.2", + "tsx": "^3.13.0" + } +} From 07397b6f4492a8502c55074daf2c7889f2c42bac Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:14:42 +0530 Subject: [PATCH 11/60] Package Lock Added --- paymaster-analysis/package-lock.json | 1759 ++++++++++++++++++++++++++ 1 file changed, 1759 insertions(+) create mode 100644 paymaster-analysis/package-lock.json diff --git a/paymaster-analysis/package-lock.json b/paymaster-analysis/package-lock.json new file mode 100644 index 00000000..7205c2bd --- /dev/null +++ b/paymaster-analysis/package-lock.json @@ -0,0 +1,1759 @@ +{ + "name": "@safe-global/paymaster-analysis", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@safe-global/paymaster-analysis", + "version": "1.0.0", + "license": "GPL-3.0", + "dependencies": { + "@alchemy/aa-accounts": "^1.2.0", + "@alchemy/aa-alchemy": "^1.2.0", + "@alchemy/aa-core": "^1.2.0", + "dotenv": "^16.3.1", + "permissionless": "^0.0.11", + "viem": "^1.19.11" + }, + "devDependencies": { + "@types/node": "^20.10.2", + "tsx": "^3.13.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, + "node_modules/@alchemy/aa-accounts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alchemy/aa-accounts/-/aa-accounts-1.2.0.tgz", + "integrity": "sha512-H0iBnFZh1XGC/fGDwlmXiB0aHGJlGg0+o/KhIrgfqxvoIz6A5FnW8uaJ+ciI/MBb8ZzoK9Mo0BgNE3spYppxRQ==", + "dependencies": { + "@alchemy/aa-core": "^1.2.0", + "viem": "^1.16.2" + } + }, + "node_modules/@alchemy/aa-alchemy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alchemy/aa-alchemy/-/aa-alchemy-1.2.0.tgz", + "integrity": "sha512-KqSWARu1DqYHE0UJas1O/WngoT0uqztGCUy5eO1yWQXz40I5hFMuWDYsV2kyo8UmTsn8qsEtQpts8HhAX4LWIw==", + "dependencies": { + "@alchemy/aa-core": "^1.2.0", + "viem": "^1.16.2" + }, + "optionalDependencies": { + "alchemy-sdk": "^3.0.0" + } + }, + "node_modules/@alchemy/aa-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@alchemy/aa-core/-/aa-core-1.2.0.tgz", + "integrity": "sha512-R56LwXKlR3CkvINH/OoZTFdJNPqzngBToj5DjTxipOlz/BFFPi0siG5nmbpCHm0683E6Go+az/hNUxX0PrkopA==", + "dependencies": { + "abitype": "^0.8.3", + "eventemitter3": "^5.0.1", + "viem": "^1.16.2", + "zod": "^3.22.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "optional": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", + "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", + "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "dependencies": { + "@noble/curves": "~1.2.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "20.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", + "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/abitype": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.8.11.tgz", + "integrity": "sha512-bM4v2dKvX08sZ9IU38IN5BKmN+ZkOSd2oI4a9f0ejHYZQYV6cDr7j+d95ga0z2XHG36Y4jzoG5Z7qDqxp7fi/A==", + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "optional": true + }, + "node_modules/alchemy-sdk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.1.0.tgz", + "integrity": "sha512-KMzBo0Dq+cEqXegn4fh2sP74dhAngr9twIv2pBTyPq3/ZJs+aiXXlFzVrVUYaa6x9L7iQtqhz3YKFCuN5uvpAg==", + "optional": true, + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/providers": "^5.7.0", + "@ethersproject/units": "^5.7.0", + "@ethersproject/wallet": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "axios": "^0.26.1", + "sturdy-websocket": "^0.2.1", + "websocket": "^1.0.34" + } + }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "optional": true, + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "optional": true + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "optional": true + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "optional": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "optional": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "optional": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "optional": true + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "optional": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "optional": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "optional": true, + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "optional": true + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "optional": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "optional": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "optional": true + }, + "node_modules/isows": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", + "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "optional": true + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "optional": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "optional": true + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "optional": true + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "optional": true + }, + "node_modules/node-gyp-build": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", + "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/permissionless": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.0.11.tgz", + "integrity": "sha512-gm3004JpxtuYM3q0Lcp+LBbyx6uFN8OrvfDWmaqWa8nTQ8VdFt/btP6eILdv9ICTca8DOeadjLePKo5omyVf8g==", + "peerDependencies": { + "viem": "^1.14.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "optional": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sturdy-websocket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz", + "integrity": "sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==", + "optional": true + }, + "node_modules/tsx": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.14.0.tgz", + "integrity": "sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==", + "dev": true, + "dependencies": { + "esbuild": "~0.18.20", + "get-tsconfig": "^4.7.2", + "source-map-support": "^0.5.21" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "optional": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/viem": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/viem/-/viem-1.19.11.tgz", + "integrity": "sha512-dbsXEWDBZkByuzJXAs/e01j7dpUJ5ICF5WcyntFwf8Y97n5vnC/91lAleSa6DA5V4WJvYZbhDpYeTctsMAQnhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@scure/bip32": "1.3.2", + "@scure/bip39": "1.2.1", + "abitype": "0.9.8", + "isows": "1.0.3", + "ws": "8.13.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/abitype": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.9.8.tgz", + "integrity": "sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "optional": true, + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "optional": true, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} From a2cc50b475388cbbcb4332900608e0a438f918b4 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:15:19 +0530 Subject: [PATCH 12/60] Typescript Config Added --- paymaster-analysis/tsconfig.json | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 paymaster-analysis/tsconfig.json diff --git a/paymaster-analysis/tsconfig.json b/paymaster-analysis/tsconfig.json new file mode 100644 index 00000000..aac9d66a --- /dev/null +++ b/paymaster-analysis/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, +} \ No newline at end of file From 857265f4d8d4aee7a0dce5b87edf525057fe6bff Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:21:44 +0530 Subject: [PATCH 13/60] gitignore and env example --- paymaster-analysis/.env.example | 46 +++++++++++++++++++++++++++++++++ paymaster-analysis/.gitignore | 14 ++++++++++ 2 files changed, 60 insertions(+) create mode 100644 paymaster-analysis/.env.example create mode 100644 paymaster-analysis/.gitignore diff --git a/paymaster-analysis/.env.example b/paymaster-analysis/.env.example new file mode 100644 index 00000000..c74adb2e --- /dev/null +++ b/paymaster-analysis/.env.example @@ -0,0 +1,46 @@ +# Dummy ETH Address Private Key +PRIVATE_KEY = "0x..." # Add "0x" to Private Key if not already added. + +# Pimlico Values + +# Alchemy Values (At the time of writing this, Pimlico doesn't support Sepolia, so using Goerli.) +PIMLICO_CHAIN = "goerli" +PIMLICO_CHAIN_ID = "5" +PIMLICO_RPC_URL = "https://rpc.ankr.com/eth_goerli" +PIMLICO_API_KEY = "" # https://dashboard.pimlico.io/apikeys +PIMLICO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" +PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" +PIMLICO_ERC20_PAYMASTER_ADDRESS = "0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009" +PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" +# Make sure all nonce are unique for it to deploy account when run initially. +PIMLICO_ACCOUNT_NONCE = "1" +PIMLICO_ERC20_NONCE = "2" +PIMLICO_ERC721_NONCE = "3" + +# Alchemy Values (If you want to use goerli, use goerli chain id and corresponding API key, Gas Policy, etc.) +ALCHEMY_CHAIN = "sepolia" # or "goerli" +ALCHEMY_CHAIN_ID = "11155111" +ALCHEMY_RPC_URL = "https://eth-sepolia.g.alchemy.com/v2/Your_API_Key" +ALCHEMY_API_KEY = "" # https://dashboard.alchemy.com/apps +ALCHEMY_GAS_POLICY = "" # https://dashboard.alchemy.com/gas-manager +ALCHEMY_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" +ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" +# Make sure all nonce are unique for it to deploy account when run initially. +ALCHEMY_ACCOUNT_PAYMASTER_NONCE = "4" +ALCHEMY_ERC20_PAYMASTER_NONCE = "5" +ALCHEMY_ERC721_PAYMASTER_NONCE = "6" + +# Safe Values +SAFE_VERSION = "1.4.1" + +# Token Operation Values (Based on the chain selected, these values should also change accordingly.) +PIMLICO_ERC20_TOKEN_CONTRACT = "" +PIMLICO_ERC721_TOKEN_CONTRACT = "" +ALCHEMY_ERC20_TOKEN_CONTRACT = "" +ALCHEMY_ERC721_TOKEN_CONTRACT = "" + +# Publicly Mintable ERC20 and ERC721 Token for easier testing. Can be used for Token Operation Values. +# ERC20 Token Goerli: https://goerli.etherscan.io/token/0x3Aa475E24F7c076632467444b593E332Bc3355F9 +# ERC721 Token Goerli: https://goerli.etherscan.io/token/0xf190c05f968c53962604454ebfa380e5cda600d7 +# ERC20 Token Sepolia: https://sepolia.etherscan.io/token/0x255de08fb52fde17a3aab145de8e2ffb7fd0310f +# ERC721 Token Sepolia: https://sepolia.etherscan.io/token/0x16bc5fba06f3f5875e915c0ba6963377eb6651e1 diff --git a/paymaster-analysis/.gitignore b/paymaster-analysis/.gitignore new file mode 100644 index 00000000..9722dbfa --- /dev/null +++ b/paymaster-analysis/.gitignore @@ -0,0 +1,14 @@ +node_modules +.env + +# Hardhat files +/cache +/artifacts + +# TypeChain files +/typechain +/typechain-types + +# solidity-coverage files +/coverage +/coverage.json From f8e58d75202fe34ff61d56a635e5fc67f8b7a820 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:24:12 +0530 Subject: [PATCH 14/60] Added utils files --- paymaster-analysis/alchemy/utils/erc20.ts | 128 +++++++ paymaster-analysis/alchemy/utils/safe.ts | 331 ++++++++++++++++++ paymaster-analysis/alchemy/utils/userOp.ts | 61 ++++ paymaster-analysis/pimlico/utils/erc20.ts | 128 +++++++ paymaster-analysis/pimlico/utils/safe.ts | 353 ++++++++++++++++++++ paymaster-analysis/pimlico/utils/userOps.ts | 67 ++++ paymaster-analysis/utils/erc721.ts | 21 ++ paymaster-analysis/utils/multisend.ts | 44 +++ 8 files changed, 1133 insertions(+) create mode 100644 paymaster-analysis/alchemy/utils/erc20.ts create mode 100644 paymaster-analysis/alchemy/utils/safe.ts create mode 100644 paymaster-analysis/alchemy/utils/userOp.ts create mode 100644 paymaster-analysis/pimlico/utils/erc20.ts create mode 100644 paymaster-analysis/pimlico/utils/safe.ts create mode 100644 paymaster-analysis/pimlico/utils/userOps.ts create mode 100644 paymaster-analysis/utils/erc721.ts create mode 100644 paymaster-analysis/utils/multisend.ts diff --git a/paymaster-analysis/alchemy/utils/erc20.ts b/paymaster-analysis/alchemy/utils/erc20.ts new file mode 100644 index 00000000..4d2f4780 --- /dev/null +++ b/paymaster-analysis/alchemy/utils/erc20.ts @@ -0,0 +1,128 @@ +import dotenv from "dotenv"; +import { http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount } from "viem"; +import { goerli, sepolia } from "viem/chains"; + +dotenv.config() +const rpcURL = process.env.ALCHEMY_RPC_URL; +const chain = process.env.ALCHEMY_CHAIN; + +export const generateApproveCallData = (paymasterAddress: Address) => { + const approveData = encodeFunctionData({ + abi: [ + { + inputs: [ + { name: "_spender", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + ], + args: [ + paymasterAddress, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, + ], + }); + + return approveData; +}; + +export const generateTransferCallData = (to: Address, value: bigint) => { + const transferData = encodeFunctionData({ + abi: [ + { + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + ], + args: [to, value], + }); + + return transferData; +}; + +export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: any) => { + const erc20Decimals = await publicClient.readContract({ + abi: [ + { + inputs: [], + name: "decimals", + outputs: [{ type: "uint8" }], + type: "function", + stateMutability: "view", + }, + ], + address: erc20TokenAddress, + functionName: "decimals", + }); + + return erc20Decimals; +} + +export const getERC20Balance = async (erc20TokenAddress: string, publicClient: any, owner: string) => { + const senderERC20Balance = await publicClient.readContract({ + abi: [ + { + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "balance", type: "uint256" }], + type: "function", + stateMutability: "view", + }, + ], + address: erc20TokenAddress, + functionName: "balanceOf", + args: [owner], + }); + + return senderERC20Balance; +} + +export const mintERC20Token = async (erc20TokenAddress: string, publicClient: any, signer: PrivateKeyAccount, to: string, amount: number) => { + let walletClient; + if(chain == "sepolia"){ + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(rpcURL) + }) + } + else if(chain == "goerli"){ + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(rpcURL) + }) + } + else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) + } + const { request } = await publicClient.simulateContract({ + address: erc20TokenAddress, + abi: [ + { + inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256"}], + name: "mint", + outputs: [], + type: "function", + stateMutability: "public", + }, + ], + functionName: 'mint', + args: [to, amount], + signer + }) + await walletClient.writeContract(request) +} \ No newline at end of file diff --git a/paymaster-analysis/alchemy/utils/safe.ts b/paymaster-analysis/alchemy/utils/safe.ts new file mode 100644 index 00000000..9edaf734 --- /dev/null +++ b/paymaster-analysis/alchemy/utils/safe.ts @@ -0,0 +1,331 @@ +import { + Address, + Chain, + Client, + Hex, + PublicClient, + Transport, + concatHex, + encodeFunctionData, + encodePacked, + getContractAddress, + hexToBigInt, + keccak256, + zeroAddress, +} from "viem"; +import { InternalTx, encodeMultiSend } from "../../utils/multisend"; +import { generateApproveCallData } from "./erc20"; + +export const SAFE_ADDRESSES_MAP = { + "1.4.1": { + "11155111": { + ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", + SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", + }, + "5": { + ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", + SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", + }, + }, +} as const; + +const getInitializerCode = async ({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, +}: { + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + multiSendAddress: Address; +}) => { + const setupTxs: InternalTx[] = [ + { + to: addModuleLibAddress, + data: enableModuleCallData(safe4337ModuleAddress), + value: 0n, + operation: 1, // 1 = DelegateCall required for enabling the module + }, + ]; + + const multiSendCallData = encodeMultiSend(setupTxs); + + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "_owners", + type: "address[]", + }, + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "address", + name: "fallbackHandler", + type: "address", + }, + { + internalType: "address", + name: "paymentToken", + type: "address", + }, + { + internalType: "uint256", + name: "payment", + type: "uint256", + }, + { + internalType: "address payable", + name: "paymentReceiver", + type: "address", + }, + ], + name: "setup", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "setup", + args: [ + [owner], + 1n, + multiSendAddress, + multiSendCallData, + safe4337ModuleAddress, + zeroAddress, + 0n, + zeroAddress, + ], + }); +}; + +export const enableModuleCallData = (safe4337ModuleAddress: `0x${string}`) => { + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "modules", + type: "address[]", + }, + ], + name: "enableModules", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "enableModules", + args: [[safe4337ModuleAddress]], + }); +}; + +export const getAccountInitCode = async ({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + safeProxyFactoryAddress, + safeSingletonAddress, + saltNonce = 0n, + multiSendAddress, +}: { + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + safeProxyFactoryAddress: Address; + safeSingletonAddress: Address; + saltNonce?: bigint; + multiSendAddress: Address; +}): Promise => { + if (!owner) throw new Error("Owner account not found"); + const initializer = await getInitializerCode({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, + }); + + const initCodeCallData = encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_singleton", + type: "address", + }, + { + internalType: "bytes", + name: "initializer", + type: "bytes", + }, + { + internalType: "uint256", + name: "saltNonce", + type: "uint256", + }, + ], + name: "createProxyWithNonce", + outputs: [ + { + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "createProxyWithNonce", + args: [safeSingletonAddress, initializer, saltNonce], + }); + + return concatHex([safeProxyFactoryAddress, initCodeCallData]); +}; + +export const EIP712_SAFE_OPERATION_TYPE = { + SafeOp: [ + { type: "address", name: "safe" }, + { type: "bytes", name: "callData" }, + { type: "uint256", name: "nonce" }, + { type: "uint256", name: "preVerificationGas" }, + { type: "uint256", name: "verificationGasLimit" }, + { type: "uint256", name: "callGasLimit" }, + { type: "uint256", name: "maxFeePerGas" }, + { type: "uint256", name: "maxPriorityFeePerGas" }, + { type: "address", name: "entryPoint" }, + ], +}; + +export const encodeCallData = (params: { + to: Address; + value: bigint; + data: `0x${string}`; +}) => { + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint8", + name: "operation", + type: "uint8", + }, + ], + name: "executeUserOp", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "executeUserOp", + args: [params.to, params.value, params.data, 0], + }); +}; + +export const getAccountAddress = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>({ + client, + owner, + addModuleLibAddress, + safe4337ModuleAddress, + safeProxyFactoryAddress, + safeSingletonAddress, + saltNonce = 0n, + multiSendAddress, +}: { + client: PublicClient; + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + safeProxyFactoryAddress: Address; + safeSingletonAddress: Address; + saltNonce?: bigint; + multiSendAddress: Address; +}): Promise
=> { + const proxyCreationCode = await client.readContract({ + abi: [ + { + inputs: [], + name: "proxyCreationCode", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "pure", + type: "function", + }, + ], + address: safeProxyFactoryAddress, + functionName: "proxyCreationCode", + }); + + const deploymentCode = encodePacked( + ["bytes", "uint256"], + [proxyCreationCode, hexToBigInt(safeSingletonAddress)] + ); + + const initializer = await getInitializerCode({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, + }); + + const salt = keccak256( + encodePacked( + ["bytes32", "uint256"], + [keccak256(encodePacked(["bytes"], [initializer])), saltNonce] + ) + ); + + return getContractAddress({ + from: safeProxyFactoryAddress, + salt, + bytecode: deploymentCode, + opcode: "CREATE2", + }); +}; diff --git a/paymaster-analysis/alchemy/utils/userOp.ts b/paymaster-analysis/alchemy/utils/userOp.ts new file mode 100644 index 00000000..a5454e1d --- /dev/null +++ b/paymaster-analysis/alchemy/utils/userOp.ts @@ -0,0 +1,61 @@ +import dotenv from "dotenv"; +import type { Address } from "abitype" +import type { Hex, PrivateKeyAccount } from "viem" +import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; + +dotenv.config() +const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const chainID = Number(process.env.ALCHEMY_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; + +export type UserOperation = { + sender: Address + nonce: bigint + initCode: Hex + callData: Hex + callGasLimit: bigint + verificationGasLimit: bigint + preVerificationGas: bigint + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + paymasterAndData: Hex + signature: Hex +} + +export const signUserOperation = async (userOperation: UserOperation, signer: PrivateKeyAccount) => { + const signatures = [ + { + signer: signer.address, + data: await signer.signTypedData({ + domain: { + chainId: chainID, + verifyingContract: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + }, + types: EIP712_SAFE_OPERATION_TYPE, + primaryType: "SafeOp", + message: { + safe: userOperation.sender, + callData: userOperation.callData, + nonce: userOperation.nonce, + preVerificationGas: userOperation.preVerificationGas, + verificationGasLimit: userOperation.verificationGasLimit, + callGasLimit: userOperation.callGasLimit, + maxFeePerGas: userOperation.maxFeePerGas, + maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + entryPoint: ENTRY_POINT_ADDRESS, + }, + }), + }, + ]; + + signatures.sort((left, right) => + left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()) + ); + + let signatureBytes: Address = "0x"; + for (const sig of signatures) { + signatureBytes += sig.data.slice(2); + } + + return signatureBytes; + }; \ No newline at end of file diff --git a/paymaster-analysis/pimlico/utils/erc20.ts b/paymaster-analysis/pimlico/utils/erc20.ts new file mode 100644 index 00000000..a168df6e --- /dev/null +++ b/paymaster-analysis/pimlico/utils/erc20.ts @@ -0,0 +1,128 @@ +import dotenv from "dotenv"; +import { http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount } from "viem"; +import { goerli, sepolia } from "viem/chains"; + +dotenv.config() +const rpcURL = process.env.PIMLICO_RPC_URL; +const chain = process.env.PIMLICO_CHAIN; + +export const generateApproveCallData = (paymasterAddress: Address) => { + const approveData = encodeFunctionData({ + abi: [ + { + inputs: [ + { name: "_spender", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + ], + args: [ + paymasterAddress, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, + ], + }); + + return approveData; +}; + +export const generateTransferCallData = (to: Address, value: bigint) => { + const transferData = encodeFunctionData({ + abi: [ + { + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + ], + args: [to, value], + }); + + return transferData; +}; + +export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: any) => { + const erc20Decimals = await publicClient.readContract({ + abi: [ + { + inputs: [], + name: "decimals", + outputs: [{ type: "uint8" }], + type: "function", + stateMutability: "view", + }, + ], + address: erc20TokenAddress, + functionName: "decimals", + }); + + return erc20Decimals; +} + +export const getERC20Balance = async (erc20TokenAddress: string, publicClient: any, owner: string) => { + const senderERC20Balance = await publicClient.readContract({ + abi: [ + { + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "balance", type: "uint256" }], + type: "function", + stateMutability: "view", + }, + ], + address: erc20TokenAddress, + functionName: "balanceOf", + args: [owner], + }); + + return senderERC20Balance; +} + +export const mintERC20Token = async (erc20TokenAddress: string, publicClient: any, signer: PrivateKeyAccount, to: string, amount: number) => { + let walletClient; + if(chain == "sepolia"){ + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(rpcURL) + }) + } + else if(chain == "goerli"){ + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(rpcURL) + }) + } + else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) + } + const { request } = await publicClient.simulateContract({ + address: erc20TokenAddress, + abi: [ + { + inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256"}], + name: "mint", + outputs: [], + type: "function", + stateMutability: "public", + }, + ], + functionName: 'mint', + args: [to, amount], + signer + }) + await walletClient.writeContract(request) +} \ No newline at end of file diff --git a/paymaster-analysis/pimlico/utils/safe.ts b/paymaster-analysis/pimlico/utils/safe.ts new file mode 100644 index 00000000..6d5b60cb --- /dev/null +++ b/paymaster-analysis/pimlico/utils/safe.ts @@ -0,0 +1,353 @@ +import { + Address, + Chain, + Client, + Hex, + PublicClient, + Transport, + concatHex, + encodeFunctionData, + encodePacked, + getContractAddress, + hexToBigInt, + keccak256, + zeroAddress, +} from "viem"; +import { InternalTx, encodeMultiSend } from "../../utils/multisend"; +import { generateApproveCallData } from "./erc20"; + +export const SAFE_ADDRESSES_MAP = { + "1.4.1": { + "11155111": { + ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", + SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", + }, + "5": { + ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", + SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", + }, + }, +} as const; + +const getInitializerCode = async ({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, + erc20TokenAddress, + paymasterAddress, +}: { + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + multiSendAddress: Address; + erc20TokenAddress: Address; + paymasterAddress: Address; +}) => { + const setupTxs: InternalTx[] = [ + { + to: addModuleLibAddress, + data: enableModuleCallData(safe4337ModuleAddress), + value: 0n, + operation: 1, // 1 = DelegateCall required for enabling the module + }, + { + to: erc20TokenAddress, + data: generateApproveCallData(paymasterAddress), + value: 0n, + operation: 0, // 0 = Call + }, + ]; + + const multiSendCallData = encodeMultiSend(setupTxs); + + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "_owners", + type: "address[]", + }, + { + internalType: "uint256", + name: "_threshold", + type: "uint256", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "address", + name: "fallbackHandler", + type: "address", + }, + { + internalType: "address", + name: "paymentToken", + type: "address", + }, + { + internalType: "uint256", + name: "payment", + type: "uint256", + }, + { + internalType: "address payable", + name: "paymentReceiver", + type: "address", + }, + ], + name: "setup", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "setup", + args: [ + [owner], + 1n, + multiSendAddress, + multiSendCallData, + safe4337ModuleAddress, + zeroAddress, + 0n, + zeroAddress, + ], + }); +}; + +export const enableModuleCallData = (safe4337ModuleAddress: `0x${string}`) => { + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address[]", + name: "modules", + type: "address[]", + }, + ], + name: "enableModules", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "enableModules", + args: [[safe4337ModuleAddress]], + }); +}; + +export const getAccountInitCode = async ({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + safeProxyFactoryAddress, + safeSingletonAddress, + saltNonce = 0n, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, +}: { + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + safeProxyFactoryAddress: Address; + safeSingletonAddress: Address; + saltNonce?: bigint; + erc20TokenAddress: Address; + multiSendAddress: Address; + paymasterAddress: Address; +}): Promise => { + if (!owner) throw new Error("Owner account not found"); + const initializer = await getInitializerCode({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, + }); + + const initCodeCallData = encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_singleton", + type: "address", + }, + { + internalType: "bytes", + name: "initializer", + type: "bytes", + }, + { + internalType: "uint256", + name: "saltNonce", + type: "uint256", + }, + ], + name: "createProxyWithNonce", + outputs: [ + { + internalType: "contract SafeProxy", + name: "proxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "createProxyWithNonce", + args: [safeSingletonAddress, initializer, saltNonce], + }); + + return concatHex([safeProxyFactoryAddress, initCodeCallData]); +}; + +export const EIP712_SAFE_OPERATION_TYPE = { + SafeOp: [ + { type: "address", name: "safe" }, + { type: "bytes", name: "callData" }, + { type: "uint256", name: "nonce" }, + { type: "uint256", name: "preVerificationGas" }, + { type: "uint256", name: "verificationGasLimit" }, + { type: "uint256", name: "callGasLimit" }, + { type: "uint256", name: "maxFeePerGas" }, + { type: "uint256", name: "maxPriorityFeePerGas" }, + { type: "address", name: "entryPoint" }, + ], +}; + +export const encodeCallData = (params: { + to: Address; + value: bigint; + data: `0x${string}`; +}) => { + return encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint8", + name: "operation", + type: "uint8", + }, + ], + name: "executeUserOp", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "executeUserOp", + args: [params.to, params.value, params.data, 0], + }); +}; + +export const getAccountAddress = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>({ + client, + owner, + addModuleLibAddress, + safe4337ModuleAddress, + safeProxyFactoryAddress, + safeSingletonAddress, + saltNonce = 0n, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, +}: { + client: PublicClient; + owner: Address; + addModuleLibAddress: Address; + safe4337ModuleAddress: Address; + safeProxyFactoryAddress: Address; + safeSingletonAddress: Address; + saltNonce?: bigint; + erc20TokenAddress: Address; + multiSendAddress: Address; + paymasterAddress: Address; +}): Promise
=> { + const proxyCreationCode = await client.readContract({ + abi: [ + { + inputs: [], + name: "proxyCreationCode", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "pure", + type: "function", + }, + ], + address: safeProxyFactoryAddress, + functionName: "proxyCreationCode", + }); + + const deploymentCode = encodePacked( + ["bytes", "uint256"], + [proxyCreationCode, hexToBigInt(safeSingletonAddress)] + ); + + const initializer = await getInitializerCode({ + owner, + addModuleLibAddress, + safe4337ModuleAddress, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, + }); + + const salt = keccak256( + encodePacked( + ["bytes32", "uint256"], + [keccak256(encodePacked(["bytes"], [initializer])), saltNonce] + ) + ); + + return getContractAddress({ + from: safeProxyFactoryAddress, + salt, + bytecode: deploymentCode, + opcode: "CREATE2", + }); +}; diff --git a/paymaster-analysis/pimlico/utils/userOps.ts b/paymaster-analysis/pimlico/utils/userOps.ts new file mode 100644 index 00000000..5a5a31ad --- /dev/null +++ b/paymaster-analysis/pimlico/utils/userOps.ts @@ -0,0 +1,67 @@ +import dotenv from "dotenv"; +import type { Address } from "abitype" +import type { PrivateKeyAccount } from "viem" +import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; +import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; + +dotenv.config() +const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const chain = process.env.PIMLICO_CHAIN; +const chainID = Number(process.env.PIMLICO_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; + +export const submitUserOperation = async (userOperation: UserOperation, bundlerClient: any) => { + const userOperationHash = await bundlerClient.sendUserOperation({ + userOperation, + entryPoint: ENTRY_POINT_ADDRESS, + }); + console.log(`UserOperation submitted. Hash: ${userOperationHash}`); + console.log(`UserOp Link: https://jiffyscan.xyz/userOpHash/${userOperationHash}?network=`+chain+"\n"); + + console.log("Querying for receipts..."); + const receipt = await bundlerClient.waitForUserOperationReceipt({ + hash: userOperationHash, + }); + console.log( + `Receipt found!\nTransaction hash: ${receipt.receipt.transactionHash}` + ); + console.log(`Transaction Link: https://`+chain+`.etherscan.io/tx/${receipt.receipt.transactionHash}`); +}; + +export const signUserOperation = async (userOperation: UserOperation, signer: PrivateKeyAccount) => { + const signatures = [ + { + signer: signer.address, + data: await signer.signTypedData({ + domain: { + chainId: chainID, + verifyingContract: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + }, + types: EIP712_SAFE_OPERATION_TYPE, + primaryType: "SafeOp", + message: { + safe: userOperation.sender, + callData: userOperation.callData, + nonce: userOperation.nonce, + preVerificationGas: userOperation.preVerificationGas, + verificationGasLimit: userOperation.verificationGasLimit, + callGasLimit: userOperation.callGasLimit, + maxFeePerGas: userOperation.maxFeePerGas, + maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + entryPoint: ENTRY_POINT_ADDRESS, + }, + }), + }, + ]; + + signatures.sort((left, right) => + left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()) + ); + + let signatureBytes: Address = "0x"; + for (const sig of signatures) { + signatureBytes += sig.data.slice(2); + } + + return signatureBytes; + }; \ No newline at end of file diff --git a/paymaster-analysis/utils/erc721.ts b/paymaster-analysis/utils/erc721.ts new file mode 100644 index 00000000..b7dcaba2 --- /dev/null +++ b/paymaster-analysis/utils/erc721.ts @@ -0,0 +1,21 @@ +import { Address, encodeFunctionData } from "viem"; + +export const generateMintingCallData = (to: Address) => { + const transferData = encodeFunctionData({ + abi: [ + { + inputs: [ + { name: "_to", type: "address" }, + ], + name: "safeMint", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + ], + args: [to], + }); + + return transferData; +}; diff --git a/paymaster-analysis/utils/multisend.ts b/paymaster-analysis/utils/multisend.ts new file mode 100644 index 00000000..6b0f4d7b --- /dev/null +++ b/paymaster-analysis/utils/multisend.ts @@ -0,0 +1,44 @@ +import { encodePacked, encodeFunctionData } from "viem"; + +export type InternalTx = { + to: `0x${string}`; + data: `0x${string}`; + value: bigint; + operation: 0 | 1; +}; + +const encodeInternalTransaction = (tx: InternalTx): string => { + const encoded = encodePacked( + ["uint8", "address", "uint256", "uint256", "bytes"], + [ + tx.operation, + tx.to, + tx.value, + BigInt(tx.data.slice(2).length / 2), + tx.data, + ] + ); + return encoded.slice(2); +}; + +export const encodeMultiSend = (txs: InternalTx[]): `0x${string}` => { + const data: `0x${string}` = `0x${txs + .map((tx) => encodeInternalTransaction(tx)) + .join("")}`; + + return encodeFunctionData({ + abi: [ + { + inputs: [ + { internalType: "bytes", name: "transactions", type: "bytes" }, + ], + name: "multiSend", + outputs: [], + stateMutability: "payable", + type: "function", + }, + ], + functionName: "multiSend", + args: [data], + }); +}; From 628fdace09074646f643c39c79220a40fb20df4c Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:24:32 +0530 Subject: [PATCH 15/60] package.json refactored --- paymaster-analysis/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/paymaster-analysis/package.json b/paymaster-analysis/package.json index 08cd3fd8..dee4edd3 100644 --- a/paymaster-analysis/package.json +++ b/paymaster-analysis/package.json @@ -6,11 +6,9 @@ "license": "GPL-3.0", "type": "module", "scripts": { - "alchemy": "tsx ./alchemy/alchemy.ts", "pimlico:account": "tsx ./pimlico/pimlico-account.ts", "pimlico:erc20": "tsx ./pimlico/pimlico-erc20.ts", "pimlico:erc721": "tsx ./pimlico/pimlico-erc721.ts", - "alchemy:account:bundler": "tsx ./alchemy/alchemy-account-bundler.ts", "alchemy:account": "tsx ./alchemy/alchemy-account.ts", "alchemy:erc20": "tsx ./alchemy/alchemy-erc20.ts", "alchemy:erc721": "tsx ./alchemy/alchemy-erc721.ts" From 2ff95b60b9133a2ced32d4c479c3bba0e7470918 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:27:15 +0530 Subject: [PATCH 16/60] Pimlico Paymaster Analysis Added --- paymaster-analysis/pimlico/README.md | 185 ++++++++++++++++++ paymaster-analysis/pimlico/pimlico-account.ts | 150 ++++++++++++++ paymaster-analysis/pimlico/pimlico-erc20.ts | 174 ++++++++++++++++ paymaster-analysis/pimlico/pimlico-erc721.ts | 157 +++++++++++++++ 4 files changed, 666 insertions(+) create mode 100644 paymaster-analysis/pimlico/README.md create mode 100644 paymaster-analysis/pimlico/pimlico-account.ts create mode 100644 paymaster-analysis/pimlico/pimlico-erc20.ts create mode 100644 paymaster-analysis/pimlico/pimlico-erc721.ts diff --git a/paymaster-analysis/pimlico/README.md b/paymaster-analysis/pimlico/README.md new file mode 100644 index 00000000..6f1e1ac7 --- /dev/null +++ b/paymaster-analysis/pimlico/README.md @@ -0,0 +1,185 @@ +# Pimlico Paymaster Analysis + +## Safe Deployment with Pimlico Paymaster (USDC) + +``` +npm run pimlico:account + +> @safe-global/aa-analysis@1.0.0 pimlico:account +> tsx ./pimlico/pimlico-account.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x3680E646f69c94269540AB157C18B7C271D14E6d +Address Link: https://goerli.etherscan.io/address/0x3680E646f69c94269540AB157C18B7C271D14E6d + +Safe Wallet ERC20 Balance: 0 + +Please deposit atleast 1 USDC Token for paying the Paymaster. + +Updated Safe Wallet USDC Balance: 1 + +Nonce for the sender received from EntryPoint. + +Deploying a new Safe. + +UserOperation submitted. Hash: 0xda816afbbb3d3daffe3049d1f661f423a9cc30f6de3d43e3bf2394f1311dcbbc +UserOp Link: https://jiffyscan.xyz/userOpHash/0xda816afbbb3d3daffe3049d1f661f423a9cc30f6de3d43e3bf2394f1311dcbbc?network=goerli + +Querying for receipts... +Receipt found! +Transaction hash: 0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1 +Transaction Link: https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1 +``` + +Gas Usage: 499796 + +## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (USDC) + +``` +npm run pimlico:erc20 + +> @safe-global/aa-analysis@1.0.0 pimlico:erc20 +> tsx ./pimlico/pimlico-erc20.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x466e2F6ccF2e7B6d44a51b0C6072Ed597154Ec4c +Address Link: https://goerli.etherscan.io/address/0x466e2F6ccF2e7B6d44a51b0C6072Ed597154Ec4c + +Safe Wallet USDC Balance: 0 + +Please deposit atleast 1 USDC Token for paying the Paymaster. + +Updated Safe Wallet USDC Balance: 1 + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Nonce for the sender received from EntryPoint. + +Deploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx. + +UserOperation submitted. Hash: 0xab76c14cd3d8fa6dae9202b5f3f52fb6aae2e0050240efb9300d9cf32861c040 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xab76c14cd3d8fa6dae9202b5f3f52fb6aae2e0050240efb9300d9cf32861c040?network=goerli + +Querying for receipts... +Receipt found! +Transaction hash: 0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a +Transaction Link: https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a +``` + +Gas Usage: 511091 + +## ERC20 Transaction with Pimlico Paymaster (USDC) + +``` +npm run pimlico:erc20 + +> @safe-global/aa-analysis@1.0.0 pimlico:erc20 +> tsx ./pimlico/pimlico-erc20.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xcdf86Aa2002e56A3F9e37A9a28CA91cdfC1994ac +Address Link: https://goerli.etherscan.io/address/0xcdf86Aa2002e56A3F9e37A9a28CA91cdfC1994ac + +Safe Wallet USDC Balance: 2 + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Nonce for the sender received from EntryPoint. + +The Safe is already deployed. Sending 1 USDC from the Safe to itself. + +UserOperation submitted. Hash: 0xb1c4a82e42e0b5c11853b22a8699717ab5fea4b5189932b9960a676a01490403 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xb1c4a82e42e0b5c11853b22a8699717ab5fea4b5189932b9960a676a01490403?network=goerli + +Querying for receipts... +Receipt found! +Transaction hash: 0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d +Transaction Link: https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d +``` + +Gas Usage: 200038 + +## Safe Deployment + ERC721 Transaction with Pimlico Paymaster (USDC) + +``` +npm run pimlico:erc721 + +> @safe-global/aa-analysis@1.0.0 pimlico:erc721 +> tsx ./pimlico/pimlico-erc721.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb +Address Link: https://goerli.etherscan.io/address/0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb + +Safe Wallet USDC Balance: 0 + +Please deposit atleast 2 USDC Token for paying the Paymaster. + +Updated Safe Wallet USDC Balance: 2 + +Nonce for the sender received from EntryPoint. +Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx +UserOperation submitted. Hash: 0x4e587fa1e1598f8f02f9e6d9f19091a5355b45c703e857b6d344181d0e124c88 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x4e587fa1e1598f8f02f9e6d9f19091a5355b45c703e857b6d344181d0e124c88?network=goerli + +Querying for receipts... +Receipt found! +Transaction hash: 0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d +Transaction Link: https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d +``` + +Gas Usage: 558029 + +## ERC721 Transaction with Pimlico Paymaster (USDC) + +``` +npm run pimlico:erc721 + +> @safe-global/aa-analysis@1.0.0 pimlico:erc721 +> tsx ./pimlico/pimlico-erc721.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb +Address Link: https://goerli.etherscan.io/address/0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb + +Safe Wallet USDC Balance: 1 + +Please deposit atleast 2 USDC Token for paying the Paymaster. + +Updated Safe Wallet USDC Balance: 2 + +Nonce for the sender received from EntryPoint. +The Safe is already deployed. Minting 1 ERC721 Token to the Safe. +UserOperation submitted. Hash: 0xee2a5f5f415273d661440d402f6bc8f6303870651a8790ebb745aeeda031bfc4 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xee2a5f5f415273d661440d402f6bc8f6303870651a8790ebb745aeeda031bfc4?network=goerli + +Querying for receipts... +Receipt found! +Transaction hash: 0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928 +Transaction Link: https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928 +``` + +Gas Usage: 229913 diff --git a/paymaster-analysis/pimlico/pimlico-account.ts b/paymaster-analysis/pimlico/pimlico-account.ts new file mode 100644 index 00000000..43a8b185 --- /dev/null +++ b/paymaster-analysis/pimlico/pimlico-account.ts @@ -0,0 +1,150 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; +import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli } from "viem/chains"; +import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { setTimeout } from "timers/promises"; +import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; + +dotenv.config() + +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.PIMLICO_ACCOUNT_NONCE); +const chain = process.env.PIMLICO_CHAIN; +const chainID = Number(process.env.PIMLICO_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.PIMLICO_RPC_URL; +const apiKey = process.env.PIMLICO_API_KEY; +const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; +const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Pimlico API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let bundlerClient; +let publicClient; +if(chain == "goerli"){ + bundlerClient = createClient({ + transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), + chain: goerli, + }) + .extend(bundlerActions) + .extend(pimlicoBundlerActions); + + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); + +// TODO This and in other files, this might be made easier with chain substituted at the right place. +if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); +const usdcAmount = BigInt(10 ** usdcDecimals); +let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); +console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + +if(senderUSDCBalance < usdcAmount) { + console.log("\nPlease deposit atleast 1 USDC Token for paying the Paymaster."); + while (senderUSDCBalance < usdcAmount) { + await setTimeout(30000); + senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + } + console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); +} + +const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +if (contractCode) { + console.log("\nThe Safe is already deployed.\n"); +} else { + console.log("\nDeploying a new Safe.\n"); + + const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: senderAddress, + data: "0xe75235b8", // getThreshold() of the Safe + value: 0n, + }), + callGasLimit: 100_000n, // hardcode it for now at a high value + verificationGasLimit: 500_000n, // hardcode it for now at a high value + preVerificationGas: 50_000n, // hardcode it for now at a high value + maxFeePerGas: gasPriceResult.fast.maxFeePerGas, + maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, + paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field + signature: "0x", + }; + + sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); + + await submitUserOperation(sponsoredUserOperation, bundlerClient); +} \ No newline at end of file diff --git a/paymaster-analysis/pimlico/pimlico-erc20.ts b/paymaster-analysis/pimlico/pimlico-erc20.ts new file mode 100644 index 00000000..166c54dc --- /dev/null +++ b/paymaster-analysis/pimlico/pimlico-erc20.ts @@ -0,0 +1,174 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; +import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli } from "viem/chains"; +import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { setTimeout } from "timers/promises"; +import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; + +dotenv.config() + +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.PIMLICO_ERC20_NONCE); +const chain = process.env.PIMLICO_CHAIN; +const chainID = Number(process.env.PIMLICO_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.PIMLICO_RPC_URL; +const apiKey = process.env.PIMLICO_API_KEY; +const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; +const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; +const erc20TokenAddress = process.env.PIMLICO_ERC20_TOKEN_CONTRACT; + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Pimlico API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let bundlerClient; +let publicClient; +if(chain == "goerli"){ + bundlerClient = createClient({ + transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), + chain: goerli, + }) + .extend(bundlerActions) + .extend(pimlicoBundlerActions); + + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); + +if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// Fetch USDC balance of sender +const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); +const usdcAmount = BigInt(10 ** usdcDecimals); +let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); +console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + +if(senderUSDCBalance < BigInt(2) * usdcAmount) { + console.log("\nPlease deposit atleast 2 USDC Token for paying the Paymaster."); + while (senderUSDCBalance < BigInt(2) * usdcAmount) { + await setTimeout(30000); + senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + } + console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); +} + +// Token Configurations +const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); +const erc20Amount = BigInt(10 ** erc20Decimals); +let senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); +console.log("\nSafe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); + +// Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). +if (senderERC20Balance < erc20Amount ) { + console.log("\nMinting ERC20 Tokens to Safe Wallet."); + await mintERC20Token(erc20TokenAddress, publicClient, signer, senderAddress, erc20Amount); + + while (senderERC20Balance < erc20Amount) { + await setTimeout(15000); + senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); + } + console.log("\nUpdated Safe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); +} + +const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +if (contractCode) { + console.log( + "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n" + ); +} else { + console.log( + "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n" + ); +} + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + // Send 1 ERC20 to the signer. + callData: encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), + value: 0n, + }), + callGasLimit: 100_000n, // hardcode it for now at a high value + verificationGasLimit: 500_000n, // hardcode it for now at a high value + preVerificationGas: 50_000n, // hardcode it for now at a high value + maxFeePerGas: gasPriceResult.fast.maxFeePerGas, + maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, + paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); + +await submitUserOperation(sponsoredUserOperation, bundlerClient); diff --git a/paymaster-analysis/pimlico/pimlico-erc721.ts b/paymaster-analysis/pimlico/pimlico-erc721.ts new file mode 100644 index 00000000..c793c73d --- /dev/null +++ b/paymaster-analysis/pimlico/pimlico-erc721.ts @@ -0,0 +1,157 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; +import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli } from "viem/chains"; +import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { setTimeout } from "timers/promises"; +import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { generateMintingCallData } from "../utils/erc721"; + +dotenv.config() + +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.PIMLICO_ERC721_NONCE); +const chain = process.env.PIMLICO_CHAIN; +const chainID = Number(process.env.PIMLICO_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.PIMLICO_RPC_URL; +const apiKey = process.env.PIMLICO_API_KEY; +const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; +const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; +const erc721TokenAddress = process.env.PIMLICO_ERC721_TOKEN_CONTRACT; + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Pimlico API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let bundlerClient; +let publicClient; +if(chain == "goerli"){ + bundlerClient = createClient({ + transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), + chain: goerli, + }) + .extend(bundlerActions) + .extend(pimlicoBundlerActions); + + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + erc20TokenAddress: usdcTokenAddress, + multiSendAddress, + paymasterAddress: erc20PaymasterAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); + +if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// Fetch USDC balance of sender +const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); +const usdcAmount = BigInt(10 ** usdcDecimals); +let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); +console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + +if(senderUSDCBalance < BigInt(2) * usdcAmount) { + console.log("\nPlease deposit atleast 2 USDC Token for paying the Paymaster."); + while (senderUSDCBalance < BigInt(2) * usdcAmount) { + await setTimeout(30000); + senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + } + console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); +} + +const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +if (contractCode) { + console.log( + "The Safe is already deployed. Minting 1 ERC721 Token to the Safe." + ); +} else { + console.log( + "Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx" + ); +} + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + // Minting 1 ERC721 Token to the Safe itself + callData: encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(senderAddress), + value: 0n, + }), + callGasLimit: 100_000n, // hardcode it for now at a high value + verificationGasLimit: 500_000n, // hardcode it for now at a high value + preVerificationGas: 50_000n, // hardcode it for now at a high value + maxFeePerGas: gasPriceResult.fast.maxFeePerGas, + maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, + paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); + +await submitUserOperation(sponsoredUserOperation, bundlerClient); From 80d7694b44389db07019bf13fe5aab649fe9933c Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:27:33 +0530 Subject: [PATCH 17/60] Alchemy Paymaster Analysis Added --- paymaster-analysis/alchemy/README.md | 168 +++++++++++ paymaster-analysis/alchemy/alchemy-account.ts | 258 ++++++++++++++++ paymaster-analysis/alchemy/alchemy-erc20.ts | 277 ++++++++++++++++++ paymaster-analysis/alchemy/alchemy-erc721.ts | 256 ++++++++++++++++ 4 files changed, 959 insertions(+) create mode 100644 paymaster-analysis/alchemy/README.md create mode 100644 paymaster-analysis/alchemy/alchemy-account.ts create mode 100644 paymaster-analysis/alchemy/alchemy-erc20.ts create mode 100644 paymaster-analysis/alchemy/alchemy-erc721.ts diff --git a/paymaster-analysis/alchemy/README.md b/paymaster-analysis/alchemy/README.md new file mode 100644 index 00000000..8f5a867d --- /dev/null +++ b/paymaster-analysis/alchemy/README.md @@ -0,0 +1,168 @@ +# Alchemy Paymaster Analysis + +## Safe Deployment with Alchemy Paymaster + +NPM Command to run: +``` +npm run alchemy:account + +> @safe-global/aa-analysis@1.0.0 alchemy:account +> tsx ./alchemy/alchemy-account.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x66075bF48502407f46cDE90FA9e88b05748A16DA +Address Link: https://sepolia.etherscan.io/address/0x66075bF48502407f46cDE90FA9e88b05748A16DA + +Nonce for the sender received from EntryPoint. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +Safe Account Creation User Operation Successfully Created! +UserOp Link: https://jiffyscan.xyz/userOpHash/0xd48c4e1fb7f7c8e6b33091ed11e7deb7e1f7bf81ce2840ca9b31f5bef7dda9fb?network=sepolia +``` + +Transaction Link: https://sepolia.etherscan.io/tx/https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa + +Gas Usage: 408370 + +## Safe Deployment + ERC20 Transaction with Alchemy Paymaster + +NPM Command to run: +``` +npm run alchemy:erc20 + +> @safe-global/aa-analysis@1.0.0 alchemy:erc20 +> tsx ./alchemy/alchemy-erc20.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x40726566E5BCb54A99Dc791D075067B5bC188D0D +Address Link: https://sepolia.etherscan.io/address/0x40726566E5BCb54A99Dc791D075067B5bC188D0D + +Safe Wallet ERC20 Balance: 0 +Minting Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Nonce for the sender received from EntryPoint. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +Safe Account Creation User Operation Successfully Created! +UserOp Link: https://jiffyscan.xyz/userOpHash/0xa6e7e47f8df520d1a7324fd4fd9e67a521ef6723122a198347fd15dcb0d60dde?network=sepolia +``` + +Transaction Link: https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb + +Gas Usage: 419691 + +## ERC20 Transaction with Alchemy Paymaster + +NPM Command to run: +``` +npm run alchemy:erc20 + +> @safe-global/aa-analysis@1.0.0 alchemy:erc20 +> tsx ./alchemy/alchemy-erc20.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xe433Edadd1eD908FE5541366C1F837b9B60162d9 +Address Link: https://sepolia.etherscan.io/address/0xe433Edadd1eD908FE5541366C1F837b9B60162d9 + +Safe Wallet ERC20 Balance: 1 + +Nonce for the sender received from EntryPoint. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +Safe Account Creation User Operation Successfully Created! +UserOp Link: https://jiffyscan.xyz/userOpHash/0xe61b3e204d9dda83e318c17de6dab6eb2fb10bae7786959e93e2c4e1d63df8e8?network=sepolia +``` + +Transaction Link: https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2 + +Gas Usage: 131418 + +## Safe Deployment + ERC721 Transaction with Alchemy Paymaster + +NPM Command to run: +``` +npm run alchemy:erc721 + +> @safe-global/aa-analysis@1.0.0 alchemy:erc721 +> tsx ./alchemy/alchemy-erc721.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x249d03e36b3eDDDB3E862959ef3a1cB28Cf01dD2 +Address Link: https://sepolia.etherscan.io/address/0x249d03e36b3eDDDB3E862959ef3a1cB28Cf01dD2 + +Nonce for the sender received from EntryPoint. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +Safe Account Creation User Operation Successfully Created! +UserOp Link: https://jiffyscan.xyz/userOpHash/0x79f121c77c2842d906c9cd193445f73242c08c3a231b49aee654eaecf448689f?network=sepolia +``` + +Transaction Link: https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba + +Gas Usage: 448953 + +## ERC721 Transaction with Alchemy Paymaster + +NPM Command to run: +``` +npm run alchemy:erc721 + +> @safe-global/aa-analysis@1.0.0 alchemy:erc721 +> tsx ./alchemy/alchemy-erc721.ts + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xe433Edadd1eD908FE5541366C1F837b9B60162d9 +Address Link: https://sepolia.etherscan.io/address/0xe433Edadd1eD908FE5541366C1F837b9B60162d9 + +Nonce for the sender received from EntryPoint. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +Safe Account Creation User Operation Successfully Created! +UserOp Link: https://jiffyscan.xyz/userOpHash/0x0f93595c74153ca7d8968ad5206834b53e1a50463835862fe375de472e969420?network=sepolia +``` + +Transaction Link: https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1 + +Gas Usage: 194878 diff --git a/paymaster-analysis/alchemy/alchemy-account.ts b/paymaster-analysis/alchemy/alchemy-account.ts new file mode 100644 index 00000000..22d99bbd --- /dev/null +++ b/paymaster-analysis/alchemy/alchemy-account.ts @@ -0,0 +1,258 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, signUserOperation } from "./utils/userOp"; +import { Address, Hash, createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli, sepolia } from "viem/chains"; +import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { setTimeout } from "timers/promises"; + +dotenv.config() +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.ALCHEMY_ACCOUNT_PAYMASTER_NONCE); +const chain = process.env.ALCHEMY_CHAIN; +const chainID = Number(process.env.ALCHEMY_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.ALCHEMY_RPC_URL; +const policyID = process.env.ALCHEMY_GAS_POLICY; +const apiKey = process.env.ALCHEMY_API_KEY; + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Alchemy API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let publicClient; +if(chain == "sepolia"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: sepolia, + }); +} +else if(chain == "goerli"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// The console log in this function could be removed. +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); + +// TODO This and in other files, this might be made easier with chain substituted at the right place. +if(chain == "sepolia"){ + console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); +} +else if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: senderAddress, + data: "0xe75235b8", // getThreshold() of the Safe. TODO: Check if this could be removed. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") + +const gasOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'alchemy_requestGasAndPaymasterAndData', + params: [ + { + policyId: policyID, + entryPoint: ENTRY_POINT_ADDRESS, + dummySignature: sponsoredUserOperation.signature, + userOperation: { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData + } + } + ] + }) +}; + +let responseValues; + +if(chain == "sepolia"){ + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} +console.log("\nReceived Paymaster Data from Alchemy.") + +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; +sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") + +// console.log(sponsoredUserOperation); + +const options = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_sendUserOperation', + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: sponsoredUserOperation.callGasLimit, + verificationGasLimit: sponsoredUserOperation.verificationGasLimit, + preVerificationGas: sponsoredUserOperation.preVerificationGas, + maxFeePerGas: sponsoredUserOperation.maxFeePerGas, + maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData + }, + ENTRY_POINT_ADDRESS + ] + }) +}; + +if(chain == "sepolia"){ + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +if(responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!") + console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain); + + const hashOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_getUserOperationReceipt', + params: [responseValues.result], + entryPoint: ENTRY_POINT_ADDRESS, + }) + }; + let runOnce = true; + + while (responseValues.result == null || runOnce) { + await setTimeout(25000); + await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); + runOnce = false; + } + + if(responseValues.result) { + console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") + } + else { + console.log("\n"+responseValues.error); + } +} +else { + console.log("\n"+responseValues.error.message); +} \ No newline at end of file diff --git a/paymaster-analysis/alchemy/alchemy-erc20.ts b/paymaster-analysis/alchemy/alchemy-erc20.ts new file mode 100644 index 00000000..7ba5a10c --- /dev/null +++ b/paymaster-analysis/alchemy/alchemy-erc20.ts @@ -0,0 +1,277 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, signUserOperation } from "./utils/userOp"; +import { Address, Hash, createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli, sepolia } from "viem/chains"; +import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { setTimeout } from "timers/promises"; + +dotenv.config() +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.ALCHEMY_ERC20_PAYMASTER_NONCE); +const chain = process.env.ALCHEMY_CHAIN; +const chainID = Number(process.env.ALCHEMY_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.ALCHEMY_RPC_URL; +const policyID = process.env.ALCHEMY_GAS_POLICY; +const apiKey = process.env.ALCHEMY_API_KEY; +const erc20TokenAddress = process.env.ALCHEMY_ERC20_TOKEN_CONTRACT; + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Alchemy API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let publicClient; +if(chain == "sepolia"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: sepolia, + }); +} +else if(chain == "goerli"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// The console log in this function could be removed. +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); + +if(chain == "sepolia"){ + console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); +} +else if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// Token Configurations + +const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); +const erc20Amount = BigInt(10 ** erc20Decimals); +let senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); +console.log("\nSafe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); + +// Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). +if (senderERC20Balance < erc20Amount ) { + console.log("\nMinting ERC20 Tokens to Safe Wallet."); + await mintERC20Token(erc20TokenAddress, publicClient, signer, senderAddress, erc20Amount); + + while (senderERC20Balance < erc20Amount) { + await setTimeout(15000); + senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); + } + console.log("\nUpdated Safe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); +} + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") + +const gasOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'alchemy_requestGasAndPaymasterAndData', + params: [ + { + policyId: policyID, + entryPoint: ENTRY_POINT_ADDRESS, + dummySignature: sponsoredUserOperation.signature, + userOperation: { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData + } + } + ] + }) +}; + +let responseValues; + +if(chain == "sepolia"){ + // TODO This and other places and files can use ALCHEMY_RPC_URL + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} +console.log("\nReceived Paymaster Data from Alchemy.") + +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; +sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") + +const options = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_sendUserOperation', + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: sponsoredUserOperation.callGasLimit, + verificationGasLimit: sponsoredUserOperation.verificationGasLimit, + preVerificationGas: sponsoredUserOperation.preVerificationGas, + maxFeePerGas: sponsoredUserOperation.maxFeePerGas, + maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData + }, + ENTRY_POINT_ADDRESS + ] + }) +}; + +if(chain == "sepolia"){ + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +if(responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!") + console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain+"\n"); + + const hashOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_getUserOperationReceipt', + params: [responseValues.result], + entryPoint: ENTRY_POINT_ADDRESS, + }) + }; + let runOnce = true; + + while (responseValues.result == null || runOnce) { + await setTimeout(25000); + await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); + runOnce = false; + } + + if(responseValues.result) { + console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") + } + else { + console.log("\n"+responseValues.error); + } +} +else { + console.log("\n"+responseValues.error.message); +} diff --git a/paymaster-analysis/alchemy/alchemy-erc721.ts b/paymaster-analysis/alchemy/alchemy-erc721.ts new file mode 100644 index 00000000..74a5c2dc --- /dev/null +++ b/paymaster-analysis/alchemy/alchemy-erc721.ts @@ -0,0 +1,256 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { UserOperation, signUserOperation } from "./utils/userOp"; +import { Address, Hash, createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { goerli, sepolia } from "viem/chains"; +import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { generateMintingCallData } from "../utils/erc721"; + +dotenv.config() +const privateKey = process.env.PRIVATE_KEY; +const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; +const saltNonce = BigInt(process.env.ALCHEMY_ERC721_PAYMASTER_NONCE); +const chain = process.env.ALCHEMY_CHAIN; +const chainID = Number(process.env.ALCHEMY_CHAIN_ID); +const safeVersion = process.env.SAFE_VERSION; +const rpcURL = process.env.ALCHEMY_RPC_URL; +const policyID = process.env.ALCHEMY_GAS_POLICY; +const apiKey = process.env.ALCHEMY_API_KEY; +const erc721TokenAddress = process.env.ALCHEMY_ERC721_TOKEN_CONTRACT; + + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Alchemy API key" + ); +} + +if(!privateKey){ + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let publicClient; +if(chain == "sepolia"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: sepolia, + }); +} +else if(chain == "goerli"){ + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: goerli, + }); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +// The console log in this function could be removed. +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); +if(chain == "sepolia"){ + console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); +} +else if(chain == "goerli"){ + console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: ENTRY_POINT_ADDRESS, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint.") + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") + +const gasOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'alchemy_requestGasAndPaymasterAndData', + params: [ + { + policyId: policyID, + entryPoint: ENTRY_POINT_ADDRESS, + dummySignature: sponsoredUserOperation.signature, + userOperation: { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData + } + } + ] + }) +}; + +let responseValues; + +if(chain == "sepolia"){ + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} +console.log("\nReceived Paymaster Data from Alchemy.") + +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; +sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; + +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") + +const options = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_sendUserOperation', + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: sponsoredUserOperation.callGasLimit, + verificationGasLimit: sponsoredUserOperation.verificationGasLimit, + preVerificationGas: sponsoredUserOperation.preVerificationGas, + maxFeePerGas: sponsoredUserOperation.maxFeePerGas, + maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData + }, + ENTRY_POINT_ADDRESS + ] + }) +}; + +if(chain == "sepolia"){ + await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else if(chain == "goerli"){ + await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); +} +else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." + ) +} + +if(responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!") + console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain+"\n"); + + const hashOptions = { + method: 'POST', + headers: {accept: 'application/json', 'content-type': 'application/json'}, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_getUserOperationReceipt', + params: [responseValues.result], + entryPoint: ENTRY_POINT_ADDRESS, + }) + }; + let runOnce = true; + + while (responseValues.result == null || runOnce) { + await setTimeout(25000); + await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) + .then(response => response.json()) + .then(response => responseValues = response) + .catch(err => console.error(err)); + runOnce = false; + } + + if(responseValues.result) { + console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") + } + else { + console.log("\n"+responseValues.error); + } +} +else { + console.log("\n"+responseValues.error.message); +} \ No newline at end of file From ffb691dda2ebf9600832f13c0b25933f76cb12f2 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:35:08 +0530 Subject: [PATCH 18/60] Timeout module added to ERC721 --- paymaster-analysis/alchemy/alchemy-erc721.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/paymaster-analysis/alchemy/alchemy-erc721.ts b/paymaster-analysis/alchemy/alchemy-erc721.ts index 74a5c2dc..2c840693 100644 --- a/paymaster-analysis/alchemy/alchemy-erc721.ts +++ b/paymaster-analysis/alchemy/alchemy-erc721.ts @@ -6,6 +6,7 @@ import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; import { generateMintingCallData } from "../utils/erc721"; +import { setTimeout } from "timers/promises"; dotenv.config() const privateKey = process.env.PRIVATE_KEY; From e3ee3d8126830daf51705c0f7385cfb6671a8588 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:35:29 +0530 Subject: [PATCH 19/60] README Added --- paymaster-analysis/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 paymaster-analysis/README.md diff --git a/paymaster-analysis/README.md b/paymaster-analysis/README.md new file mode 100644 index 00000000..b90bd9bf --- /dev/null +++ b/paymaster-analysis/README.md @@ -0,0 +1,20 @@ +# Paymaster Analysis + +## How to run? + +1. Rename the `.env.example` to `.env`. +2. Fill the required values of `.env`. +3. Based on which paymaster to run, check the `package.json` file to see the script. Further, you can check the `README.md` files in the corresponding paymaster folders to see the individual command. + +NOTE: If you run a paymaster analysis twice or more without changing the salt for Safe Creation, then only the operation will execute through paymaster, rather than Safe Creation and Operation. + +## Gas Usage Results + +| Type of Transaction | Without Paymaster | Pimlico (USDC Paymaster) | Alchemy (ETH Paymaster) | +|---|---|---|---| +| Safe Deployment with 4337 Module | 358975 | [499796](https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1) | [408370](https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa) | +| Safe Deployment with 4337 Module + ERC20 Transfer | 369890 | [511091](https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a) | [419691](https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb) | +| ERC20 Transfer using Safe with 4337 Module Enabled | 93674 | [200038](https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d) | [131418](https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2) | +| Safe Deployment with 4337 Module + ERC721 Minting | 411677 | [558029](https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d) | [448953](https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba) | +| ERC721 Minting using Safe with 4337 Module Enabled | 135449 | [229913](https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928) | [194878](https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1) | +| | | | | \ No newline at end of file From fdfb26765367590b1c2ccdded928eb71794b6de5 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 14:40:02 +0530 Subject: [PATCH 20/60] prettier added and executed --- paymaster-analysis/README.md | 16 +- paymaster-analysis/alchemy/README.md | 7 +- paymaster-analysis/alchemy/alchemy-account.ts | 280 ++++++++------- paymaster-analysis/alchemy/alchemy-erc20.ts | 320 +++++++++++------- paymaster-analysis/alchemy/alchemy-erc721.ts | 282 ++++++++------- paymaster-analysis/alchemy/utils/erc20.ts | 68 ++-- paymaster-analysis/alchemy/utils/safe.ts | 28 +- paymaster-analysis/alchemy/utils/userOp.ts | 108 +++--- paymaster-analysis/package-lock.json | 16 + paymaster-analysis/package.json | 4 +- paymaster-analysis/pimlico/pimlico-account.ts | 127 ++++--- paymaster-analysis/pimlico/pimlico-erc20.ts | 159 ++++++--- paymaster-analysis/pimlico/pimlico-erc721.ts | 127 ++++--- paymaster-analysis/pimlico/utils/erc20.ts | 68 ++-- paymaster-analysis/pimlico/utils/safe.ts | 36 +- paymaster-analysis/pimlico/utils/userOps.ts | 109 +++--- paymaster-analysis/tsconfig.json | 16 +- paymaster-analysis/utils/erc721.ts | 4 +- paymaster-analysis/utils/multisend.ts | 2 +- 19 files changed, 1075 insertions(+), 702 deletions(-) diff --git a/paymaster-analysis/README.md b/paymaster-analysis/README.md index b90bd9bf..0e1e116d 100644 --- a/paymaster-analysis/README.md +++ b/paymaster-analysis/README.md @@ -10,11 +10,11 @@ NOTE: If you run a paymaster analysis twice or more without changing the salt fo ## Gas Usage Results -| Type of Transaction | Without Paymaster | Pimlico (USDC Paymaster) | Alchemy (ETH Paymaster) | -|---|---|---|---| -| Safe Deployment with 4337 Module | 358975 | [499796](https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1) | [408370](https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa) | -| Safe Deployment with 4337 Module + ERC20 Transfer | 369890 | [511091](https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a) | [419691](https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb) | -| ERC20 Transfer using Safe with 4337 Module Enabled | 93674 | [200038](https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d) | [131418](https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2) | -| Safe Deployment with 4337 Module + ERC721 Minting | 411677 | [558029](https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d) | [448953](https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba) | -| ERC721 Minting using Safe with 4337 Module Enabled | 135449 | [229913](https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928) | [194878](https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1) | -| | | | | \ No newline at end of file +| Type of Transaction | Without Paymaster | Pimlico (USDC Paymaster) | Alchemy (ETH Paymaster) | +| -------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| Safe Deployment with 4337 Module | 358975 | [499796](https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1) | [408370](https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa) | +| Safe Deployment with 4337 Module + ERC20 Transfer | 369890 | [511091](https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a) | [419691](https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb) | +| ERC20 Transfer using Safe with 4337 Module Enabled | 93674 | [200038](https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d) | [131418](https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2) | +| Safe Deployment with 4337 Module + ERC721 Minting | 411677 | [558029](https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d) | [448953](https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba) | +| ERC721 Minting using Safe with 4337 Module Enabled | 135449 | [229913](https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928) | [194878](https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1) | +| | | | | diff --git a/paymaster-analysis/alchemy/README.md b/paymaster-analysis/alchemy/README.md index 8f5a867d..9d49e070 100644 --- a/paymaster-analysis/alchemy/README.md +++ b/paymaster-analysis/alchemy/README.md @@ -3,8 +3,9 @@ ## Safe Deployment with Alchemy Paymaster NPM Command to run: + ``` -npm run alchemy:account +npm run alchemy:account > @safe-global/aa-analysis@1.0.0 alchemy:account > tsx ./alchemy/alchemy-account.ts @@ -35,6 +36,7 @@ Gas Usage: 408370 ## Safe Deployment + ERC20 Transaction with Alchemy Paymaster NPM Command to run: + ``` npm run alchemy:erc20 @@ -72,6 +74,7 @@ Gas Usage: 419691 ## ERC20 Transaction with Alchemy Paymaster NPM Command to run: + ``` npm run alchemy:erc20 @@ -106,6 +109,7 @@ Gas Usage: 131418 ## Safe Deployment + ERC721 Transaction with Alchemy Paymaster NPM Command to run: + ``` npm run alchemy:erc721 @@ -138,6 +142,7 @@ Gas Usage: 448953 ## ERC721 Transaction with Alchemy Paymaster NPM Command to run: + ``` npm run alchemy:erc721 diff --git a/paymaster-analysis/alchemy/alchemy-account.ts b/paymaster-analysis/alchemy/alchemy-account.ts index 22d99bbd..646db448 100644 --- a/paymaster-analysis/alchemy/alchemy-account.ts +++ b/paymaster-analysis/alchemy/alchemy-account.ts @@ -4,10 +4,15 @@ import { UserOperation, signUserOperation } from "./utils/userOp"; import { Address, Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; -import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; import { setTimeout } from "timers/promises"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; @@ -21,13 +26,13 @@ const apiKey = process.env.ALCHEMY_API_KEY; if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Alchemy API key" + "Please replace the `apiKey` env variable with your Alchemy API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -35,31 +40,33 @@ const signer = privateKeyToAccount(privateKey as Hash); console.log("Signer Extracted from Private Key."); let publicClient; -if(chain == "sepolia"){ +if (chain == "sepolia") { publicClient = createPublicClient({ transport: http(rpcURL), chain: sepolia, }); -} -else if(chain == "goerli"){ +} else if (chain == "goerli") { publicClient = createPublicClient({ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); @@ -68,64 +75,73 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); // TODO This and in other files, this might be made easier with chain substituted at the right place. -if(chain == "sepolia"){ - console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); -} -else if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "sepolia") { + console.log( + "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, + ); +} else if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: senderAddress, - data: "0xe75235b8", // getThreshold() of the Safe. TODO: Check if this could be removed. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: senderAddress, + data: "0xe75235b8", // getThreshold() of the Safe. TODO: Check if this could be removed. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", }; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); const gasOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'alchemy_requestGasAndPaymasterAndData', + jsonrpc: "2.0", + method: "alchemy_requestGasAndPaymasterAndData", params: [ { policyId: policyID, @@ -133,60 +149,66 @@ const gasOptions = { dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData - } - } - ] - }) + callData: sponsoredUserOperation.callData, + }, + }, + ], + }), }; let responseValues; -if(chain == "sepolia"){ - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { +if (chain == "sepolia") { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -console.log("\nReceived Paymaster Data from Alchemy.") +console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.verificationGasLimit = + responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = + responseValues.result.paymasterAndData; sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = + responseValues.result.maxPriorityFeePerGas; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); // console.log(sponsoredUserOperation); const options = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_sendUserOperation', + jsonrpc: "2.0", + method: "eth_sendUserOperation", params: [ { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, callData: sponsoredUserOperation.callData, callGasLimit: sponsoredUserOperation.callGasLimit, @@ -195,64 +217,74 @@ const options = { maxFeePerGas: sponsoredUserOperation.maxFeePerGas, maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData + paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS - ] - }) + ENTRY_POINT_ADDRESS, + ], + }), }; -if(chain == "sepolia"){ - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { +if (chain == "sepolia") { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -if(responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!") - console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain); +if (responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!"); + console.log( + "UserOp Link: https://jiffyscan.xyz/userOpHash/" + + responseValues.result + + "?network=" + + chain, + ); const hashOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_getUserOperationReceipt', + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", params: [responseValues.result], entryPoint: ENTRY_POINT_ADDRESS, - }) + }), }; let runOnce = true; while (responseValues.result == null || runOnce) { await setTimeout(25000); - await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); runOnce = false; } - if(responseValues.result) { - console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") - } - else { - console.log("\n"+responseValues.error); + if (responseValues.result) { + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues.result.receipt.transactionHash + + "\n", + ); + } else { + console.log("\n" + responseValues.error); } +} else { + console.log("\n" + responseValues.error.message); } -else { - console.log("\n"+responseValues.error.message); -} \ No newline at end of file diff --git a/paymaster-analysis/alchemy/alchemy-erc20.ts b/paymaster-analysis/alchemy/alchemy-erc20.ts index 7ba5a10c..0bef2ec6 100644 --- a/paymaster-analysis/alchemy/alchemy-erc20.ts +++ b/paymaster-analysis/alchemy/alchemy-erc20.ts @@ -4,11 +4,21 @@ import { UserOperation, signUserOperation } from "./utils/userOp"; import { Address, Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; -import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; -import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; +import { + generateTransferCallData, + getERC20Decimals, + getERC20Balance, + mintERC20Token, +} from "./utils/erc20"; import { setTimeout } from "timers/promises"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; @@ -23,13 +33,13 @@ const erc20TokenAddress = process.env.ALCHEMY_ERC20_TOKEN_CONTRACT; if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Alchemy API key" + "Please replace the `apiKey` env variable with your Alchemy API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -37,31 +47,33 @@ const signer = privateKeyToAccount(privateKey as Hash); console.log("Signer Extracted from Private Key."); let publicClient; -if(chain == "sepolia"){ +if (chain == "sepolia") { publicClient = createPublicClient({ transport: http(rpcURL), chain: sepolia, }); -} -else if(chain == "goerli"){ +} else if (chain == "goerli") { publicClient = createPublicClient({ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); @@ -70,82 +82,111 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -if(chain == "sepolia"){ - console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); -} -else if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "sepolia") { + console.log( + "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, + ); +} else if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // Token Configurations const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); const erc20Amount = BigInt(10 ** erc20Decimals); -let senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); -console.log("\nSafe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); +let senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, +); +console.log( + "\nSafe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), +); // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). -if (senderERC20Balance < erc20Amount ) { +if (senderERC20Balance < erc20Amount) { console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token(erc20TokenAddress, publicClient, signer, senderAddress, erc20Amount); + await mintERC20Token( + erc20TokenAddress, + publicClient, + signer, + senderAddress, + erc20Amount, + ); while (senderERC20Balance < erc20Amount) { await setTimeout(15000); - senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); + senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); } - console.log("\nUpdated Safe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); + console.log( + "\nUpdated Safe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), + ); } const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: erc20TokenAddress, - data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", }; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); const gasOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'alchemy_requestGasAndPaymasterAndData', + jsonrpc: "2.0", + method: "alchemy_requestGasAndPaymasterAndData", params: [ { policyId: policyID, @@ -153,59 +194,65 @@ const gasOptions = { dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData - } - } - ] - }) + callData: sponsoredUserOperation.callData, + }, + }, + ], + }), }; let responseValues; -if(chain == "sepolia"){ +if (chain == "sepolia") { // TODO This and other places and files can use ALCHEMY_RPC_URL - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -console.log("\nReceived Paymaster Data from Alchemy.") +console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.verificationGasLimit = + responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = + responseValues.result.paymasterAndData; sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = + responseValues.result.maxPriorityFeePerGas; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); const options = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_sendUserOperation', + jsonrpc: "2.0", + method: "eth_sendUserOperation", params: [ { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, callData: sponsoredUserOperation.callData, callGasLimit: sponsoredUserOperation.callGasLimit, @@ -214,64 +261,75 @@ const options = { maxFeePerGas: sponsoredUserOperation.maxFeePerGas, maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData + paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS - ] - }) + ENTRY_POINT_ADDRESS, + ], + }), }; -if(chain == "sepolia"){ - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { +if (chain == "sepolia") { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -if(responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!") - console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain+"\n"); +if (responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!"); + console.log( + "UserOp Link: https://jiffyscan.xyz/userOpHash/" + + responseValues.result + + "?network=" + + chain + + "\n", + ); const hashOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_getUserOperationReceipt', + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", params: [responseValues.result], entryPoint: ENTRY_POINT_ADDRESS, - }) + }), }; let runOnce = true; while (responseValues.result == null || runOnce) { await setTimeout(25000); - await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); runOnce = false; } - if(responseValues.result) { - console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") + if (responseValues.result) { + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues.result.receipt.transactionHash + + "\n", + ); + } else { + console.log("\n" + responseValues.error); } - else { - console.log("\n"+responseValues.error); - } -} -else { - console.log("\n"+responseValues.error.message); +} else { + console.log("\n" + responseValues.error.message); } diff --git a/paymaster-analysis/alchemy/alchemy-erc721.ts b/paymaster-analysis/alchemy/alchemy-erc721.ts index 2c840693..6013b5b9 100644 --- a/paymaster-analysis/alchemy/alchemy-erc721.ts +++ b/paymaster-analysis/alchemy/alchemy-erc721.ts @@ -4,11 +4,16 @@ import { UserOperation, signUserOperation } from "./utils/userOp"; import { Address, Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; -import { SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; import { generateMintingCallData } from "../utils/erc721"; import { setTimeout } from "timers/promises"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; @@ -21,16 +26,15 @@ const policyID = process.env.ALCHEMY_GAS_POLICY; const apiKey = process.env.ALCHEMY_API_KEY; const erc721TokenAddress = process.env.ALCHEMY_ERC721_TOKEN_CONTRACT; - if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Alchemy API key" + "Please replace the `apiKey` env variable with your Alchemy API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -38,31 +42,33 @@ const signer = privateKeyToAccount(privateKey as Hash); console.log("Signer Extracted from Private Key."); let publicClient; -if(chain == "sepolia"){ +if (chain == "sepolia") { publicClient = createPublicClient({ transport: http(rpcURL), chain: sepolia, }); -} -else if(chain == "goerli"){ +} else if (chain == "goerli") { publicClient = createPublicClient({ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); @@ -71,62 +77,71 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -if(chain == "sepolia"){ - console.log("Address Link: https://sepolia.etherscan.io/address/"+senderAddress); -} -else if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "sepolia") { + console.log( + "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, + ); +} else if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: erc721TokenAddress, - data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", }; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); const gasOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'alchemy_requestGasAndPaymasterAndData', + jsonrpc: "2.0", + method: "alchemy_requestGasAndPaymasterAndData", params: [ { policyId: policyID, @@ -134,58 +149,64 @@ const gasOptions = { dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData - } - } - ] - }) + callData: sponsoredUserOperation.callData, + }, + }, + ], + }), }; let responseValues; -if(chain == "sepolia"){ - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, gasOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { +if (chain == "sepolia") { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -console.log("\nReceived Paymaster Data from Alchemy.") +console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; +sponsoredUserOperation.preVerificationGas = + responseValues.result.preVerificationGas; sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = responseValues.result.paymasterAndData; +sponsoredUserOperation.verificationGasLimit = + responseValues.result.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = + responseValues.result.paymasterAndData; sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = responseValues.result.maxPriorityFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = + responseValues.result.maxPriorityFeePerGas; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.") +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); const options = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_sendUserOperation', + jsonrpc: "2.0", + method: "eth_sendUserOperation", params: [ { sender: sponsoredUserOperation.sender, - nonce: "0x"+sponsoredUserOperation.nonce.toString(16), + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), initCode: sponsoredUserOperation.initCode, callData: sponsoredUserOperation.callData, callGasLimit: sponsoredUserOperation.callGasLimit, @@ -194,64 +215,75 @@ const options = { maxFeePerGas: sponsoredUserOperation.maxFeePerGas, maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData + paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS - ] - }) + ENTRY_POINT_ADDRESS, + ], + }), }; -if(chain == "sepolia"){ - await fetch('https://eth-sepolia.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else if(chain == "goerli"){ - await fetch('https://eth-goerli.g.alchemy.com/v2/'+apiKey, options) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); -} -else { +if (chain == "sepolia") { + await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else if (chain == "goerli") { + await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } -if(responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!") - console.log("UserOp Link: https://jiffyscan.xyz/userOpHash/"+responseValues.result+"?network="+chain+"\n"); +if (responseValues.result) { + console.log("\nSafe Account Creation User Operation Successfully Created!"); + console.log( + "UserOp Link: https://jiffyscan.xyz/userOpHash/" + + responseValues.result + + "?network=" + + chain + + "\n", + ); const hashOptions = { - method: 'POST', - headers: {accept: 'application/json', 'content-type': 'application/json'}, + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, - jsonrpc: '2.0', - method: 'eth_getUserOperationReceipt', + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", params: [responseValues.result], entryPoint: ENTRY_POINT_ADDRESS, - }) + }), }; let runOnce = true; while (responseValues.result == null || runOnce) { await setTimeout(25000); - await fetch('https://eth-'+chain+'.g.alchemy.com/v2/'+apiKey, hashOptions) - .then(response => response.json()) - .then(response => responseValues = response) - .catch(err => console.error(err)); + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); runOnce = false; } - if(responseValues.result) { - console.log("\nTransaction Link: https://"+chain+".etherscan.io/tx/"+responseValues.result.receipt.transactionHash+"\n") - } - else { - console.log("\n"+responseValues.error); + if (responseValues.result) { + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues.result.receipt.transactionHash + + "\n", + ); + } else { + console.log("\n" + responseValues.error); } +} else { + console.log("\n" + responseValues.error.message); } -else { - console.log("\n"+responseValues.error.message); -} \ No newline at end of file diff --git a/paymaster-analysis/alchemy/utils/erc20.ts b/paymaster-analysis/alchemy/utils/erc20.ts index 4d2f4780..eb3291b8 100644 --- a/paymaster-analysis/alchemy/utils/erc20.ts +++ b/paymaster-analysis/alchemy/utils/erc20.ts @@ -1,8 +1,14 @@ import dotenv from "dotenv"; -import { http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount } from "viem"; +import { + http, + Address, + encodeFunctionData, + createWalletClient, + PrivateKeyAccount, +} from "viem"; import { goerli, sepolia } from "viem/chains"; -dotenv.config() +dotenv.config(); const rpcURL = process.env.ALCHEMY_RPC_URL; const chain = process.env.ALCHEMY_CHAIN; @@ -51,7 +57,10 @@ export const generateTransferCallData = (to: Address, value: bigint) => { return transferData; }; -export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: any) => { +export const getERC20Decimals = async ( + erc20TokenAddress: string, + publicClient: any, +) => { const erc20Decimals = await publicClient.readContract({ abi: [ { @@ -67,9 +76,13 @@ export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: }); return erc20Decimals; -} +}; -export const getERC20Balance = async (erc20TokenAddress: string, publicClient: any, owner: string) => { +export const getERC20Balance = async ( + erc20TokenAddress: string, + publicClient: any, + owner: string, +) => { const senderERC20Balance = await publicClient.readContract({ abi: [ { @@ -86,43 +99,50 @@ export const getERC20Balance = async (erc20TokenAddress: string, publicClient: a }); return senderERC20Balance; -} +}; -export const mintERC20Token = async (erc20TokenAddress: string, publicClient: any, signer: PrivateKeyAccount, to: string, amount: number) => { +export const mintERC20Token = async ( + erc20TokenAddress: string, + publicClient: any, + signer: PrivateKeyAccount, + to: string, + amount: number, +) => { let walletClient; - if(chain == "sepolia"){ + if (chain == "sepolia") { walletClient = createWalletClient({ account: signer, chain: sepolia, - transport: http(rpcURL) - }) - } - else if(chain == "goerli"){ + transport: http(rpcURL), + }); + } else if (chain == "goerli") { walletClient = createWalletClient({ account: signer, chain: goerli, - transport: http(rpcURL) - }) - } - else { + transport: http(rpcURL), + }); + } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const { request } = await publicClient.simulateContract({ address: erc20TokenAddress, abi: [ { - inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256"}], + inputs: [ + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, + ], name: "mint", outputs: [], type: "function", stateMutability: "public", }, ], - functionName: 'mint', + functionName: "mint", args: [to, amount], - signer - }) - await walletClient.writeContract(request) -} \ No newline at end of file + signer, + }); + await walletClient.writeContract(request); +}; diff --git a/paymaster-analysis/alchemy/utils/safe.ts b/paymaster-analysis/alchemy/utils/safe.ts index 9edaf734..a767d65d 100644 --- a/paymaster-analysis/alchemy/utils/safe.ts +++ b/paymaster-analysis/alchemy/utils/safe.ts @@ -161,11 +161,11 @@ export const getAccountInitCode = async ({ }): Promise => { if (!owner) throw new Error("Owner account not found"); const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - multiSendAddress, - }); + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, + }); const initCodeCallData = encodeFunctionData({ abi: [ @@ -263,7 +263,7 @@ export const encodeCallData = (params: { export const getAccountAddress = async < TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + TChain extends Chain | undefined = Chain | undefined, >({ client, owner, @@ -305,21 +305,21 @@ export const getAccountAddress = async < const deploymentCode = encodePacked( ["bytes", "uint256"], - [proxyCreationCode, hexToBigInt(safeSingletonAddress)] + [proxyCreationCode, hexToBigInt(safeSingletonAddress)], ); const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - multiSendAddress, - }); + owner, + addModuleLibAddress, + safe4337ModuleAddress, + multiSendAddress, + }); const salt = keccak256( encodePacked( ["bytes32", "uint256"], - [keccak256(encodePacked(["bytes"], [initializer])), saltNonce] - ) + [keccak256(encodePacked(["bytes"], [initializer])), saltNonce], + ), ); return getContractAddress({ diff --git a/paymaster-analysis/alchemy/utils/userOp.ts b/paymaster-analysis/alchemy/utils/userOp.ts index a5454e1d..add40790 100644 --- a/paymaster-analysis/alchemy/utils/userOp.ts +++ b/paymaster-analysis/alchemy/utils/userOp.ts @@ -1,61 +1,65 @@ import dotenv from "dotenv"; -import type { Address } from "abitype" -import type { Hex, PrivateKeyAccount } from "viem" +import type { Address } from "abitype"; +import type { Hex, PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; -dotenv.config() +dotenv.config(); const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const chainID = Number(process.env.ALCHEMY_CHAIN_ID); const safeVersion = process.env.SAFE_VERSION; export type UserOperation = { - sender: Address - nonce: bigint - initCode: Hex - callData: Hex - callGasLimit: bigint - verificationGasLimit: bigint - preVerificationGas: bigint - maxFeePerGas: bigint - maxPriorityFeePerGas: bigint - paymasterAndData: Hex - signature: Hex -} + sender: Address; + nonce: bigint; + initCode: Hex; + callData: Hex; + callGasLimit: bigint; + verificationGasLimit: bigint; + preVerificationGas: bigint; + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + paymasterAndData: Hex; + signature: Hex; +}; -export const signUserOperation = async (userOperation: UserOperation, signer: PrivateKeyAccount) => { - const signatures = [ - { - signer: signer.address, - data: await signer.signTypedData({ - domain: { - chainId: chainID, - verifyingContract: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - }, - types: EIP712_SAFE_OPERATION_TYPE, - primaryType: "SafeOp", - message: { - safe: userOperation.sender, - callData: userOperation.callData, - nonce: userOperation.nonce, - preVerificationGas: userOperation.preVerificationGas, - verificationGasLimit: userOperation.verificationGasLimit, - callGasLimit: userOperation.callGasLimit, - maxFeePerGas: userOperation.maxFeePerGas, - maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - entryPoint: ENTRY_POINT_ADDRESS, - }, - }), - }, - ]; - - signatures.sort((left, right) => - left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()) - ); - - let signatureBytes: Address = "0x"; - for (const sig of signatures) { - signatureBytes += sig.data.slice(2); - } - - return signatureBytes; - }; \ No newline at end of file +export const signUserOperation = async ( + userOperation: UserOperation, + signer: PrivateKeyAccount, +) => { + const signatures = [ + { + signer: signer.address, + data: await signer.signTypedData({ + domain: { + chainId: chainID, + verifyingContract: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + }, + types: EIP712_SAFE_OPERATION_TYPE, + primaryType: "SafeOp", + message: { + safe: userOperation.sender, + callData: userOperation.callData, + nonce: userOperation.nonce, + preVerificationGas: userOperation.preVerificationGas, + verificationGasLimit: userOperation.verificationGasLimit, + callGasLimit: userOperation.callGasLimit, + maxFeePerGas: userOperation.maxFeePerGas, + maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + entryPoint: ENTRY_POINT_ADDRESS, + }, + }), + }, + ]; + + signatures.sort((left, right) => + left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()), + ); + + let signatureBytes: Address = "0x"; + for (const sig of signatures) { + signatureBytes += sig.data.slice(2); + } + + return signatureBytes; +}; diff --git a/paymaster-analysis/package-lock.json b/paymaster-analysis/package-lock.json index 7205c2bd..da688681 100644 --- a/paymaster-analysis/package-lock.json +++ b/paymaster-analysis/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@types/node": "^20.10.2", + "prettier": "^3.1.0", "tsx": "^3.13.0" } }, @@ -1525,6 +1526,21 @@ "viem": "^1.14.0" } }, + "node_modules/prettier": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", diff --git a/paymaster-analysis/package.json b/paymaster-analysis/package.json index dee4edd3..69c89ac6 100644 --- a/paymaster-analysis/package.json +++ b/paymaster-analysis/package.json @@ -11,7 +11,8 @@ "pimlico:erc721": "tsx ./pimlico/pimlico-erc721.ts", "alchemy:account": "tsx ./alchemy/alchemy-account.ts", "alchemy:erc20": "tsx ./alchemy/alchemy-erc20.ts", - "alchemy:erc721": "tsx ./alchemy/alchemy-erc721.ts" + "alchemy:erc721": "tsx ./alchemy/alchemy-erc721.ts", + "fmt": "prettier --ignore-path .gitignore --write ." }, "repository": { "type": "git", @@ -36,6 +37,7 @@ }, "devDependencies": { "@types/node": "^20.10.2", + "prettier": "^3.1.0", "tsx": "^3.13.0" } } diff --git a/paymaster-analysis/pimlico/pimlico-account.ts b/paymaster-analysis/pimlico/pimlico-account.ts index 43a8b185..7e32b3ce 100644 --- a/paymaster-analysis/pimlico/pimlico-account.ts +++ b/paymaster-analysis/pimlico/pimlico-account.ts @@ -1,16 +1,38 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { + UserOperation, + bundlerActions, + getSenderAddress, +} from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { + Address, + Hash, + createClient, + createPublicClient, + http, + toHex, +} from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; -import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { + EIP712_SAFE_OPERATION_TYPE, + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { + generateTransferCallData, + getERC20Decimals, + getERC20Balance, + mintERC20Token, +} from "./utils/erc20"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; @@ -26,13 +48,13 @@ const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Pimlico API key" + "Please replace the `apiKey` env variable with your Pimlico API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -41,7 +63,7 @@ console.log("Signer Extracted from Private Key."); let bundlerClient; let publicClient; -if(chain == "goerli"){ +if (chain == "goerli") { bundlerClient = createClient({ transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), chain: goerli, @@ -53,19 +75,22 @@ if(chain == "goerli"){ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -76,10 +101,14 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -88,27 +117,44 @@ const senderAddress = await getAccountAddress({ console.log("\nCounterfactual Sender Address Created:", senderAddress); // TODO This and in other files, this might be made easier with chain substituted at the right place. -if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); -console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); - -if(senderUSDCBalance < usdcAmount) { - console.log("\nPlease deposit atleast 1 USDC Token for paying the Paymaster."); +let senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, +); +console.log( + "\nSafe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), +); + +if (senderUSDCBalance < usdcAmount) { + console.log( + "\nPlease deposit atleast 1 USDC Token for paying the Paymaster.", + ); while (senderUSDCBalance < usdcAmount) { await setTimeout(30000); - senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, + ); } - console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + console.log( + "\nUpdated Safe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), + ); } const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); @@ -117,7 +163,7 @@ const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); @@ -143,8 +189,11 @@ if (contractCode) { paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field signature: "0x", }; - - sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); - + + sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + ); + await submitUserOperation(sponsoredUserOperation, bundlerClient); -} \ No newline at end of file +} diff --git a/paymaster-analysis/pimlico/pimlico-erc20.ts b/paymaster-analysis/pimlico/pimlico-erc20.ts index 166c54dc..6799146e 100644 --- a/paymaster-analysis/pimlico/pimlico-erc20.ts +++ b/paymaster-analysis/pimlico/pimlico-erc20.ts @@ -1,16 +1,38 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { + UserOperation, + bundlerActions, + getSenderAddress, +} from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { + Address, + Hash, + createClient, + createPublicClient, + http, + toHex, +} from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; -import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { + EIP712_SAFE_OPERATION_TYPE, + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { + generateTransferCallData, + getERC20Decimals, + getERC20Balance, + mintERC20Token, +} from "./utils/erc20"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; @@ -27,13 +49,13 @@ const erc20TokenAddress = process.env.PIMLICO_ERC20_TOKEN_CONTRACT; if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Pimlico API key" + "Please replace the `apiKey` env variable with your Pimlico API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -42,7 +64,7 @@ console.log("Signer Extracted from Private Key."); let bundlerClient; let publicClient; -if(chain == "goerli"){ +if (chain == "goerli") { bundlerClient = createClient({ transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), chain: goerli, @@ -54,19 +76,22 @@ if(chain == "goerli"){ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -77,10 +102,14 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -88,46 +117,83 @@ const senderAddress = await getAccountAddress({ }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); -console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); - -if(senderUSDCBalance < BigInt(2) * usdcAmount) { - console.log("\nPlease deposit atleast 2 USDC Token for paying the Paymaster."); +let senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, +); +console.log( + "\nSafe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), +); + +if (senderUSDCBalance < BigInt(2) * usdcAmount) { + console.log( + "\nPlease deposit atleast 2 USDC Token for paying the Paymaster.", + ); while (senderUSDCBalance < BigInt(2) * usdcAmount) { await setTimeout(30000); - senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, + ); } - console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + console.log( + "\nUpdated Safe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), + ); } // Token Configurations const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); const erc20Amount = BigInt(10 ** erc20Decimals); -let senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); -console.log("\nSafe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); +let senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, +); +console.log( + "\nSafe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), +); // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). -if (senderERC20Balance < erc20Amount ) { +if (senderERC20Balance < erc20Amount) { console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token(erc20TokenAddress, publicClient, signer, senderAddress, erc20Amount); + await mintERC20Token( + erc20TokenAddress, + publicClient, + signer, + senderAddress, + erc20Amount, + ); while (senderERC20Balance < erc20Amount) { await setTimeout(15000); - senderERC20Balance = await getERC20Balance(erc20TokenAddress, publicClient, senderAddress); + senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); } - console.log("\nUpdated Safe Wallet ERC20 Balance:", Number(senderERC20Balance / erc20Amount)); + console.log( + "\nUpdated Safe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), + ); } const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); @@ -136,17 +202,17 @@ const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); if (contractCode) { console.log( - "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n" + "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n", ); } else { console.log( - "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n" + "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n", ); } @@ -169,6 +235,9 @@ const sponsoredUserOperation: UserOperation = { signature: "0x", }; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); - +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); + await submitUserOperation(sponsoredUserOperation, bundlerClient); diff --git a/paymaster-analysis/pimlico/pimlico-erc721.ts b/paymaster-analysis/pimlico/pimlico-erc721.ts index c793c73d..704fb465 100644 --- a/paymaster-analysis/pimlico/pimlico-erc721.ts +++ b/paymaster-analysis/pimlico/pimlico-erc721.ts @@ -1,17 +1,39 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { + UserOperation, + bundlerActions, + getSenderAddress, +} from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Address, Hash, createClient, createPublicClient, http, toHex } from "viem"; +import { + Address, + Hash, + createClient, + createPublicClient, + http, + toHex, +} from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; -import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, getAccountInitCode } from "./utils/safe"; +import { + EIP712_SAFE_OPERATION_TYPE, + SAFE_ADDRESSES_MAP, + encodeCallData, + getAccountAddress, + getAccountInitCode, +} from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { generateTransferCallData, getERC20Decimals, getERC20Balance, mintERC20Token } from "./utils/erc20"; +import { + generateTransferCallData, + getERC20Decimals, + getERC20Balance, + mintERC20Token, +} from "./utils/erc20"; import { generateMintingCallData } from "../utils/erc721"; -dotenv.config() +dotenv.config(); const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; @@ -28,13 +50,13 @@ const erc721TokenAddress = process.env.PIMLICO_ERC721_TOKEN_CONTRACT; if (apiKey === undefined) { throw new Error( - "Please replace the `apiKey` env variable with your Pimlico API key" + "Please replace the `apiKey` env variable with your Pimlico API key", ); } -if(!privateKey){ +if (!privateKey) { throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key." + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", ); } @@ -43,7 +65,7 @@ console.log("Signer Extracted from Private Key."); let bundlerClient; let publicClient; -if(chain == "goerli"){ +if (chain == "goerli") { bundlerClient = createClient({ transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), chain: goerli, @@ -55,19 +77,22 @@ if(chain == "goerli"){ transport: http(rpcURL), chain: goerli, }); -} -else { +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -78,10 +103,14 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, erc20TokenAddress: usdcTokenAddress, multiSendAddress, @@ -89,28 +118,45 @@ const senderAddress = await getAccountAddress({ }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -if(chain == "goerli"){ - console.log("Address Link: https://goerli.etherscan.io/address/"+senderAddress); -} -else { +if (chain == "goerli") { + console.log( + "Address Link: https://goerli.etherscan.io/address/" + senderAddress, + ); +} else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); -console.log("\nSafe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); - -if(senderUSDCBalance < BigInt(2) * usdcAmount) { - console.log("\nPlease deposit atleast 2 USDC Token for paying the Paymaster."); +let senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, +); +console.log( + "\nSafe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), +); + +if (senderUSDCBalance < BigInt(2) * usdcAmount) { + console.log( + "\nPlease deposit atleast 2 USDC Token for paying the Paymaster.", + ); while (senderUSDCBalance < BigInt(2) * usdcAmount) { await setTimeout(30000); - senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress); + senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, + publicClient, + senderAddress, + ); } - console.log("\nUpdated Safe Wallet USDC Balance:", Number(senderUSDCBalance / usdcAmount)); + console.log( + "\nUpdated Safe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), + ); } const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); @@ -119,17 +165,17 @@ const newNonce = await getAccountNonce(publicClient, { entryPoint: ENTRY_POINT_ADDRESS, sender: senderAddress, }); -console.log("\nNonce for the sender received from EntryPoint.") +console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); if (contractCode) { console.log( - "The Safe is already deployed. Minting 1 ERC721 Token to the Safe." + "The Safe is already deployed. Minting 1 ERC721 Token to the Safe.", ); } else { console.log( - "Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx" + "Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx", ); } @@ -152,6 +198,9 @@ const sponsoredUserOperation: UserOperation = { signature: "0x", }; -sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer); - +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, +); + await submitUserOperation(sponsoredUserOperation, bundlerClient); diff --git a/paymaster-analysis/pimlico/utils/erc20.ts b/paymaster-analysis/pimlico/utils/erc20.ts index a168df6e..99049a95 100644 --- a/paymaster-analysis/pimlico/utils/erc20.ts +++ b/paymaster-analysis/pimlico/utils/erc20.ts @@ -1,8 +1,14 @@ import dotenv from "dotenv"; -import { http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount } from "viem"; +import { + http, + Address, + encodeFunctionData, + createWalletClient, + PrivateKeyAccount, +} from "viem"; import { goerli, sepolia } from "viem/chains"; -dotenv.config() +dotenv.config(); const rpcURL = process.env.PIMLICO_RPC_URL; const chain = process.env.PIMLICO_CHAIN; @@ -51,7 +57,10 @@ export const generateTransferCallData = (to: Address, value: bigint) => { return transferData; }; -export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: any) => { +export const getERC20Decimals = async ( + erc20TokenAddress: string, + publicClient: any, +) => { const erc20Decimals = await publicClient.readContract({ abi: [ { @@ -67,9 +76,13 @@ export const getERC20Decimals = async (erc20TokenAddress: string, publicClient: }); return erc20Decimals; -} +}; -export const getERC20Balance = async (erc20TokenAddress: string, publicClient: any, owner: string) => { +export const getERC20Balance = async ( + erc20TokenAddress: string, + publicClient: any, + owner: string, +) => { const senderERC20Balance = await publicClient.readContract({ abi: [ { @@ -86,43 +99,50 @@ export const getERC20Balance = async (erc20TokenAddress: string, publicClient: a }); return senderERC20Balance; -} +}; -export const mintERC20Token = async (erc20TokenAddress: string, publicClient: any, signer: PrivateKeyAccount, to: string, amount: number) => { +export const mintERC20Token = async ( + erc20TokenAddress: string, + publicClient: any, + signer: PrivateKeyAccount, + to: string, + amount: number, +) => { let walletClient; - if(chain == "sepolia"){ + if (chain == "sepolia") { walletClient = createWalletClient({ account: signer, chain: sepolia, - transport: http(rpcURL) - }) - } - else if(chain == "goerli"){ + transport: http(rpcURL), + }); + } else if (chain == "goerli") { walletClient = createWalletClient({ account: signer, chain: goerli, - transport: http(rpcURL) - }) - } - else { + transport: http(rpcURL), + }); + } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network." - ) + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); } const { request } = await publicClient.simulateContract({ address: erc20TokenAddress, abi: [ { - inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256"}], + inputs: [ + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, + ], name: "mint", outputs: [], type: "function", stateMutability: "public", }, ], - functionName: 'mint', + functionName: "mint", args: [to, amount], - signer - }) - await walletClient.writeContract(request) -} \ No newline at end of file + signer, + }); + await walletClient.writeContract(request); +}; diff --git a/paymaster-analysis/pimlico/utils/safe.ts b/paymaster-analysis/pimlico/utils/safe.ts index 6d5b60cb..5f6adc91 100644 --- a/paymaster-analysis/pimlico/utils/safe.ts +++ b/paymaster-analysis/pimlico/utils/safe.ts @@ -175,13 +175,13 @@ export const getAccountInitCode = async ({ }): Promise => { if (!owner) throw new Error("Owner account not found"); const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - erc20TokenAddress, - multiSendAddress, - paymasterAddress, - }); + owner, + addModuleLibAddress, + safe4337ModuleAddress, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, + }); const initCodeCallData = encodeFunctionData({ abi: [ @@ -279,7 +279,7 @@ export const encodeCallData = (params: { export const getAccountAddress = async < TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + TChain extends Chain | undefined = Chain | undefined, >({ client, owner, @@ -325,23 +325,23 @@ export const getAccountAddress = async < const deploymentCode = encodePacked( ["bytes", "uint256"], - [proxyCreationCode, hexToBigInt(safeSingletonAddress)] + [proxyCreationCode, hexToBigInt(safeSingletonAddress)], ); const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - erc20TokenAddress, - multiSendAddress, - paymasterAddress, - }); + owner, + addModuleLibAddress, + safe4337ModuleAddress, + erc20TokenAddress, + multiSendAddress, + paymasterAddress, + }); const salt = keccak256( encodePacked( ["bytes32", "uint256"], - [keccak256(encodePacked(["bytes"], [initializer])), saltNonce] - ) + [keccak256(encodePacked(["bytes"], [initializer])), saltNonce], + ), ); return getContractAddress({ diff --git a/paymaster-analysis/pimlico/utils/userOps.ts b/paymaster-analysis/pimlico/utils/userOps.ts index 5a5a31ad..3095c077 100644 --- a/paymaster-analysis/pimlico/utils/userOps.ts +++ b/paymaster-analysis/pimlico/utils/userOps.ts @@ -1,67 +1,86 @@ import dotenv from "dotenv"; -import type { Address } from "abitype" -import type { PrivateKeyAccount } from "viem" +import type { Address } from "abitype"; +import type { PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; -import { UserOperation, bundlerActions, getSenderAddress } from "permissionless"; +import { + UserOperation, + bundlerActions, + getSenderAddress, +} from "permissionless"; -dotenv.config() +dotenv.config(); const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const chain = process.env.PIMLICO_CHAIN; const chainID = Number(process.env.PIMLICO_CHAIN_ID); const safeVersion = process.env.SAFE_VERSION; -export const submitUserOperation = async (userOperation: UserOperation, bundlerClient: any) => { +export const submitUserOperation = async ( + userOperation: UserOperation, + bundlerClient: any, +) => { const userOperationHash = await bundlerClient.sendUserOperation({ userOperation, entryPoint: ENTRY_POINT_ADDRESS, }); console.log(`UserOperation submitted. Hash: ${userOperationHash}`); - console.log(`UserOp Link: https://jiffyscan.xyz/userOpHash/${userOperationHash}?network=`+chain+"\n"); + console.log( + `UserOp Link: https://jiffyscan.xyz/userOpHash/${userOperationHash}?network=` + + chain + + "\n", + ); console.log("Querying for receipts..."); const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOperationHash, }); console.log( - `Receipt found!\nTransaction hash: ${receipt.receipt.transactionHash}` + `Receipt found!\nTransaction hash: ${receipt.receipt.transactionHash}`, + ); + console.log( + `Transaction Link: https://` + + chain + + `.etherscan.io/tx/${receipt.receipt.transactionHash}`, ); - console.log(`Transaction Link: https://`+chain+`.etherscan.io/tx/${receipt.receipt.transactionHash}`); }; -export const signUserOperation = async (userOperation: UserOperation, signer: PrivateKeyAccount) => { - const signatures = [ - { - signer: signer.address, - data: await signer.signTypedData({ - domain: { - chainId: chainID, - verifyingContract: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - }, - types: EIP712_SAFE_OPERATION_TYPE, - primaryType: "SafeOp", - message: { - safe: userOperation.sender, - callData: userOperation.callData, - nonce: userOperation.nonce, - preVerificationGas: userOperation.preVerificationGas, - verificationGasLimit: userOperation.verificationGasLimit, - callGasLimit: userOperation.callGasLimit, - maxFeePerGas: userOperation.maxFeePerGas, - maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - entryPoint: ENTRY_POINT_ADDRESS, - }, - }), - }, - ]; - - signatures.sort((left, right) => - left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()) - ); - - let signatureBytes: Address = "0x"; - for (const sig of signatures) { - signatureBytes += sig.data.slice(2); - } - - return signatureBytes; - }; \ No newline at end of file +export const signUserOperation = async ( + userOperation: UserOperation, + signer: PrivateKeyAccount, +) => { + const signatures = [ + { + signer: signer.address, + data: await signer.signTypedData({ + domain: { + chainId: chainID, + verifyingContract: + SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + }, + types: EIP712_SAFE_OPERATION_TYPE, + primaryType: "SafeOp", + message: { + safe: userOperation.sender, + callData: userOperation.callData, + nonce: userOperation.nonce, + preVerificationGas: userOperation.preVerificationGas, + verificationGasLimit: userOperation.verificationGasLimit, + callGasLimit: userOperation.callGasLimit, + maxFeePerGas: userOperation.maxFeePerGas, + maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + entryPoint: ENTRY_POINT_ADDRESS, + }, + }), + }, + ]; + + signatures.sort((left, right) => + left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()), + ); + + let signatureBytes: Address = "0x"; + for (const sig of signatures) { + signatureBytes += sig.data.slice(2); + } + + return signatureBytes; +}; diff --git a/paymaster-analysis/tsconfig.json b/paymaster-analysis/tsconfig.json index aac9d66a..cd04af0b 100644 --- a/paymaster-analysis/tsconfig.json +++ b/paymaster-analysis/tsconfig.json @@ -9,7 +9,7 @@ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ @@ -22,9 +22,9 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ + "module": "ES2022" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "Node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ @@ -71,11 +71,11 @@ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -97,5 +97,5 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, -} \ No newline at end of file + } +} diff --git a/paymaster-analysis/utils/erc721.ts b/paymaster-analysis/utils/erc721.ts index b7dcaba2..297fd911 100644 --- a/paymaster-analysis/utils/erc721.ts +++ b/paymaster-analysis/utils/erc721.ts @@ -4,9 +4,7 @@ export const generateMintingCallData = (to: Address) => { const transferData = encodeFunctionData({ abi: [ { - inputs: [ - { name: "_to", type: "address" }, - ], + inputs: [{ name: "_to", type: "address" }], name: "safeMint", outputs: [], payable: false, diff --git a/paymaster-analysis/utils/multisend.ts b/paymaster-analysis/utils/multisend.ts index 6b0f4d7b..bb8f6c63 100644 --- a/paymaster-analysis/utils/multisend.ts +++ b/paymaster-analysis/utils/multisend.ts @@ -16,7 +16,7 @@ const encodeInternalTransaction = (tx: InternalTx): string => { tx.value, BigInt(tx.data.slice(2).length / 2), tx.data, - ] + ], ); return encoded.slice(2); }; From bece65c2ff12b6bebbf847ffac1cae3a8cccdce8 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 17:43:34 +0530 Subject: [PATCH 21/60] Faucet address added --- paymaster-analysis/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paymaster-analysis/.env.example b/paymaster-analysis/.env.example index c74adb2e..a27cb69d 100644 --- a/paymaster-analysis/.env.example +++ b/paymaster-analysis/.env.example @@ -11,7 +11,7 @@ PIMLICO_API_KEY = "" # https://dashboard.pimlico.io/apikeys PIMLICO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" PIMLICO_ERC20_PAYMASTER_ADDRESS = "0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009" -PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" +PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" # https://usdcfaucet.com/ # Make sure all nonce are unique for it to deploy account when run initially. PIMLICO_ACCOUNT_NONCE = "1" PIMLICO_ERC20_NONCE = "2" From 3e10d7bee3dd87ec72830185e8a809901fb48e43 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 17:44:00 +0530 Subject: [PATCH 22/60] General cleanup --- paymaster-analysis/alchemy/alchemy-account.ts | 2 +- paymaster-analysis/alchemy/alchemy-erc20.ts | 2 +- paymaster-analysis/alchemy/alchemy-erc721.ts | 2 +- paymaster-analysis/alchemy/utils/safe.ts | 2 - paymaster-analysis/pimlico/pimlico-account.ts | 6 -- paymaster-analysis/pimlico/pimlico-erc20.ts | 4 - paymaster-analysis/pimlico/pimlico-erc721.ts | 6 -- paymaster-analysis/pimlico/utils/safe.ts | 1 - paymaster-analysis/pimlico/utils/userOps.ts | 2 - paymaster-analysis/tsconfig.json | 85 ------------------- 10 files changed, 3 insertions(+), 109 deletions(-) diff --git a/paymaster-analysis/alchemy/alchemy-account.ts b/paymaster-analysis/alchemy/alchemy-account.ts index 646db448..8e122e9f 100644 --- a/paymaster-analysis/alchemy/alchemy-account.ts +++ b/paymaster-analysis/alchemy/alchemy-account.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "./utils/userOp"; -import { Address, Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { diff --git a/paymaster-analysis/alchemy/alchemy-erc20.ts b/paymaster-analysis/alchemy/alchemy-erc20.ts index 0bef2ec6..47545604 100644 --- a/paymaster-analysis/alchemy/alchemy-erc20.ts +++ b/paymaster-analysis/alchemy/alchemy-erc20.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "./utils/userOp"; -import { Address, Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { diff --git a/paymaster-analysis/alchemy/alchemy-erc721.ts b/paymaster-analysis/alchemy/alchemy-erc721.ts index 6013b5b9..41d12d14 100644 --- a/paymaster-analysis/alchemy/alchemy-erc721.ts +++ b/paymaster-analysis/alchemy/alchemy-erc721.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "./utils/userOp"; -import { Address, Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { diff --git a/paymaster-analysis/alchemy/utils/safe.ts b/paymaster-analysis/alchemy/utils/safe.ts index a767d65d..36b961d5 100644 --- a/paymaster-analysis/alchemy/utils/safe.ts +++ b/paymaster-analysis/alchemy/utils/safe.ts @@ -1,7 +1,6 @@ import { Address, Chain, - Client, Hex, PublicClient, Transport, @@ -14,7 +13,6 @@ import { zeroAddress, } from "viem"; import { InternalTx, encodeMultiSend } from "../../utils/multisend"; -import { generateApproveCallData } from "./erc20"; export const SAFE_ADDRESSES_MAP = { "1.4.1": { diff --git a/paymaster-analysis/pimlico/pimlico-account.ts b/paymaster-analysis/pimlico/pimlico-account.ts index 7e32b3ce..ad237500 100644 --- a/paymaster-analysis/pimlico/pimlico-account.ts +++ b/paymaster-analysis/pimlico/pimlico-account.ts @@ -3,21 +3,17 @@ import { getAccountNonce } from "permissionless"; import { UserOperation, bundlerActions, - getSenderAddress, } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; import { - Address, Hash, createClient, createPublicClient, http, - toHex, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { - EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, @@ -26,10 +22,8 @@ import { import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; import { - generateTransferCallData, getERC20Decimals, getERC20Balance, - mintERC20Token, } from "./utils/erc20"; dotenv.config(); diff --git a/paymaster-analysis/pimlico/pimlico-erc20.ts b/paymaster-analysis/pimlico/pimlico-erc20.ts index 6799146e..70cc8ea3 100644 --- a/paymaster-analysis/pimlico/pimlico-erc20.ts +++ b/paymaster-analysis/pimlico/pimlico-erc20.ts @@ -3,21 +3,17 @@ import { getAccountNonce } from "permissionless"; import { UserOperation, bundlerActions, - getSenderAddress, } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; import { - Address, Hash, createClient, createPublicClient, http, - toHex, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { - EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, diff --git a/paymaster-analysis/pimlico/pimlico-erc721.ts b/paymaster-analysis/pimlico/pimlico-erc721.ts index 704fb465..bbb5545c 100644 --- a/paymaster-analysis/pimlico/pimlico-erc721.ts +++ b/paymaster-analysis/pimlico/pimlico-erc721.ts @@ -3,21 +3,17 @@ import { getAccountNonce } from "permissionless"; import { UserOperation, bundlerActions, - getSenderAddress, } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; import { - Address, Hash, createClient, createPublicClient, http, - toHex, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { - EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP, encodeCallData, getAccountAddress, @@ -26,10 +22,8 @@ import { import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; import { - generateTransferCallData, getERC20Decimals, getERC20Balance, - mintERC20Token, } from "./utils/erc20"; import { generateMintingCallData } from "../utils/erc721"; diff --git a/paymaster-analysis/pimlico/utils/safe.ts b/paymaster-analysis/pimlico/utils/safe.ts index 5f6adc91..b637b07d 100644 --- a/paymaster-analysis/pimlico/utils/safe.ts +++ b/paymaster-analysis/pimlico/utils/safe.ts @@ -1,7 +1,6 @@ import { Address, Chain, - Client, Hex, PublicClient, Transport, diff --git a/paymaster-analysis/pimlico/utils/userOps.ts b/paymaster-analysis/pimlico/utils/userOps.ts index 3095c077..737c930a 100644 --- a/paymaster-analysis/pimlico/utils/userOps.ts +++ b/paymaster-analysis/pimlico/utils/userOps.ts @@ -4,8 +4,6 @@ import type { PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; import { UserOperation, - bundlerActions, - getSenderAddress, } from "permissionless"; dotenv.config(); diff --git a/paymaster-analysis/tsconfig.json b/paymaster-analysis/tsconfig.json index cd04af0b..aa4eb960 100644 --- a/paymaster-analysis/tsconfig.json +++ b/paymaster-analysis/tsconfig.json @@ -1,101 +1,16 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "ES2022" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "Node" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } From 12f050adaa2a2e216cd2969e0dbd768edf8606c9 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 4 Dec 2023 17:44:46 +0530 Subject: [PATCH 23/60] Prettier changes --- paymaster-analysis/pimlico/pimlico-account.ts | 17 +++-------------- paymaster-analysis/pimlico/pimlico-erc20.ts | 12 ++---------- paymaster-analysis/pimlico/pimlico-erc721.ts | 17 +++-------------- paymaster-analysis/pimlico/utils/userOps.ts | 4 +--- 4 files changed, 9 insertions(+), 41 deletions(-) diff --git a/paymaster-analysis/pimlico/pimlico-account.ts b/paymaster-analysis/pimlico/pimlico-account.ts index ad237500..05e80554 100644 --- a/paymaster-analysis/pimlico/pimlico-account.ts +++ b/paymaster-analysis/pimlico/pimlico-account.ts @@ -1,16 +1,8 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { - UserOperation, - bundlerActions, -} from "permissionless"; +import { UserOperation, bundlerActions } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { - Hash, - createClient, - createPublicClient, - http, -} from "viem"; +import { Hash, createClient, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { @@ -21,10 +13,7 @@ import { } from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { - getERC20Decimals, - getERC20Balance, -} from "./utils/erc20"; +import { getERC20Decimals, getERC20Balance } from "./utils/erc20"; dotenv.config(); diff --git a/paymaster-analysis/pimlico/pimlico-erc20.ts b/paymaster-analysis/pimlico/pimlico-erc20.ts index 70cc8ea3..d6a98abe 100644 --- a/paymaster-analysis/pimlico/pimlico-erc20.ts +++ b/paymaster-analysis/pimlico/pimlico-erc20.ts @@ -1,16 +1,8 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { - UserOperation, - bundlerActions, -} from "permissionless"; +import { UserOperation, bundlerActions } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { - Hash, - createClient, - createPublicClient, - http, -} from "viem"; +import { Hash, createClient, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { diff --git a/paymaster-analysis/pimlico/pimlico-erc721.ts b/paymaster-analysis/pimlico/pimlico-erc721.ts index bbb5545c..302223ee 100644 --- a/paymaster-analysis/pimlico/pimlico-erc721.ts +++ b/paymaster-analysis/pimlico/pimlico-erc721.ts @@ -1,16 +1,8 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { - UserOperation, - bundlerActions, -} from "permissionless"; +import { UserOperation, bundlerActions } from "permissionless"; import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { - Hash, - createClient, - createPublicClient, - http, -} from "viem"; +import { Hash, createClient, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli } from "viem/chains"; import { @@ -21,10 +13,7 @@ import { } from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { - getERC20Decimals, - getERC20Balance, -} from "./utils/erc20"; +import { getERC20Decimals, getERC20Balance } from "./utils/erc20"; import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); diff --git a/paymaster-analysis/pimlico/utils/userOps.ts b/paymaster-analysis/pimlico/utils/userOps.ts index 737c930a..606f9a63 100644 --- a/paymaster-analysis/pimlico/utils/userOps.ts +++ b/paymaster-analysis/pimlico/utils/userOps.ts @@ -2,9 +2,7 @@ import dotenv from "dotenv"; import type { Address } from "abitype"; import type { PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; -import { - UserOperation, -} from "permissionless"; +import { UserOperation } from "permissionless"; dotenv.config(); const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; From f1fd9e2fc81d3c2de55d1b592c6c393ec2e2a2f7 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:01:22 +0530 Subject: [PATCH 24/60] Update paymaster-analysis/README.md --- paymaster-analysis/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paymaster-analysis/README.md b/paymaster-analysis/README.md index 0e1e116d..186520d7 100644 --- a/paymaster-analysis/README.md +++ b/paymaster-analysis/README.md @@ -4,7 +4,7 @@ 1. Rename the `.env.example` to `.env`. 2. Fill the required values of `.env`. -3. Based on which paymaster to run, check the `package.json` file to see the script. Further, you can check the `README.md` files in the corresponding paymaster folders to see the individual command. +3. Based on which paymaster to run, check the `package.json` file to see the script. Furthermore, you can check the `README.md` files in the corresponding paymaster folders to see the individual command. NOTE: If you run a paymaster analysis twice or more without changing the salt for Safe Creation, then only the operation will execute through paymaster, rather than Safe Creation and Operation. From 77512f28772a663adbe6f9d4c1abf23de1781a26 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:09:04 +0530 Subject: [PATCH 25/60] Typo removed --- paymaster-analysis/.env.example | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/paymaster-analysis/.env.example b/paymaster-analysis/.env.example index a27cb69d..37d5a098 100644 --- a/paymaster-analysis/.env.example +++ b/paymaster-analysis/.env.example @@ -1,9 +1,7 @@ # Dummy ETH Address Private Key PRIVATE_KEY = "0x..." # Add "0x" to Private Key if not already added. -# Pimlico Values - -# Alchemy Values (At the time of writing this, Pimlico doesn't support Sepolia, so using Goerli.) +# Pimlico Values (At the time of writing this, Pimlico doesn't support Sepolia, so using Goerli.) PIMLICO_CHAIN = "goerli" PIMLICO_CHAIN_ID = "5" PIMLICO_RPC_URL = "https://rpc.ankr.com/eth_goerli" From c88260e64a7c34d7e8938073f1177f84aff2a4c1 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:13:22 +0530 Subject: [PATCH 26/60] Token Operation Values moved in .env --- paymaster-analysis/.env.example | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/paymaster-analysis/.env.example b/paymaster-analysis/.env.example index 37d5a098..93e0194f 100644 --- a/paymaster-analysis/.env.example +++ b/paymaster-analysis/.env.example @@ -14,6 +14,9 @@ PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" # http PIMLICO_ACCOUNT_NONCE = "1" PIMLICO_ERC20_NONCE = "2" PIMLICO_ERC721_NONCE = "3" +# Pimlico Token Operation Values (Based on the chain selected, these values should also change accordingly.) +PIMLICO_ERC20_TOKEN_CONTRACT = "" +PIMLICO_ERC721_TOKEN_CONTRACT = "" # Alchemy Values (If you want to use goerli, use goerli chain id and corresponding API key, Gas Policy, etc.) ALCHEMY_CHAIN = "sepolia" # or "goerli" @@ -27,16 +30,13 @@ ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" ALCHEMY_ACCOUNT_PAYMASTER_NONCE = "4" ALCHEMY_ERC20_PAYMASTER_NONCE = "5" ALCHEMY_ERC721_PAYMASTER_NONCE = "6" +# Alchemy Token Operation Values (Based on the chain selected, these values should also change accordingly.) +ALCHEMY_ERC20_TOKEN_CONTRACT = "" +ALCHEMY_ERC721_TOKEN_CONTRACT = "" # Safe Values SAFE_VERSION = "1.4.1" -# Token Operation Values (Based on the chain selected, these values should also change accordingly.) -PIMLICO_ERC20_TOKEN_CONTRACT = "" -PIMLICO_ERC721_TOKEN_CONTRACT = "" -ALCHEMY_ERC20_TOKEN_CONTRACT = "" -ALCHEMY_ERC721_TOKEN_CONTRACT = "" - # Publicly Mintable ERC20 and ERC721 Token for easier testing. Can be used for Token Operation Values. # ERC20 Token Goerli: https://goerli.etherscan.io/token/0x3Aa475E24F7c076632467444b593E332Bc3355F9 # ERC721 Token Goerli: https://goerli.etherscan.io/token/0xf190c05f968c53962604454ebfa380e5cda600d7 From f3ea00bbd21f42c0fc4f9e8f279f93faced3716b Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:17:47 +0530 Subject: [PATCH 27/60] Made package as private --- paymaster-analysis/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paymaster-analysis/package.json b/paymaster-analysis/package.json index 69c89ac6..97df0f43 100644 --- a/paymaster-analysis/package.json +++ b/paymaster-analysis/package.json @@ -1,10 +1,11 @@ { - "name": "@safe-global/paymaster-analysis", + "name": "@safe-global/4337-gas-metering", "version": "1.0.0", "description": "Account Abstraction Paymaster Analysis for Safe with 4337 Module", "homepage": "https://github.com/safe-global/safe-modules/paymaster-analysis", "license": "GPL-3.0", "type": "module", + "private": true, "scripts": { "pimlico:account": "tsx ./pimlico/pimlico-account.ts", "pimlico:erc20": "tsx ./pimlico/pimlico-erc20.ts", From d8604bf9c41ee8570bf57b71d5c42ff048185dff Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:34:58 +0530 Subject: [PATCH 28/60] Renamed to 4337-gas-metering --- {paymaster-analysis => 4337-gas-metering}/.env.example | 0 {paymaster-analysis => 4337-gas-metering}/.gitignore | 0 {paymaster-analysis => 4337-gas-metering}/README.md | 0 {paymaster-analysis => 4337-gas-metering}/alchemy/README.md | 0 .../alchemy/alchemy-account.ts | 0 .../alchemy/alchemy-erc20.ts | 0 .../alchemy/alchemy-erc721.ts | 0 .../alchemy/utils/erc20.ts | 0 .../alchemy/utils/safe.ts | 0 .../alchemy/utils/userOp.ts | 0 {paymaster-analysis => 4337-gas-metering}/package-lock.json | 4 ++-- {paymaster-analysis => 4337-gas-metering}/package.json | 4 ++-- {paymaster-analysis => 4337-gas-metering}/pimlico/README.md | 0 .../pimlico/pimlico-account.ts | 0 .../pimlico/pimlico-erc20.ts | 0 .../pimlico/pimlico-erc721.ts | 0 .../pimlico/utils/erc20.ts | 0 .../pimlico/utils/safe.ts | 0 .../pimlico/utils/userOps.ts | 0 {paymaster-analysis => 4337-gas-metering}/tsconfig.json | 0 {paymaster-analysis => 4337-gas-metering}/utils/erc721.ts | 0 {paymaster-analysis => 4337-gas-metering}/utils/multisend.ts | 0 22 files changed, 4 insertions(+), 4 deletions(-) rename {paymaster-analysis => 4337-gas-metering}/.env.example (100%) rename {paymaster-analysis => 4337-gas-metering}/.gitignore (100%) rename {paymaster-analysis => 4337-gas-metering}/README.md (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/README.md (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/alchemy-account.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/alchemy-erc20.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/alchemy-erc721.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/utils/erc20.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/utils/safe.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/alchemy/utils/userOp.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/package-lock.json (99%) rename {paymaster-analysis => 4337-gas-metering}/package.json (87%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/README.md (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/pimlico-account.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/pimlico-erc20.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/pimlico-erc721.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/utils/erc20.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/utils/safe.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/pimlico/utils/userOps.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/tsconfig.json (100%) rename {paymaster-analysis => 4337-gas-metering}/utils/erc721.ts (100%) rename {paymaster-analysis => 4337-gas-metering}/utils/multisend.ts (100%) diff --git a/paymaster-analysis/.env.example b/4337-gas-metering/.env.example similarity index 100% rename from paymaster-analysis/.env.example rename to 4337-gas-metering/.env.example diff --git a/paymaster-analysis/.gitignore b/4337-gas-metering/.gitignore similarity index 100% rename from paymaster-analysis/.gitignore rename to 4337-gas-metering/.gitignore diff --git a/paymaster-analysis/README.md b/4337-gas-metering/README.md similarity index 100% rename from paymaster-analysis/README.md rename to 4337-gas-metering/README.md diff --git a/paymaster-analysis/alchemy/README.md b/4337-gas-metering/alchemy/README.md similarity index 100% rename from paymaster-analysis/alchemy/README.md rename to 4337-gas-metering/alchemy/README.md diff --git a/paymaster-analysis/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts similarity index 100% rename from paymaster-analysis/alchemy/alchemy-account.ts rename to 4337-gas-metering/alchemy/alchemy-account.ts diff --git a/paymaster-analysis/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts similarity index 100% rename from paymaster-analysis/alchemy/alchemy-erc20.ts rename to 4337-gas-metering/alchemy/alchemy-erc20.ts diff --git a/paymaster-analysis/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts similarity index 100% rename from paymaster-analysis/alchemy/alchemy-erc721.ts rename to 4337-gas-metering/alchemy/alchemy-erc721.ts diff --git a/paymaster-analysis/alchemy/utils/erc20.ts b/4337-gas-metering/alchemy/utils/erc20.ts similarity index 100% rename from paymaster-analysis/alchemy/utils/erc20.ts rename to 4337-gas-metering/alchemy/utils/erc20.ts diff --git a/paymaster-analysis/alchemy/utils/safe.ts b/4337-gas-metering/alchemy/utils/safe.ts similarity index 100% rename from paymaster-analysis/alchemy/utils/safe.ts rename to 4337-gas-metering/alchemy/utils/safe.ts diff --git a/paymaster-analysis/alchemy/utils/userOp.ts b/4337-gas-metering/alchemy/utils/userOp.ts similarity index 100% rename from paymaster-analysis/alchemy/utils/userOp.ts rename to 4337-gas-metering/alchemy/utils/userOp.ts diff --git a/paymaster-analysis/package-lock.json b/4337-gas-metering/package-lock.json similarity index 99% rename from paymaster-analysis/package-lock.json rename to 4337-gas-metering/package-lock.json index da688681..b1c46d6d 100644 --- a/paymaster-analysis/package-lock.json +++ b/4337-gas-metering/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@safe-global/paymaster-analysis", + "name": "@safe-global/4337-gas-metering", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@safe-global/paymaster-analysis", + "name": "@safe-global/4337-gas-metering", "version": "1.0.0", "license": "GPL-3.0", "dependencies": { diff --git a/paymaster-analysis/package.json b/4337-gas-metering/package.json similarity index 87% rename from paymaster-analysis/package.json rename to 4337-gas-metering/package.json index 97df0f43..2fe116f0 100644 --- a/paymaster-analysis/package.json +++ b/4337-gas-metering/package.json @@ -1,8 +1,8 @@ { "name": "@safe-global/4337-gas-metering", "version": "1.0.0", - "description": "Account Abstraction Paymaster Analysis for Safe with 4337 Module", - "homepage": "https://github.com/safe-global/safe-modules/paymaster-analysis", + "description": "Gas Metering Analysis for Safe with 4337 Module", + "homepage": "https://github.com/safe-global/safe-modules/4337-gas-metering", "license": "GPL-3.0", "type": "module", "private": true, diff --git a/paymaster-analysis/pimlico/README.md b/4337-gas-metering/pimlico/README.md similarity index 100% rename from paymaster-analysis/pimlico/README.md rename to 4337-gas-metering/pimlico/README.md diff --git a/paymaster-analysis/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts similarity index 100% rename from paymaster-analysis/pimlico/pimlico-account.ts rename to 4337-gas-metering/pimlico/pimlico-account.ts diff --git a/paymaster-analysis/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts similarity index 100% rename from paymaster-analysis/pimlico/pimlico-erc20.ts rename to 4337-gas-metering/pimlico/pimlico-erc20.ts diff --git a/paymaster-analysis/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts similarity index 100% rename from paymaster-analysis/pimlico/pimlico-erc721.ts rename to 4337-gas-metering/pimlico/pimlico-erc721.ts diff --git a/paymaster-analysis/pimlico/utils/erc20.ts b/4337-gas-metering/pimlico/utils/erc20.ts similarity index 100% rename from paymaster-analysis/pimlico/utils/erc20.ts rename to 4337-gas-metering/pimlico/utils/erc20.ts diff --git a/paymaster-analysis/pimlico/utils/safe.ts b/4337-gas-metering/pimlico/utils/safe.ts similarity index 100% rename from paymaster-analysis/pimlico/utils/safe.ts rename to 4337-gas-metering/pimlico/utils/safe.ts diff --git a/paymaster-analysis/pimlico/utils/userOps.ts b/4337-gas-metering/pimlico/utils/userOps.ts similarity index 100% rename from paymaster-analysis/pimlico/utils/userOps.ts rename to 4337-gas-metering/pimlico/utils/userOps.ts diff --git a/paymaster-analysis/tsconfig.json b/4337-gas-metering/tsconfig.json similarity index 100% rename from paymaster-analysis/tsconfig.json rename to 4337-gas-metering/tsconfig.json diff --git a/paymaster-analysis/utils/erc721.ts b/4337-gas-metering/utils/erc721.ts similarity index 100% rename from paymaster-analysis/utils/erc721.ts rename to 4337-gas-metering/utils/erc721.ts diff --git a/paymaster-analysis/utils/multisend.ts b/4337-gas-metering/utils/multisend.ts similarity index 100% rename from paymaster-analysis/utils/multisend.ts rename to 4337-gas-metering/utils/multisend.ts From c3562715dd3655a6c8263ab69d60530959d6f1a5 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 11:53:05 +0530 Subject: [PATCH 29/60] Unified ERC20.ts --- 4337-gas-metering/alchemy/alchemy-erc20.ts | 5 +- 4337-gas-metering/pimlico/pimlico-account.ts | 4 +- 4337-gas-metering/pimlico/pimlico-erc20.ts | 6 +- 4337-gas-metering/pimlico/pimlico-erc721.ts | 4 +- 4337-gas-metering/pimlico/utils/erc20.ts | 148 ------------------ .../{alchemy => }/utils/erc20.ts | 54 +++++-- 6 files changed, 50 insertions(+), 171 deletions(-) delete mode 100644 4337-gas-metering/pimlico/utils/erc20.ts rename 4337-gas-metering/{alchemy => }/utils/erc20.ts (70%) diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts index 47545604..ab12193d 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy-erc20.ts @@ -15,10 +15,11 @@ import { getERC20Decimals, getERC20Balance, mintERC20Token, -} from "./utils/erc20"; +} from "../utils/erc20"; import { setTimeout } from "timers/promises"; dotenv.config(); +const paymaster = "alchemy"; const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; @@ -132,6 +133,8 @@ if (senderERC20Balance < erc20Amount) { signer, senderAddress, erc20Amount, + chain, + paymaster, ); while (senderERC20Balance < erc20Amount) { diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index 05e80554..f0aa16c6 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -13,10 +13,10 @@ import { } from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { getERC20Decimals, getERC20Balance } from "./utils/erc20"; +import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; dotenv.config(); - +const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index d6a98abe..0f08ffd1 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -18,10 +18,10 @@ import { getERC20Decimals, getERC20Balance, mintERC20Token, -} from "./utils/erc20"; +} from "../utils/erc20"; dotenv.config(); - +const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; @@ -168,6 +168,8 @@ if (senderERC20Balance < erc20Amount) { signer, senderAddress, erc20Amount, + chain, + paymaster, ); while (senderERC20Balance < erc20Amount) { diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index 302223ee..1567c21b 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -13,11 +13,11 @@ import { } from "./utils/safe"; import { submitUserOperation, signUserOperation } from "./utils/userOps"; import { setTimeout } from "timers/promises"; -import { getERC20Decimals, getERC20Balance } from "./utils/erc20"; +import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); - +const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; diff --git a/4337-gas-metering/pimlico/utils/erc20.ts b/4337-gas-metering/pimlico/utils/erc20.ts deleted file mode 100644 index 99049a95..00000000 --- a/4337-gas-metering/pimlico/utils/erc20.ts +++ /dev/null @@ -1,148 +0,0 @@ -import dotenv from "dotenv"; -import { - http, - Address, - encodeFunctionData, - createWalletClient, - PrivateKeyAccount, -} from "viem"; -import { goerli, sepolia } from "viem/chains"; - -dotenv.config(); -const rpcURL = process.env.PIMLICO_RPC_URL; -const chain = process.env.PIMLICO_CHAIN; - -export const generateApproveCallData = (paymasterAddress: Address) => { - const approveData = encodeFunctionData({ - abi: [ - { - inputs: [ - { name: "_spender", type: "address" }, - { name: "_value", type: "uint256" }, - ], - name: "approve", - outputs: [{ name: "", type: "bool" }], - payable: false, - stateMutability: "nonpayable", - type: "function", - }, - ], - args: [ - paymasterAddress, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, - ], - }); - - return approveData; -}; - -export const generateTransferCallData = (to: Address, value: bigint) => { - const transferData = encodeFunctionData({ - abi: [ - { - inputs: [ - { name: "_to", type: "address" }, - { name: "_value", type: "uint256" }, - ], - name: "transfer", - outputs: [{ name: "", type: "bool" }], - payable: false, - stateMutability: "nonpayable", - type: "function", - }, - ], - args: [to, value], - }); - - return transferData; -}; - -export const getERC20Decimals = async ( - erc20TokenAddress: string, - publicClient: any, -) => { - const erc20Decimals = await publicClient.readContract({ - abi: [ - { - inputs: [], - name: "decimals", - outputs: [{ type: "uint8" }], - type: "function", - stateMutability: "view", - }, - ], - address: erc20TokenAddress, - functionName: "decimals", - }); - - return erc20Decimals; -}; - -export const getERC20Balance = async ( - erc20TokenAddress: string, - publicClient: any, - owner: string, -) => { - const senderERC20Balance = await publicClient.readContract({ - abi: [ - { - inputs: [{ name: "_owner", type: "address" }], - name: "balanceOf", - outputs: [{ name: "balance", type: "uint256" }], - type: "function", - stateMutability: "view", - }, - ], - address: erc20TokenAddress, - functionName: "balanceOf", - args: [owner], - }); - - return senderERC20Balance; -}; - -export const mintERC20Token = async ( - erc20TokenAddress: string, - publicClient: any, - signer: PrivateKeyAccount, - to: string, - amount: number, -) => { - let walletClient; - if (chain == "sepolia") { - walletClient = createWalletClient({ - account: signer, - chain: sepolia, - transport: http(rpcURL), - }); - } else if (chain == "goerli") { - walletClient = createWalletClient({ - account: signer, - chain: goerli, - transport: http(rpcURL), - }); - } else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); - } - const { request } = await publicClient.simulateContract({ - address: erc20TokenAddress, - abi: [ - { - inputs: [ - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, - ], - name: "mint", - outputs: [], - type: "function", - stateMutability: "public", - }, - ], - functionName: "mint", - args: [to, amount], - signer, - }); - await walletClient.writeContract(request); -}; diff --git a/4337-gas-metering/alchemy/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts similarity index 70% rename from 4337-gas-metering/alchemy/utils/erc20.ts rename to 4337-gas-metering/utils/erc20.ts index eb3291b8..f375e56e 100644 --- a/4337-gas-metering/alchemy/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -9,8 +9,8 @@ import { import { goerli, sepolia } from "viem/chains"; dotenv.config(); -const rpcURL = process.env.ALCHEMY_RPC_URL; -const chain = process.env.ALCHEMY_CHAIN; +const pimlicoRPCURL = process.env.PIMLICO_RPC_URL; +const alchemyRPCURL = process.env.ALCHEMY_RPC_URL; export const generateApproveCallData = (paymasterAddress: Address) => { const approveData = encodeFunctionData({ @@ -107,23 +107,45 @@ export const mintERC20Token = async ( signer: PrivateKeyAccount, to: string, amount: number, + chain: string, + paymaster: string, ) => { let walletClient; - if (chain == "sepolia") { - walletClient = createWalletClient({ - account: signer, - chain: sepolia, - transport: http(rpcURL), - }); - } else if (chain == "goerli") { - walletClient = createWalletClient({ - account: signer, - chain: goerli, - transport: http(rpcURL), - }); - } else { + if(paymaster == "pimlico"){ + if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(pimlicoRPCURL), + }); + } else { + throw new Error( + "For Pimlico, current code only support using Goerli. Please make required changes if you want to use custom network.", + ); + } + } + else if(paymaster == "alchemy") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(alchemyRPCURL), + }); + } else if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(alchemyRPCURL), + }); + } else { + throw new Error( + "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + ); + } + } + else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", ); } const { request } = await publicClient.simulateContract({ From 26afc7efc0839a5f221ad662068b9a455f8b2f83 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 12:10:33 +0530 Subject: [PATCH 30/60] Unified UserOp.ts --- 4337-gas-metering/alchemy/alchemy-account.ts | 16 +++-- 4337-gas-metering/alchemy/alchemy-erc20.ts | 16 +++-- 4337-gas-metering/alchemy/alchemy-erc721.ts | 16 +++-- 4337-gas-metering/alchemy/utils/userOp.ts | 65 ------------------- 4337-gas-metering/pimlico/pimlico-account.ts | 10 +-- 4337-gas-metering/pimlico/pimlico-erc20.ts | 10 +-- 4337-gas-metering/pimlico/pimlico-erc721.ts | 10 +-- .../{pimlico => }/utils/userOps.ts | 11 ++-- 8 files changed, 54 insertions(+), 100 deletions(-) delete mode 100644 4337-gas-metering/alchemy/utils/userOp.ts rename 4337-gas-metering/{pimlico => }/utils/userOps.ts (90%) diff --git a/4337-gas-metering/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts index 8e122e9f..5789d193 100644 --- a/4337-gas-metering/alchemy/alchemy-account.ts +++ b/4337-gas-metering/alchemy/alchemy-account.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "./utils/userOp"; +import { UserOperation, signUserOperation } from "../utils/userOp"; import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -14,7 +14,7 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.ALCHEMY_ACCOUNT_PAYMASTER_NONCE); const chain = process.env.ALCHEMY_CHAIN; @@ -104,7 +104,7 @@ if (chain == "sepolia") { } const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -132,6 +132,8 @@ const sponsoredUserOperation: UserOperation = { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); @@ -145,7 +147,7 @@ const gasOptions = { params: [ { policyId: policyID, - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, @@ -193,6 +195,8 @@ sponsoredUserOperation.maxPriorityFeePerGas = sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); @@ -219,7 +223,7 @@ const options = { signature: sponsoredUserOperation.signature, paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS, + entryPointAddress, ], }), }; @@ -257,7 +261,7 @@ if (responseValues.result) { jsonrpc: "2.0", method: "eth_getUserOperationReceipt", params: [responseValues.result], - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, }), }; let runOnce = true; diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts index ab12193d..2cd55fa6 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy-erc20.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "./utils/userOp"; +import { UserOperation, signUserOperation } from "../utils/userOp"; import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -21,7 +21,7 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const paymaster = "alchemy"; const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.ALCHEMY_ERC20_PAYMASTER_NONCE); const chain = process.env.ALCHEMY_CHAIN; @@ -152,7 +152,7 @@ if (senderERC20Balance < erc20Amount) { } const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -180,6 +180,8 @@ const sponsoredUserOperation: UserOperation = { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); @@ -193,7 +195,7 @@ const gasOptions = { params: [ { policyId: policyID, - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, @@ -242,6 +244,8 @@ sponsoredUserOperation.maxPriorityFeePerGas = sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); @@ -266,7 +270,7 @@ const options = { signature: sponsoredUserOperation.signature, paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS, + entryPointAddress, ], }), }; @@ -305,7 +309,7 @@ if (responseValues.result) { jsonrpc: "2.0", method: "eth_getUserOperationReceipt", params: [responseValues.result], - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, }), }; let runOnce = true; diff --git a/4337-gas-metering/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts index 41d12d14..75baed84 100644 --- a/4337-gas-metering/alchemy/alchemy-erc721.ts +++ b/4337-gas-metering/alchemy/alchemy-erc721.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "./utils/userOp"; +import { UserOperation, signUserOperation } from "../utils/userOp"; import { Hash, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -15,7 +15,7 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.ALCHEMY_ERC721_PAYMASTER_NONCE); const chain = process.env.ALCHEMY_CHAIN; @@ -104,7 +104,7 @@ if (chain == "sepolia") { } const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -132,6 +132,8 @@ const sponsoredUserOperation: UserOperation = { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); @@ -145,7 +147,7 @@ const gasOptions = { params: [ { policyId: policyID, - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, dummySignature: sponsoredUserOperation.signature, userOperation: { sender: sponsoredUserOperation.sender, @@ -193,6 +195,8 @@ sponsoredUserOperation.maxPriorityFeePerGas = sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); @@ -217,7 +221,7 @@ const options = { signature: sponsoredUserOperation.signature, paymasterAndData: sponsoredUserOperation.paymasterAndData, }, - ENTRY_POINT_ADDRESS, + entryPointAddress, ], }), }; @@ -256,7 +260,7 @@ if (responseValues.result) { jsonrpc: "2.0", method: "eth_getUserOperationReceipt", params: [responseValues.result], - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, }), }; let runOnce = true; diff --git a/4337-gas-metering/alchemy/utils/userOp.ts b/4337-gas-metering/alchemy/utils/userOp.ts deleted file mode 100644 index add40790..00000000 --- a/4337-gas-metering/alchemy/utils/userOp.ts +++ /dev/null @@ -1,65 +0,0 @@ -import dotenv from "dotenv"; -import type { Address } from "abitype"; -import type { Hex, PrivateKeyAccount } from "viem"; -import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; - -dotenv.config(); -const ENTRY_POINT_ADDRESS = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; -const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; - -export type UserOperation = { - sender: Address; - nonce: bigint; - initCode: Hex; - callData: Hex; - callGasLimit: bigint; - verificationGasLimit: bigint; - preVerificationGas: bigint; - maxFeePerGas: bigint; - maxPriorityFeePerGas: bigint; - paymasterAndData: Hex; - signature: Hex; -}; - -export const signUserOperation = async ( - userOperation: UserOperation, - signer: PrivateKeyAccount, -) => { - const signatures = [ - { - signer: signer.address, - data: await signer.signTypedData({ - domain: { - chainId: chainID, - verifyingContract: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - }, - types: EIP712_SAFE_OPERATION_TYPE, - primaryType: "SafeOp", - message: { - safe: userOperation.sender, - callData: userOperation.callData, - nonce: userOperation.nonce, - preVerificationGas: userOperation.preVerificationGas, - verificationGasLimit: userOperation.verificationGasLimit, - callGasLimit: userOperation.callGasLimit, - maxFeePerGas: userOperation.maxFeePerGas, - maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - entryPoint: ENTRY_POINT_ADDRESS, - }, - }), - }, - ]; - - signatures.sort((left, right) => - left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()), - ); - - let signatureBytes: Address = "0x"; - for (const sig of signatures) { - signatureBytes += sig.data.slice(2); - } - - return signatureBytes; -}; diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index f0aa16c6..304bf4de 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -11,14 +11,14 @@ import { getAccountAddress, getAccountInitCode, } from "./utils/safe"; -import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.PIMLICO_ACCOUNT_NONCE); const chain = process.env.PIMLICO_CHAIN; @@ -143,7 +143,7 @@ if (senderUSDCBalance < usdcAmount) { const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -176,7 +176,9 @@ if (contractCode) { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); - await submitUserOperation(sponsoredUserOperation, bundlerClient); + await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); } diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index 0f08ffd1..8fd53215 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -11,7 +11,7 @@ import { getAccountAddress, getAccountInitCode, } from "./utils/safe"; -import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { generateTransferCallData, @@ -23,7 +23,7 @@ import { dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.PIMLICO_ERC20_NONCE); const chain = process.env.PIMLICO_CHAIN; @@ -189,7 +189,7 @@ if (senderERC20Balance < erc20Amount) { const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -228,6 +228,8 @@ const sponsoredUserOperation: UserOperation = { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); -await submitUserOperation(sponsoredUserOperation, bundlerClient); +await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index 1567c21b..81aefcc5 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -11,7 +11,7 @@ import { getAccountAddress, getAccountInitCode, } from "./utils/safe"; -import { submitUserOperation, signUserOperation } from "./utils/userOps"; +import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; import { generateMintingCallData } from "../utils/erc721"; @@ -19,7 +19,7 @@ import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; +const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; const saltNonce = BigInt(process.env.PIMLICO_ERC721_NONCE); const chain = process.env.PIMLICO_CHAIN; @@ -145,7 +145,7 @@ if (senderUSDCBalance < BigInt(2) * usdcAmount) { const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); const newNonce = await getAccountNonce(publicClient, { - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); @@ -184,6 +184,8 @@ const sponsoredUserOperation: UserOperation = { sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, + chainID, + entryPointAddress, ); -await submitUserOperation(sponsoredUserOperation, bundlerClient); +await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); diff --git a/4337-gas-metering/pimlico/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts similarity index 90% rename from 4337-gas-metering/pimlico/utils/userOps.ts rename to 4337-gas-metering/utils/userOps.ts index 606f9a63..9a0182b5 100644 --- a/4337-gas-metering/pimlico/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -5,18 +5,17 @@ import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; import { UserOperation } from "permissionless"; dotenv.config(); -const ENTRY_POINT_ADDRESS = process.env.PIMLICO_ENTRYPOINT_ADDRESS; -const chain = process.env.PIMLICO_CHAIN; -const chainID = Number(process.env.PIMLICO_CHAIN_ID); const safeVersion = process.env.SAFE_VERSION; export const submitUserOperation = async ( userOperation: UserOperation, bundlerClient: any, + entryPointAddress: any, + chain: string, ) => { const userOperationHash = await bundlerClient.sendUserOperation({ userOperation, - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, }); console.log(`UserOperation submitted. Hash: ${userOperationHash}`); console.log( @@ -42,6 +41,8 @@ export const submitUserOperation = async ( export const signUserOperation = async ( userOperation: UserOperation, signer: PrivateKeyAccount, + chainID: any, + entryPointAddress: any, ) => { const signatures = [ { @@ -63,7 +64,7 @@ export const signUserOperation = async ( callGasLimit: userOperation.callGasLimit, maxFeePerGas: userOperation.maxFeePerGas, maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - entryPoint: ENTRY_POINT_ADDRESS, + entryPoint: entryPointAddress, }, }), }, From 2883502008345e13e18f2017fe54572eb1fc2b88 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 12:36:24 +0530 Subject: [PATCH 31/60] Unified safe.ts --- 4337-gas-metering/alchemy/alchemy-account.ts | 8 +- 4337-gas-metering/alchemy/alchemy-erc20.ts | 8 +- 4337-gas-metering/alchemy/alchemy-erc721.ts | 8 +- 4337-gas-metering/alchemy/utils/safe.ts | 329 ------------------ 4337-gas-metering/pimlico/pimlico-account.ts | 6 +- 4337-gas-metering/pimlico/pimlico-erc20.ts | 6 +- 4337-gas-metering/pimlico/pimlico-erc721.ts | 6 +- 4337-gas-metering/{pimlico => }/utils/safe.ts | 27 +- 8 files changed, 42 insertions(+), 356 deletions(-) delete mode 100644 4337-gas-metering/alchemy/utils/safe.ts rename 4337-gas-metering/{pimlico => }/utils/safe.ts (97%) diff --git a/4337-gas-metering/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts index 5789d193..8f7ab516 100644 --- a/4337-gas-metering/alchemy/alchemy-account.ts +++ b/4337-gas-metering/alchemy/alchemy-account.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "../utils/userOp"; -import { Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { @@ -9,7 +9,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { setTimeout } from "timers/promises"; dotenv.config(); @@ -69,6 +69,8 @@ const initCode = await getAccountInitCode({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nInit Code Created."); @@ -85,6 +87,8 @@ const senderAddress = await getAccountAddress({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts index 2cd55fa6..fc3fc8b1 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy-erc20.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "../utils/userOp"; -import { Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { @@ -9,7 +9,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { generateTransferCallData, getERC20Decimals, @@ -77,6 +77,8 @@ const initCode = await getAccountInitCode({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nInit Code Created."); @@ -93,6 +95,8 @@ const senderAddress = await getAccountAddress({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); diff --git a/4337-gas-metering/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts index 75baed84..fe9f8975 100644 --- a/4337-gas-metering/alchemy/alchemy-erc721.ts +++ b/4337-gas-metering/alchemy/alchemy-erc721.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; import { UserOperation, signUserOperation } from "../utils/userOp"; -import { Hash, createPublicClient, http } from "viem"; +import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { @@ -9,7 +9,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { generateMintingCallData } from "../utils/erc721"; import { setTimeout } from "timers/promises"; @@ -71,6 +71,8 @@ const initCode = await getAccountInitCode({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nInit Code Created."); @@ -87,6 +89,8 @@ const senderAddress = await getAccountAddress({ SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, multiSendAddress, + zeroAddress, + zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); if (chain == "sepolia") { diff --git a/4337-gas-metering/alchemy/utils/safe.ts b/4337-gas-metering/alchemy/utils/safe.ts deleted file mode 100644 index 36b961d5..00000000 --- a/4337-gas-metering/alchemy/utils/safe.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { - Address, - Chain, - Hex, - PublicClient, - Transport, - concatHex, - encodeFunctionData, - encodePacked, - getContractAddress, - hexToBigInt, - keccak256, - zeroAddress, -} from "viem"; -import { InternalTx, encodeMultiSend } from "../../utils/multisend"; - -export const SAFE_ADDRESSES_MAP = { - "1.4.1": { - "11155111": { - ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", - SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", - SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", - SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", - }, - "5": { - ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", - SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", - SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", - SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", - }, - }, -} as const; - -const getInitializerCode = async ({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - multiSendAddress, -}: { - owner: Address; - addModuleLibAddress: Address; - safe4337ModuleAddress: Address; - multiSendAddress: Address; -}) => { - const setupTxs: InternalTx[] = [ - { - to: addModuleLibAddress, - data: enableModuleCallData(safe4337ModuleAddress), - value: 0n, - operation: 1, // 1 = DelegateCall required for enabling the module - }, - ]; - - const multiSendCallData = encodeMultiSend(setupTxs); - - return encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address[]", - name: "_owners", - type: "address[]", - }, - { - internalType: "uint256", - name: "_threshold", - type: "uint256", - }, - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", - }, - { - internalType: "address", - name: "fallbackHandler", - type: "address", - }, - { - internalType: "address", - name: "paymentToken", - type: "address", - }, - { - internalType: "uint256", - name: "payment", - type: "uint256", - }, - { - internalType: "address payable", - name: "paymentReceiver", - type: "address", - }, - ], - name: "setup", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - ], - functionName: "setup", - args: [ - [owner], - 1n, - multiSendAddress, - multiSendCallData, - safe4337ModuleAddress, - zeroAddress, - 0n, - zeroAddress, - ], - }); -}; - -export const enableModuleCallData = (safe4337ModuleAddress: `0x${string}`) => { - return encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address[]", - name: "modules", - type: "address[]", - }, - ], - name: "enableModules", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - ], - functionName: "enableModules", - args: [[safe4337ModuleAddress]], - }); -}; - -export const getAccountInitCode = async ({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - safeProxyFactoryAddress, - safeSingletonAddress, - saltNonce = 0n, - multiSendAddress, -}: { - owner: Address; - addModuleLibAddress: Address; - safe4337ModuleAddress: Address; - safeProxyFactoryAddress: Address; - safeSingletonAddress: Address; - saltNonce?: bigint; - multiSendAddress: Address; -}): Promise => { - if (!owner) throw new Error("Owner account not found"); - const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - multiSendAddress, - }); - - const initCodeCallData = encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address", - name: "_singleton", - type: "address", - }, - { - internalType: "bytes", - name: "initializer", - type: "bytes", - }, - { - internalType: "uint256", - name: "saltNonce", - type: "uint256", - }, - ], - name: "createProxyWithNonce", - outputs: [ - { - internalType: "contract SafeProxy", - name: "proxy", - type: "address", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - ], - functionName: "createProxyWithNonce", - args: [safeSingletonAddress, initializer, saltNonce], - }); - - return concatHex([safeProxyFactoryAddress, initCodeCallData]); -}; - -export const EIP712_SAFE_OPERATION_TYPE = { - SafeOp: [ - { type: "address", name: "safe" }, - { type: "bytes", name: "callData" }, - { type: "uint256", name: "nonce" }, - { type: "uint256", name: "preVerificationGas" }, - { type: "uint256", name: "verificationGasLimit" }, - { type: "uint256", name: "callGasLimit" }, - { type: "uint256", name: "maxFeePerGas" }, - { type: "uint256", name: "maxPriorityFeePerGas" }, - { type: "address", name: "entryPoint" }, - ], -}; - -export const encodeCallData = (params: { - to: Address; - value: bigint; - data: `0x${string}`; -}) => { - return encodeFunctionData({ - abi: [ - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", - }, - { - internalType: "uint8", - name: "operation", - type: "uint8", - }, - ], - name: "executeUserOp", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - ], - functionName: "executeUserOp", - args: [params.to, params.value, params.data, 0], - }); -}; - -export const getAccountAddress = async < - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, ->({ - client, - owner, - addModuleLibAddress, - safe4337ModuleAddress, - safeProxyFactoryAddress, - safeSingletonAddress, - saltNonce = 0n, - multiSendAddress, -}: { - client: PublicClient; - owner: Address; - addModuleLibAddress: Address; - safe4337ModuleAddress: Address; - safeProxyFactoryAddress: Address; - safeSingletonAddress: Address; - saltNonce?: bigint; - multiSendAddress: Address; -}): Promise
=> { - const proxyCreationCode = await client.readContract({ - abi: [ - { - inputs: [], - name: "proxyCreationCode", - outputs: [ - { - internalType: "bytes", - name: "", - type: "bytes", - }, - ], - stateMutability: "pure", - type: "function", - }, - ], - address: safeProxyFactoryAddress, - functionName: "proxyCreationCode", - }); - - const deploymentCode = encodePacked( - ["bytes", "uint256"], - [proxyCreationCode, hexToBigInt(safeSingletonAddress)], - ); - - const initializer = await getInitializerCode({ - owner, - addModuleLibAddress, - safe4337ModuleAddress, - multiSendAddress, - }); - - const salt = keccak256( - encodePacked( - ["bytes32", "uint256"], - [keccak256(encodePacked(["bytes"], [initializer])), saltNonce], - ), - ); - - return getContractAddress({ - from: safeProxyFactoryAddress, - salt, - bytecode: deploymentCode, - opcode: "CREATE2", - }); -}; diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index 304bf4de..090f3c05 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -10,7 +10,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; @@ -75,8 +75,8 @@ const initCode = await getAccountInitCode({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nInit Code Created."); @@ -93,8 +93,8 @@ const senderAddress = await getAccountAddress({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index 8fd53215..10e88459 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -10,7 +10,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { @@ -81,8 +81,8 @@ const initCode = await getAccountInitCode({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nInit Code Created."); @@ -99,8 +99,8 @@ const senderAddress = await getAccountAddress({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index 81aefcc5..c2929740 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -10,7 +10,7 @@ import { encodeCallData, getAccountAddress, getAccountInitCode, -} from "./utils/safe"; +} from "../utils/safe"; import { submitUserOperation, signUserOperation } from "../utils/userOps"; import { setTimeout } from "timers/promises"; import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; @@ -77,8 +77,8 @@ const initCode = await getAccountInitCode({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nInit Code Created."); @@ -95,8 +95,8 @@ const senderAddress = await getAccountAddress({ safeSingletonAddress: SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - erc20TokenAddress: usdcTokenAddress, multiSendAddress, + erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); diff --git a/4337-gas-metering/pimlico/utils/safe.ts b/4337-gas-metering/utils/safe.ts similarity index 97% rename from 4337-gas-metering/pimlico/utils/safe.ts rename to 4337-gas-metering/utils/safe.ts index b637b07d..0ca42d8f 100644 --- a/4337-gas-metering/pimlico/utils/safe.ts +++ b/4337-gas-metering/utils/safe.ts @@ -12,7 +12,7 @@ import { keccak256, zeroAddress, } from "viem"; -import { InternalTx, encodeMultiSend } from "../../utils/multisend"; +import { InternalTx, encodeMultiSend } from "./multisend"; import { generateApproveCallData } from "./erc20"; export const SAFE_ADDRESSES_MAP = { @@ -47,20 +47,23 @@ const getInitializerCode = async ({ erc20TokenAddress: Address; paymasterAddress: Address; }) => { - const setupTxs: InternalTx[] = [ + let setupTxs: InternalTx[] = [ { to: addModuleLibAddress, data: enableModuleCallData(safe4337ModuleAddress), value: 0n, operation: 1, // 1 = DelegateCall required for enabling the module - }, - { + } + ]; + + if(erc20TokenAddress != zeroAddress && paymasterAddress != zeroAddress){ + setupTxs.push({ to: erc20TokenAddress, data: generateApproveCallData(paymasterAddress), value: 0n, operation: 0, // 0 = Call - }, - ]; + }) + } const multiSendCallData = encodeMultiSend(setupTxs); @@ -158,8 +161,8 @@ export const getAccountInitCode = async ({ safeProxyFactoryAddress, safeSingletonAddress, saltNonce = 0n, - erc20TokenAddress, multiSendAddress, + erc20TokenAddress, paymasterAddress, }: { owner: Address; @@ -168,8 +171,8 @@ export const getAccountInitCode = async ({ safeProxyFactoryAddress: Address; safeSingletonAddress: Address; saltNonce?: bigint; - erc20TokenAddress: Address; multiSendAddress: Address; + erc20TokenAddress: Address; paymasterAddress: Address; }): Promise => { if (!owner) throw new Error("Owner account not found"); @@ -177,8 +180,8 @@ export const getAccountInitCode = async ({ owner, addModuleLibAddress, safe4337ModuleAddress, - erc20TokenAddress, multiSendAddress, + erc20TokenAddress, paymasterAddress, }); @@ -287,8 +290,8 @@ export const getAccountAddress = async < safeProxyFactoryAddress, safeSingletonAddress, saltNonce = 0n, - erc20TokenAddress, multiSendAddress, + erc20TokenAddress, paymasterAddress, }: { client: PublicClient; @@ -298,8 +301,8 @@ export const getAccountAddress = async < safeProxyFactoryAddress: Address; safeSingletonAddress: Address; saltNonce?: bigint; - erc20TokenAddress: Address; multiSendAddress: Address; + erc20TokenAddress: Address; paymasterAddress: Address; }): Promise
=> { const proxyCreationCode = await client.readContract({ @@ -331,8 +334,8 @@ export const getAccountAddress = async < owner, addModuleLibAddress, safe4337ModuleAddress, - erc20TokenAddress, multiSendAddress, + erc20TokenAddress, paymasterAddress, }); From 54d975f7d2c459e607a8680b43a2ec67e4f23021 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 12:38:06 +0530 Subject: [PATCH 32/60] Cleaned up --- 4337-gas-metering/pimlico/pimlico-account.ts | 7 ++++++- 4337-gas-metering/pimlico/pimlico-erc20.ts | 7 ++++++- 4337-gas-metering/pimlico/pimlico-erc721.ts | 7 ++++++- 4337-gas-metering/utils/erc20.ts | 8 +++----- 4337-gas-metering/utils/safe.ts | 6 +++--- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index 090f3c05..29b5b194 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -180,5 +180,10 @@ if (contractCode) { entryPointAddress, ); - await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); + await submitUserOperation( + sponsoredUserOperation, + bundlerClient, + entryPointAddress, + chain, + ); } diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index 10e88459..af37f750 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -232,4 +232,9 @@ sponsoredUserOperation.signature = await signUserOperation( entryPointAddress, ); -await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); +await submitUserOperation( + sponsoredUserOperation, + bundlerClient, + entryPointAddress, + chain, +); diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index c2929740..8d2adb27 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -188,4 +188,9 @@ sponsoredUserOperation.signature = await signUserOperation( entryPointAddress, ); -await submitUserOperation(sponsoredUserOperation, bundlerClient, entryPointAddress, chain); +await submitUserOperation( + sponsoredUserOperation, + bundlerClient, + entryPointAddress, + chain, +); diff --git a/4337-gas-metering/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts index f375e56e..da724541 100644 --- a/4337-gas-metering/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -111,7 +111,7 @@ export const mintERC20Token = async ( paymaster: string, ) => { let walletClient; - if(paymaster == "pimlico"){ + if (paymaster == "pimlico") { if (chain == "goerli") { walletClient = createWalletClient({ account: signer, @@ -123,8 +123,7 @@ export const mintERC20Token = async ( "For Pimlico, current code only support using Goerli. Please make required changes if you want to use custom network.", ); } - } - else if(paymaster == "alchemy") { + } else if (paymaster == "alchemy") { if (chain == "sepolia") { walletClient = createWalletClient({ account: signer, @@ -142,8 +141,7 @@ export const mintERC20Token = async ( "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", ); } - } - else { + } else { throw new Error( "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", ); diff --git a/4337-gas-metering/utils/safe.ts b/4337-gas-metering/utils/safe.ts index 0ca42d8f..6da13654 100644 --- a/4337-gas-metering/utils/safe.ts +++ b/4337-gas-metering/utils/safe.ts @@ -53,16 +53,16 @@ const getInitializerCode = async ({ data: enableModuleCallData(safe4337ModuleAddress), value: 0n, operation: 1, // 1 = DelegateCall required for enabling the module - } + }, ]; - if(erc20TokenAddress != zeroAddress && paymasterAddress != zeroAddress){ + if (erc20TokenAddress != zeroAddress && paymasterAddress != zeroAddress) { setupTxs.push({ to: erc20TokenAddress, data: generateApproveCallData(paymasterAddress), value: 0n, operation: 0, // 0 = Call - }) + }); } const multiSendCallData = encodeMultiSend(setupTxs); From 7a80fd184d89b5522ecbb13cbb0f6fc056ea4fff Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 20:10:40 +0530 Subject: [PATCH 33/60] Code Refactoring --- 4337-gas-metering/alchemy/alchemy-account.ts | 367 +++++++++---------- 4337-gas-metering/alchemy/alchemy-erc20.ts | 174 ++++----- 4337-gas-metering/alchemy/alchemy-erc721.ts | 161 ++++---- 4337-gas-metering/pimlico/pimlico-account.ts | 71 ++-- 4337-gas-metering/pimlico/pimlico-erc20.ts | 72 ++-- 4337-gas-metering/pimlico/pimlico-erc721.ts | 72 ++-- 4337-gas-metering/utils/erc20.ts | 4 +- 4337-gas-metering/utils/userOps.ts | 26 +- 8 files changed, 435 insertions(+), 512 deletions(-) diff --git a/4337-gas-metering/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts index 8f7ab516..b0439c05 100644 --- a/4337-gas-metering/alchemy/alchemy-account.ts +++ b/4337-gas-metering/alchemy/alchemy-account.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "../utils/userOp"; +import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -14,15 +14,23 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.ALCHEMY_ACCOUNT_PAYMASTER_NONCE); +const entryPointAddress = process.env + .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.ALCHEMY_ACCOUNT_PAYMASTER_NONCE as string); const chain = process.env.ALCHEMY_CHAIN; const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.ALCHEMY_RPC_URL; const policyID = process.env.ALCHEMY_GAS_POLICY; const apiKey = process.env.ALCHEMY_API_KEY; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -52,60 +60,37 @@ if (chain == "sepolia") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } -// The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); - -// TODO This and in other files, this might be made easier with chain substituted at the right place. -if (chain == "sepolia") { - console.log( - "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, - ); -} else if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); const newNonce = await getAccountNonce(publicClient, { entryPoint: entryPointAddress, @@ -115,184 +100,174 @@ console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); -const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: senderAddress, - data: "0xe75235b8", // getThreshold() of the Safe. TODO: Check if this could be removed. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", -}; +if (contractCode) { + console.log("\nThe Safe is already deployed.\n"); +} else { + console.log("\nDeploying a new Safe.\n"); + const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: encodeCallData({ + to: senderAddress, + data: "0x", // getThreshold() of the Safe. TODO: Check if this could be removed. + value: 0n, + }), + callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data + verificationGasLimit: 0n, + preVerificationGas: 0n, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymasterAndData: "0x", + signature: "0x", + }; -sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, -); -console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); + sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + chainID, + entryPointAddress, + ); + console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); -const gasOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "alchemy_requestGasAndPaymasterAndData", - params: [ - { - policyId: policyID, - entryPoint: entryPointAddress, - dummySignature: sponsoredUserOperation.signature, - userOperation: { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, + const gasOptions = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "alchemy_requestGasAndPaymasterAndData", + params: [ + { + policyId: policyID, + entryPoint: entryPointAddress, + dummySignature: sponsoredUserOperation.signature, + userOperation: { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + }, }, - }, - ], - }), -}; + ], + }), + }; -let responseValues; + let responseValues; -if (chain == "sepolia") { - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) + await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) .then((response) => response.json()) .then((response) => (responseValues = response)) .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} -console.log("\nReceived Paymaster Data from Alchemy."); - -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = - responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = - responseValues.result.paymasterAndData; -sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = - responseValues.result.maxPriorityFeePerGas; + console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, -); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); - -// console.log(sponsoredUserOperation); + let rv; + if (responseValues && responseValues["result"]) { + rv = responseValues["result"] as suoData; + } -const options = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_sendUserOperation", - params: [ - { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - callGasLimit: sponsoredUserOperation.callGasLimit, - verificationGasLimit: sponsoredUserOperation.verificationGasLimit, - preVerificationGas: sponsoredUserOperation.preVerificationGas, - maxFeePerGas: sponsoredUserOperation.maxFeePerGas, - maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, - signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData, - }, - entryPointAddress, - ], - }), -}; + sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; + sponsoredUserOperation.callGasLimit = rv?.callGasLimit; + sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; + sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; + sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; + sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; -if (chain == "sepolia") { - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + chainID, + entryPointAddress, ); -} - -if (responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!"); console.log( - "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues.result + - "?network=" + - chain, + "\nSigned Real Data including Paymaster Data Created by Alchemy.", ); - const hashOptions = { + const options = { method: "POST", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ id: 1, jsonrpc: "2.0", - method: "eth_getUserOperationReceipt", - params: [responseValues.result], - entryPoint: entryPointAddress, + method: "eth_sendUserOperation", + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: sponsoredUserOperation.callGasLimit, + verificationGasLimit: sponsoredUserOperation.verificationGasLimit, + preVerificationGas: sponsoredUserOperation.preVerificationGas, + maxFeePerGas: sponsoredUserOperation.maxFeePerGas, + maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData, + }, + entryPointAddress, + ], }), }; - let runOnce = true; - while (responseValues.result == null || runOnce) { - await setTimeout(25000); - await fetch( - "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, - hashOptions, - ) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - runOnce = false; - } + await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); - if (responseValues.result) { + if (responseValues && responseValues["result"]) { + console.log("UserOperation submitted. Hash:", responseValues["result"]); console.log( - "\nTransaction Link: https://" + - chain + - ".etherscan.io/tx/" + - responseValues.result.receipt.transactionHash + - "\n", + "UserOp Link: https://jiffyscan.xyz/userOpHash/" + + responseValues["result"] + + "?network=" + + chain, ); + + const hashOptions = { + method: "POST", + headers: { + accept: "application/json", + "content-type": "application/json", + }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", + params: [responseValues["result"]], + entryPoint: entryPointAddress, + }), + }; + let runOnce = true; + + while (responseValues["result"] == null || runOnce) { + await setTimeout(25000); + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + runOnce = false; + } + + if ( + responseValues["result"] && + responseValues["result"]["receipt"]["transactionHash"] + ) { + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues["result"]["receipt"]["transactionHash"] + + "\n", + ); + } else { + console.log("\n" + responseValues["error"]); + } } else { - console.log("\n" + responseValues.error); + if (responseValues && responseValues["error"]["message"]) { + console.log("\n" + responseValues["error"]["message"]); + } } -} else { - console.log("\n" + responseValues.error.message); } diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts index fc3fc8b1..52c61f93 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy-erc20.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "../utils/userOp"; +import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -21,16 +21,25 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const paymaster = "alchemy"; const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.ALCHEMY_ERC20_PAYMASTER_NONCE); +const entryPointAddress = process.env + .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.ALCHEMY_ERC20_PAYMASTER_NONCE as string); const chain = process.env.ALCHEMY_CHAIN; const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.ALCHEMY_RPC_URL; const policyID = process.env.ALCHEMY_GAS_POLICY; const apiKey = process.env.ALCHEMY_API_KEY; -const erc20TokenAddress = process.env.ALCHEMY_ERC20_TOKEN_CONTRACT; +const erc20TokenAddress = process.env + .ALCHEMY_ERC20_TOKEN_CONTRACT as `0x${string}`; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -60,62 +69,39 @@ if (chain == "sepolia") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } -// The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); - -if (chain == "sepolia") { - console.log( - "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, - ); -} else if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); // Token Configurations - const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); const erc20Amount = BigInt(10 ** erc20Decimals); let senderERC20Balance = await getERC20Balance( @@ -163,6 +149,16 @@ console.log("\nNonce for the sender received from EntryPoint."); const contractCode = await publicClient.getBytecode({ address: senderAddress }); +if (contractCode) { + console.log( + "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n", + ); +} else { + console.log( + "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n", + ); +} + const sponsoredUserOperation: UserOperation = { sender: senderAddress, nonce: newNonce, @@ -214,36 +210,23 @@ const gasOptions = { let responseValues; -if (chain == "sepolia") { - // TODO This and other places and files can use ALCHEMY_RPC_URL - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions,) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = - responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = - responseValues.result.paymasterAndData; -sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = - responseValues.result.maxPriorityFeePerGas; +let rv; +if (responseValues && responseValues["result"]) { + rv = responseValues["result"] as suoData; +} + +sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; +sponsoredUserOperation.callGasLimit = rv?.callGasLimit; +sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; +sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, @@ -279,30 +262,18 @@ const options = { }), }; -if (chain == "sepolia") { - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); -if (responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!"); +if (responseValues && responseValues["result"]) { + console.log("UserOperation submitted. Hash:", responseValues["result"]); console.log( "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues.result + + responseValues["result"] + "?network=" + - chain + - "\n", + chain, ); const hashOptions = { @@ -312,13 +283,13 @@ if (responseValues.result) { id: 1, jsonrpc: "2.0", method: "eth_getUserOperationReceipt", - params: [responseValues.result], + params: [responseValues["result"]], entryPoint: entryPointAddress, }), }; let runOnce = true; - while (responseValues.result == null || runOnce) { + while (responseValues["result"] == null || runOnce) { await setTimeout(25000); await fetch( "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, @@ -330,17 +301,22 @@ if (responseValues.result) { runOnce = false; } - if (responseValues.result) { + if ( + responseValues["result"] && + responseValues["result"]["receipt"]["transactionHash"] + ) { console.log( "\nTransaction Link: https://" + chain + ".etherscan.io/tx/" + - responseValues.result.receipt.transactionHash + + responseValues["result"]["receipt"]["transactionHash"] + "\n", ); } else { - console.log("\n" + responseValues.error); + console.log("\n" + responseValues["error"]); } } else { - console.log("\n" + responseValues.error.message); + if (responseValues && responseValues["error"]["message"]) { + console.log("\n" + responseValues["error"]["message"]); + } } diff --git a/4337-gas-metering/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts index fe9f8975..2a616371 100644 --- a/4337-gas-metering/alchemy/alchemy-erc721.ts +++ b/4337-gas-metering/alchemy/alchemy-erc721.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation } from "../utils/userOp"; +import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; import { Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; @@ -15,16 +15,25 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.ALCHEMY_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.ALCHEMY_ERC721_PAYMASTER_NONCE); +const entryPointAddress = process.env + .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.ALCHEMY_ERC721_PAYMASTER_NONCE as string); const chain = process.env.ALCHEMY_CHAIN; const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.ALCHEMY_RPC_URL; const policyID = process.env.ALCHEMY_GAS_POLICY; const apiKey = process.env.ALCHEMY_API_KEY; -const erc721TokenAddress = process.env.ALCHEMY_ERC721_TOKEN_CONTRACT; +const erc721TokenAddress = process.env + .ALCHEMY_ERC721_TOKEN_CONTRACT as `0x${string}`; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -54,58 +63,37 @@ if (chain == "sepolia") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } -// The console log in this function could be removed. const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, - zeroAddress, - zeroAddress, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -if (chain == "sepolia") { - console.log( - "Address Link: https://sepolia.etherscan.io/address/" + senderAddress, - ); -} else if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); const newNonce = await getAccountNonce(publicClient, { entryPoint: entryPointAddress, @@ -166,35 +154,23 @@ const gasOptions = { let responseValues; -if (chain == "sepolia") { - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); console.log("\nReceived Paymaster Data from Alchemy."); -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.preVerificationGas = - responseValues.result.preVerificationGas; -sponsoredUserOperation.callGasLimit = responseValues.result.callGasLimit; -sponsoredUserOperation.verificationGasLimit = - responseValues.result.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = - responseValues.result.paymasterAndData; -sponsoredUserOperation.maxFeePerGas = responseValues.result.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = - responseValues.result.maxPriorityFeePerGas; +let rv; +if (responseValues && responseValues["result"]) { + rv = responseValues["result"] as suoData; +} + +sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; +sponsoredUserOperation.callGasLimit = rv?.callGasLimit; +sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; +sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; +sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, @@ -230,30 +206,18 @@ const options = { }), }; -if (chain == "sepolia") { - await fetch("https://eth-sepolia.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else if (chain == "goerli") { - await fetch("https://eth-goerli.g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); -if (responseValues.result) { - console.log("\nSafe Account Creation User Operation Successfully Created!"); +if (responseValues && responseValues["result"]) { + console.log("UserOperation submitted. Hash:", responseValues["result"]); console.log( "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues.result + + responseValues["result"] + "?network=" + - chain + - "\n", + chain, ); const hashOptions = { @@ -263,13 +227,13 @@ if (responseValues.result) { id: 1, jsonrpc: "2.0", method: "eth_getUserOperationReceipt", - params: [responseValues.result], + params: [responseValues["result"]], entryPoint: entryPointAddress, }), }; let runOnce = true; - while (responseValues.result == null || runOnce) { + while (responseValues["result"] == null || runOnce) { await setTimeout(25000); await fetch( "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, @@ -281,17 +245,22 @@ if (responseValues.result) { runOnce = false; } - if (responseValues.result) { + if ( + responseValues["result"] && + responseValues["result"]["receipt"]["transactionHash"] + ) { console.log( "\nTransaction Link: https://" + chain + ".etherscan.io/tx/" + - responseValues.result.receipt.transactionHash + + responseValues["result"]["receipt"]["transactionHash"] + "\n", ); } else { - console.log("\n" + responseValues.error); + console.log("\n" + responseValues["error"]); } } else { - console.log("\n" + responseValues.error.message); + if (responseValues && responseValues["error"]["message"]) { + console.log("\n" + responseValues["error"]["message"]); + } } diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index 29b5b194..0f8b8bc3 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -18,16 +18,26 @@ import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.PIMLICO_ACCOUNT_NONCE); +const entryPointAddress = process.env + .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.PIMLICO_ACCOUNT_NONCE as string); const chain = process.env.PIMLICO_CHAIN; const chainID = Number(process.env.PIMLICO_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.PIMLICO_RPC_URL; const apiKey = process.env.PIMLICO_API_KEY; -const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; -const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; +const erc20PaymasterAddress = process.env + .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; +const usdcTokenAddress = process.env + .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -60,22 +70,18 @@ if (chain == "goerli") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Pimlico code only support Goerli. Please make required changes if you want to use custom network.", ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); @@ -84,31 +90,18 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -// TODO This and in other files, this might be made easier with chain substituted at the right place. -if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); @@ -161,12 +154,12 @@ if (contractCode) { initCode: contractCode ? "0x" : initCode, callData: encodeCallData({ to: senderAddress, - data: "0xe75235b8", // getThreshold() of the Safe + data: "0x", // getThreshold() of the Safe value: 0n, }), - callGasLimit: 100_000n, // hardcode it for now at a high value - verificationGasLimit: 500_000n, // hardcode it for now at a high value - preVerificationGas: 50_000n, // hardcode it for now at a high value + callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value + verificationGasLimit: 500_000n, + preVerificationGas: 50_000n, maxFeePerGas: gasPriceResult.fast.maxFeePerGas, maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index af37f750..68e815d2 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -23,17 +23,28 @@ import { dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.PIMLICO_ERC20_NONCE); +const entryPointAddress = process.env + .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.PIMLICO_ERC20_NONCE as string); const chain = process.env.PIMLICO_CHAIN; const chainID = Number(process.env.PIMLICO_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.PIMLICO_RPC_URL; const apiKey = process.env.PIMLICO_API_KEY; -const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; -const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; -const erc20TokenAddress = process.env.PIMLICO_ERC20_TOKEN_CONTRACT; +const erc20PaymasterAddress = process.env + .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; +const usdcTokenAddress = process.env + .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; +const erc20TokenAddress = process.env + .PIMLICO_ERC20_TOKEN_CONTRACT as `0x${string}`; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -66,22 +77,18 @@ if (chain == "goerli") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); @@ -90,30 +97,17 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); - -if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); @@ -216,9 +210,9 @@ const sponsoredUserOperation: UserOperation = { data: generateTransferCallData(signer.address, erc20Amount), value: 0n, }), - callGasLimit: 100_000n, // hardcode it for now at a high value - verificationGasLimit: 500_000n, // hardcode it for now at a high value - preVerificationGas: 50_000n, // hardcode it for now at a high value + callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value + verificationGasLimit: 500_000n, + preVerificationGas: 50_000n, maxFeePerGas: gasPriceResult.fast.maxFeePerGas, maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index 8d2adb27..e63e8c3d 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -19,17 +19,28 @@ import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); const paymaster = "pimlico"; const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env.PIMLICO_ENTRYPOINT_ADDRESS; -const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS; -const saltNonce = BigInt(process.env.PIMLICO_ERC721_NONCE); +const entryPointAddress = process.env + .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; +const saltNonce = BigInt(process.env.PIMLICO_ERC721_NONCE as string); const chain = process.env.PIMLICO_CHAIN; const chainID = Number(process.env.PIMLICO_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION; +const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.PIMLICO_RPC_URL; const apiKey = process.env.PIMLICO_API_KEY; -const erc20PaymasterAddress = process.env.PIMLICO_ERC20_PAYMASTER_ADDRESS; -const usdcTokenAddress = process.env.PIMLICO_USDC_TOKEN_ADDRESS; -const erc721TokenAddress = process.env.PIMLICO_ERC721_TOKEN_CONTRACT; +const erc20PaymasterAddress = process.env + .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; +const usdcTokenAddress = process.env + .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; +const erc721TokenAddress = process.env + .PIMLICO_ERC721_TOKEN_CONTRACT as `0x${string}`; +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} if (apiKey === undefined) { throw new Error( @@ -62,22 +73,18 @@ if (chain == "goerli") { }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } const initCode = await getAccountInitCode({ owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); @@ -86,30 +93,17 @@ console.log("\nInit Code Created."); const senderAddress = await getAccountAddress({ client: publicClient, owner: signer.address, - addModuleLibAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_SINGLETON_ADDRESS, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, saltNonce: saltNonce, - multiSendAddress, + multiSendAddress: multiSendAddress, erc20TokenAddress: usdcTokenAddress, paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); - -if (chain == "goerli") { - console.log( - "Address Link: https://goerli.etherscan.io/address/" + senderAddress, - ); -} else { - throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", - ); -} +console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); @@ -172,9 +166,9 @@ const sponsoredUserOperation: UserOperation = { data: generateMintingCallData(senderAddress), value: 0n, }), - callGasLimit: 100_000n, // hardcode it for now at a high value - verificationGasLimit: 500_000n, // hardcode it for now at a high value - preVerificationGas: 50_000n, // hardcode it for now at a high value + callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value + verificationGasLimit: 500_000n, + preVerificationGas: 50_000n, maxFeePerGas: gasPriceResult.fast.maxFeePerGas, maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field diff --git a/4337-gas-metering/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts index da724541..aa948400 100644 --- a/4337-gas-metering/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -106,7 +106,7 @@ export const mintERC20Token = async ( publicClient: any, signer: PrivateKeyAccount, to: string, - amount: number, + amount: BigInt, chain: string, paymaster: string, ) => { @@ -138,7 +138,7 @@ export const mintERC20Token = async ( }); } else { throw new Error( - "Current code only support Sepolia and Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } } else { diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 9a0182b5..e9c78296 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -1,12 +1,34 @@ import dotenv from "dotenv"; import type { Address } from "abitype"; -import type { PrivateKeyAccount } from "viem"; +import type { Hex, PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; -import { UserOperation } from "permissionless"; dotenv.config(); const safeVersion = process.env.SAFE_VERSION; +export type UserOperation = { + sender: Address; + nonce: bigint; + initCode: Hex; + callData: Hex; + callGasLimit: bigint; + verificationGasLimit: bigint; + preVerificationGas: bigint; + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + paymasterAndData: Hex; + signature: Hex; +}; + +export type suoData = { + preVerificationGas: any; + callGasLimit: any; + verificationGasLimit: any; + paymasterAndData: any; + maxFeePerGas: any; + maxPriorityFeePerGas: any; +}; + export const submitUserOperation = async ( userOperation: UserOperation, bundlerClient: any, From 93470678a218ef1ff304d6353dcc9a933291e183 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 14 Dec 2023 20:11:36 +0530 Subject: [PATCH 34/60] Cleaned up --- 4337-gas-metering/alchemy/alchemy-account.ts | 9 +++++++-- 4337-gas-metering/alchemy/alchemy-erc20.ts | 6 ++++-- 4337-gas-metering/alchemy/alchemy-erc721.ts | 4 +++- 4337-gas-metering/pimlico/pimlico-account.ts | 4 +++- 4337-gas-metering/pimlico/pimlico-erc20.ts | 4 +++- 4337-gas-metering/pimlico/pimlico-erc721.ts | 4 +++- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/4337-gas-metering/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts index b0439c05..47a112af 100644 --- a/4337-gas-metering/alchemy/alchemy-account.ts +++ b/4337-gas-metering/alchemy/alchemy-account.ts @@ -90,7 +90,9 @@ const senderAddress = await getAccountAddress({ paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); const newNonce = await getAccountNonce(publicClient, { entryPoint: entryPointAddress, @@ -155,7 +157,10 @@ if (contractCode) { let responseValues; - await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + gasOptions, + ) .then((response) => response.json()) .then((response) => (responseValues = response)) .catch((err) => console.error(err)); diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy-erc20.ts index 52c61f93..bd9bdbc6 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy-erc20.ts @@ -99,7 +99,9 @@ const senderAddress = await getAccountAddress({ paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); // Token Configurations const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); @@ -210,7 +212,7 @@ const gasOptions = { let responseValues; -await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions,) +await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) .then((response) => response.json()) .then((response) => (responseValues = response)) .catch((err) => console.error(err)); diff --git a/4337-gas-metering/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts index 2a616371..ac8c83e9 100644 --- a/4337-gas-metering/alchemy/alchemy-erc721.ts +++ b/4337-gas-metering/alchemy/alchemy-erc721.ts @@ -93,7 +93,9 @@ const senderAddress = await getAccountAddress({ paymasterAddress: zeroAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); const newNonce = await getAccountNonce(publicClient, { entryPoint: entryPointAddress, diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts index 0f8b8bc3..ed098bd1 100644 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ b/4337-gas-metering/pimlico/pimlico-account.ts @@ -101,7 +101,9 @@ const senderAddress = await getAccountAddress({ }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico-erc20.ts index 68e815d2..9ce822c4 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico-erc20.ts @@ -107,7 +107,9 @@ const senderAddress = await getAccountAddress({ paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts index e63e8c3d..8f57ea30 100644 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ b/4337-gas-metering/pimlico/pimlico-erc721.ts @@ -103,7 +103,9 @@ const senderAddress = await getAccountAddress({ paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log("Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); From e88bc2192736c8e14e05f7fc125f3ed5ae5e834b Mon Sep 17 00:00:00 2001 From: Shebin John Date: Fri, 15 Dec 2023 13:03:41 +0530 Subject: [PATCH 35/60] Alchemy Code Redundancy Removed --- 4337-gas-metering/.env.example | 4 +- 4337-gas-metering/alchemy/alchemy-account.ts | 278 ------------------ 4337-gas-metering/alchemy/alchemy-erc721.ts | 268 ----------------- .../alchemy/{alchemy-erc20.ts => alchemy.ts} | 135 ++++++--- 4337-gas-metering/package.json | 7 +- 5 files changed, 93 insertions(+), 599 deletions(-) delete mode 100644 4337-gas-metering/alchemy/alchemy-account.ts delete mode 100644 4337-gas-metering/alchemy/alchemy-erc721.ts rename 4337-gas-metering/alchemy/{alchemy-erc20.ts => alchemy.ts} (81%) diff --git a/4337-gas-metering/.env.example b/4337-gas-metering/.env.example index 93e0194f..1c0ad950 100644 --- a/4337-gas-metering/.env.example +++ b/4337-gas-metering/.env.example @@ -27,9 +27,7 @@ ALCHEMY_GAS_POLICY = "" # https://dashboard.alchemy.com/gas-manager ALCHEMY_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" # Make sure all nonce are unique for it to deploy account when run initially. -ALCHEMY_ACCOUNT_PAYMASTER_NONCE = "4" -ALCHEMY_ERC20_PAYMASTER_NONCE = "5" -ALCHEMY_ERC721_PAYMASTER_NONCE = "6" +ALCHEMY_NONCE = "2" # Alchemy Token Operation Values (Based on the chain selected, these values should also change accordingly.) ALCHEMY_ERC20_TOKEN_CONTRACT = "" ALCHEMY_ERC721_TOKEN_CONTRACT = "" diff --git a/4337-gas-metering/alchemy/alchemy-account.ts b/4337-gas-metering/alchemy/alchemy-account.ts deleted file mode 100644 index 47a112af..00000000 --- a/4337-gas-metering/alchemy/alchemy-account.ts +++ /dev/null @@ -1,278 +0,0 @@ -import dotenv from "dotenv"; -import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; -import { Hash, createPublicClient, http, zeroAddress } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { goerli, sepolia } from "viem/chains"; -import { - SAFE_ADDRESSES_MAP, - encodeCallData, - getAccountAddress, - getAccountInitCode, -} from "../utils/safe"; -import { setTimeout } from "timers/promises"; - -dotenv.config(); -const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env - .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; -const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.ALCHEMY_ACCOUNT_PAYMASTER_NONCE as string); -const chain = process.env.ALCHEMY_CHAIN; -const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION as string; -const rpcURL = process.env.ALCHEMY_RPC_URL; -const policyID = process.env.ALCHEMY_GAS_POLICY; -const apiKey = process.env.ALCHEMY_API_KEY; -const safeAddresses = ( - SAFE_ADDRESSES_MAP as Record> -)[safeVersion]; -let chainAddresses; -if (safeAddresses) { - chainAddresses = safeAddresses[chainID]; -} - -if (apiKey === undefined) { - throw new Error( - "Please replace the `apiKey` env variable with your Alchemy API key", - ); -} - -if (!privateKey) { - throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", - ); -} - -const signer = privateKeyToAccount(privateKey as Hash); -console.log("Signer Extracted from Private Key."); - -let publicClient; -if (chain == "sepolia") { - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: sepolia, - }); -} else if (chain == "goerli") { - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: goerli, - }); -} else { - throw new Error( - "Current code only support limited networks. Please make required changes if you want to use custom network.", - ); -} - -const initCode = await getAccountInitCode({ - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: zeroAddress, - paymasterAddress: zeroAddress, -}); -console.log("\nInit Code Created."); - -const senderAddress = await getAccountAddress({ - client: publicClient, - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: zeroAddress, - paymasterAddress: zeroAddress, -}); -console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log( - "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, -); - -const newNonce = await getAccountNonce(publicClient, { - entryPoint: entryPointAddress, - sender: senderAddress, -}); -console.log("\nNonce for the sender received from EntryPoint."); - -const contractCode = await publicClient.getBytecode({ address: senderAddress }); - -if (contractCode) { - console.log("\nThe Safe is already deployed.\n"); -} else { - console.log("\nDeploying a new Safe.\n"); - const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: senderAddress, - data: "0x", // getThreshold() of the Safe. TODO: Check if this could be removed. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", - }; - - sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, - ); - console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); - - const gasOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "alchemy_requestGasAndPaymasterAndData", - params: [ - { - policyId: policyID, - entryPoint: entryPointAddress, - dummySignature: sponsoredUserOperation.signature, - userOperation: { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - }, - }, - ], - }), - }; - - let responseValues; - - await fetch( - "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, - gasOptions, - ) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - console.log("\nReceived Paymaster Data from Alchemy."); - - let rv; - if (responseValues && responseValues["result"]) { - rv = responseValues["result"] as suoData; - } - - sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; - sponsoredUserOperation.callGasLimit = rv?.callGasLimit; - sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; - sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; - sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; - sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; - - sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, - ); - console.log( - "\nSigned Real Data including Paymaster Data Created by Alchemy.", - ); - - const options = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_sendUserOperation", - params: [ - { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - callGasLimit: sponsoredUserOperation.callGasLimit, - verificationGasLimit: sponsoredUserOperation.verificationGasLimit, - preVerificationGas: sponsoredUserOperation.preVerificationGas, - maxFeePerGas: sponsoredUserOperation.maxFeePerGas, - maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, - signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData, - }, - entryPointAddress, - ], - }), - }; - - await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - - if (responseValues && responseValues["result"]) { - console.log("UserOperation submitted. Hash:", responseValues["result"]); - console.log( - "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues["result"] + - "?network=" + - chain, - ); - - const hashOptions = { - method: "POST", - headers: { - accept: "application/json", - "content-type": "application/json", - }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_getUserOperationReceipt", - params: [responseValues["result"]], - entryPoint: entryPointAddress, - }), - }; - let runOnce = true; - - while (responseValues["result"] == null || runOnce) { - await setTimeout(25000); - await fetch( - "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, - hashOptions, - ) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - runOnce = false; - } - - if ( - responseValues["result"] && - responseValues["result"]["receipt"]["transactionHash"] - ) { - console.log( - "\nTransaction Link: https://" + - chain + - ".etherscan.io/tx/" + - responseValues["result"]["receipt"]["transactionHash"] + - "\n", - ); - } else { - console.log("\n" + responseValues["error"]); - } - } else { - if (responseValues && responseValues["error"]["message"]) { - console.log("\n" + responseValues["error"]["message"]); - } - } -} diff --git a/4337-gas-metering/alchemy/alchemy-erc721.ts b/4337-gas-metering/alchemy/alchemy-erc721.ts deleted file mode 100644 index ac8c83e9..00000000 --- a/4337-gas-metering/alchemy/alchemy-erc721.ts +++ /dev/null @@ -1,268 +0,0 @@ -import dotenv from "dotenv"; -import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; -import { Hash, createPublicClient, http, zeroAddress } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { goerli, sepolia } from "viem/chains"; -import { - SAFE_ADDRESSES_MAP, - encodeCallData, - getAccountAddress, - getAccountInitCode, -} from "../utils/safe"; -import { generateMintingCallData } from "../utils/erc721"; -import { setTimeout } from "timers/promises"; - -dotenv.config(); -const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env - .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; -const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.ALCHEMY_ERC721_PAYMASTER_NONCE as string); -const chain = process.env.ALCHEMY_CHAIN; -const chainID = Number(process.env.ALCHEMY_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION as string; -const rpcURL = process.env.ALCHEMY_RPC_URL; -const policyID = process.env.ALCHEMY_GAS_POLICY; -const apiKey = process.env.ALCHEMY_API_KEY; -const erc721TokenAddress = process.env - .ALCHEMY_ERC721_TOKEN_CONTRACT as `0x${string}`; -const safeAddresses = ( - SAFE_ADDRESSES_MAP as Record> -)[safeVersion]; -let chainAddresses; -if (safeAddresses) { - chainAddresses = safeAddresses[chainID]; -} - -if (apiKey === undefined) { - throw new Error( - "Please replace the `apiKey` env variable with your Alchemy API key", - ); -} - -if (!privateKey) { - throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", - ); -} - -const signer = privateKeyToAccount(privateKey as Hash); -console.log("Signer Extracted from Private Key."); - -let publicClient; -if (chain == "sepolia") { - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: sepolia, - }); -} else if (chain == "goerli") { - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: goerli, - }); -} else { - throw new Error( - "Current code only support limited networks. Please make required changes if you want to use custom network.", - ); -} - -const initCode = await getAccountInitCode({ - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: zeroAddress, - paymasterAddress: zeroAddress, -}); -console.log("\nInit Code Created."); - -const senderAddress = await getAccountAddress({ - client: publicClient, - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: zeroAddress, - paymasterAddress: zeroAddress, -}); -console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log( - "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, -); - -const newNonce = await getAccountNonce(publicClient, { - entryPoint: entryPointAddress, - sender: senderAddress, -}); -console.log("\nNonce for the sender received from EntryPoint."); - -const contractCode = await publicClient.getBytecode({ address: senderAddress }); - -const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: erc721TokenAddress, - data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. - value: 0n, - }), - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymasterAndData: "0x", - signature: "0x", -}; - -sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, -); -console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); - -const gasOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "alchemy_requestGasAndPaymasterAndData", - params: [ - { - policyId: policyID, - entryPoint: entryPointAddress, - dummySignature: sponsoredUserOperation.signature, - userOperation: { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - }, - }, - ], - }), -}; - -let responseValues; - -await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -console.log("\nReceived Paymaster Data from Alchemy."); - -let rv; -if (responseValues && responseValues["result"]) { - rv = responseValues["result"] as suoData; -} - -sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; -sponsoredUserOperation.callGasLimit = rv?.callGasLimit; -sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; -sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; - -sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, -); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); - -const options = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_sendUserOperation", - params: [ - { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - callGasLimit: sponsoredUserOperation.callGasLimit, - verificationGasLimit: sponsoredUserOperation.verificationGasLimit, - preVerificationGas: sponsoredUserOperation.preVerificationGas, - maxFeePerGas: sponsoredUserOperation.maxFeePerGas, - maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, - signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData, - }, - entryPointAddress, - ], - }), -}; - -await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - -if (responseValues && responseValues["result"]) { - console.log("UserOperation submitted. Hash:", responseValues["result"]); - console.log( - "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues["result"] + - "?network=" + - chain, - ); - - const hashOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_getUserOperationReceipt", - params: [responseValues["result"]], - entryPoint: entryPointAddress, - }), - }; - let runOnce = true; - - while (responseValues["result"] == null || runOnce) { - await setTimeout(25000); - await fetch( - "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, - hashOptions, - ) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - runOnce = false; - } - - if ( - responseValues["result"] && - responseValues["result"]["receipt"]["transactionHash"] - ) { - console.log( - "\nTransaction Link: https://" + - chain + - ".etherscan.io/tx/" + - responseValues["result"]["receipt"]["transactionHash"] + - "\n", - ); - } else { - console.log("\n" + responseValues["error"]); - } -} else { - if (responseValues && responseValues["error"]["message"]) { - console.log("\n" + responseValues["error"]["message"]); - } -} diff --git a/4337-gas-metering/alchemy/alchemy-erc20.ts b/4337-gas-metering/alchemy/alchemy.ts similarity index 81% rename from 4337-gas-metering/alchemy/alchemy-erc20.ts rename to 4337-gas-metering/alchemy/alchemy.ts index bd9bdbc6..a9b22886 100644 --- a/4337-gas-metering/alchemy/alchemy-erc20.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -16,23 +16,44 @@ import { getERC20Balance, mintERC20Token, } from "../utils/erc20"; +import { generateMintingCallData } from "../utils/erc721"; import { setTimeout } from "timers/promises"; dotenv.config(); const paymaster = "alchemy"; + const privateKey = process.env.PRIVATE_KEY; + const entryPointAddress = process.env .ALCHEMY_ENTRYPOINT_ADDRESS as `0x${string}`; const multiSendAddress = process.env.ALCHEMY_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.ALCHEMY_ERC20_PAYMASTER_NONCE as string); + +const saltNonce = BigInt(process.env.ALCHEMY_NONCE as string); + const chain = process.env.ALCHEMY_CHAIN; const chainID = Number(process.env.ALCHEMY_CHAIN_ID); + const safeVersion = process.env.SAFE_VERSION as string; + const rpcURL = process.env.ALCHEMY_RPC_URL; const policyID = process.env.ALCHEMY_GAS_POLICY; const apiKey = process.env.ALCHEMY_API_KEY; + const erc20TokenAddress = process.env .ALCHEMY_ERC20_TOKEN_CONTRACT as `0x${string}`; +const erc721TokenAddress = process.env + .ALCHEMY_ERC721_TOKEN_CONTRACT as `0x${string}`; + +const argv = process.argv.slice(2); +if (argv.length != 1) { + throw new Error("TX Type Argument not passed."); // account || erc20 || erc721 +} + +const txType: string = argv[0]; +if (txType != "account" && txType != "erc20" && txType != "erc721") { + throw new Error("TX Type Argument Invalid"); +} + const safeAddresses = ( SAFE_ADDRESSES_MAP as Record> )[safeVersion]; @@ -103,43 +124,17 @@ console.log( "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, ); -// Token Configurations -const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); -const erc20Amount = BigInt(10 ** erc20Decimals); -let senderERC20Balance = await getERC20Balance( - erc20TokenAddress, - publicClient, - senderAddress, -); -console.log( - "\nSafe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), -); - -// Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). -if (senderERC20Balance < erc20Amount) { - console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token( - erc20TokenAddress, - publicClient, - signer, - senderAddress, - erc20Amount, - chain, - paymaster, - ); +const contractCode = await publicClient.getBytecode({ address: senderAddress }); - while (senderERC20Balance < erc20Amount) { - await setTimeout(15000); - senderERC20Balance = await getERC20Balance( - erc20TokenAddress, - publicClient, - senderAddress, - ); +if (contractCode) { + console.log("\nThe Safe is already deployed."); + if (txType == "account") { + console.log("\n"); + process.exit(0); } +} else { console.log( - "\nUpdated Safe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), + "\nDeploying a new Safe and executing calldata passed with it (if any).", ); } @@ -149,27 +144,73 @@ const newNonce = await getAccountNonce(publicClient, { }); console.log("\nNonce for the sender received from EntryPoint."); -const contractCode = await publicClient.getBytecode({ address: senderAddress }); +let txCallData!: `0x${string}`; -if (contractCode) { - console.log( - "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n", +if (txType == "account") { + txCallData = encodeCallData({ + to: senderAddress, + data: "0x", + value: 0n, + }); +} else if (txType == "erc20") { + // Token Configurations + const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); + const erc20Amount = BigInt(10 ** erc20Decimals); + let senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, ); -} else { console.log( - "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n", + "\nSafe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), ); + + // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). + if (senderERC20Balance < erc20Amount) { + console.log("\nMinting ERC20 Tokens to Safe Wallet."); + await mintERC20Token( + erc20TokenAddress, + publicClient, + signer, + senderAddress, + erc20Amount, + chain, + paymaster, + ); + + while (senderERC20Balance < erc20Amount) { + await setTimeout(15000); + senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); + } + console.log( + "\nUpdated Safe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), + ); + } + + txCallData = encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. + value: 0n, + }); +} else if (txType == "erc721") { + txCallData = encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. + value: 0n, + }); } const sponsoredUserOperation: UserOperation = { sender: senderAddress, nonce: newNonce, initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: erc20TokenAddress, - data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. - value: 0n, - }), + callData: txCallData, callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data verificationGasLimit: 0n, preVerificationGas: 0n, @@ -236,7 +277,7 @@ sponsoredUserOperation.signature = await signUserOperation( chainID, entryPointAddress, ); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy."); +console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.\n"); const options = { method: "POST", diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index 2fe116f0..c1600d05 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -10,9 +10,10 @@ "pimlico:account": "tsx ./pimlico/pimlico-account.ts", "pimlico:erc20": "tsx ./pimlico/pimlico-erc20.ts", "pimlico:erc721": "tsx ./pimlico/pimlico-erc721.ts", - "alchemy:account": "tsx ./alchemy/alchemy-account.ts", - "alchemy:erc20": "tsx ./alchemy/alchemy-erc20.ts", - "alchemy:erc721": "tsx ./alchemy/alchemy-erc721.ts", + "alchemy:account": "tsx ./alchemy/alchemy.ts account", + "alchemy:erc20": "tsx ./alchemy/alchemy.ts erc20", + "alchemy:erc721": "tsx ./alchemy/alchemy.ts erc721", + "alchemy": "tsx ./alchemy/alchemy.ts", "fmt": "prettier --ignore-path .gitignore --write ." }, "repository": { From 224dce0d565a49e2ebf714122cb1aee091af0603 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Fri, 15 Dec 2023 14:36:42 +0530 Subject: [PATCH 36/60] Pimlico Code Redundancy Removed --- 4337-gas-metering/.env.example | 6 +- 4337-gas-metering/alchemy/alchemy.ts | 4 +- 4337-gas-metering/package.json | 7 +- 4337-gas-metering/pimlico/pimlico-account.ts | 184 ----------------- 4337-gas-metering/pimlico/pimlico-erc721.ts | 192 ------------------ .../pimlico/{pimlico-erc20.ts => pimlico.ts} | 149 +++++++++----- 6 files changed, 103 insertions(+), 439 deletions(-) delete mode 100644 4337-gas-metering/pimlico/pimlico-account.ts delete mode 100644 4337-gas-metering/pimlico/pimlico-erc721.ts rename 4337-gas-metering/pimlico/{pimlico-erc20.ts => pimlico.ts} (72%) diff --git a/4337-gas-metering/.env.example b/4337-gas-metering/.env.example index 1c0ad950..cbf889d1 100644 --- a/4337-gas-metering/.env.example +++ b/4337-gas-metering/.env.example @@ -9,11 +9,9 @@ PIMLICO_API_KEY = "" # https://dashboard.pimlico.io/apikeys PIMLICO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" PIMLICO_ERC20_PAYMASTER_ADDRESS = "0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009" -PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" # https://usdcfaucet.com/ +PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" # https://faucet.circle.com/ # Make sure all nonce are unique for it to deploy account when run initially. -PIMLICO_ACCOUNT_NONCE = "1" -PIMLICO_ERC20_NONCE = "2" -PIMLICO_ERC721_NONCE = "3" +PIMLICO_NONCE = "1" # Pimlico Token Operation Values (Based on the chain selected, these values should also change accordingly.) PIMLICO_ERC20_TOKEN_CONTRACT = "" PIMLICO_ERC721_TOKEN_CONTRACT = "" diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index a9b22886..9d8f43e4 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -277,7 +277,9 @@ sponsoredUserOperation.signature = await signUserOperation( chainID, entryPointAddress, ); -console.log("\nSigned Real Data including Paymaster Data Created by Alchemy.\n"); +console.log( + "\nSigned Real Data including Paymaster Data Created by Alchemy.\n", +); const options = { method: "POST", diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index c1600d05..2c1500cc 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -7,9 +7,10 @@ "type": "module", "private": true, "scripts": { - "pimlico:account": "tsx ./pimlico/pimlico-account.ts", - "pimlico:erc20": "tsx ./pimlico/pimlico-erc20.ts", - "pimlico:erc721": "tsx ./pimlico/pimlico-erc721.ts", + "pimlico:account": "tsx ./pimlico/pimlico.ts account", + "pimlico:erc20": "tsx ./pimlico/pimlico.ts erc20", + "pimlico:erc721": "tsx ./pimlico/pimlico.ts erc721", + "pimlico": "tsx ./pimlico/pimlico.ts", "alchemy:account": "tsx ./alchemy/alchemy.ts account", "alchemy:erc20": "tsx ./alchemy/alchemy.ts erc20", "alchemy:erc721": "tsx ./alchemy/alchemy.ts erc721", diff --git a/4337-gas-metering/pimlico/pimlico-account.ts b/4337-gas-metering/pimlico/pimlico-account.ts deleted file mode 100644 index ed098bd1..00000000 --- a/4337-gas-metering/pimlico/pimlico-account.ts +++ /dev/null @@ -1,184 +0,0 @@ -import dotenv from "dotenv"; -import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions } from "permissionless"; -import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Hash, createClient, createPublicClient, http } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { goerli } from "viem/chains"; -import { - SAFE_ADDRESSES_MAP, - encodeCallData, - getAccountAddress, - getAccountInitCode, -} from "../utils/safe"; -import { submitUserOperation, signUserOperation } from "../utils/userOps"; -import { setTimeout } from "timers/promises"; -import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; - -dotenv.config(); -const paymaster = "pimlico"; -const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env - .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; -const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.PIMLICO_ACCOUNT_NONCE as string); -const chain = process.env.PIMLICO_CHAIN; -const chainID = Number(process.env.PIMLICO_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION as string; -const rpcURL = process.env.PIMLICO_RPC_URL; -const apiKey = process.env.PIMLICO_API_KEY; -const erc20PaymasterAddress = process.env - .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; -const usdcTokenAddress = process.env - .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; -const safeAddresses = ( - SAFE_ADDRESSES_MAP as Record> -)[safeVersion]; -let chainAddresses; -if (safeAddresses) { - chainAddresses = safeAddresses[chainID]; -} - -if (apiKey === undefined) { - throw new Error( - "Please replace the `apiKey` env variable with your Pimlico API key", - ); -} - -if (!privateKey) { - throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", - ); -} - -const signer = privateKeyToAccount(privateKey as Hash); -console.log("Signer Extracted from Private Key."); - -let bundlerClient; -let publicClient; -if (chain == "goerli") { - bundlerClient = createClient({ - transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), - chain: goerli, - }) - .extend(bundlerActions) - .extend(pimlicoBundlerActions); - - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: goerli, - }); -} else { - throw new Error( - "Pimlico code only support Goerli. Please make required changes if you want to use custom network.", - ); -} - -const initCode = await getAccountInitCode({ - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: usdcTokenAddress, - paymasterAddress: erc20PaymasterAddress, -}); -console.log("\nInit Code Created."); - -const senderAddress = await getAccountAddress({ - client: publicClient, - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: usdcTokenAddress, - paymasterAddress: erc20PaymasterAddress, -}); -console.log("\nCounterfactual Sender Address Created:", senderAddress); - -console.log( - "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, -); - -const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); -const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, - publicClient, - senderAddress, -); -console.log( - "\nSafe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), -); - -if (senderUSDCBalance < usdcAmount) { - console.log( - "\nPlease deposit atleast 1 USDC Token for paying the Paymaster.", - ); - while (senderUSDCBalance < usdcAmount) { - await setTimeout(30000); - senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, - publicClient, - senderAddress, - ); - } - console.log( - "\nUpdated Safe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), - ); -} - -const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); - -const newNonce = await getAccountNonce(publicClient, { - entryPoint: entryPointAddress, - sender: senderAddress, -}); -console.log("\nNonce for the sender received from EntryPoint."); - -const contractCode = await publicClient.getBytecode({ address: senderAddress }); - -if (contractCode) { - console.log("\nThe Safe is already deployed.\n"); -} else { - console.log("\nDeploying a new Safe.\n"); - - const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: encodeCallData({ - to: senderAddress, - data: "0x", // getThreshold() of the Safe - value: 0n, - }), - callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value - verificationGasLimit: 500_000n, - preVerificationGas: 50_000n, - maxFeePerGas: gasPriceResult.fast.maxFeePerGas, - maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, - paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field - signature: "0x", - }; - - sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, - ); - - await submitUserOperation( - sponsoredUserOperation, - bundlerClient, - entryPointAddress, - chain, - ); -} diff --git a/4337-gas-metering/pimlico/pimlico-erc721.ts b/4337-gas-metering/pimlico/pimlico-erc721.ts deleted file mode 100644 index 8f57ea30..00000000 --- a/4337-gas-metering/pimlico/pimlico-erc721.ts +++ /dev/null @@ -1,192 +0,0 @@ -import dotenv from "dotenv"; -import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions } from "permissionless"; -import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Hash, createClient, createPublicClient, http } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { goerli } from "viem/chains"; -import { - SAFE_ADDRESSES_MAP, - encodeCallData, - getAccountAddress, - getAccountInitCode, -} from "../utils/safe"; -import { submitUserOperation, signUserOperation } from "../utils/userOps"; -import { setTimeout } from "timers/promises"; -import { getERC20Decimals, getERC20Balance } from "../utils/erc20"; -import { generateMintingCallData } from "../utils/erc721"; - -dotenv.config(); -const paymaster = "pimlico"; -const privateKey = process.env.PRIVATE_KEY; -const entryPointAddress = process.env - .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; -const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.PIMLICO_ERC721_NONCE as string); -const chain = process.env.PIMLICO_CHAIN; -const chainID = Number(process.env.PIMLICO_CHAIN_ID); -const safeVersion = process.env.SAFE_VERSION as string; -const rpcURL = process.env.PIMLICO_RPC_URL; -const apiKey = process.env.PIMLICO_API_KEY; -const erc20PaymasterAddress = process.env - .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; -const usdcTokenAddress = process.env - .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; -const erc721TokenAddress = process.env - .PIMLICO_ERC721_TOKEN_CONTRACT as `0x${string}`; -const safeAddresses = ( - SAFE_ADDRESSES_MAP as Record> -)[safeVersion]; -let chainAddresses; -if (safeAddresses) { - chainAddresses = safeAddresses[chainID]; -} - -if (apiKey === undefined) { - throw new Error( - "Please replace the `apiKey` env variable with your Pimlico API key", - ); -} - -if (!privateKey) { - throw new Error( - "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", - ); -} - -const signer = privateKeyToAccount(privateKey as Hash); -console.log("Signer Extracted from Private Key."); - -let bundlerClient; -let publicClient; -if (chain == "goerli") { - bundlerClient = createClient({ - transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), - chain: goerli, - }) - .extend(bundlerActions) - .extend(pimlicoBundlerActions); - - publicClient = createPublicClient({ - transport: http(rpcURL), - chain: goerli, - }); -} else { - throw new Error( - "Current code only support limited networks. Please make required changes if you want to use custom network.", - ); -} - -const initCode = await getAccountInitCode({ - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: usdcTokenAddress, - paymasterAddress: erc20PaymasterAddress, -}); -console.log("\nInit Code Created."); - -const senderAddress = await getAccountAddress({ - client: publicClient, - owner: signer.address, - addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, - safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, - safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, - safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, - saltNonce: saltNonce, - multiSendAddress: multiSendAddress, - erc20TokenAddress: usdcTokenAddress, - paymasterAddress: erc20PaymasterAddress, -}); -console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log( - "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, -); - -// Fetch USDC balance of sender -const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); -const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, - publicClient, - senderAddress, -); -console.log( - "\nSafe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), -); - -if (senderUSDCBalance < BigInt(2) * usdcAmount) { - console.log( - "\nPlease deposit atleast 2 USDC Token for paying the Paymaster.", - ); - while (senderUSDCBalance < BigInt(2) * usdcAmount) { - await setTimeout(30000); - senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, - publicClient, - senderAddress, - ); - } - console.log( - "\nUpdated Safe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), - ); -} - -const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); - -const newNonce = await getAccountNonce(publicClient, { - entryPoint: entryPointAddress, - sender: senderAddress, -}); -console.log("\nNonce for the sender received from EntryPoint."); - -const contractCode = await publicClient.getBytecode({ address: senderAddress }); - -if (contractCode) { - console.log( - "The Safe is already deployed. Minting 1 ERC721 Token to the Safe.", - ); -} else { - console.log( - "Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx", - ); -} - -const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - // Minting 1 ERC721 Token to the Safe itself - callData: encodeCallData({ - to: erc721TokenAddress, - data: generateMintingCallData(senderAddress), - value: 0n, - }), - callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value - verificationGasLimit: 500_000n, - preVerificationGas: 50_000n, - maxFeePerGas: gasPriceResult.fast.maxFeePerGas, - maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, - paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field - signature: "0x", -}; - -sponsoredUserOperation.signature = await signUserOperation( - sponsoredUserOperation, - signer, - chainID, - entryPointAddress, -); - -await submitUserOperation( - sponsoredUserOperation, - bundlerClient, - entryPointAddress, - chain, -); diff --git a/4337-gas-metering/pimlico/pimlico-erc20.ts b/4337-gas-metering/pimlico/pimlico.ts similarity index 72% rename from 4337-gas-metering/pimlico/pimlico-erc20.ts rename to 4337-gas-metering/pimlico/pimlico.ts index 9ce822c4..0b0392b6 100644 --- a/4337-gas-metering/pimlico/pimlico-erc20.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -19,25 +19,46 @@ import { getERC20Balance, mintERC20Token, } from "../utils/erc20"; +import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); const paymaster = "pimlico"; + const privateKey = process.env.PRIVATE_KEY; + const entryPointAddress = process.env .PIMLICO_ENTRYPOINT_ADDRESS as `0x${string}`; const multiSendAddress = process.env.PIMLICO_MULTISEND_ADDRESS as `0x${string}`; -const saltNonce = BigInt(process.env.PIMLICO_ERC20_NONCE as string); + +const saltNonce = BigInt(process.env.PIMLICO_NONCE as string); + const chain = process.env.PIMLICO_CHAIN; const chainID = Number(process.env.PIMLICO_CHAIN_ID); + const safeVersion = process.env.SAFE_VERSION as string; + const rpcURL = process.env.PIMLICO_RPC_URL; const apiKey = process.env.PIMLICO_API_KEY; + const erc20PaymasterAddress = process.env .PIMLICO_ERC20_PAYMASTER_ADDRESS as `0x${string}`; const usdcTokenAddress = process.env .PIMLICO_USDC_TOKEN_ADDRESS as `0x${string}`; const erc20TokenAddress = process.env .PIMLICO_ERC20_TOKEN_CONTRACT as `0x${string}`; +const erc721TokenAddress = process.env + .PIMLICO_ERC721_TOKEN_CONTRACT as `0x${string}`; + +const argv = process.argv.slice(2); +if (argv.length != 1) { + throw new Error("TX Type Argument not passed."); // account || erc20 || erc721 +} + +const txType: string = argv[0]; +if (txType != "account" && txType != "erc20" && txType != "erc721") { + throw new Error("TX Type Argument Invalid"); +} + const safeAddresses = ( SAFE_ADDRESSES_MAP as Record> )[safeVersion]; @@ -111,6 +132,26 @@ console.log( "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, ); +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +if (contractCode) { + console.log("\nThe Safe is already deployed."); + if (txType == "account") { + console.log(""); + process.exit(0); + } +} else { + console.log( + "\nDeploying a new Safe and executing calldata passed with it (if any).\n", + ); +} + +const newNonce = await getAccountNonce(publicClient, { + entryPoint: entryPointAddress, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint."); + // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); @@ -124,11 +165,11 @@ console.log( Number(senderUSDCBalance / usdcAmount), ); -if (senderUSDCBalance < BigInt(2) * usdcAmount) { +if (senderUSDCBalance < BigInt(1) * usdcAmount) { console.log( "\nPlease deposit atleast 2 USDC Token for paying the Paymaster.", ); - while (senderUSDCBalance < BigInt(2) * usdcAmount) { + while (senderUSDCBalance < BigInt(1) * usdcAmount) { await setTimeout(30000); senderUSDCBalance = await getERC20Balance( usdcTokenAddress, @@ -142,82 +183,80 @@ if (senderUSDCBalance < BigInt(2) * usdcAmount) { ); } -// Token Configurations -const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); -const erc20Amount = BigInt(10 ** erc20Decimals); -let senderERC20Balance = await getERC20Balance( - erc20TokenAddress, - publicClient, - senderAddress, -); -console.log( - "\nSafe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), -); +let txCallData!: `0x${string}`; -// Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). -if (senderERC20Balance < erc20Amount) { - console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token( +if (txType == "account") { + txCallData = encodeCallData({ + to: senderAddress, + data: "0x", + value: 0n, + }); +} else if (txType == "erc20") { + // Token Configurations + const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); + const erc20Amount = BigInt(10 ** erc20Decimals); + let senderERC20Balance = await getERC20Balance( erc20TokenAddress, publicClient, - signer, senderAddress, - erc20Amount, - chain, - paymaster, + ); + console.log( + "\nSafe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), ); - while (senderERC20Balance < erc20Amount) { - await setTimeout(15000); - senderERC20Balance = await getERC20Balance( + // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). + if (senderERC20Balance < erc20Amount) { + console.log("\nMinting ERC20 Tokens to Safe Wallet."); + await mintERC20Token( erc20TokenAddress, publicClient, + signer, senderAddress, + erc20Amount, + chain, + paymaster, + ); + + while (senderERC20Balance < erc20Amount) { + await setTimeout(15000); + senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); + } + console.log( + "\nUpdated Safe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), ); } - console.log( - "\nUpdated Safe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), - ); + txCallData = encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. + value: 0n, + }); +} else if (txType == "erc721") { + txCallData = encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. + value: 0n, + }); } const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); -const newNonce = await getAccountNonce(publicClient, { - entryPoint: entryPointAddress, - sender: senderAddress, -}); -console.log("\nNonce for the sender received from EntryPoint."); - -const contractCode = await publicClient.getBytecode({ address: senderAddress }); - -if (contractCode) { - console.log( - "\nThe Safe is already deployed. Sending 1 ERC20 from the Safe to Signer.\n", - ); -} else { - console.log( - "\nDeploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx.\n", - ); -} - const sponsoredUserOperation: UserOperation = { sender: senderAddress, nonce: newNonce, initCode: contractCode ? "0x" : initCode, - // Send 1 ERC20 to the signer. - callData: encodeCallData({ - to: erc20TokenAddress, - data: generateTransferCallData(signer.address, erc20Amount), - value: 0n, - }), + callData: txCallData, callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value verificationGasLimit: 500_000n, preVerificationGas: 50_000n, maxFeePerGas: gasPriceResult.fast.maxFeePerGas, maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, - paymasterAndData: erc20PaymasterAddress, // to use the erc20 paymaster, put its address in the paymasterAndData field + paymasterAndData: erc20PaymasterAddress, signature: "0x", }; From 9034d2b1c3f6712f37efb40e1b58c50d3b496d44 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Fri, 15 Dec 2023 16:29:20 +0530 Subject: [PATCH 37/60] signUserOperation was updated --- 4337-gas-metering/alchemy/alchemy.ts | 2 ++ 4337-gas-metering/pimlico/pimlico.ts | 1 + 4337-gas-metering/utils/userOps.ts | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index 9d8f43e4..7173418b 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -225,6 +225,7 @@ sponsoredUserOperation.signature = await signUserOperation( signer, chainID, entryPointAddress, + chainAddresses.SAFE_4337_MODULE_ADDRESS, ); console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); @@ -276,6 +277,7 @@ sponsoredUserOperation.signature = await signUserOperation( signer, chainID, entryPointAddress, + chainAddresses.SAFE_4337_MODULE_ADDRESS, ); console.log( "\nSigned Real Data including Paymaster Data Created by Alchemy.\n", diff --git a/4337-gas-metering/pimlico/pimlico.ts b/4337-gas-metering/pimlico/pimlico.ts index 0b0392b6..ee448a9e 100644 --- a/4337-gas-metering/pimlico/pimlico.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -265,6 +265,7 @@ sponsoredUserOperation.signature = await signUserOperation( signer, chainID, entryPointAddress, + chainAddresses.SAFE_4337_MODULE_ADDRESS, ); await submitUserOperation( diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index e9c78296..6afad37c 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -65,6 +65,7 @@ export const signUserOperation = async ( signer: PrivateKeyAccount, chainID: any, entryPointAddress: any, + safe4337ModuleAddress: any, ) => { const signatures = [ { @@ -72,8 +73,7 @@ export const signUserOperation = async ( data: await signer.signTypedData({ domain: { chainId: chainID, - verifyingContract: - SAFE_ADDRESSES_MAP[safeVersion][chainID].SAFE_4337_MODULE_ADDRESS, + verifyingContract: safe4337ModuleAddress, }, types: EIP712_SAFE_OPERATION_TYPE, primaryType: "SafeOp", From a9df6124339ceb8a1f179d03c7c009af9592082d Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 17:57:04 +0530 Subject: [PATCH 38/60] env file updated --- 4337-gas-metering/.env.example | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/4337-gas-metering/.env.example b/4337-gas-metering/.env.example index cbf889d1..c85000a7 100644 --- a/4337-gas-metering/.env.example +++ b/4337-gas-metering/.env.example @@ -1,22 +1,23 @@ # Dummy ETH Address Private Key PRIVATE_KEY = "0x..." # Add "0x" to Private Key if not already added. -# Pimlico Values (At the time of writing this, Pimlico doesn't support Sepolia, so using Goerli.) -PIMLICO_CHAIN = "goerli" -PIMLICO_CHAIN_ID = "5" -PIMLICO_RPC_URL = "https://rpc.ankr.com/eth_goerli" +# Pimlico Values +PIMLICO_CHAIN = "mumbai" # or "goerli" +PIMLICO_CHAIN_ID = "80001" +PIMLICO_RPC_URL = "https://rpc.ankr.com/polygon_mumbai" PIMLICO_API_KEY = "" # https://dashboard.pimlico.io/apikeys +PIMLICO_GAS_POLICY = "" # https://dashboard.pimlico.io/sponsorship-policies PIMLICO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" -PIMLICO_ERC20_PAYMASTER_ADDRESS = "0xEc43912D8C772A0Eba5a27ea5804Ba14ab502009" -PIMLICO_USDC_TOKEN_ADDRESS = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F" # https://faucet.circle.com/ +PIMLICO_ERC20_PAYMASTER_ADDRESS = "0x32aCDFeA07a614E52403d2c1feB747aa8079A353" # https://docs.pimlico.io/erc20-paymaster/reference/contracts +PIMLICO_USDC_TOKEN_ADDRESS = "0x0FA8781a83E46826621b3BC094Ea2A0212e71B23" # Mumbai USDC Address used by Pimlico # Make sure all nonce are unique for it to deploy account when run initially. PIMLICO_NONCE = "1" # Pimlico Token Operation Values (Based on the chain selected, these values should also change accordingly.) -PIMLICO_ERC20_TOKEN_CONTRACT = "" -PIMLICO_ERC721_TOKEN_CONTRACT = "" +PIMLICO_ERC20_TOKEN_CONTRACT = "0x255de08fb52fde17a3aab145de8e2ffb7fd0310f" +PIMLICO_ERC721_TOKEN_CONTRACT = "0x16bc5fba06f3f5875e915c0ba6963377eb6651e1" -# Alchemy Values (If you want to use goerli, use goerli chain id and corresponding API key, Gas Policy, etc.) +# Alchemy Values ALCHEMY_CHAIN = "sepolia" # or "goerli" ALCHEMY_CHAIN_ID = "11155111" ALCHEMY_RPC_URL = "https://eth-sepolia.g.alchemy.com/v2/Your_API_Key" @@ -27,8 +28,8 @@ ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" # Make sure all nonce are unique for it to deploy account when run initially. ALCHEMY_NONCE = "2" # Alchemy Token Operation Values (Based on the chain selected, these values should also change accordingly.) -ALCHEMY_ERC20_TOKEN_CONTRACT = "" -ALCHEMY_ERC721_TOKEN_CONTRACT = "" +ALCHEMY_ERC20_TOKEN_CONTRACT = "0x255de08fb52fde17a3aab145de8e2ffb7fd0310f" +ALCHEMY_ERC721_TOKEN_CONTRACT = "0x16bc5fba06f3f5875e915c0ba6963377eb6651e1" # Safe Values SAFE_VERSION = "1.4.1" @@ -38,3 +39,5 @@ SAFE_VERSION = "1.4.1" # ERC721 Token Goerli: https://goerli.etherscan.io/token/0xf190c05f968c53962604454ebfa380e5cda600d7 # ERC20 Token Sepolia: https://sepolia.etherscan.io/token/0x255de08fb52fde17a3aab145de8e2ffb7fd0310f # ERC721 Token Sepolia: https://sepolia.etherscan.io/token/0x16bc5fba06f3f5875e915c0ba6963377eb6651e1 +# ERC20 Token Mumbai: https://mumbai.polygonscan.com/address/0x255de08fb52fde17a3aab145de8e2ffb7fd0310f +# ERC721 Token Mumbai: https://mumbai.polygonscan.com/address/0x16bc5fba06f3f5875e915c0ba6963377eb6651e1 From e0f87dfe4230dd837aac6f6e1906ec251f0c60f8 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 17:57:51 +0530 Subject: [PATCH 39/60] npm script updated --- 4337-gas-metering/package.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index 2c1500cc..2830e82d 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -7,15 +7,25 @@ "type": "module", "private": true, "scripts": { - "pimlico:account": "tsx ./pimlico/pimlico.ts account", - "pimlico:erc20": "tsx ./pimlico/pimlico.ts erc20", - "pimlico:erc721": "tsx ./pimlico/pimlico.ts erc721", - "pimlico": "tsx ./pimlico/pimlico.ts", "alchemy:account": "tsx ./alchemy/alchemy.ts account", + "alchemy:account:paymaster": "tsx ./alchemy/alchemy.ts account paymaster=true", + "alchemy:native-transfer": "tsx ./alchemy/alchemy.ts native-transfer", + "alchemy:native-transfer:paymaster": "tsx ./alchemy/alchemy.ts native-transfer paymaster=true", "alchemy:erc20": "tsx ./alchemy/alchemy.ts erc20", + "alchemy:erc20:paymaster": "tsx ./alchemy/alchemy.ts erc20 paymaster=true", "alchemy:erc721": "tsx ./alchemy/alchemy.ts erc721", + "alchemy:erc721:paymaster": "tsx ./alchemy/alchemy.ts erc721 paymaster=true", "alchemy": "tsx ./alchemy/alchemy.ts", - "fmt": "prettier --ignore-path .gitignore --write ." + "fmt": "prettier --ignore-path .gitignore --write .", + "pimlico:account": "tsx ./pimlico/pimlico.ts account", + "pimlico:account:paymaster": "tsx ./pimlico/pimlico.ts account paymaster=true", + "pimlico:native-transfer": "tsx ./pimlico/pimlico.ts native-transfer", + "pimlico:native-transfer:paymaster": "tsx ./pimlico/pimlico.ts native-transfer paymaster=true", + "pimlico:erc20": "tsx ./pimlico/pimlico.ts erc20", + "pimlico:erc20:paymaster": "tsx ./pimlico/pimlico.ts erc20 paymaster=true", + "pimlico:erc721": "tsx ./pimlico/pimlico.ts erc721", + "pimlico:erc721:paymaster": "tsx ./pimlico/pimlico.ts erc721 paymaster=true", + "pimlico": "tsx ./pimlico/pimlico.ts" }, "repository": { "type": "git", From 5657923b3e6d456ad96495be2913a7fd5def006e Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 17:58:24 +0530 Subject: [PATCH 40/60] npm dependencies locked in a certain version --- 4337-gas-metering/package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index 2830e82d..e3a2dbc5 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -41,16 +41,17 @@ "url": "https://github.com/safe-global/safe-modules/issues" }, "dependencies": { - "@alchemy/aa-accounts": "^1.2.0", - "@alchemy/aa-alchemy": "^1.2.0", - "@alchemy/aa-core": "^1.2.0", - "dotenv": "^16.3.1", - "permissionless": "^0.0.11", - "viem": "^1.19.11" + "@alchemy/aa-accounts": "1.2.0", + "@alchemy/aa-alchemy": "1.2.0", + "@alchemy/aa-core": "1.2.0", + "alchemy-sdk": "3.1.0", + "dotenv": "16.3.1", + "permissionless": "0.0.16", + "viem": "1.19.11" }, "devDependencies": { - "@types/node": "^20.10.2", - "prettier": "^3.1.0", - "tsx": "^3.13.0" + "@types/node": "20.10.2", + "prettier": "3.1.0", + "tsx": "3.13.0" } } From 4fd5bfd508ba8f2232a6eabd54bb51ff1edbf4d6 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 17:58:49 +0530 Subject: [PATCH 41/60] package-lock auto updated --- 4337-gas-metering/package-lock.json | 136 ++++++++-------------------- 1 file changed, 37 insertions(+), 99 deletions(-) diff --git a/4337-gas-metering/package-lock.json b/4337-gas-metering/package-lock.json index b1c46d6d..286c3905 100644 --- a/4337-gas-metering/package-lock.json +++ b/4337-gas-metering/package-lock.json @@ -9,17 +9,18 @@ "version": "1.0.0", "license": "GPL-3.0", "dependencies": { - "@alchemy/aa-accounts": "^1.2.0", - "@alchemy/aa-alchemy": "^1.2.0", - "@alchemy/aa-core": "^1.2.0", - "dotenv": "^16.3.1", - "permissionless": "^0.0.11", - "viem": "^1.19.11" + "@alchemy/aa-accounts": "1.2.0", + "@alchemy/aa-alchemy": "1.2.0", + "@alchemy/aa-core": "1.2.0", + "alchemy-sdk": "3.1.0", + "dotenv": "16.3.1", + "permissionless": "0.0.16", + "viem": "1.19.11" }, "devDependencies": { - "@types/node": "^20.10.2", - "prettier": "^3.1.0", - "tsx": "^3.13.0" + "@types/node": "20.10.2", + "prettier": "3.1.0", + "tsx": "3.13.0" } }, "node_modules/@adraffy/ens-normalize": { @@ -425,7 +426,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -452,7 +452,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -477,7 +476,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -500,7 +498,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -523,7 +520,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0" } @@ -542,7 +538,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/properties": "^5.7.0" @@ -562,7 +557,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -583,7 +577,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/logger": "^5.7.0" } @@ -602,7 +595,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bignumber": "^5.7.0" } @@ -621,7 +613,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0", @@ -649,7 +640,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", @@ -676,7 +666,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/basex": "^5.7.0", @@ -706,7 +695,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", @@ -737,7 +725,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "js-sha3": "0.8.0" @@ -756,8 +743,7 @@ "type": "individual", "url": "https://www.buymeacoffee.com/ricmoo" } - ], - "optional": true + ] }, "node_modules/@ethersproject/networks": { "version": "5.7.1", @@ -773,7 +759,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/logger": "^5.7.0" } @@ -792,7 +777,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/sha2": "^5.7.0" @@ -812,7 +796,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/logger": "^5.7.0" } @@ -831,7 +814,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", @@ -869,7 +851,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0" @@ -889,7 +870,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0" @@ -909,7 +889,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -930,7 +909,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -954,7 +932,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/constants": "^5.7.0", @@ -975,7 +952,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -1002,7 +978,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/constants": "^5.7.0", @@ -1023,7 +998,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", @@ -1056,7 +1030,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/base64": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -1079,7 +1052,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "optional": true, "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", @@ -1144,9 +1116,9 @@ } }, "node_modules/@types/node": { - "version": "20.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", - "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", + "version": "20.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.2.tgz", + "integrity": "sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1169,14 +1141,12 @@ "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "optional": true + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" }, "node_modules/alchemy-sdk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.1.0.tgz", "integrity": "sha512-KMzBo0Dq+cEqXegn4fh2sP74dhAngr9twIv2pBTyPq3/ZJs+aiXXlFzVrVUYaa6x9L7iQtqhz3YKFCuN5uvpAg==", - "optional": true, "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0", @@ -1198,7 +1168,6 @@ "version": "0.26.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "optional": true, "dependencies": { "follow-redirects": "^1.14.8" } @@ -1206,20 +1175,17 @@ "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "optional": true + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "optional": true + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "optional": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -1232,7 +1198,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", "hasInstallScript": true, - "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -1244,7 +1209,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "optional": true, "dependencies": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -1254,7 +1218,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, "dependencies": { "ms": "2.0.0" } @@ -1274,7 +1237,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "optional": true, "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -1288,15 +1250,13 @@ "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "optional": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "hasInstallScript": true, - "optional": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -1310,7 +1270,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "optional": true, "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -1321,7 +1280,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "optional": true, "dependencies": { "d": "^1.0.1", "ext": "^1.1.2" @@ -1373,7 +1331,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "optional": true, "dependencies": { "type": "^2.7.2" } @@ -1381,8 +1338,7 @@ "node_modules/ext/node_modules/type": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "optional": true + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/follow-redirects": { "version": "1.15.3", @@ -1394,7 +1350,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "optional": true, "engines": { "node": ">=4.0" }, @@ -1434,7 +1389,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "optional": true, "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -1444,7 +1398,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "optional": true, "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -1454,14 +1407,12 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "optional": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "optional": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/isows": { "version": "1.0.3", @@ -1480,38 +1431,32 @@ "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "optional": true + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "optional": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "optional": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "optional": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "optional": true + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/node-gyp-build": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", - "optional": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -1519,11 +1464,11 @@ } }, "node_modules/permissionless": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.0.11.tgz", - "integrity": "sha512-gm3004JpxtuYM3q0Lcp+LBbyx6uFN8OrvfDWmaqWa8nTQ8VdFt/btP6eILdv9ICTca8DOeadjLePKo5omyVf8g==", + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.0.16.tgz", + "integrity": "sha512-tuGMf1l6X+WD8X2/WZAaF0Rqj7ysWX++eiUNgG/A+Qe1vP+e4Oik57jVUOW7Yo/VXQ9zU3luEC2GmwrFeo4b8w==", "peerDependencies": { - "viem": "^1.14.0" + "viem": "^1.19.4" } }, "node_modules/prettier": { @@ -1553,8 +1498,7 @@ "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "optional": true + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, "node_modules/source-map": { "version": "0.6.1", @@ -1578,13 +1522,12 @@ "node_modules/sturdy-websocket": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz", - "integrity": "sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==", - "optional": true + "integrity": "sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==" }, "node_modules/tsx": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.14.0.tgz", - "integrity": "sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", + "integrity": "sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==", "dev": true, "dependencies": { "esbuild": "~0.18.20", @@ -1601,14 +1544,12 @@ "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "optional": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -1637,7 +1578,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, - "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -1721,7 +1661,6 @@ "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "optional": true, "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", @@ -1758,7 +1697,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "optional": true, "engines": { "node": ">=0.10.32" } From 0f9311f1becd429fa30e04c7328e29feb90ffdae Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 17:59:14 +0530 Subject: [PATCH 42/60] Native Transfer util added --- 4337-gas-metering/utils/nativeTransfer.ts | 86 +++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 4337-gas-metering/utils/nativeTransfer.ts diff --git a/4337-gas-metering/utils/nativeTransfer.ts b/4337-gas-metering/utils/nativeTransfer.ts new file mode 100644 index 00000000..f62b74d9 --- /dev/null +++ b/4337-gas-metering/utils/nativeTransfer.ts @@ -0,0 +1,86 @@ +import dotenv from "dotenv"; +import { http, createWalletClient, PrivateKeyAccount } from "viem"; +import { goerli, polygonMumbai, sepolia } from "viem/chains"; +import { setTimeout } from "timers/promises"; + +dotenv.config(); +const pimlicoRPCURL = process.env.PIMLICO_RPC_URL; +const alchemyRPCURL = process.env.ALCHEMY_RPC_URL; + +export const transferETH = async ( + publicClient: any, + signer: PrivateKeyAccount, + receiver: `0x${string}`, + amount: bigint, + chain: string, + paymaster: string, +) => { + let walletClient; + if (paymaster == "pimlico") { + if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(pimlicoRPCURL), + }); + } else if (chain == "mumbai") { + walletClient = createWalletClient({ + account: signer, + chain: polygonMumbai, + transport: http(pimlicoRPCURL), + }); + } else { + throw new Error( + "For Pimlico, current code only support using Goerli. Please make required changes if you want to use custom network.", + ); + } + } else if (paymaster == "alchemy") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(alchemyRPCURL), + }); + } else if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(alchemyRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } + } else { + throw new Error( + "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", + ); + } + + let userETHBalance = await publicClient.getBalance({ + address: signer.address, + }); + + if (userETHBalance < amount) { + console.log( + "\nSigner does not have enough balance to deposit to Safe. Deposit atleast", + amount, + "wei.", + ); + while (userETHBalance < amount) { + await setTimeout(15000); + userETHBalance = await publicClient.getBalance({ + address: signer.address, + }); + } + console.log( + "\nSigner now have enough balance for depositing ETH to Safe Transfer.", + ); + } + + await walletClient.sendTransaction({ + to: receiver, + value: amount, + }); +}; From 46c8093326d899b3d2981494b9319c65f592ffe7 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 18:01:47 +0530 Subject: [PATCH 43/60] utils updated --- 4337-gas-metering/utils/erc20.ts | 97 +++++++- 4337-gas-metering/utils/safe.ts | 6 + 4337-gas-metering/utils/userOps.ts | 385 ++++++++++++++++++++++++++++- 3 files changed, 478 insertions(+), 10 deletions(-) diff --git a/4337-gas-metering/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts index aa948400..91797643 100644 --- a/4337-gas-metering/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -6,7 +6,7 @@ import { createWalletClient, PrivateKeyAccount, } from "viem"; -import { goerli, sepolia } from "viem/chains"; +import { goerli, polygonMumbai, sepolia } from "viem/chains"; dotenv.config(); const pimlicoRPCURL = process.env.PIMLICO_RPC_URL; @@ -118,9 +118,15 @@ export const mintERC20Token = async ( chain: goerli, transport: http(pimlicoRPCURL), }); + } else if (chain == "mumbai") { + walletClient = createWalletClient({ + account: signer, + chain: polygonMumbai, + transport: http(pimlicoRPCURL), + }); } else { throw new Error( - "For Pimlico, current code only support using Goerli. Please make required changes if you want to use custom network.", + "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } } else if (paymaster == "alchemy") { @@ -162,7 +168,92 @@ export const mintERC20Token = async ( ], functionName: "mint", args: [to, amount], - signer, + account: signer, + }); + await walletClient.writeContract(request); +}; + +export const transferERC20Token = async ( + erc20TokenAddress: string, + publicClient: any, + signer: PrivateKeyAccount, + to: string, + amount: BigInt, + chain: string, + paymaster: string, +) => { + let walletClient; + if (paymaster == "pimlico") { + if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(pimlicoRPCURL), + }); + } else if (chain == "mumbai") { + walletClient = createWalletClient({ + account: signer, + chain: polygonMumbai, + transport: http(pimlicoRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } + } else if (paymaster == "alchemy") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(alchemyRPCURL), + }); + } else if (chain == "goerli") { + walletClient = createWalletClient({ + account: signer, + chain: goerli, + transport: http(alchemyRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } + } else { + throw new Error( + "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", + ); + } + + const signerERC20Bal = await getERC20Balance( + erc20TokenAddress, + publicClient, + signer.address, + ); + if (signerERC20Bal < amount) { + console.log( + "Signer does not have enough Tokens to transfer. Please transfer required funds.", + ); + process.exit(0); + } + + const { request } = await publicClient.simulateContract({ + address: erc20TokenAddress, + abi: [ + { + inputs: [ + { name: "recipient", type: "address" }, + { name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + type: "function", + stateMutability: "public", + }, + ], + functionName: "transfer", + args: [to, amount], + account: signer, }); await walletClient.writeContract(request); }; diff --git a/4337-gas-metering/utils/safe.ts b/4337-gas-metering/utils/safe.ts index 6da13654..d0818418 100644 --- a/4337-gas-metering/utils/safe.ts +++ b/4337-gas-metering/utils/safe.ts @@ -29,6 +29,12 @@ export const SAFE_ADDRESSES_MAP = { SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", }, + "80001": { + ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", + SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", + SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", + }, }, } as const; diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 6afad37c..1a176b59 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -1,11 +1,27 @@ import dotenv from "dotenv"; import type { Address } from "abitype"; import type { Hex, PrivateKeyAccount } from "viem"; -import { EIP712_SAFE_OPERATION_TYPE, SAFE_ADDRESSES_MAP } from "./safe"; +import { + EIP712_SAFE_OPERATION_TYPE, + SAFE_ADDRESSES_MAP, + encodeCallData, +} from "./safe"; +import { Alchemy } from "alchemy-sdk"; +import { setTimeout } from "timers/promises"; +import { + generateTransferCallData, + getERC20Balance, + getERC20Decimals, + mintERC20Token, +} from "./erc20"; +import { generateMintingCallData } from "./erc721"; +import { transferETH } from "./nativeTransfer"; dotenv.config(); const safeVersion = process.env.SAFE_VERSION; +export const txTypes = ["account", "erc20", "erc721", "native-transfer"]; + export type UserOperation = { sender: Address; nonce: bigint; @@ -20,6 +36,7 @@ export type UserOperation = { signature: Hex; }; +// Sponsored User Operation Data export type suoData = { preVerificationGas: any; callGasLimit: any; @@ -29,7 +46,13 @@ export type suoData = { maxPriorityFeePerGas: any; }; -export const submitUserOperation = async ( +export type gasData = { + preVerificationGas: any; + callGasLimit: any; + verificationGasLimit: any; +}; + +export const submitUserOperationPimlico = async ( userOperation: UserOperation, bundlerClient: any, entryPointAddress: any, @@ -53,11 +76,17 @@ export const submitUserOperation = async ( console.log( `Receipt found!\nTransaction hash: ${receipt.receipt.transactionHash}`, ); - console.log( - `Transaction Link: https://` + - chain + - `.etherscan.io/tx/${receipt.receipt.transactionHash}`, - ); + if (chain == "mumbai") { + console.log( + `Transaction Link: https://mumbai.polygonscan.com/tx/${receipt.receipt.transactionHash}`, + ); + } else { + console.log( + `Transaction Link: https://` + + chain + + `.etherscan.io/tx/${receipt.receipt.transactionHash}`, + ); + } }; export const signUserOperation = async ( @@ -103,3 +132,345 @@ export const signUserOperation = async ( return signatureBytes; }; + +export const getGasValuesFromAlchemyPaymaster = async ( + policyID: string | undefined, + entryPointAddress: `0x${string}`, + sponsoredUserOperation: UserOperation, + chain: string, + apiKey: string, +) => { + const gasOptions = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "alchemy_requestGasAndPaymasterAndData", + params: [ + { + policyId: policyID, + entryPoint: entryPointAddress, + dummySignature: sponsoredUserOperation.signature, + userOperation: { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + }, + }, + ], + }), + }; + + let rv; + let responseValues; + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + gasOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + console.log("\nReceived Paymaster Data from Alchemy."); + + if (responseValues && responseValues["result"]) { + rv = responseValues["result"] as suoData; + } + return rv; +}; + +export const getFeeValuesFromAlchemy = async ( + chain: string, + apiKey: string, +) => { + const feeOptions = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "rundler_maxPriorityFeePerGas", + }), + }; + + let responseValues; + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + feeOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + console.log("\nReceived Fee Data from Alchemy."); + + let rvFee; + if (responseValues && responseValues["result"]) { + rvFee = responseValues["result"] as bigint; + } + return rvFee as bigint; +}; + +export const getMaxFeePerGas = async ( + alchemy: Alchemy, + maxPriorityFeePerGas: bigint, +) => { + let maxFeePerGas; + + // Get the latest Block Number + let latestBlockNum = await alchemy.core.getBlockNumber(); + + // Get latest Block Details + let rvBlock = await alchemy.core.getBlock(latestBlockNum); + if (rvBlock && rvBlock.baseFeePerGas) { + maxFeePerGas = + ((BigInt(rvBlock.baseFeePerGas._hex) + BigInt(maxPriorityFeePerGas)) * + 15n) / + 10n; // Adding a buffer. Recommended is atleast 50%. + // https://docs.alchemy.com/reference/bundler-api-fee-logic + } + + return ("0x" + maxFeePerGas?.toString(16)) as any; +}; + +export const getGasValuesFromAlchemy = async ( + entryPointAddress: `0x${string}`, + sponsoredUserOperation: UserOperation, + chain: string, + apiKey: string, +) => { + const gasOptions = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "eth_estimateUserOperationGas", + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: "0x1", + verificationGasLimit: "0x1", + preVerificationGas: "0x1", + maxFeePerGas: sponsoredUserOperation.maxFeePerGas.toString(16), + maxPriorityFeePerGas: + sponsoredUserOperation.maxPriorityFeePerGas.toString(16), + signature: sponsoredUserOperation.signature, + paymasterAndData: "0x", + }, + entryPointAddress, + ], + }), + }; + + let responseValues; + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + gasOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + console.log("\nReceived Gas Data from Alchemy."); + + let rvGas; + if (responseValues && responseValues["result"]) { + rvGas = responseValues["result"] as gasData; + } + + return rvGas; +}; + +export const submitUserOperationAlchemy = async ( + entryPointAddress: `0x${string}`, + sponsoredUserOperation: UserOperation, + chain: string, + apiKey: string, +) => { + const options = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "eth_sendUserOperation", + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + callGasLimit: sponsoredUserOperation.callGasLimit.toString(16), + verificationGasLimit: + sponsoredUserOperation.verificationGasLimit.toString(16), + preVerificationGas: + sponsoredUserOperation.preVerificationGas.toString(16), + maxFeePerGas: sponsoredUserOperation.maxFeePerGas.toString(16), + maxPriorityFeePerGas: + sponsoredUserOperation.maxPriorityFeePerGas.toString(16), + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData, + }, + entryPointAddress, + ], + }), + }; + + let responseValues; + await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + + if (responseValues && responseValues["result"]) { + console.log("UserOperation submitted. Hash:", responseValues["result"]); + console.log( + "UserOp Link: https://jiffyscan.xyz/userOpHash/" + + responseValues["result"] + + "?network=" + + chain, + ); + + const hashOptions = { + method: "POST", + headers: { + accept: "application/json", + "content-type": "application/json", + }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", + params: [responseValues["result"]], + entryPoint: entryPointAddress, + }), + }; + let runOnce = true; + + while (responseValues["result"] == null || runOnce) { + await setTimeout(25000); + await fetch( + "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + runOnce = false; + } + + if ( + responseValues["result"] && + responseValues["result"]["receipt"]["transactionHash"] + ) { + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues["result"]["receipt"]["transactionHash"] + + "\n", + ); + } else { + console.log("\n" + responseValues["error"]); + } + } else { + if (responseValues && responseValues["error"]["message"]) { + console.log("\n" + responseValues["error"]["message"]); + } + } +}; + +export const createCallData = async ( + chain: string, + publicClient: any, + signer: PrivateKeyAccount, + txType: string, + senderAddress: `0x${string}`, + erc20TokenAddress: `0x${string}`, + erc721TokenAddress: `0x${string}`, + paymaster: string, +) => { + let txCallData!: `0x${string}`; + if (txType == "account") { + txCallData = encodeCallData({ + to: senderAddress, + data: "0x", + value: 0n, + }); + } else if (txType == "erc20") { + // Token Configurations + const erc20Decimals = await getERC20Decimals( + erc20TokenAddress, + publicClient, + ); + const erc20Amount = BigInt(10 ** erc20Decimals); + let senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); + console.log( + "\nSafe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), + ); + + // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). + if (senderERC20Balance < erc20Amount) { + console.log("\nMinting ERC20 Tokens to Safe Wallet."); + await mintERC20Token( + erc20TokenAddress, + publicClient, + signer, + senderAddress, + erc20Amount, + chain, + paymaster, + ); + + while (senderERC20Balance < erc20Amount) { + await setTimeout(15000); + senderERC20Balance = await getERC20Balance( + erc20TokenAddress, + publicClient, + senderAddress, + ); + } + console.log( + "\nUpdated Safe Wallet ERC20 Balance:", + Number(senderERC20Balance / erc20Amount), + ); + } + + txCallData = encodeCallData({ + to: erc20TokenAddress, + data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. + value: 0n, + }); + } else if (txType == "erc721") { + txCallData = encodeCallData({ + to: erc721TokenAddress, + data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. + value: 0n, + }); + } else if (txType == "native-transfer") { + const weiToSend = 1000000000000n; // 0.000001 ETH + await transferETH( + publicClient, + signer, + senderAddress, + weiToSend, + chain, + paymaster, + ); + txCallData = encodeCallData({ + to: signer.address, + data: "0x", + value: weiToSend, + }); + } + console.log("\nAppropriate calldata created."); + return txCallData; +}; From 03421bc609c5ba577f674704082d759e7670f11d Mon Sep 17 00:00:00 2001 From: Shebin John Date: Mon, 18 Dec 2023 18:06:56 +0530 Subject: [PATCH 44/60] Native Transfer, Paymaster, Mumbai Support, etc --- 4337-gas-metering/alchemy/alchemy.ts | 322 ++++++++++----------------- 4337-gas-metering/pimlico/pimlico.ts | 226 ++++++++++--------- 2 files changed, 245 insertions(+), 303 deletions(-) diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index 7173418b..5f55145c 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -1,23 +1,35 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { UserOperation, signUserOperation, suoData } from "../utils/userOps"; -import { Hash, createPublicClient, http, zeroAddress } from "viem"; +import { Network, Alchemy } from "alchemy-sdk"; +import { setTimeout } from "timers/promises"; +import { + Client, + Hash, + createPublicClient, + formatEther, + http, + parseEther, + zeroAddress, +} from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { goerli, sepolia } from "viem/chains"; import { SAFE_ADDRESSES_MAP, - encodeCallData, getAccountAddress, getAccountInitCode, } from "../utils/safe"; import { - generateTransferCallData, - getERC20Decimals, - getERC20Balance, - mintERC20Token, -} from "../utils/erc20"; -import { generateMintingCallData } from "../utils/erc721"; -import { setTimeout } from "timers/promises"; + UserOperation, + signUserOperation, + txTypes, + getGasValuesFromAlchemyPaymaster, + getFeeValuesFromAlchemy, + getMaxFeePerGas, + getGasValuesFromAlchemy, + submitUserOperationAlchemy, + createCallData, +} from "../utils/userOps"; +import { transferETH } from "../utils/nativeTransfer"; dotenv.config(); const paymaster = "alchemy"; @@ -45,12 +57,19 @@ const erc721TokenAddress = process.env .ALCHEMY_ERC721_TOKEN_CONTRACT as `0x${string}`; const argv = process.argv.slice(2); -if (argv.length != 1) { - throw new Error("TX Type Argument not passed."); // account || erc20 || erc721 +let usePaymaster!: boolean; +if (argv.length < 1 || argv.length > 2) { + throw new Error("TX Type Argument not passed."); +} else if (argv.length == 2 && argv[1] == "paymaster=true") { + if (policyID) { + usePaymaster = true; + } else { + throw new Error("Paymaster requires policyID to be set."); + } } const txType: string = argv[0]; -if (txType != "account" && txType != "erc20" && txType != "erc721") { +if (!txTypes.includes(txType)) { throw new Error("TX Type Argument Invalid"); } @@ -78,22 +97,33 @@ const signer = privateKeyToAccount(privateKey as Hash); console.log("Signer Extracted from Private Key."); let publicClient; +let settings; if (chain == "sepolia") { publicClient = createPublicClient({ transport: http(rpcURL), chain: sepolia, }); + settings = { + apiKey: apiKey, + network: Network.ETH_SEPOLIA, + }; } else if (chain == "goerli") { publicClient = createPublicClient({ transport: http(rpcURL), chain: goerli, }); + settings = { + apiKey: apiKey, + network: Network.ETH_GOERLI, + }; } else { throw new Error( "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } +const alchemy = new Alchemy(settings); + const initCode = await getAccountInitCode({ owner: signer.address, addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, @@ -129,7 +159,6 @@ const contractCode = await publicClient.getBytecode({ address: senderAddress }); if (contractCode) { console.log("\nThe Safe is already deployed."); if (txType == "account") { - console.log("\n"); process.exit(0); } } else { @@ -138,84 +167,33 @@ if (contractCode) { ); } -const newNonce = await getAccountNonce(publicClient, { +const newNonce = await getAccountNonce(publicClient as Client, { entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); -let txCallData!: `0x${string}`; - -if (txType == "account") { - txCallData = encodeCallData({ - to: senderAddress, - data: "0x", - value: 0n, - }); -} else if (txType == "erc20") { - // Token Configurations - const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); - const erc20Amount = BigInt(10 ** erc20Decimals); - let senderERC20Balance = await getERC20Balance( - erc20TokenAddress, - publicClient, - senderAddress, - ); - console.log( - "\nSafe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), - ); - - // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). - if (senderERC20Balance < erc20Amount) { - console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token( - erc20TokenAddress, - publicClient, - signer, - senderAddress, - erc20Amount, - chain, - paymaster, - ); - - while (senderERC20Balance < erc20Amount) { - await setTimeout(15000); - senderERC20Balance = await getERC20Balance( - erc20TokenAddress, - publicClient, - senderAddress, - ); - } - console.log( - "\nUpdated Safe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), - ); - } - - txCallData = encodeCallData({ - to: erc20TokenAddress, - data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. - value: 0n, - }); -} else if (txType == "erc721") { - txCallData = encodeCallData({ - to: erc721TokenAddress, - data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. - value: 0n, - }); -} +let txCallData: `0x${string}` = await createCallData( + chain, + publicClient, + signer, + txType, + senderAddress, + erc20TokenAddress, + erc721TokenAddress, + paymaster, +); const sponsoredUserOperation: UserOperation = { sender: senderAddress, nonce: newNonce, initCode: contractCode ? "0x" : initCode, callData: txCallData, - callGasLimit: 0n, // All Gas Values will be filled by Paymaster Response Data - verificationGasLimit: 0n, - preVerificationGas: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, + callGasLimit: 1n, // All Gas Values will be filled by Estimation Response Data. + verificationGasLimit: 1n, + preVerificationGas: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, paymasterAndData: "0x", signature: "0x", }; @@ -229,48 +207,64 @@ sponsoredUserOperation.signature = await signUserOperation( ); console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); -const gasOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "alchemy_requestGasAndPaymasterAndData", - params: [ - { - policyId: policyID, - entryPoint: entryPointAddress, - dummySignature: sponsoredUserOperation.signature, - userOperation: { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - }, - }, - ], - }), -}; +if (usePaymaster) { + let rvGas = await getGasValuesFromAlchemyPaymaster( + policyID, + entryPointAddress, + sponsoredUserOperation, + chain, + apiKey, + ); -let responseValues; + sponsoredUserOperation.preVerificationGas = rvGas?.preVerificationGas; + sponsoredUserOperation.callGasLimit = rvGas?.callGasLimit; + sponsoredUserOperation.verificationGasLimit = rvGas?.verificationGasLimit; + sponsoredUserOperation.paymasterAndData = rvGas?.paymasterAndData; + sponsoredUserOperation.maxFeePerGas = rvGas?.maxFeePerGas; + sponsoredUserOperation.maxPriorityFeePerGas = rvGas?.maxPriorityFeePerGas; +} else { + sponsoredUserOperation.maxPriorityFeePerGas = await getFeeValuesFromAlchemy( + chain, + apiKey, + ); + sponsoredUserOperation.maxFeePerGas = await getMaxFeePerGas( + alchemy, + sponsoredUserOperation.maxPriorityFeePerGas, + ); -await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, gasOptions) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); -console.log("\nReceived Paymaster Data from Alchemy."); + let rvGas = await getGasValuesFromAlchemy( + entryPointAddress, + sponsoredUserOperation, + chain, + apiKey, + ); -let rv; -if (responseValues && responseValues["result"]) { - rv = responseValues["result"] as suoData; -} + sponsoredUserOperation.preVerificationGas = rvGas?.preVerificationGas; + sponsoredUserOperation.callGasLimit = rvGas?.callGasLimit; + sponsoredUserOperation.verificationGasLimit = rvGas?.verificationGasLimit; -sponsoredUserOperation.preVerificationGas = rv?.preVerificationGas; -sponsoredUserOperation.callGasLimit = rv?.callGasLimit; -sponsoredUserOperation.verificationGasLimit = rv?.verificationGasLimit; -sponsoredUserOperation.paymasterAndData = rv?.paymasterAndData; -sponsoredUserOperation.maxFeePerGas = rv?.maxFeePerGas; -sponsoredUserOperation.maxPriorityFeePerGas = rv?.maxPriorityFeePerGas; + const weiToSend = parseEther("0.02"); + const safeETHBalance = await publicClient.getBalance({ + address: senderAddress, + }); + if (safeETHBalance < weiToSend) { + console.log( + "\nTransferring", + formatEther(weiToSend - safeETHBalance), + "ETH to Safe for transaction.", + ); + await transferETH( + publicClient, + signer, + senderAddress, + weiToSend - safeETHBalance, + chain, + paymaster, + ); + await setTimeout(30000); // Sometimes it takes time to index. + console.log("\nTransferred required ETH for the transaction."); + } +} sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, @@ -283,87 +277,9 @@ console.log( "\nSigned Real Data including Paymaster Data Created by Alchemy.\n", ); -const options = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_sendUserOperation", - params: [ - { - sender: sponsoredUserOperation.sender, - nonce: "0x" + sponsoredUserOperation.nonce.toString(16), - initCode: sponsoredUserOperation.initCode, - callData: sponsoredUserOperation.callData, - callGasLimit: sponsoredUserOperation.callGasLimit, - verificationGasLimit: sponsoredUserOperation.verificationGasLimit, - preVerificationGas: sponsoredUserOperation.preVerificationGas, - maxFeePerGas: sponsoredUserOperation.maxFeePerGas, - maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, - signature: sponsoredUserOperation.signature, - paymasterAndData: sponsoredUserOperation.paymasterAndData, - }, - entryPointAddress, - ], - }), -}; - -await fetch("https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, options) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - -if (responseValues && responseValues["result"]) { - console.log("UserOperation submitted. Hash:", responseValues["result"]); - console.log( - "UserOp Link: https://jiffyscan.xyz/userOpHash/" + - responseValues["result"] + - "?network=" + - chain, - ); - - const hashOptions = { - method: "POST", - headers: { accept: "application/json", "content-type": "application/json" }, - body: JSON.stringify({ - id: 1, - jsonrpc: "2.0", - method: "eth_getUserOperationReceipt", - params: [responseValues["result"]], - entryPoint: entryPointAddress, - }), - }; - let runOnce = true; - - while (responseValues["result"] == null || runOnce) { - await setTimeout(25000); - await fetch( - "https://eth-" + chain + ".g.alchemy.com/v2/" + apiKey, - hashOptions, - ) - .then((response) => response.json()) - .then((response) => (responseValues = response)) - .catch((err) => console.error(err)); - runOnce = false; - } - - if ( - responseValues["result"] && - responseValues["result"]["receipt"]["transactionHash"] - ) { - console.log( - "\nTransaction Link: https://" + - chain + - ".etherscan.io/tx/" + - responseValues["result"]["receipt"]["transactionHash"] + - "\n", - ); - } else { - console.log("\n" + responseValues["error"]); - } -} else { - if (responseValues && responseValues["error"]["message"]) { - console.log("\n" + responseValues["error"]["message"]); - } -} +await submitUserOperationAlchemy( + entryPointAddress, + sponsoredUserOperation, + chain, + apiKey, +); diff --git a/4337-gas-metering/pimlico/pimlico.ts b/4337-gas-metering/pimlico/pimlico.ts index ee448a9e..b452510d 100644 --- a/4337-gas-metering/pimlico/pimlico.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -1,25 +1,29 @@ import dotenv from "dotenv"; -import { getAccountNonce } from "permissionless"; -import { UserOperation, bundlerActions } from "permissionless"; -import { pimlicoBundlerActions } from "permissionless/actions/pimlico"; -import { Hash, createClient, createPublicClient, http } from "viem"; +import { getAccountNonce, UserOperation, bundlerActions } from "permissionless"; +import { + pimlicoBundlerActions, + pimlicoPaymasterActions, +} from "permissionless/actions/pimlico"; +import { setTimeout } from "timers/promises"; +import { Client, Hash, createClient, createPublicClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; -import { goerli } from "viem/chains"; +import { goerli, polygonMumbai } from "viem/chains"; import { SAFE_ADDRESSES_MAP, - encodeCallData, getAccountAddress, getAccountInitCode, } from "../utils/safe"; -import { submitUserOperation, signUserOperation } from "../utils/userOps"; -import { setTimeout } from "timers/promises"; import { - generateTransferCallData, + submitUserOperationPimlico, + signUserOperation, + txTypes, + createCallData, +} from "../utils/userOps"; +import { getERC20Decimals, getERC20Balance, - mintERC20Token, + transferERC20Token, } from "../utils/erc20"; -import { generateMintingCallData } from "../utils/erc721"; dotenv.config(); const paymaster = "pimlico"; @@ -38,6 +42,7 @@ const chainID = Number(process.env.PIMLICO_CHAIN_ID); const safeVersion = process.env.SAFE_VERSION as string; const rpcURL = process.env.PIMLICO_RPC_URL; +const policyID = process.env.PIMLICO_GAS_POLICY; const apiKey = process.env.PIMLICO_API_KEY; const erc20PaymasterAddress = process.env @@ -50,12 +55,19 @@ const erc721TokenAddress = process.env .PIMLICO_ERC721_TOKEN_CONTRACT as `0x${string}`; const argv = process.argv.slice(2); -if (argv.length != 1) { - throw new Error("TX Type Argument not passed."); // account || erc20 || erc721 +let usePaymaster!: boolean; +if (argv.length < 1 || argv.length > 2) { + throw new Error("TX Type Argument not passed."); +} else if (argv.length == 2 && argv[1] == "paymaster=true") { + if (policyID) { + usePaymaster = true; + } else { + throw new Error("Paymaster requires policyID to be set."); + } } const txType: string = argv[0]; -if (txType != "account" && txType != "erc20" && txType != "erc721") { +if (!txTypes.includes(txType)) { throw new Error("TX Type Argument Invalid"); } @@ -84,6 +96,7 @@ console.log("Signer Extracted from Private Key."); let bundlerClient; let publicClient; +let pimlicoPaymasterClient; if (chain == "goerli") { bundlerClient = createClient({ transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), @@ -96,6 +109,28 @@ if (chain == "goerli") { transport: http(rpcURL), chain: goerli, }); + + pimlicoPaymasterClient = createClient({ + transport: http(`https://api.pimlico.io/v2/${chain}/rpc?apikey=${apiKey}`), + chain: goerli, + }).extend(pimlicoPaymasterActions); +} else if (chain == "mumbai") { + bundlerClient = createClient({ + transport: http(`https://api.pimlico.io/v1/${chain}/rpc?apikey=${apiKey}`), + chain: polygonMumbai, + }) + .extend(bundlerActions) + .extend(pimlicoBundlerActions); + + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: polygonMumbai, + }); + + pimlicoPaymasterClient = createClient({ + transport: http(`https://api.pimlico.io/v2/${chain}/rpc?apikey=${apiKey}`), + chain: polygonMumbai, + }).extend(pimlicoPaymasterActions); } else { throw new Error( "Current code only support limited networks. Please make required changes if you want to use custom network.", @@ -128,138 +163,129 @@ const senderAddress = await getAccountAddress({ paymasterAddress: erc20PaymasterAddress, }); console.log("\nCounterfactual Sender Address Created:", senderAddress); -console.log( - "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, -); +if (chain == "mumbai") { + console.log( + "Address Link: https://mumbai.polygonscan.com/address/" + senderAddress, + ); +} else { + console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, + ); +} const contractCode = await publicClient.getBytecode({ address: senderAddress }); if (contractCode) { console.log("\nThe Safe is already deployed."); if (txType == "account") { - console.log(""); process.exit(0); } } else { console.log( - "\nDeploying a new Safe and executing calldata passed with it (if any).\n", + "\nDeploying a new Safe and executing calldata passed with it (if any).", ); } -const newNonce = await getAccountNonce(publicClient, { +const newNonce = await getAccountNonce(publicClient as Client, { entryPoint: entryPointAddress, sender: senderAddress, }); console.log("\nNonce for the sender received from EntryPoint."); -// Fetch USDC balance of sender -const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); -const usdcAmount = BigInt(10 ** usdcDecimals); -let senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, +let txCallData: `0x${string}` = await createCallData( + chain, publicClient, + signer, + txType, senderAddress, -); -console.log( - "\nSafe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), + erc20TokenAddress, + erc721TokenAddress, + paymaster, ); -if (senderUSDCBalance < BigInt(1) * usdcAmount) { - console.log( - "\nPlease deposit atleast 2 USDC Token for paying the Paymaster.", - ); - while (senderUSDCBalance < BigInt(1) * usdcAmount) { - await setTimeout(30000); - senderUSDCBalance = await getERC20Balance( - usdcTokenAddress, - publicClient, - senderAddress, - ); - } - console.log( - "\nUpdated Safe Wallet USDC Balance:", - Number(senderUSDCBalance / usdcAmount), - ); -} +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: txCallData, + callGasLimit: 1n, // All Gas Values will be filled by Estimation Response Data. + verificationGasLimit: 1n, + preVerificationGas: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + paymasterAndData: erc20PaymasterAddress, + signature: "0x", +}; + +const gasEstimate = await bundlerClient.estimateUserOperationGas({ + userOperation: sponsoredUserOperation, + entryPoint: entryPointAddress, +}); +const maxGasPriceResult = await bundlerClient.getUserOperationGasPrice(); -let txCallData!: `0x${string}`; +sponsoredUserOperation.callGasLimit = gasEstimate.callGasLimit; +sponsoredUserOperation.verificationGasLimit = gasEstimate.verificationGasLimit; +sponsoredUserOperation.preVerificationGas = gasEstimate.preVerificationGas; +sponsoredUserOperation.maxFeePerGas = maxGasPriceResult.fast.maxFeePerGas; +sponsoredUserOperation.maxPriorityFeePerGas = + maxGasPriceResult.fast.maxPriorityFeePerGas; -if (txType == "account") { - txCallData = encodeCallData({ - to: senderAddress, - data: "0x", - value: 0n, +if (usePaymaster) { + const sponsorResult = await pimlicoPaymasterClient.sponsorUserOperation({ + userOperation: sponsoredUserOperation, + entryPoint: entryPointAddress, + sponsorshipPolicyId: policyID, }); -} else if (txType == "erc20") { - // Token Configurations - const erc20Decimals = await getERC20Decimals(erc20TokenAddress, publicClient); - const erc20Amount = BigInt(10 ** erc20Decimals); - let senderERC20Balance = await getERC20Balance( - erc20TokenAddress, + + sponsoredUserOperation.callGasLimit = sponsorResult.callGasLimit; + sponsoredUserOperation.verificationGasLimit = + sponsorResult.verificationGasLimit; + sponsoredUserOperation.preVerificationGas = sponsorResult.preVerificationGas; + sponsoredUserOperation.paymasterAndData = sponsorResult.paymasterAndData; + +} else { + + // Fetch USDC balance of sender + const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); + const usdcAmount = BigInt(10 ** usdcDecimals); + let senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, publicClient, senderAddress, ); console.log( - "\nSafe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), + "\nSafe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), ); - // Trying to mint tokens (Make sure ERC20 Token Contract is mintable by anyone). - if (senderERC20Balance < erc20Amount) { - console.log("\nMinting ERC20 Tokens to Safe Wallet."); - await mintERC20Token( - erc20TokenAddress, + if (senderUSDCBalance < BigInt(1) * usdcAmount) { + console.log( + "\nTransferring 1 USDC Token for paying the Paymaster from Sender to Safe.", + ); + await transferERC20Token( + usdcTokenAddress, publicClient, signer, senderAddress, - erc20Amount, + BigInt(1) * usdcAmount, chain, paymaster, ); - - while (senderERC20Balance < erc20Amount) { + while (senderUSDCBalance < BigInt(1) * usdcAmount) { await setTimeout(15000); - senderERC20Balance = await getERC20Balance( - erc20TokenAddress, + senderUSDCBalance = await getERC20Balance( + usdcTokenAddress, publicClient, senderAddress, ); } console.log( - "\nUpdated Safe Wallet ERC20 Balance:", - Number(senderERC20Balance / erc20Amount), + "\nUpdated Safe Wallet USDC Balance:", + Number(senderUSDCBalance / usdcAmount), ); } - txCallData = encodeCallData({ - to: erc20TokenAddress, - data: generateTransferCallData(signer.address, erc20Amount), // transfer() function call with corresponding data. - value: 0n, - }); -} else if (txType == "erc721") { - txCallData = encodeCallData({ - to: erc721TokenAddress, - data: generateMintingCallData(signer.address), // safeMint() function call with corresponding data. - value: 0n, - }); } -const gasPriceResult = await bundlerClient.getUserOperationGasPrice(); - -const sponsoredUserOperation: UserOperation = { - sender: senderAddress, - nonce: newNonce, - initCode: contractCode ? "0x" : initCode, - callData: txCallData, - callGasLimit: 100_000n, // Gas Values Hardcoded for now at a high value - verificationGasLimit: 500_000n, - preVerificationGas: 50_000n, - maxFeePerGas: gasPriceResult.fast.maxFeePerGas, - maxPriorityFeePerGas: gasPriceResult.fast.maxPriorityFeePerGas, - paymasterAndData: erc20PaymasterAddress, - signature: "0x", -}; - sponsoredUserOperation.signature = await signUserOperation( sponsoredUserOperation, signer, @@ -268,7 +294,7 @@ sponsoredUserOperation.signature = await signUserOperation( chainAddresses.SAFE_4337_MODULE_ADDRESS, ); -await submitUserOperation( +await submitUserOperationPimlico( sponsoredUserOperation, bundlerClient, entryPointAddress, From 086a6a4966704de231df9cdad1eefe9b679a7ced Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 12:17:36 +0530 Subject: [PATCH 45/60] Module v0.2 compatibility --- 4337-gas-metering/pimlico/pimlico.ts | 3 ++- 4337-gas-metering/utils/safe.ts | 22 +++++++++++++--------- 4337-gas-metering/utils/userOps.ts | 13 ++++++++----- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/4337-gas-metering/pimlico/pimlico.ts b/4337-gas-metering/pimlico/pimlico.ts index b452510d..c6693eae 100644 --- a/4337-gas-metering/pimlico/pimlico.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -1,5 +1,5 @@ import dotenv from "dotenv"; -import { getAccountNonce, UserOperation, bundlerActions } from "permissionless"; +import { getAccountNonce, bundlerActions } from "permissionless"; import { pimlicoBundlerActions, pimlicoPaymasterActions, @@ -14,6 +14,7 @@ import { getAccountInitCode, } from "../utils/safe"; import { + UserOperation, submitUserOperationPimlico, signUserOperation, txTypes, diff --git a/4337-gas-metering/utils/safe.ts b/4337-gas-metering/utils/safe.ts index d0818418..7e45fb30 100644 --- a/4337-gas-metering/utils/safe.ts +++ b/4337-gas-metering/utils/safe.ts @@ -18,20 +18,20 @@ import { generateApproveCallData } from "./erc20"; export const SAFE_ADDRESSES_MAP = { "1.4.1": { "11155111": { - ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", - SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + ADD_MODULES_LIB_ADDRESS: "0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb", + SAFE_4337_MODULE_ADDRESS: "0xa581c4A4DB7175302464fF3C06380BC3270b4037", SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", }, "5": { - ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", - SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + ADD_MODULES_LIB_ADDRESS: "0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb", + SAFE_4337_MODULE_ADDRESS: "0xa581c4A4DB7175302464fF3C06380BC3270b4037", SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", }, "80001": { - ADD_MODULES_LIB_ADDRESS: "0x191EFDC03615B575922289DC339F4c70aC5C30Af", - SAFE_4337_MODULE_ADDRESS: "0x39E54Bb2b3Aa444b4B39DEe15De3b7809c36Fc38", + ADD_MODULES_LIB_ADDRESS: "0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb", + SAFE_4337_MODULE_ADDRESS: "0xa581c4A4DB7175302464fF3C06380BC3270b4037", SAFE_PROXY_FACTORY_ADDRESS: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67", SAFE_SINGLETON_ADDRESS: "0x41675C099F32341bf84BFc5382aF534df5C7461a", }, @@ -233,13 +233,17 @@ export const getAccountInitCode = async ({ export const EIP712_SAFE_OPERATION_TYPE = { SafeOp: [ { type: "address", name: "safe" }, - { type: "bytes", name: "callData" }, { type: "uint256", name: "nonce" }, - { type: "uint256", name: "preVerificationGas" }, - { type: "uint256", name: "verificationGasLimit" }, + { type: "bytes", name: "initCode" }, + { type: "bytes", name: "callData" }, { type: "uint256", name: "callGasLimit" }, + { type: "uint256", name: "verificationGasLimit" }, + { type: "uint256", name: "preVerificationGas" }, { type: "uint256", name: "maxFeePerGas" }, { type: "uint256", name: "maxPriorityFeePerGas" }, + { type: "bytes", name: "paymasterAndData" }, + { type: "uint48", name: "validAfter" }, + { type: "uint48", name: "validUntil" }, { type: "address", name: "entryPoint" }, ], }; diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 1a176b59..6b842272 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -3,7 +3,6 @@ import type { Address } from "abitype"; import type { Hex, PrivateKeyAccount } from "viem"; import { EIP712_SAFE_OPERATION_TYPE, - SAFE_ADDRESSES_MAP, encodeCallData, } from "./safe"; import { Alchemy } from "alchemy-sdk"; @@ -108,13 +107,17 @@ export const signUserOperation = async ( primaryType: "SafeOp", message: { safe: userOperation.sender, - callData: userOperation.callData, nonce: userOperation.nonce, - preVerificationGas: userOperation.preVerificationGas, - verificationGasLimit: userOperation.verificationGasLimit, + initCode: userOperation.initCode, + callData: userOperation.callData, callGasLimit: userOperation.callGasLimit, + verificationGasLimit: userOperation.verificationGasLimit, + preVerificationGas: userOperation.preVerificationGas, maxFeePerGas: userOperation.maxFeePerGas, maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + paymasterAndData: userOperation.paymasterAndData, + validAfter: "0x000000000000", + validUntil: "0x000000000000", entryPoint: entryPointAddress, }, }), @@ -125,7 +128,7 @@ export const signUserOperation = async ( left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()), ); - let signatureBytes: Address = "0x"; + let signatureBytes: Address = "0x000000000000000000000000"; for (const sig of signatures) { signatureBytes += sig.data.slice(2); } From e3d153a2bcdb4cc8e18bce8884da15b82885a964 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:25:14 +0530 Subject: [PATCH 46/60] Native Transfer Tests Added --- 4337/test/gas/Gas.spec.ts | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index f2895c46..ea8f1fd1 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -70,6 +70,99 @@ describe('Gas Metering', () => { }) }) + describe('Safe Deployment + Enabling 4337 Module + Native Transfers', () => { + it('Safe with 4337 Module Deployment + Native Transfer', async () => { + const { user, entryPoint, validator, safe } = await setupTests() + const amount = ethers.parseEther('0.00001') + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + await user.sendTransaction({ + to: safe.address, + value: amount, + }) + let safeBalBefore = await ethers.provider.getBalance(safe.address) + expect(safeBalBefore).to.equal(amount) + + const safeOp = buildSafeUserOpTransaction( + safe.address, + user.address, + amount, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + + const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) + + const userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await logGas('Safe with 4337 Module Deployment + Native Transfer', entryPoint.executeUserOp(userOp, 0)) + + let safeBalAfter = await ethers.provider.getBalance(safe.address) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + expect(safeBalAfter).to.equal(0) + }) + + it('Safe with 4337 Module Native Transfer', async () => { + const { user, entryPoint, validator, safe } = await setupTests() + + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0) + + let safeOp = buildSafeUserOpTransaction( + safe.address, + safe.address, + 0, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) + let userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await entryPoint.executeUserOp(userOp, 0) + expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) + + // Now Native Transfer + const amount = ethers.parseEther('0.00001') + expect(await ethers.provider.getBalance(safe.address)).to.equal(0) + await user.sendTransaction({ + to: safe.address, + value: amount, + }) + expect(await ethers.provider.getBalance(safe.address)).to.equal(amount) + + safeOp = buildSafeUserOpTransaction( + safe.address, + user.address, + amount, + '0x', + await entryPoint.getNonce(safe.address, 0), + await entryPoint.getAddress(), + ) + signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) + userOp = buildUserOperationFromSafeUserOperation({ + safeAddress: safe.address, + safeOp, + signature, + initCode: safe.getInitCode(), + }) + + await logGas('Safe with 4337 Module Native Transfer', entryPoint.executeUserOp(userOp, 0)) + + expect(await ethers.provider.getBalance(safe.address)).to.equal(0) + }) + }) + describe('Safe Deployment + Enabling 4337 Module + Token Operations', () => { it('Safe with 4337 Module Deployment + ERC20 Token Transfer', async () => { const { user, entryPoint, validator, safe, erc20Token } = await setupTests() From 97e95d3750d0cbb7e13cf373b9b3f1909ebe17d7 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:26:14 +0530 Subject: [PATCH 47/60] Added check for ETH Transfer --- 4337-gas-metering/alchemy/alchemy.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index 5f55145c..9a9a3763 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -244,7 +244,7 @@ if (usePaymaster) { sponsoredUserOperation.verificationGasLimit = rvGas?.verificationGasLimit; const weiToSend = parseEther("0.02"); - const safeETHBalance = await publicClient.getBalance({ + let safeETHBalance = await publicClient.getBalance({ address: senderAddress, }); if (safeETHBalance < weiToSend) { @@ -261,7 +261,12 @@ if (usePaymaster) { chain, paymaster, ); - await setTimeout(30000); // Sometimes it takes time to index. + while(safeETHBalance < weiToSend){ + await setTimeout(30000); // Sometimes it takes time to index. + safeETHBalance = await publicClient.getBalance({ + address: senderAddress, + }); + } console.log("\nTransferred required ETH for the transaction."); } } From 4cae569ea5eccca8cbbcdd38e3d2ee874cc588c6 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:27:42 +0530 Subject: [PATCH 48/60] Added Pimlico & Alchemy Gas Usage README --- 4337-gas-metering/alchemy/README.md | 516 ++++++++++++++++++++++++---- 4337-gas-metering/pimlico/README.md | 472 +++++++++++++++++++++---- 2 files changed, 858 insertions(+), 130 deletions(-) diff --git a/4337-gas-metering/alchemy/README.md b/4337-gas-metering/alchemy/README.md index 9d49e070..9a7b707c 100644 --- a/4337-gas-metering/alchemy/README.md +++ b/4337-gas-metering/alchemy/README.md @@ -1,173 +1,571 @@ -# Alchemy Paymaster Analysis +# Alchemy -## Safe Deployment with Alchemy Paymaster - -NPM Command to run: +## Safe Deployment with Alchemy Paymaster (Own Sponsorship) ``` npm run alchemy:account -> @safe-global/aa-analysis@1.0.0 alchemy:account -> tsx ./alchemy/alchemy-account.ts +> @safe-global/4337-gas-metering@1.0.0 alchemy:account +> tsx ./alchemy/alchemy.ts account Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x66075bF48502407f46cDE90FA9e88b05748A16DA -Address Link: https://sepolia.etherscan.io/address/0x66075bF48502407f46cDE90FA9e88b05748A16DA +Counterfactual Sender Address Created: 0xbf2AE89D8565bc948772097082f2e000FF72CBAB +Address Link: https://sepolia.etherscan.io/address/0xbf2AE89D8565bc948772097082f2e000FF72CBAB + +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. +Appropriate calldata created. + Signed Dummy Data for Paymaster Data Creation from Alchemy. -Received Paymaster Data from Alchemy. +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.02 ETH to Safe for transaction. + +Transferred required ETH for the transaction. Signed Real Data including Paymaster Data Created by Alchemy. -Safe Account Creation User Operation Successfully Created! -UserOp Link: https://jiffyscan.xyz/userOpHash/0xd48c4e1fb7f7c8e6b33091ed11e7deb7e1f7bf81ce2840ca9b31f5bef7dda9fb?network=sepolia +UserOperation submitted. Hash: 0xa27fdf3167c094f36a312c14f9c9f46dc6ef205aaece8fb395132d5204c2e85f +UserOp Link: https://jiffyscan.xyz/userOpHash/0xa27fdf3167c094f36a312c14f9c9f46dc6ef205aaece8fb395132d5204c2e85f?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88 + +Gas Used (Account or Paymaster): 417476 +Gas Used (Transaction): 417074 +``` + +## Safe Deployment + Native Transfer with Alchemy Paymaster (Own Sponsorship) + ``` +npm run alchemy:native-transfer + +> @safe-global/4337-gas-metering@1.0.0 alchemy:native-transfer +> tsx ./alchemy/alchemy.ts native-transfer + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x0cc883d620a0E313971bd713D1556Ec4dc6821f1 +Address Link: https://sepolia.etherscan.io/address/0x0cc883d620a0E313971bd713D1556Ec4dc6821f1 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.02 ETH to Safe for transaction. + +Transferred required ETH for the transaction. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x19b5f1f34fd95466e5865cf10647baf68eed16e815a15a3665c2301c19a07337 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x19b5f1f34fd95466e5865cf10647baf68eed16e815a15a3665c2301c19a07337?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2 + +Gas Used (Account or Paymaster): 424919 +Gas Used (Transaction): 424505 +``` + +## Native Transfer with Alchemy Paymaster (Own Sponsorship) + +``` +npm run alchemy:native-transfer + +> @safe-global/4337-gas-metering@1.0.0 alchemy:native-transfer +> tsx ./alchemy/alchemy.ts native-transfer + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x0cc883d620a0E313971bd713D1556Ec4dc6821f1 +Address Link: https://sepolia.etherscan.io/address/0x0cc883d620a0E313971bd713D1556Ec4dc6821f1 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.01127255359589216 ETH to Safe for transaction. + +Transferred required ETH for the transaction. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0xd5b3d3a2f80eba8abd56309a30f2f4eb61c7de6ca3f0c00cc446cb2b570d1c23 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xd5b3d3a2f80eba8abd56309a30f2f4eb61c7de6ca3f0c00cc446cb2b570d1c23?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b + +Gas Used (Account or Paymaster): 107379 +Gas Used (Transaction): 107057 +``` + +## Safe Deployment + ERC20 Transaction with Alchemy Paymaster (Own Sponsorship) + +``` +npm run alchemy:erc20 + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc20 +> tsx ./alchemy/alchemy.ts erc20 -Transaction Link: https://sepolia.etherscan.io/tx/https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x395E28c040ADe3470B60eE4024a37FeA85ec3Df1 +Address Link: https://sepolia.etherscan.io/address/0x395E28c040ADe3470B60eE4024a37FeA85ec3Df1 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Fee Data from Alchemy. -Gas Usage: 408370 +Received Gas Data from Alchemy. -## Safe Deployment + ERC20 Transaction with Alchemy Paymaster +Transferring 0.02 ETH to Safe for transaction. -NPM Command to run: +Transferred required ETH for the transaction. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x7a588948e8fd155e40ad848a5bb08cb8f903af59639f645655748376150cb082 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x7a588948e8fd155e40ad848a5bb08cb8f903af59639f645655748376150cb082?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143 + +Gas Used (Account or Paymaster): 432797 +Gas Used (Transaction): 427599 +``` + +## ERC20 Transaction with Alchemy Paymaster (Own Sponsorship) ``` npm run alchemy:erc20 -> @safe-global/aa-analysis@1.0.0 alchemy:erc20 -> tsx ./alchemy/alchemy-erc20.ts +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc20 +> tsx ./alchemy/alchemy.ts erc20 Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x40726566E5BCb54A99Dc791D075067B5bC188D0D -Address Link: https://sepolia.etherscan.io/address/0x40726566E5BCb54A99Dc791D075067B5bC188D0D +Counterfactual Sender Address Created: 0x395E28c040ADe3470B60eE4024a37FeA85ec3Df1 +Address Link: https://sepolia.etherscan.io/address/0x395E28c040ADe3470B60eE4024a37FeA85ec3Df1 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. Safe Wallet ERC20 Balance: 0 -Minting Tokens to Safe Wallet. + +Minting ERC20 Tokens to Safe Wallet. Updated Safe Wallet ERC20 Balance: 1 +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.014251001650899964 ETH to Safe for transaction. + +Transferred required ETH for the transaction. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0xe8c6de35003a3b7a3164b5bad8ec9008a9f034459084cea05d45bcfa8b00a8b6 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xe8c6de35003a3b7a3164b5bad8ec9008a9f034459084cea05d45bcfa8b00a8b6?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d + +Gas Used (Account or Paymaster): 115268 +Gas Used (Transaction): 110174 +``` + +## Safe Deployment + ERC721 Transaction with Alchemy Paymaster (Own Sponsorship) + +``` +npm run alchemy:erc721 + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc721 +> tsx ./alchemy/alchemy.ts erc721 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x998CFAdDe39b44c7f94eD4694FD134F764956eC1 +Address Link: https://sepolia.etherscan.io/address/0x998CFAdDe39b44c7f94eD4694FD134F764956eC1 + +Deploying a new Safe and executing calldata passed with it (if any). + Nonce for the sender received from EntryPoint. +Appropriate calldata created. + Signed Dummy Data for Paymaster Data Creation from Alchemy. -Received Paymaster Data from Alchemy. +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.02 ETH to Safe for transaction. + +Transferred required ETH for the transaction. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x516e6526c5617165d5570999494b428f7be3e864dc28b7094829be3e42f87714 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x516e6526c5617165d5570999494b428f7be3e864dc28b7094829be3e42f87714?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6 + +Gas Used (Account or Paymaster): 457274 +Gas Used (Transaction): 456870 +``` + +## ERC721 Transaction with Alchemy Paymaster (Own Sponsorship) + +``` +npm run alchemy:erc721 + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc721 +> tsx ./alchemy/alchemy.ts erc721 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x998CFAdDe39b44c7f94eD4694FD134F764956eC1 +Address Link: https://sepolia.etherscan.io/address/0x998CFAdDe39b44c7f94eD4694FD134F764956eC1 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Fee Data from Alchemy. + +Received Gas Data from Alchemy. + +Transferring 0.013259139013776724 ETH to Safe for transaction. + +Transferred required ETH for the transaction. Signed Real Data including Paymaster Data Created by Alchemy. -Safe Account Creation User Operation Successfully Created! -UserOp Link: https://jiffyscan.xyz/userOpHash/0xa6e7e47f8df520d1a7324fd4fd9e67a521ef6723122a198347fd15dcb0d60dde?network=sepolia +UserOperation submitted. Hash: 0x236f50d14082d4d6df8b9b3a68d837579c80253d7aebd7ed9f224a42f9914e4f +UserOp Link: https://jiffyscan.xyz/userOpHash/0x236f50d14082d4d6df8b9b3a68d837579c80253d7aebd7ed9f224a42f9914e4f?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e + +Gas Used (Account or Paymaster): 139744 +Gas Used (Transaction): 139420 +``` + +## Safe Deployment with Alchemy Paymaster (Gas Policy) + ``` +npm run alchemy:account:paymaster -Transaction Link: https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb +> @safe-global/4337-gas-metering@1.0.0 alchemy:account:paymaster +> tsx ./alchemy/alchemy.ts account paymaster=true -Gas Usage: 419691 +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xb00f9dEda8C1822Ab7d5b721518054F3C4437ed3 +Address Link: https://sepolia.etherscan.io/address/0xb00f9dEda8C1822Ab7d5b721518054F3C4437ed3 -## ERC20 Transaction with Alchemy Paymaster +Deploying a new Safe and executing calldata passed with it (if any). -NPM Command to run: +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x53f77a84f9f1662bc729e1afe39038ba1b7d6a8118bf4f91d7889cbcf7fb04ca +UserOp Link: https://jiffyscan.xyz/userOpHash/0x53f77a84f9f1662bc729e1afe39038ba1b7d6a8118bf4f91d7889cbcf7fb04ca?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1 + +Gas Used (Account or Paymaster): 411685 +Gas Used (Transaction): 411372 ``` -npm run alchemy:erc20 -> @safe-global/aa-analysis@1.0.0 alchemy:erc20 -> tsx ./alchemy/alchemy-erc20.ts +## Safe Deployment + Native Transfer with Alchemy Paymaster (Gas Policy) + +``` +npm run alchemy:native-transfer:paymaster + +> @safe-global/4337-gas-metering@1.0.0 alchemy:native-transfer:paymaster +> tsx ./alchemy/alchemy.ts native-transfer paymaster=true Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0xe433Edadd1eD908FE5541366C1F837b9B60162d9 -Address Link: https://sepolia.etherscan.io/address/0xe433Edadd1eD908FE5541366C1F837b9B60162d9 +Counterfactual Sender Address Created: 0xA6317eA7527e846cf0d47F66f1A29f20e9Fe27cB +Address Link: https://sepolia.etherscan.io/address/0xA6317eA7527e846cf0d47F66f1A29f20e9Fe27cB -Safe Wallet ERC20 Balance: 1 +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. +Appropriate calldata created. + Signed Dummy Data for Paymaster Data Creation from Alchemy. Received Paymaster Data from Alchemy. Signed Real Data including Paymaster Data Created by Alchemy. -Safe Account Creation User Operation Successfully Created! -UserOp Link: https://jiffyscan.xyz/userOpHash/0xe61b3e204d9dda83e318c17de6dab6eb2fb10bae7786959e93e2c4e1d63df8e8?network=sepolia +UserOperation submitted. Hash: 0xac1f808deffef50dcadf59c2ed689e6c56f13a210e8631f098fb2df4f99b6ac9 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xac1f808deffef50dcadf59c2ed689e6c56f13a210e8631f098fb2df4f99b6ac9?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672 + +Gas Used (Account or Paymaster): 419104 +Gas Used (Transaction): 418779 ``` -Transaction Link: https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2 +## Native Transfer with Alchemy Paymaster (Gas Policy) -Gas Usage: 131418 +``` +npm run alchemy:native-transfer:paymaster -## Safe Deployment + ERC721 Transaction with Alchemy Paymaster +> @safe-global/4337-gas-metering@1.0.0 alchemy:native-transfer:paymaster +> tsx ./alchemy/alchemy.ts native-transfer paymaster=true -NPM Command to run: +Signer Extracted from Private Key. +Init Code Created. + +Counterfactual Sender Address Created: 0xA6317eA7527e846cf0d47F66f1A29f20e9Fe27cB +Address Link: https://sepolia.etherscan.io/address/0xA6317eA7527e846cf0d47F66f1A29f20e9Fe27cB + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x27978ad0d669dde49ea8b35a3db25e12360de6b2843b49b392f3da5946aa66ed +UserOp Link: https://jiffyscan.xyz/userOpHash/0x27978ad0d669dde49ea8b35a3db25e12360de6b2843b49b392f3da5946aa66ed?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a + +Gas Used (Account or Paymaster): 130440 +Gas Used (Transaction): 130202 ``` -npm run alchemy:erc721 -> @safe-global/aa-analysis@1.0.0 alchemy:erc721 -> tsx ./alchemy/alchemy-erc721.ts +## Safe Deployment + ERC20 Transaction with Alchemy Paymaster (Gas Policy) + +``` +npm run alchemy:erc20:paymaster + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc20:paymaster +> tsx ./alchemy/alchemy.ts erc20 paymaster=true Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x249d03e36b3eDDDB3E862959ef3a1cB28Cf01dD2 -Address Link: https://sepolia.etherscan.io/address/0x249d03e36b3eDDDB3E862959ef3a1cB28Cf01dD2 +Counterfactual Sender Address Created: 0xbF41EE996c17E6cC237C4186ABBcd9DCe197286B +Address Link: https://sepolia.etherscan.io/address/0xbF41EE996c17E6cC237C4186ABBcd9DCe197286B + +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. + Signed Dummy Data for Paymaster Data Creation from Alchemy. Received Paymaster Data from Alchemy. Signed Real Data including Paymaster Data Created by Alchemy. -Safe Account Creation User Operation Successfully Created! -UserOp Link: https://jiffyscan.xyz/userOpHash/0x79f121c77c2842d906c9cd193445f73242c08c3a231b49aee654eaecf448689f?network=sepolia +UserOperation submitted. Hash: 0x34a820e77e07b698bc13c81d7f4872889d2330beeffd7c862f18e6073e19d7af +UserOp Link: https://jiffyscan.xyz/userOpHash/0x34a820e77e07b698bc13c81d7f4872889d2330beeffd7c862f18e6073e19d7af?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a + +Gas Used (Account or Paymaster): 427047 +Gas Used (Transaction): 421926 ``` -Transaction Link: https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba +## ERC20 Transaction with Alchemy Paymaster (Gas Policy) + +``` +npm run alchemy:erc20:paymaster + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc20:paymaster +> tsx ./alchemy/alchemy.ts erc20 paymaster=true + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xbF41EE996c17E6cC237C4186ABBcd9DCe197286B +Address Link: https://sepolia.etherscan.io/address/0xbF41EE996c17E6cC237C4186ABBcd9DCe197286B + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 -Gas Usage: 448953 +Appropriate calldata created. -## ERC721 Transaction with Alchemy Paymaster +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. -NPM Command to run: +Signed Real Data including Paymaster Data Created by Alchemy. +UserOperation submitted. Hash: 0xab669bd1093de63befc2a9c453d366a35beba1275b5d5c7e2413cc7a779b6280 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xab669bd1093de63befc2a9c453d366a35beba1275b5d5c7e2413cc7a779b6280?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1 + +Gas Used (Account or Paymaster): 138404 +Gas Used (Transaction): 133394 ``` -npm run alchemy:erc721 -> @safe-global/aa-analysis@1.0.0 alchemy:erc721 -> tsx ./alchemy/alchemy-erc721.ts +## Safe Deployment + ERC721 Transaction with Alchemy Paymaster (Gas Policy) + +``` +npm run alchemy:erc721:paymaster + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc721:paymaster +> tsx ./alchemy/alchemy.ts erc721 paymaster=true Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0xe433Edadd1eD908FE5541366C1F837b9B60162d9 -Address Link: https://sepolia.etherscan.io/address/0xe433Edadd1eD908FE5541366C1F837b9B60162d9 +Counterfactual Sender Address Created: 0x6Bea0dbCcD353b648F2e5C09106B36C2351208c4 +Address Link: https://sepolia.etherscan.io/address/0x6Bea0dbCcD353b648F2e5C09106B36C2351208c4 + +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. +Appropriate calldata created. + Signed Dummy Data for Paymaster Data Creation from Alchemy. Received Paymaster Data from Alchemy. Signed Real Data including Paymaster Data Created by Alchemy. -Safe Account Creation User Operation Successfully Created! -UserOp Link: https://jiffyscan.xyz/userOpHash/0x0f93595c74153ca7d8968ad5206834b53e1a50463835862fe375de472e969420?network=sepolia +UserOperation submitted. Hash: 0xcb415603dee2458f4b2d0f69a83264758c8fded6c3a91ec6ac513153bccdf15b +UserOp Link: https://jiffyscan.xyz/userOpHash/0xcb415603dee2458f4b2d0f69a83264758c8fded6c3a91ec6ac513153bccdf15b?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84 + +Gas Used (Account or Paymaster): 451515 +Gas Used (Transaction): 451200 +``` + +## ERC721 Transaction with Alchemy Paymaster (Gas Policy) + ``` +npm run alchemy:erc721:paymaster + +> @safe-global/4337-gas-metering@1.0.0 alchemy:erc721:paymaster +> tsx ./alchemy/alchemy.ts erc721 paymaster=true -Transaction Link: https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1 +Signer Extracted from Private Key. -Gas Usage: 194878 +Init Code Created. + +Counterfactual Sender Address Created: 0x6Bea0dbCcD353b648F2e5C09106B36C2351208c4 +Address Link: https://sepolia.etherscan.io/address/0x6Bea0dbCcD353b648F2e5C09106B36C2351208c4 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Paymaster Data Creation from Alchemy. + +Received Paymaster Data from Alchemy. + +Signed Real Data including Paymaster Data Created by Alchemy. + +UserOperation submitted. Hash: 0x4404a43075c523e59f909acc7379c323eb1427b781bc48b5002007d167e63c83 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x4404a43075c523e59f909acc7379c323eb1427b781bc48b5002007d167e63c83?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a + +Gas Used (Account or Paymaster): 162859 +Gas Used (Transaction): 162654 +``` diff --git a/4337-gas-metering/pimlico/README.md b/4337-gas-metering/pimlico/README.md index 6f1e1ac7..e42a1633 100644 --- a/4337-gas-metering/pimlico/README.md +++ b/4337-gas-metering/pimlico/README.md @@ -1,61 +1,137 @@ -# Pimlico Paymaster Analysis +# Pimlico -## Safe Deployment with Pimlico Paymaster (USDC) +## Safe Deployment with Pimlico Paymaster (Own Sponsorship) ``` npm run pimlico:account -> @safe-global/aa-analysis@1.0.0 pimlico:account -> tsx ./pimlico/pimlico-account.ts +> @safe-global/4337-gas-metering@1.0.0 pimlico:account +> tsx ./pimlico/pimlico.ts account Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x3680E646f69c94269540AB157C18B7C271D14E6d -Address Link: https://goerli.etherscan.io/address/0x3680E646f69c94269540AB157C18B7C271D14E6d +Counterfactual Sender Address Created: 0x49Bcc15610B8bb5C35392E9bcCa19af516BeF408 +Address Link: https://mumbai.polygonscan.com/address/0x49Bcc15610B8bb5C35392E9bcCa19af516BeF408 -Safe Wallet ERC20 Balance: 0 +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. -Please deposit atleast 1 USDC Token for paying the Paymaster. +Appropriate calldata created. + +Safe Wallet USDC Balance: 0 + +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x81ba032e86c9169c9f295a1d435458ba7d7c0cab95a575b84081539e6266e461 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x81ba032e86c9169c9f295a1d435458ba7d7c0cab95a575b84081539e6266e461?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28 +Transaction Link: https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28 + +Gas Used (Account or Paymaster): 504830 +Gas Used (Transaction): 506573 +``` + +## Safe Deployment + Native Transfer with Pimlico Paymaster (Own Sponsorship) + +``` +npm run pimlico:native-transfer + +> @safe-global/4337-gas-metering@1.0.0 pimlico:native-transfer +> tsx ./pimlico/pimlico.ts native-transfer + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x32965E785bC35EaD9C837dd4e602E260B84f2594 +Address Link: https://mumbai.polygonscan.com/address/0x32965E785bC35EaD9C837dd4e602E260B84f2594 + +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. -Deploying a new Safe. +Appropriate calldata created. + +Safe Wallet USDC Balance: 0 + +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. -UserOperation submitted. Hash: 0xda816afbbb3d3daffe3049d1f661f423a9cc30f6de3d43e3bf2394f1311dcbbc -UserOp Link: https://jiffyscan.xyz/userOpHash/0xda816afbbb3d3daffe3049d1f661f423a9cc30f6de3d43e3bf2394f1311dcbbc?network=goerli +Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x33550a7468bb4949c8cdc0b333cc9aa0e289f7e080e85274275644ef8d8786c9 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x33550a7468bb4949c8cdc0b333cc9aa0e289f7e080e85274275644ef8d8786c9?network=mumbai Querying for receipts... Receipt found! -Transaction hash: 0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1 -Transaction Link: https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1 -``` +Transaction hash: 0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3 +Transaction Link: https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3 -Gas Usage: 499796 +Gas Used (Account or Paymaster): 509312 +Gas Used (Transaction): 511055 +``` -## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (USDC) +## Native Transfer with Pimlico Paymaster (Own Sponsorship) ``` -npm run pimlico:erc20 +npm run pimlico:native-transfer -> @safe-global/aa-analysis@1.0.0 pimlico:erc20 -> tsx ./pimlico/pimlico-erc20.ts +> @safe-global/4337-gas-metering@1.0.0 pimlico:native-transfer +> tsx ./pimlico/pimlico.ts native-transfer Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x466e2F6ccF2e7B6d44a51b0C6072Ed597154Ec4c -Address Link: https://goerli.etherscan.io/address/0x466e2F6ccF2e7B6d44a51b0C6072Ed597154Ec4c +Counterfactual Sender Address Created: 0x32965E785bC35EaD9C837dd4e602E260B84f2594 +Address Link: https://mumbai.polygonscan.com/address/0x32965E785bC35EaD9C837dd4e602E260B84f2594 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. Safe Wallet USDC Balance: 0 -Please deposit atleast 1 USDC Token for paying the Paymaster. +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x1da0059783a7bd4d752bcc7b1a44c06f01236ba38687d502e9a69d7c84b1230e +UserOp Link: https://jiffyscan.xyz/userOpHash/0x1da0059783a7bd4d752bcc7b1a44c06f01236ba38687d502e9a69d7c84b1230e?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186 +Transaction Link: https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186 + +Gas Used (Account or Paymaster): 197382 +Gas Used (Transaction): 199262 +``` + +## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (Own Sponsorship) + +``` +npm run pimlico:erc20 + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc20 +> tsx ./pimlico/pimlico.ts erc20 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x70E545d18b53413c47051a56b063AdE487a209Ff +Address Link: https://mumbai.polygonscan.com/address/0x70E545d18b53413c47051a56b063AdE487a209Ff + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. Safe Wallet ERC20 Balance: 0 @@ -63,37 +139,43 @@ Minting ERC20 Tokens to Safe Wallet. Updated Safe Wallet ERC20 Balance: 1 -Nonce for the sender received from EntryPoint. +Appropriate calldata created. + +Safe Wallet USDC Balance: 0 -Deploying a new Safe and transfering 1 ERC20 from Safe to Signer in one tx. +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. -UserOperation submitted. Hash: 0xab76c14cd3d8fa6dae9202b5f3f52fb6aae2e0050240efb9300d9cf32861c040 -UserOp Link: https://jiffyscan.xyz/userOpHash/0xab76c14cd3d8fa6dae9202b5f3f52fb6aae2e0050240efb9300d9cf32861c040?network=goerli +Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x4d175a2c4e151b7745e03ce9936e06fbc3115118d06e5b8bef5211add2151821 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x4d175a2c4e151b7745e03ce9936e06fbc3115118d06e5b8bef5211add2151821?network=mumbai Querying for receipts... Receipt found! -Transaction hash: 0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a -Transaction Link: https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a -``` +Transaction hash: 0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623 +Transaction Link: https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623 -Gas Usage: 511091 +Gas Used (Account or Paymaster): 517210 +Gas Used (Transaction): 514156 +``` -## ERC20 Transaction with Pimlico Paymaster (USDC) +## ERC20 Transaction with Pimlico Paymaster (Own Sponsorship) ``` npm run pimlico:erc20 -> @safe-global/aa-analysis@1.0.0 pimlico:erc20 -> tsx ./pimlico/pimlico-erc20.ts +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc20 +> tsx ./pimlico/pimlico.ts erc20 Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0xcdf86Aa2002e56A3F9e37A9a28CA91cdfC1994ac -Address Link: https://goerli.etherscan.io/address/0xcdf86Aa2002e56A3F9e37A9a28CA91cdfC1994ac +Counterfactual Sender Address Created: 0x70E545d18b53413c47051a56b063AdE487a209Ff +Address Link: https://mumbai.polygonscan.com/address/0x70E545d18b53413c47051a56b063AdE487a209Ff -Safe Wallet USDC Balance: 2 +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. Safe Wallet ERC20 Balance: 0 @@ -101,85 +183,333 @@ Minting ERC20 Tokens to Safe Wallet. Updated Safe Wallet ERC20 Balance: 1 -Nonce for the sender received from EntryPoint. +Appropriate calldata created. -The Safe is already deployed. Sending 1 USDC from the Safe to itself. +Safe Wallet USDC Balance: 0 -UserOperation submitted. Hash: 0xb1c4a82e42e0b5c11853b22a8699717ab5fea4b5189932b9960a676a01490403 -UserOp Link: https://jiffyscan.xyz/userOpHash/0xb1c4a82e42e0b5c11853b22a8699717ab5fea4b5189932b9960a676a01490403?network=goerli +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. + +Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0xd4475cf9151629cb44236b4da541996b103c4e89a075c2cb9375f533421da1e1 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xd4475cf9151629cb44236b4da541996b103c4e89a075c2cb9375f533421da1e1?network=mumbai Querying for receipts... Receipt found! -Transaction hash: 0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d -Transaction Link: https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d +Transaction hash: 0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91 +Transaction Link: https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91 + +Gas Used (Account or Paymaster): 205268 +Gas Used (Transaction): 202387 +``` + +## Safe Deployment + ERC721 Transaction with Pimlico Paymaster (Own Sponsorship) + ``` +npm run pimlico:erc721 + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc721 +> tsx ./pimlico/pimlico.ts erc721 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x6306048538368FD9009102B10EdB6B38Fa6d48a7 +Address Link: https://mumbai.polygonscan.com/address/0x6306048538368FD9009102B10EdB6B38Fa6d48a7 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Safe Wallet USDC Balance: 0 -Gas Usage: 200038 +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. -## Safe Deployment + ERC721 Transaction with Pimlico Paymaster (USDC) +Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x24a391b1114c2ed44caea101d4a01011bb765c1f85e4d3f69fb16aae79ab2fac +UserOp Link: https://jiffyscan.xyz/userOpHash/0x24a391b1114c2ed44caea101d4a01011bb765c1f85e4d3f69fb16aae79ab2fac?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006 +Transaction Link: https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006 + +Gas Used (Account or Paymaster): 541670 +Gas Used (Transaction): 543411 +``` + +## ERC721 Transaction with Pimlico Paymaster (Own Sponsorship) ``` npm run pimlico:erc721 -> @safe-global/aa-analysis@1.0.0 pimlico:erc721 -> tsx ./pimlico/pimlico-erc721.ts +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc721 +> tsx ./pimlico/pimlico.ts erc721 Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb -Address Link: https://goerli.etherscan.io/address/0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb +Counterfactual Sender Address Created: 0x6306048538368FD9009102B10EdB6B38Fa6d48a7 +Address Link: https://mumbai.polygonscan.com/address/0x6306048538368FD9009102B10EdB6B38Fa6d48a7 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. Safe Wallet USDC Balance: 0 -Please deposit atleast 2 USDC Token for paying the Paymaster. +Transferring 1 USDC Token for paying the Paymaster from Sender to Safe. + +Updated Safe Wallet USDC Balance: 1 +UserOperation submitted. Hash: 0x05499bd6a9c3b6ecf4bd2ec5be00ae3f1e5597daca258b2a27c58b330a90cb28 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x05499bd6a9c3b6ecf4bd2ec5be00ae3f1e5597daca258b2a27c58b330a90cb28?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192 +Transaction Link: https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192 + +Gas Used (Account or Paymaster): 229741 +Gas Used (Transaction): 231619 +``` + +## Safe Deployment with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:account:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:account:paymaster +> tsx ./pimlico/pimlico.ts account paymaster=true + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8ac55DeB4a707BDD62f63D98570B82736b3FBC64 +Address Link: https://mumbai.polygonscan.com/address/0x8ac55DeB4a707BDD62f63D98570B82736b3FBC64 -Updated Safe Wallet USDC Balance: 2 +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. -Deploying a new Safe and Minting 1 ERC721 Token to the Safe in one tx -UserOperation submitted. Hash: 0x4e587fa1e1598f8f02f9e6d9f19091a5355b45c703e857b6d344181d0e124c88 -UserOp Link: https://jiffyscan.xyz/userOpHash/0x4e587fa1e1598f8f02f9e6d9f19091a5355b45c703e857b6d344181d0e124c88?network=goerli + +Appropriate calldata created. +UserOperation submitted. Hash: 0xbf6edac0683e35c855467cb9822eb6d151d7826ee15404ddb618d906800092dc +UserOp Link: https://jiffyscan.xyz/userOpHash/0xbf6edac0683e35c855467cb9822eb6d151d7826ee15404ddb618d906800092dc?network=mumbai Querying for receipts... Receipt found! -Transaction hash: 0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d -Transaction Link: https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d +Transaction hash: 0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a +Transaction Link: https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a + +Gas Used (Account or Paymaster): 446245 +Gas Used (Transaction): 448172 ``` -Gas Usage: 558029 +## Safe Deployment + Native Transfer with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:native-transfer:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:native-transfer:paymaster +> tsx ./pimlico/pimlico.ts native-transfer paymaster=true + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8fE158E24Aa2330F002aabB967815a817FE4F478 +Address Link: https://mumbai.polygonscan.com/address/0x8fE158E24Aa2330F002aabB967815a817FE4F478 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. +UserOperation submitted. Hash: 0x7d2ca6fba592799c4c9aa5e38ea24bd31d4516a53030b09b263341912bf819bc +UserOp Link: https://jiffyscan.xyz/userOpHash/0x7d2ca6fba592799c4c9aa5e38ea24bd31d4516a53030b09b263341912bf819bc?network=mumbai -## ERC721 Transaction with Pimlico Paymaster (USDC) +Querying for receipts... +Receipt found! +Transaction hash: 0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4 +Transaction Link: https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4 +Gas Used (Account or Paymaster): 453652 +Gas Used (Transaction): 455615 ``` -npm run pimlico:erc721 -> @safe-global/aa-analysis@1.0.0 pimlico:erc721 -> tsx ./pimlico/pimlico-erc721.ts +## Native Transfer with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:native-transfer:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:native-transfer:paymaster +> tsx ./pimlico/pimlico.ts native-transfer paymaster=true + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8fE158E24Aa2330F002aabB967815a817FE4F478 +Address Link: https://mumbai.polygonscan.com/address/0x8fE158E24Aa2330F002aabB967815a817FE4F478 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. +UserOperation submitted. Hash: 0x38fd87397a93359b50265f3bf388b0a03a5f1845b977a0a7b1fb6ac053241eb9 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x38fd87397a93359b50265f3bf388b0a03a5f1845b977a0a7b1fb6ac053241eb9?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e +Transaction Link: https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e + +Gas Used (Account or Paymaster): 120998 +Gas Used (Transaction): 123064 +``` + +## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:erc20:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc20:paymaster +> tsx ./pimlico/pimlico.ts erc20 paymaster=true + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8aaADBe50a15e1aFfe7D4363D4e00540E8e0db7D +Address Link: https://mumbai.polygonscan.com/address/0x8aaADBe50a15e1aFfe7D4363D4e00540E8e0db7D + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. +UserOperation submitted. Hash: 0xaa85e8c6f94695fb829541e55eda8b5b5f23a8cca4541f4a53d62b1280861736 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xaa85e8c6f94695fb829541e55eda8b5b5f23a8cca4541f4a53d62b1280861736?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271 +Transaction Link: https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271 + +Gas Used (Account or Paymaster): 461859 +Gas Used (Transaction): 459014 +``` + +## ERC20 Transaction with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:erc20:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc20:paymaster +> tsx ./pimlico/pimlico.ts erc20 paymaster=true Signer Extracted from Private Key. Init Code Created. -Counterfactual Sender Address Created: 0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb -Address Link: https://goerli.etherscan.io/address/0x6633A7dD8122bbb7802984D3787f1D87e52c9cDb +Counterfactual Sender Address Created: 0x8aaADBe50a15e1aFfe7D4363D4e00540E8e0db7D +Address Link: https://mumbai.polygonscan.com/address/0x8aaADBe50a15e1aFfe7D4363D4e00540E8e0db7D + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. +UserOperation submitted. Hash: 0xbc1283f136edac0ea47d140c2ab11568a33584021cd0530eec3a2a5515136822 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xbc1283f136edac0ea47d140c2ab11568a33584021cd0530eec3a2a5515136822?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496 +Transaction Link: https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496 + +Gas Used (Account or Paymaster): 129190 +Gas Used (Transaction): 126461 +``` + +## Safe Deployment + ERC721 Transaction with Pimlico Paymaster (Gas Policy) + +``` +npm run pimlico:erc721:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc721:paymaster +> tsx ./pimlico/pimlico.ts erc721 paymaster=true -Safe Wallet USDC Balance: 1 +Signer Extracted from Private Key. + +Init Code Created. -Please deposit atleast 2 USDC Token for paying the Paymaster. +Counterfactual Sender Address Created: 0x07a49F28A360B7799AeEBC9907bE605daFc13a30 +Address Link: https://mumbai.polygonscan.com/address/0x07a49F28A360B7799AeEBC9907bE605daFc13a30 -Updated Safe Wallet USDC Balance: 2 +Deploying a new Safe and executing calldata passed with it (if any). Nonce for the sender received from EntryPoint. -The Safe is already deployed. Minting 1 ERC721 Token to the Safe. -UserOperation submitted. Hash: 0xee2a5f5f415273d661440d402f6bc8f6303870651a8790ebb745aeeda031bfc4 -UserOp Link: https://jiffyscan.xyz/userOpHash/0xee2a5f5f415273d661440d402f6bc8f6303870651a8790ebb745aeeda031bfc4?network=goerli + +Appropriate calldata created. +UserOperation submitted. Hash: 0x9b917c637eed529c8ae13eeb00ed8fdf3aac711dea7efdac7c048ba16bd9c8e3 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x9b917c637eed529c8ae13eeb00ed8fdf3aac711dea7efdac7c048ba16bd9c8e3?network=mumbai Querying for receipts... Receipt found! -Transaction hash: 0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928 -Transaction Link: https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928 +Transaction hash: 0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535 +Transaction Link: https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535 + +Gas Used (Account or Paymaster): 486237 +Gas Used (Transaction): 488186 +``` + +## ERC721 Transaction with Pimlico Paymaster (Gas Policy) + ``` +npm run pimlico:erc721:paymaster + +> @safe-global/4337-gas-metering@1.0.0 pimlico:erc721:paymaster +> tsx ./pimlico/pimlico.ts erc721 paymaster=true -Gas Usage: 229913 +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x07a49F28A360B7799AeEBC9907bE605daFc13a30 +Address Link: https://mumbai.polygonscan.com/address/0x07a49F28A360B7799AeEBC9907bE605daFc13a30 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. +UserOperation submitted. Hash: 0x8a82cfc2396e1031f09e8f9725e276ded8e2a741a70264d6b23aaafc314e7105 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x8a82cfc2396e1031f09e8f9725e276ded8e2a741a70264d6b23aaafc314e7105?network=mumbai + +Querying for receipts... +Receipt found! +Transaction hash: 0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652 +Transaction Link: https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652 + +Gas Used (Account or Paymaster): 153569 +Gas Used (Transaction): 155645 +``` From 83f26d6b96075e36595a8e07b6b89546a6b6ce8c Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:35:09 +0530 Subject: [PATCH 49/60] Gas Logging in UserOps --- 4337-gas-metering/utils/userOps.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 6b842272..1983def7 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -1,10 +1,7 @@ import dotenv from "dotenv"; import type { Address } from "abitype"; -import type { Hex, PrivateKeyAccount } from "viem"; -import { - EIP712_SAFE_OPERATION_TYPE, - encodeCallData, -} from "./safe"; +import { fromHex, type Hex, type PrivateKeyAccount } from "viem"; +import { EIP712_SAFE_OPERATION_TYPE, encodeCallData } from "./safe"; import { Alchemy } from "alchemy-sdk"; import { setTimeout } from "timers/promises"; import { @@ -86,6 +83,8 @@ export const submitUserOperationPimlico = async ( `.etherscan.io/tx/${receipt.receipt.transactionHash}`, ); } + console.log(`\nGas Used (Account or Paymaster): ${receipt.actualGasUsed}`); + console.log(`Gas Used (Transaction): ${receipt.receipt.gasUsed}\n`); }; export const signUserOperation = async ( @@ -373,9 +372,18 @@ export const submitUserOperationAlchemy = async ( "\nTransaction Link: https://" + chain + ".etherscan.io/tx/" + - responseValues["result"]["receipt"]["transactionHash"] + - "\n", + responseValues["result"]["receipt"]["transactionHash"], + ); + let actualGasUsed = fromHex( + responseValues["result"]["actualGasUsed"], + "number", + ); + let gasUsed = fromHex( + responseValues["result"]["receipt"]["gasUsed"], + "number", ); + console.log(`\nGas Used (Account or Paymaster): ${actualGasUsed}`); + console.log(`Gas Used (Transaction): ${gasUsed}\n`); } else { console.log("\n" + responseValues["error"]); } From d72c8f3bd833eb63f58de3bb1aa1884e14d63711 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:35:29 +0530 Subject: [PATCH 50/60] Paymaster and Test Gas Results Added --- 4337-gas-metering/README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/4337-gas-metering/README.md b/4337-gas-metering/README.md index 186520d7..2420bad3 100644 --- a/4337-gas-metering/README.md +++ b/4337-gas-metering/README.md @@ -4,17 +4,16 @@ 1. Rename the `.env.example` to `.env`. 2. Fill the required values of `.env`. -3. Based on which paymaster to run, check the `package.json` file to see the script. Furthermore, you can check the `README.md` files in the corresponding paymaster folders to see the individual command. +3. Based on which paymaster to run, check the `package.json` file to see the `script`. Furthermore, you can check the `README.md` files in the corresponding paymaster folders to see the individual command and their possible results. -NOTE: If you run a paymaster analysis twice or more without changing the salt for Safe Creation, then only the operation will execute through paymaster, rather than Safe Creation and Operation. +NOTE: If you run a paymaster analysis twice or more without changing the salt for Safe Creation, then only the operation will execute through paymaster (if any), rather than Safe Creation and Operation. ## Gas Usage Results -| Type of Transaction | Without Paymaster | Pimlico (USDC Paymaster) | Alchemy (ETH Paymaster) | -| -------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| Safe Deployment with 4337 Module | 358975 | [499796](https://goerli.etherscan.io/tx/0x6ed6566395a3525a860207bc4a26ab3f568dcf787de4f8477cac9ad667af9cd1) | [408370](https://sepolia.etherscan.io/tx/0x7dfffc6893755ec533cb3488abbc4ed155dccc2d91e22ab86b445ae06ef943aa) | -| Safe Deployment with 4337 Module + ERC20 Transfer | 369890 | [511091](https://goerli.etherscan.io/tx/0x07d650f552c115aadc18c717eb1e64bec69ea5a49c760a02ff7ae392a154b03a) | [419691](https://sepolia.etherscan.io/tx/0xd5fa12e541394fe893b068c03d030c4bdcc3ff269de56b20153954246039b8eb) | -| ERC20 Transfer using Safe with 4337 Module Enabled | 93674 | [200038](https://goerli.etherscan.io/tx/0x8cf80187949edd0306bbf21fc998cbbedf59f0f3a4f51a67013536db98bc339d) | [131418](https://sepolia.etherscan.io/tx/0xb2bea2d2ec6b8d9b8cdb62e119a94ff8829b718f6f818e41c2170bba04fb33e2) | -| Safe Deployment with 4337 Module + ERC721 Minting | 411677 | [558029](https://goerli.etherscan.io/tx/0x63bdc3173c90ed3bff9f7c889156914d482c0fce2652fa006271d2aa0c25fa8d) | [448953](https://sepolia.etherscan.io/tx/0x24e1f66c2da65d53bef53f70935b270892e451827dc3ed382d4990598aa11eba) | -| ERC721 Minting using Safe with 4337 Module Enabled | 135449 | [229913](https://goerli.etherscan.io/tx/0x553dbe52083f5e56bc75eaf389812df810a4556ecd291d7310f17335d8ebb928) | [194878](https://sepolia.etherscan.io/tx/0xf002a715320c8ebc181e2debedf86eafd335c5d0163f79d628f994f7023ee8e1) | -| | | | | +| | **With 4337?** | **Account Creation** | **Account Creation + Native Transfer** | **Native Transfer** | **Account Creation + ERC20 Transfer** | **ERC20 Transfer** | **Account Creation + ERC721 Minting** | **ERC721 Minting** | +| :---------------------------------------------------: | :------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | +| **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 358975 | 363906 | 87690 | 369890 | 93674 | 411677 | 135449 | +| **Pimlico (USDC Paymaster)** | Yes | [506573](https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28) | [511055](https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3) | [199262](https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186) | [514156](https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623) | [202387](https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91) | [543411](https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006) | [231619](https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192) | +| **Pimlico (MATIC - Gas Policy)** | Yes | [448172](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a) | [455615](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4) | [123064](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e) | [459014](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271) | [126461](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496) | [488186](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535) | [155645](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652) | +| **Alchemy (ETH from Safe)** | Yes | [417074](https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88) | [424505](https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2) | [107057](https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b) | [427599](https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143) | [110174](https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d) | [456870](https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6) | [139420](https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e) | +| **Alchemy (ETH - Gas Policy)** | Yes | [411372](https://sepolia.etherscan.io/tx/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1) | [418779](https://sepolia.etherscan.io/tx/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672) | [130202](https://sepolia.etherscan.io/tx/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a) | [421926](https://sepolia.etherscan.io/tx/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a) | [133394](https://sepolia.etherscan.io/tx/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1) | [451200](https://sepolia.etherscan.io/tx/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84) | [162654](https://sepolia.etherscan.io/tx/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a) | From 391064b88e733ee3f469fd21c1195948f54ce74f Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 16:35:55 +0530 Subject: [PATCH 51/60] Formatting Changes --- 4337-gas-metering/alchemy/README.md | 6 +++--- 4337-gas-metering/alchemy/alchemy.ts | 2 +- 4337-gas-metering/pimlico/README.md | 10 +++++----- 4337-gas-metering/pimlico/pimlico.ts | 2 -- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/4337-gas-metering/alchemy/README.md b/4337-gas-metering/alchemy/README.md index 9a7b707c..7036e75f 100644 --- a/4337-gas-metering/alchemy/README.md +++ b/4337-gas-metering/alchemy/README.md @@ -129,7 +129,7 @@ Gas Used (Transaction): 107057 ## Safe Deployment + ERC20 Transaction with Alchemy Paymaster (Own Sponsorship) ``` -npm run alchemy:erc20 +npm run alchemy:erc20 > @safe-global/4337-gas-metering@1.0.0 alchemy:erc20 > tsx ./alchemy/alchemy.ts erc20 @@ -309,7 +309,7 @@ Gas Used (Transaction): 139420 ## Safe Deployment with Alchemy Paymaster (Gas Policy) ``` -npm run alchemy:account:paymaster +npm run alchemy:account:paymaster > @safe-global/4337-gas-metering@1.0.0 alchemy:account:paymaster > tsx ./alchemy/alchemy.ts account paymaster=true @@ -417,7 +417,7 @@ Gas Used (Transaction): 130202 ## Safe Deployment + ERC20 Transaction with Alchemy Paymaster (Gas Policy) ``` -npm run alchemy:erc20:paymaster +npm run alchemy:erc20:paymaster > @safe-global/4337-gas-metering@1.0.0 alchemy:erc20:paymaster > tsx ./alchemy/alchemy.ts erc20 paymaster=true diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index 9a9a3763..592ce36c 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -261,7 +261,7 @@ if (usePaymaster) { chain, paymaster, ); - while(safeETHBalance < weiToSend){ + while (safeETHBalance < weiToSend) { await setTimeout(30000); // Sometimes it takes time to index. safeETHBalance = await publicClient.getBalance({ address: senderAddress, diff --git a/4337-gas-metering/pimlico/README.md b/4337-gas-metering/pimlico/README.md index e42a1633..87f6018a 100644 --- a/4337-gas-metering/pimlico/README.md +++ b/4337-gas-metering/pimlico/README.md @@ -117,7 +117,7 @@ Gas Used (Transaction): 199262 ## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (Own Sponsorship) ``` -npm run pimlico:erc20 +npm run pimlico:erc20 > @safe-global/4337-gas-metering@1.0.0 pimlico:erc20 > tsx ./pimlico/pimlico.ts erc20 @@ -281,7 +281,7 @@ Gas Used (Transaction): 231619 ## Safe Deployment with Pimlico Paymaster (Gas Policy) ``` -npm run pimlico:account:paymaster +npm run pimlico:account:paymaster > @safe-global/4337-gas-metering@1.0.0 pimlico:account:paymaster > tsx ./pimlico/pimlico.ts account paymaster=true @@ -313,7 +313,7 @@ Gas Used (Transaction): 448172 ## Safe Deployment + Native Transfer with Pimlico Paymaster (Gas Policy) ``` -npm run pimlico:native-transfer:paymaster +npm run pimlico:native-transfer:paymaster > @safe-global/4337-gas-metering@1.0.0 pimlico:native-transfer:paymaster > tsx ./pimlico/pimlico.ts native-transfer paymaster=true @@ -377,7 +377,7 @@ Gas Used (Transaction): 123064 ## Safe Deployment + ERC20 Transaction with Pimlico Paymaster (Gas Policy) ``` -npm run pimlico:erc20:paymaster +npm run pimlico:erc20:paymaster > @safe-global/4337-gas-metering@1.0.0 pimlico:erc20:paymaster > tsx ./pimlico/pimlico.ts erc20 paymaster=true @@ -453,7 +453,7 @@ Gas Used (Transaction): 126461 ## Safe Deployment + ERC721 Transaction with Pimlico Paymaster (Gas Policy) ``` -npm run pimlico:erc721:paymaster +npm run pimlico:erc721:paymaster > @safe-global/4337-gas-metering@1.0.0 pimlico:erc721:paymaster > tsx ./pimlico/pimlico.ts erc721 paymaster=true diff --git a/4337-gas-metering/pimlico/pimlico.ts b/4337-gas-metering/pimlico/pimlico.ts index c6693eae..f41026cd 100644 --- a/4337-gas-metering/pimlico/pimlico.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -243,9 +243,7 @@ if (usePaymaster) { sponsorResult.verificationGasLimit; sponsoredUserOperation.preVerificationGas = sponsorResult.preVerificationGas; sponsoredUserOperation.paymasterAndData = sponsorResult.paymasterAndData; - } else { - // Fetch USDC balance of sender const usdcDecimals = await getERC20Decimals(usdcTokenAddress, publicClient); const usdcAmount = BigInt(10 ** usdcDecimals); From 8a7323a65f7e860c80118f5c2cee0db99aed8458 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Tue, 19 Dec 2023 22:14:39 +0530 Subject: [PATCH 52/60] Lint issue rectified --- 4337/test/gas/Gas.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index ea8f1fd1..2f1d3b50 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -80,7 +80,7 @@ describe('Gas Metering', () => { to: safe.address, value: amount, }) - let safeBalBefore = await ethers.provider.getBalance(safe.address) + const safeBalBefore = await ethers.provider.getBalance(safe.address) expect(safeBalBefore).to.equal(amount) const safeOp = buildSafeUserOpTransaction( @@ -103,7 +103,7 @@ describe('Gas Metering', () => { await logGas('Safe with 4337 Module Deployment + Native Transfer', entryPoint.executeUserOp(userOp, 0)) - let safeBalAfter = await ethers.provider.getBalance(safe.address) + const safeBalAfter = await ethers.provider.getBalance(safe.address) expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0) expect(safeBalAfter).to.equal(0) }) From 33a4e05001d801adeb88e697db4c9e7e6724a5e3 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 20 Dec 2023 19:58:58 +0530 Subject: [PATCH 53/60] Gelato Based Gas Results Added --- 4337-gas-metering/README.md | 1 + 4337-gas-metering/gelato/README.md | 308 ++++++++++++++++++++++ 4337-gas-metering/gelato/gelato.ts | 225 ++++++++++++++++ 4337-gas-metering/package.json | 5 + 4337-gas-metering/utils/erc20.ts | 25 ++ 4337-gas-metering/utils/nativeTransfer.ts | 15 +- 4337-gas-metering/utils/userOps.ts | 219 ++++++++++++++- 7 files changed, 787 insertions(+), 11 deletions(-) create mode 100644 4337-gas-metering/gelato/README.md create mode 100644 4337-gas-metering/gelato/gelato.ts diff --git a/4337-gas-metering/README.md b/4337-gas-metering/README.md index 2420bad3..56046c67 100644 --- a/4337-gas-metering/README.md +++ b/4337-gas-metering/README.md @@ -13,6 +13,7 @@ NOTE: If you run a paymaster analysis twice or more without changing the salt fo | | **With 4337?** | **Account Creation** | **Account Creation + Native Transfer** | **Native Transfer** | **Account Creation + ERC20 Transfer** | **ERC20 Transfer** | **Account Creation + ERC721 Minting** | **ERC721 Minting** | | :---------------------------------------------------: | :------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | | **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 358975 | 363906 | 87690 | 369890 | 93674 | 411677 | 135449 | +| **Gelato (4337 Compatible - 1Balance)** | Yes | [397421](https://sepolia.etherscan.io/tx/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505) | [404828](https://sepolia.etherscan.io/tx/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0) | [114692](https://sepolia.etherscan.io/tx/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15) | [408160](https://sepolia.etherscan.io/tx/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d) | [118033](https://sepolia.etherscan.io/tx/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405) | [437372](https://sepolia.etherscan.io/tx/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d) | [147232](https://sepolia.etherscan.io/tx/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5) | | **Pimlico (USDC Paymaster)** | Yes | [506573](https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28) | [511055](https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3) | [199262](https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186) | [514156](https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623) | [202387](https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91) | [543411](https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006) | [231619](https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192) | | **Pimlico (MATIC - Gas Policy)** | Yes | [448172](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a) | [455615](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4) | [123064](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e) | [459014](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271) | [126461](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496) | [488186](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535) | [155645](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652) | | **Alchemy (ETH from Safe)** | Yes | [417074](https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88) | [424505](https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2) | [107057](https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b) | [427599](https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143) | [110174](https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d) | [456870](https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6) | [139420](https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e) | diff --git a/4337-gas-metering/gelato/README.md b/4337-gas-metering/gelato/README.md new file mode 100644 index 00000000..3d4517f3 --- /dev/null +++ b/4337-gas-metering/gelato/README.md @@ -0,0 +1,308 @@ +# Gelato + +## Safe Deployment with Gelato + +``` +npm run gelato:account:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:account:1balance +> tsx ./gelato/gelato.ts account + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8Af37a939fBEd9ac9AdB04270EF28DC844256CB3 +Address Link: https://sepolia.etherscan.io/address/0x8Af37a939fBEd9ac9AdB04270EF28DC844256CB3 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0x55def6ec01815152dfbf4f943f21e164559d79a974789e1a647aa7394fa80984 +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0x55def6ec01815152dfbf4f943f21e164559d79a974789e1a647aa7394fa80984 + +User OP Hash: 0xf46f8a12a949bf36e9584c8e40772162c3530ce4f72542e9da3b672ccce9019a +UserOp Link: https://jiffyscan.xyz/userOpHash/0xf46f8a12a949bf36e9584c8e40772162c3530ce4f72542e9da3b672ccce9019a?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505 + +Gas Used (Account or Paymaster): 397421 +Gas Used (Transaction): 397421 +``` + +## Safe Deployment + Native Transfer with Gelato + +``` +npm run gelato:native-transfer:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:native-transfer:1balance +> tsx ./gelato/gelato.ts native-transfer + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8652734F7404E97FEe69cf617286C5423593Bad0 +Address Link: https://sepolia.etherscan.io/address/0x8652734F7404E97FEe69cf617286C5423593Bad0 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Transferring 0.000001 ETH to Safe for native transfer. + +Transferred required ETH for the native transfer. + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0x64080f3741400714cc10a6ca486e22467a2a879935c19268d6f1fe709e7f6ad8 +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0x64080f3741400714cc10a6ca486e22467a2a879935c19268d6f1fe709e7f6ad8 + +User OP Hash: 0x2126e36afaeae4190df1e540613aa972f8d73b37d9268ee089f4440db23ae74b +UserOp Link: https://jiffyscan.xyz/userOpHash/0x2126e36afaeae4190df1e540613aa972f8d73b37d9268ee089f4440db23ae74b?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0 + +Gas Used (Account or Paymaster): 404828 +Gas Used (Transaction): 404828 +``` + +## Native Transfer with Gelato + +``` +npm run gelato:native-transfer:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:native-transfer:1balance +> tsx ./gelato/gelato.ts native-transfer + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0x8652734F7404E97FEe69cf617286C5423593Bad0 +Address Link: https://sepolia.etherscan.io/address/0x8652734F7404E97FEe69cf617286C5423593Bad0 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Transferring 0.000001 ETH to Safe for native transfer. + +Transferred required ETH for the native transfer. + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0xa2d246b5deabbcc4378f3fd3f1fe9bf1ddae3a6b4e286d7f759c27e558562754 +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0xa2d246b5deabbcc4378f3fd3f1fe9bf1ddae3a6b4e286d7f759c27e558562754 + +User OP Hash: 0xc9c86bcbe24adf9e67fbe199dd2cb9abe7cff12634f76df68aa98ee28193ac48 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xc9c86bcbe24adf9e67fbe199dd2cb9abe7cff12634f76df68aa98ee28193ac48?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15 + +Gas Used (Account or Paymaster): 114692 +Gas Used (Transaction): 114692 +``` + +## Safe Deployment + ERC20 Transaction with Gelato + +``` +npm run gelato:erc20:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:erc20:1balance +> tsx ./gelato/gelato.ts erc20 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xe86D78002637143c34d0687890c1b102D054a614 +Address Link: https://sepolia.etherscan.io/address/0xe86D78002637143c34d0687890c1b102D054a614 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0x02da9332b8528ce750fe2db6f245c90854fa5daa339911bc3b12691728bafb1b +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0x02da9332b8528ce750fe2db6f245c90854fa5daa339911bc3b12691728bafb1b + +User OP Hash: 0xf6d06a5723198af02f26a2daa0a6eec019ab539cfb97277a6aa5251e40863aa2 +UserOp Link: https://jiffyscan.xyz/userOpHash/0xf6d06a5723198af02f26a2daa0a6eec019ab539cfb97277a6aa5251e40863aa2?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d + +Gas Used (Account or Paymaster): 408160 +Gas Used (Transaction): 408160 +``` + +## ERC20 Transaction with Gelato + +``` +npm run gelato:erc20:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:erc20:1balance +> tsx ./gelato/gelato.ts erc20 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xe86D78002637143c34d0687890c1b102D054a614 +Address Link: https://sepolia.etherscan.io/address/0xe86D78002637143c34d0687890c1b102D054a614 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Safe Wallet ERC20 Balance: 0 + +Minting ERC20 Tokens to Safe Wallet. + +Updated Safe Wallet ERC20 Balance: 1 + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0x5f2e04dae7ec76037e22d250bcca19600b7c0cad4dcddc8015e629c69c22c2b3 +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0x5f2e04dae7ec76037e22d250bcca19600b7c0cad4dcddc8015e629c69c22c2b3 + +User OP Hash: 0x129341b16c3d7ffdafe17eb3bcae112eebc087ca4fef61ba503b7e460f3f12c4 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x129341b16c3d7ffdafe17eb3bcae112eebc087ca4fef61ba503b7e460f3f12c4?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405 + +Gas Used (Account or Paymaster): 118033 +Gas Used (Transaction): 118033 +``` + +## Safe Deployment + ERC721 Transaction with Gelato + +``` +npm run gelato:erc721:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:erc721:1balance +> tsx ./gelato/gelato.ts erc721 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xC8D65452DC04F13E2915916699f5B61fF647C163 +Address Link: https://sepolia.etherscan.io/address/0xC8D65452DC04F13E2915916699f5B61fF647C163 + +Deploying a new Safe and executing calldata passed with it (if any). + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0xe06c299ab9deac8ee76e40960af3b56b219dabd97488a67093a752376271fe3a +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0xe06c299ab9deac8ee76e40960af3b56b219dabd97488a67093a752376271fe3a + +User OP Hash: 0xae2b1d74f3e3e921f47db23c28f7c3f100bcaf8fe164d16ddd6b562b22519afb +UserOp Link: https://jiffyscan.xyz/userOpHash/0xae2b1d74f3e3e921f47db23c28f7c3f100bcaf8fe164d16ddd6b562b22519afb?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d + +Gas Used (Account or Paymaster): 437372 +Gas Used (Transaction): 437372 +``` + +## ERC721 Transaction with Gelato + +``` +npm run gelato:erc721:1balance + +> @safe-global/4337-gas-metering@1.0.0 gelato:erc721:1balance +> tsx ./gelato/gelato.ts erc721 + +Signer Extracted from Private Key. + +Init Code Created. + +Counterfactual Sender Address Created: 0xC8D65452DC04F13E2915916699f5B61fF647C163 +Address Link: https://sepolia.etherscan.io/address/0xC8D65452DC04F13E2915916699f5B61fF647C163 + +The Safe is already deployed. + +Nonce for the sender received from EntryPoint. + +Appropriate calldata created. + +Signed Dummy Data for Gelato. + +Received Gas Data from Gelato. + +Signed Real Data for Gelato. + +UserOperation submitted. + +Gelato Relay Task ID: 0xe201bbab015baeeaeab68f2e3a2c6e1cfe7af6704df0106b9fd3c9587c6ef61e +Gelato Relay Task Link: https://api.gelato.digital/tasks/status/0xe201bbab015baeeaeab68f2e3a2c6e1cfe7af6704df0106b9fd3c9587c6ef61e + +User OP Hash: 0x00d0e383c622a7e3b5c010ce915b300bf182988c6914a10b06efa1b933fd7d21 +UserOp Link: https://jiffyscan.xyz/userOpHash/0x00d0e383c622a7e3b5c010ce915b300bf182988c6914a10b06efa1b933fd7d21?network=sepolia + +Transaction Link: https://sepolia.etherscan.io/tx/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5 + +Gas Used (Account or Paymaster): 147232 +Gas Used (Transaction): 147232 +``` diff --git a/4337-gas-metering/gelato/gelato.ts b/4337-gas-metering/gelato/gelato.ts new file mode 100644 index 00000000..f47fe817 --- /dev/null +++ b/4337-gas-metering/gelato/gelato.ts @@ -0,0 +1,225 @@ +import dotenv from "dotenv"; +import { getAccountNonce } from "permissionless"; +import { setTimeout } from "timers/promises"; +import { + Client, + Hash, + createPublicClient, + formatEther, + http, + parseEther, + zeroAddress, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { + SAFE_ADDRESSES_MAP, + getAccountAddress, + getAccountInitCode, +} from "../utils/safe"; +import { + UserOperation, + signUserOperation, + txTypes, + getGasValuesFromGelato, + submitUserOperationGelato, + createCallData, +} from "../utils/userOps"; +import { transferETH } from "../utils/nativeTransfer"; +import { error } from "console"; + +dotenv.config(); +const paymaster = "gelato"; + +const privateKey = process.env.PRIVATE_KEY; + +const entryPointAddress = process.env + .GELATO_ENTRYPOINT_ADDRESS as `0x${string}`; +const multiSendAddress = process.env.GELATO_MULTISEND_ADDRESS as `0x${string}`; + +const saltNonce = BigInt(process.env.GELATO_NONCE as string); + +const chain = process.env.GELATO_CHAIN; +const chainID = Number(process.env.GELATO_CHAIN_ID); + +const safeVersion = process.env.SAFE_VERSION as string; + +const rpcURL = process.env.GELATO_RPC_URL; +const policyID = process.env.GELATO_GAS_POLICY; +const apiKey = process.env.GELATO_API_KEY; + +const erc20TokenAddress = process.env + .GELATO_ERC20_TOKEN_CONTRACT as `0x${string}`; +const erc721TokenAddress = process.env + .GELATO_ERC721_TOKEN_CONTRACT as `0x${string}`; + +const argv = process.argv.slice(2); +let usePaymaster!: boolean; +if (argv.length < 1 || argv.length > 2) { + throw new Error("TX Type Argument not passed."); +} else if (argv.length == 2 && argv[1] == "paymaster=true") { + if (policyID) { + usePaymaster = true; + } else { + throw new Error("Paymaster requires policyID to be set."); + } +} + +const txType: string = argv[0]; +if (!txTypes.includes(txType)) { + throw new Error("TX Type Argument Invalid"); +} + +const safeAddresses = ( + SAFE_ADDRESSES_MAP as Record> +)[safeVersion]; +let chainAddresses; +if (safeAddresses) { + chainAddresses = safeAddresses[chainID]; +} + +if (apiKey === undefined) { + throw new Error( + "Please replace the `apiKey` env variable with your Alchemy API key", + ); +} + +if (!privateKey) { + throw new Error( + "Please populate .env file with demo Private Key. Recommended to not use your personal private key.", + ); +} + +const signer = privateKeyToAccount(privateKey as Hash); +console.log("Signer Extracted from Private Key."); + +let publicClient; +if (chain == "sepolia") { + publicClient = createPublicClient({ + transport: http(rpcURL), + chain: sepolia, + }); +} else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); +} + +const initCode = await getAccountInitCode({ + owner: signer.address, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, +}); +console.log("\nInit Code Created."); + +const senderAddress = await getAccountAddress({ + client: publicClient, + owner: signer.address, + addModuleLibAddress: chainAddresses.ADD_MODULES_LIB_ADDRESS, + safe4337ModuleAddress: chainAddresses.SAFE_4337_MODULE_ADDRESS, + safeProxyFactoryAddress: chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, + safeSingletonAddress: chainAddresses.SAFE_SINGLETON_ADDRESS, + saltNonce: saltNonce, + multiSendAddress: multiSendAddress, + erc20TokenAddress: zeroAddress, + paymasterAddress: zeroAddress, +}); +console.log("\nCounterfactual Sender Address Created:", senderAddress); +console.log( + "Address Link: https://" + chain + ".etherscan.io/address/" + senderAddress, +); + +const contractCode = await publicClient.getBytecode({ address: senderAddress }); + +if (contractCode) { + console.log("\nThe Safe is already deployed."); + if (txType == "account") { + process.exit(0); + } +} else { + console.log( + "\nDeploying a new Safe and executing calldata passed with it (if any).", + ); +} + +const newNonce = await getAccountNonce(publicClient as Client, { + entryPoint: entryPointAddress, + sender: senderAddress, +}); +console.log("\nNonce for the sender received from EntryPoint."); + +let txCallData: `0x${string}` = await createCallData( + chain, + publicClient, + signer, + txType, + senderAddress, + erc20TokenAddress, + erc721TokenAddress, + paymaster, +); + +const sponsoredUserOperation: UserOperation = { + sender: senderAddress, + nonce: newNonce, + initCode: contractCode ? "0x" : initCode, + callData: txCallData, + callGasLimit: 1n, // All Gas Values will be filled by Estimation Response Data. + verificationGasLimit: 1n, + preVerificationGas: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + paymasterAndData: "0x", + signature: "0x", +}; + +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + chainID, + entryPointAddress, + chainAddresses.SAFE_4337_MODULE_ADDRESS, +); +console.log("\nSigned Dummy Data for Gelato."); + +if (usePaymaster) { + throw new Error("Currently paymaster is not supported for Gelato."); +} else { + sponsoredUserOperation.maxPriorityFeePerGas = 0n; // Gelato prefers to keep it to zero. + sponsoredUserOperation.maxFeePerGas = 0n; + + let rvGas = await getGasValuesFromGelato( + entryPointAddress, + sponsoredUserOperation, + chainID, + apiKey, + ); + + sponsoredUserOperation.preVerificationGas = rvGas?.preVerificationGas; + sponsoredUserOperation.callGasLimit = rvGas?.callGasLimit; + // sponsoredUserOperation.callGasLimit = "0x186a0" as any; + sponsoredUserOperation.verificationGasLimit = rvGas?.verificationGasLimit; +} + +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + chainID, + entryPointAddress, + chainAddresses.SAFE_4337_MODULE_ADDRESS, +); +console.log("\nSigned Real Data for Gelato."); + +await submitUserOperationGelato( + entryPointAddress, + sponsoredUserOperation, + chain, + chainID, + apiKey, +); diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index e3a2dbc5..86100b10 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -17,6 +17,11 @@ "alchemy:erc721:paymaster": "tsx ./alchemy/alchemy.ts erc721 paymaster=true", "alchemy": "tsx ./alchemy/alchemy.ts", "fmt": "prettier --ignore-path .gitignore --write .", + "gelato:account:1balance": "tsx ./gelato/gelato.ts account", + "gelato:native-transfer:1balance": "tsx ./gelato/gelato.ts native-transfer", + "gelato:erc20:1balance": "tsx ./gelato/gelato.ts erc20", + "gelato:erc721:1balance": "tsx ./gelato/gelato.ts erc721", + "gelato": "tsx ./gelato/gelato.ts", "pimlico:account": "tsx ./pimlico/pimlico.ts account", "pimlico:account:paymaster": "tsx ./pimlico/pimlico.ts account paymaster=true", "pimlico:native-transfer": "tsx ./pimlico/pimlico.ts native-transfer", diff --git a/4337-gas-metering/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts index 91797643..5433ab1a 100644 --- a/4337-gas-metering/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -11,6 +11,7 @@ import { goerli, polygonMumbai, sepolia } from "viem/chains"; dotenv.config(); const pimlicoRPCURL = process.env.PIMLICO_RPC_URL; const alchemyRPCURL = process.env.ALCHEMY_RPC_URL; +const gelatoRPCURL = process.env.GELATO_RPC_URL; export const generateApproveCallData = (paymasterAddress: Address) => { const approveData = encodeFunctionData({ @@ -147,6 +148,18 @@ export const mintERC20Token = async ( "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } + } else if (paymaster == "gelato") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(gelatoRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } } else { throw new Error( "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", @@ -219,6 +232,18 @@ export const transferERC20Token = async ( "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } + } else if (paymaster == "gelato") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(gelatoRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } } else { throw new Error( "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", diff --git a/4337-gas-metering/utils/nativeTransfer.ts b/4337-gas-metering/utils/nativeTransfer.ts index f62b74d9..af20285b 100644 --- a/4337-gas-metering/utils/nativeTransfer.ts +++ b/4337-gas-metering/utils/nativeTransfer.ts @@ -6,6 +6,7 @@ import { setTimeout } from "timers/promises"; dotenv.config(); const pimlicoRPCURL = process.env.PIMLICO_RPC_URL; const alchemyRPCURL = process.env.ALCHEMY_RPC_URL; +const gelatoRPCURL = process.env.GELATO_RPC_URL; export const transferETH = async ( publicClient: any, @@ -52,9 +53,21 @@ export const transferETH = async ( "Current code only support limited networks. Please make required changes if you want to use custom network.", ); } + } else if (paymaster == "gelato") { + if (chain == "sepolia") { + walletClient = createWalletClient({ + account: signer, + chain: sepolia, + transport: http(gelatoRPCURL), + }); + } else { + throw new Error( + "Current code only support limited networks. Please make required changes if you want to use custom network.", + ); + } } else { throw new Error( - "Current code only support Pimlico and Alchemy. Please make required changes if you want to use a different Paymaster.", + "Current code only support Pimlico, Alchemy and Gelato. Please make required changes if you want to use a different Paymaster.", ); } diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 1983def7..42ba9be2 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -1,6 +1,12 @@ import dotenv from "dotenv"; import type { Address } from "abitype"; -import { fromHex, type Hex, type PrivateKeyAccount } from "viem"; +import { + fromHex, + parseEther, + type Hex, + type PrivateKeyAccount, + formatEther, +} from "viem"; import { EIP712_SAFE_OPERATION_TYPE, encodeCallData } from "./safe"; import { Alchemy } from "alchemy-sdk"; import { setTimeout } from "timers/promises"; @@ -467,15 +473,33 @@ export const createCallData = async ( value: 0n, }); } else if (txType == "native-transfer") { - const weiToSend = 1000000000000n; // 0.000001 ETH - await transferETH( - publicClient, - signer, - senderAddress, - weiToSend, - chain, - paymaster, - ); + const weiToSend = parseEther("0.000001"); + let safeETHBalance = await publicClient.getBalance({ + address: senderAddress, + }); + if (safeETHBalance < weiToSend) { + console.log( + "\nTransferring", + formatEther(weiToSend - safeETHBalance), + "ETH to Safe for native transfer.", + ); + await transferETH( + publicClient, + signer, + senderAddress, + weiToSend - safeETHBalance, + chain, + paymaster, + ); + while (safeETHBalance < weiToSend) { + await setTimeout(30000); // Sometimes it takes time to index. + safeETHBalance = await publicClient.getBalance({ + address: senderAddress, + }); + } + console.log("\nTransferred required ETH for the native transfer."); + } + txCallData = encodeCallData({ to: signer.address, data: "0x", @@ -485,3 +509,178 @@ export const createCallData = async ( console.log("\nAppropriate calldata created."); return txCallData; }; + +export const getGasValuesFromGelato = async ( + entryPointAddress: `0x${string}`, + sponsoredUserOperation: UserOperation, + chainID: number, + apiKey: string, +) => { + const gasOptions = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 0, + jsonrpc: "2.0", + method: "eth_estimateUserOperationGas", + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + signature: sponsoredUserOperation.signature, + paymasterAndData: "0x", + }, + entryPointAddress, + ], + }), + }; + + let responseValues; + await fetch( + `https://api.gelato.digital//bundlers/${chainID}/rpc?sponsorApiKey=${apiKey}`, + gasOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + console.log("\nReceived Gas Data from Gelato."); + + let rvGas; + if (responseValues && responseValues["result"]) { + rvGas = responseValues["result"] as gasData; + } + + return rvGas; +}; + +export const submitUserOperationGelato = async ( + entryPointAddress: `0x${string}`, + sponsoredUserOperation: UserOperation, + chain: string, + chainID: number, + apiKey: string, +) => { + const options = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 0, + jsonrpc: "2.0", + method: "eth_sendUserOperation", + params: [ + { + sender: sponsoredUserOperation.sender, + nonce: "0x" + sponsoredUserOperation.nonce.toString(16), + initCode: sponsoredUserOperation.initCode, + callData: sponsoredUserOperation.callData, + signature: sponsoredUserOperation.signature, + paymasterAndData: sponsoredUserOperation.paymasterAndData, + callGasLimit: sponsoredUserOperation.callGasLimit, + verificationGasLimit: sponsoredUserOperation.verificationGasLimit, + preVerificationGas: sponsoredUserOperation.preVerificationGas, + maxFeePerGas: "0x" + sponsoredUserOperation.maxFeePerGas.toString(16), + maxPriorityFeePerGas: + "0x" + sponsoredUserOperation.maxPriorityFeePerGas.toString(16), + }, + entryPointAddress, + ], + }), + }; + + let responseValues: any; + await fetch( + `https://api.gelato.digital//bundlers/${chainID}/rpc?sponsorApiKey=${apiKey}`, + options, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + + if (responseValues && responseValues["result"]) { + console.log( + "\nUserOperation submitted.\n\nGelato Relay Task ID:", + responseValues["result"], + ); + console.log( + "Gelato Relay Task Link: https://api.gelato.digital/tasks/status/" + + responseValues["result"], + ); + + const hashOptions = { + method: "POST", + headers: { + accept: "application/json", + "content-type": "application/json", + }, + body: JSON.stringify({ + id: 0, + jsonrpc: "2.0", + method: "eth_getUserOperationReceipt", + params: [responseValues["result"]], + }), + }; + + let runOnce = true; + + while (responseValues["result"] == null || runOnce) { + await setTimeout(25000); + await fetch( + `https://api.gelato.digital//bundlers/${chainID}/rpc?sponsorApiKey=${apiKey}`, + hashOptions, + ) + .then((response) => response.json()) + .then((response) => (responseValues = response)) + .catch((err) => console.error(err)); + runOnce = false; + } + + if ( + responseValues["result"] && + responseValues["result"]["receipt"]["transactionHash"] + ) { + const rvEntryPoint = + responseValues["result"]["logs"][ + responseValues["result"]["logs"].length - 2 + ]["address"]; + + if (rvEntryPoint == entryPointAddress) { + let userOpHash = + responseValues["result"]["logs"][ + responseValues["result"]["logs"].length - 2 + ]["topics"][1]; + console.log( + "\nUser OP Hash: " + + userOpHash + + "\nUserOp Link: https://jiffyscan.xyz/userOpHash/" + + userOpHash + + "?network=" + + chain, + ); + } + console.log( + "\nTransaction Link: https://" + + chain + + ".etherscan.io/tx/" + + responseValues["result"]["receipt"]["transactionHash"], + ); + let actualGasUsed = fromHex( + responseValues["result"]["actualGasUsed"], + "number", + ); + let gasUsed = fromHex( + responseValues["result"]["receipt"]["gasUsed"], + "number", + ); + console.log(`\nGas Used (Account or Paymaster): ${actualGasUsed}`); + console.log(`Gas Used (Transaction): ${gasUsed}\n`); + } else { + console.log("\n" + responseValues["error"]); + } + } else { + if (responseValues && responseValues["message"]) { + console.log("\n" + responseValues["message"]); + } + } +}; From de3fb3b0ea3c1d2917abdbe24645e1dbbd40ea8b Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 20 Dec 2023 20:45:23 +0530 Subject: [PATCH 54/60] env.example updated with Gelato --- 4337-gas-metering/.env.example | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/4337-gas-metering/.env.example b/4337-gas-metering/.env.example index c85000a7..5d4a35fc 100644 --- a/4337-gas-metering/.env.example +++ b/4337-gas-metering/.env.example @@ -8,7 +8,7 @@ PIMLICO_RPC_URL = "https://rpc.ankr.com/polygon_mumbai" PIMLICO_API_KEY = "" # https://dashboard.pimlico.io/apikeys PIMLICO_GAS_POLICY = "" # https://dashboard.pimlico.io/sponsorship-policies PIMLICO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" -PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" +PIMLICO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" # https://github.com/safe-global/safe-deployments/blob/main/src/assets/v1.4.1/multi_send.json PIMLICO_ERC20_PAYMASTER_ADDRESS = "0x32aCDFeA07a614E52403d2c1feB747aa8079A353" # https://docs.pimlico.io/erc20-paymaster/reference/contracts PIMLICO_USDC_TOKEN_ADDRESS = "0x0FA8781a83E46826621b3BC094Ea2A0212e71B23" # Mumbai USDC Address used by Pimlico # Make sure all nonce are unique for it to deploy account when run initially. @@ -24,13 +24,27 @@ ALCHEMY_RPC_URL = "https://eth-sepolia.g.alchemy.com/v2/Your_API_Key" ALCHEMY_API_KEY = "" # https://dashboard.alchemy.com/apps ALCHEMY_GAS_POLICY = "" # https://dashboard.alchemy.com/gas-manager ALCHEMY_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" -ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" +ALCHEMY_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" # https://github.com/safe-global/safe-deployments/blob/main/src/assets/v1.4.1/multi_send.json # Make sure all nonce are unique for it to deploy account when run initially. ALCHEMY_NONCE = "2" # Alchemy Token Operation Values (Based on the chain selected, these values should also change accordingly.) ALCHEMY_ERC20_TOKEN_CONTRACT = "0x255de08fb52fde17a3aab145de8e2ffb7fd0310f" ALCHEMY_ERC721_TOKEN_CONTRACT = "0x16bc5fba06f3f5875e915c0ba6963377eb6651e1" +# Gelato Values +GELATO_CHAIN = "sepolia" +GELATO_CHAIN_ID = "11155111" +GELATO_RPC_URL = "https://rpc.ankr.com/eth_sepolia" +GELATO_API_KEY = "" # Sponsor API Key +GELATO_GAS_POLICY = "" +GELATO_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" +GELATO_MULTISEND_ADDRESS = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526" # https://github.com/safe-global/safe-deployments/blob/main/src/assets/v1.4.1/multi_send.json +# Make sure all nonce are unique for it to deploy account when run initially. +GELATO_NONCE = "3" +# Gelato Token Operation Values (Based on the chain selected, these values should also change accordingly.) +GELATO_ERC20_TOKEN_CONTRACT = "0x255de08fb52fde17a3aab145de8e2ffb7fd0310f" +GELATO_ERC721_TOKEN_CONTRACT = "0x16bc5fba06f3f5875e915c0ba6963377eb6651e1" + # Safe Values SAFE_VERSION = "1.4.1" From e2f35ba2fb2e0ec7b5bbfd8e7d1220fddc555f76 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 20 Dec 2023 22:21:25 +0530 Subject: [PATCH 55/60] Updated Gas Spec based on master changes --- 4337-gas-metering/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4337-gas-metering/README.md b/4337-gas-metering/README.md index 56046c67..d501c247 100644 --- a/4337-gas-metering/README.md +++ b/4337-gas-metering/README.md @@ -12,7 +12,7 @@ NOTE: If you run a paymaster analysis twice or more without changing the salt fo | | **With 4337?** | **Account Creation** | **Account Creation + Native Transfer** | **Native Transfer** | **Account Creation + ERC20 Transfer** | **ERC20 Transfer** | **Account Creation + ERC721 Minting** | **ERC721 Minting** | | :---------------------------------------------------: | :------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | -| **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 358975 | 363906 | 87690 | 369890 | 93674 | 411677 | 135449 | +| **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 359756 | 364687 | 88471 | 370661 | 94445 | 412448 | 136232 | | **Gelato (4337 Compatible - 1Balance)** | Yes | [397421](https://sepolia.etherscan.io/tx/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505) | [404828](https://sepolia.etherscan.io/tx/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0) | [114692](https://sepolia.etherscan.io/tx/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15) | [408160](https://sepolia.etherscan.io/tx/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d) | [118033](https://sepolia.etherscan.io/tx/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405) | [437372](https://sepolia.etherscan.io/tx/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d) | [147232](https://sepolia.etherscan.io/tx/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5) | | **Pimlico (USDC Paymaster)** | Yes | [506573](https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28) | [511055](https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3) | [199262](https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186) | [514156](https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623) | [202387](https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91) | [543411](https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006) | [231619](https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192) | | **Pimlico (MATIC - Gas Policy)** | Yes | [448172](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a) | [455615](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4) | [123064](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e) | [459014](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271) | [126461](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496) | [488186](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535) | [155645](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652) | From 23355ade599c6a672897c22463749d9264e1a7f5 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Wed, 20 Dec 2023 22:23:33 +0530 Subject: [PATCH 56/60] Updated Gas Spec based on master changes --- 4337/test/gas/Gas.spec.ts | 70 ++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/4337/test/gas/Gas.spec.ts b/4337/test/gas/Gas.spec.ts index 2f1d3b50..62970bc7 100644 --- a/4337/test/gas/Gas.spec.ts +++ b/4337/test/gas/Gas.spec.ts @@ -53,15 +53,18 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await logGas('Safe with 4337 Module Deployment', entryPoint.executeUserOp(userOp, 0)) @@ -90,15 +93,18 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await logGas('Safe with 4337 Module Deployment + Native Transfer', entryPoint.executeUserOp(userOp, 0)) @@ -120,13 +126,16 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await entryPoint.executeUserOp(userOp, 0) @@ -148,13 +157,16 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await logGas('Safe with 4337 Module Native Transfer', entryPoint.executeUserOp(userOp, 0)) @@ -180,15 +192,18 @@ describe('Gas Metering', () => { erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]), await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await logGas('Safe with 4337 Module Deployment + ERC20 Transfer', entryPoint.executeUserOp(userOp, 0)) @@ -209,13 +224,16 @@ describe('Gas Metering', () => { erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]), await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) const userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) expect(await erc721Token.balanceOf(safe.address)).to.equal(0) @@ -239,13 +257,16 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await entryPoint.executeUserOp(userOp, 0) @@ -263,13 +284,16 @@ describe('Gas Metering', () => { erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]), await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await logGas('Safe with 4337 Module ERC20 Transfer', entryPoint.executeUserOp(userOp, 0)) @@ -289,13 +313,16 @@ describe('Gas Metering', () => { '0x', await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) let userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) await entryPoint.executeUserOp(userOp, 0) @@ -311,13 +338,16 @@ describe('Gas Metering', () => { erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]), await entryPoint.getNonce(safe.address, 0), await entryPoint.getAddress(), + false, + false, + { + initCode: safe.getInitCode(), + }, ) signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())]) userOp = buildUserOperationFromSafeUserOperation({ - safeAddress: safe.address, safeOp, signature, - initCode: safe.getInitCode(), }) expect(await erc721Token.balanceOf(safe.address)).to.equal(0) From cdffaa19a57255df7572dc199173ce17fc097390 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 21 Dec 2023 14:28:33 +0530 Subject: [PATCH 57/60] Tenderly Gas Profiler Link Added --- 4337-gas-metering/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/4337-gas-metering/README.md b/4337-gas-metering/README.md index d501c247..057a4d6f 100644 --- a/4337-gas-metering/README.md +++ b/4337-gas-metering/README.md @@ -10,11 +10,11 @@ NOTE: If you run a paymaster analysis twice or more without changing the salt fo ## Gas Usage Results -| | **With 4337?** | **Account Creation** | **Account Creation + Native Transfer** | **Native Transfer** | **Account Creation + ERC20 Transfer** | **ERC20 Transfer** | **Account Creation + ERC721 Minting** | **ERC721 Minting** | -| :---------------------------------------------------: | :------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | -| **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 359756 | 364687 | 88471 | 370661 | 94445 | 412448 | 136232 | -| **Gelato (4337 Compatible - 1Balance)** | Yes | [397421](https://sepolia.etherscan.io/tx/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505) | [404828](https://sepolia.etherscan.io/tx/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0) | [114692](https://sepolia.etherscan.io/tx/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15) | [408160](https://sepolia.etherscan.io/tx/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d) | [118033](https://sepolia.etherscan.io/tx/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405) | [437372](https://sepolia.etherscan.io/tx/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d) | [147232](https://sepolia.etherscan.io/tx/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5) | -| **Pimlico (USDC Paymaster)** | Yes | [506573](https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28) | [511055](https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3) | [199262](https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186) | [514156](https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623) | [202387](https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91) | [543411](https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006) | [231619](https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192) | -| **Pimlico (MATIC - Gas Policy)** | Yes | [448172](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a) | [455615](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4) | [123064](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e) | [459014](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271) | [126461](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496) | [488186](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535) | [155645](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652) | -| **Alchemy (ETH from Safe)** | Yes | [417074](https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88) | [424505](https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2) | [107057](https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b) | [427599](https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143) | [110174](https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d) | [456870](https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6) | [139420](https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e) | -| **Alchemy (ETH - Gas Policy)** | Yes | [411372](https://sepolia.etherscan.io/tx/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1) | [418779](https://sepolia.etherscan.io/tx/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672) | [130202](https://sepolia.etherscan.io/tx/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a) | [421926](https://sepolia.etherscan.io/tx/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a) | [133394](https://sepolia.etherscan.io/tx/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1) | [451200](https://sepolia.etherscan.io/tx/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84) | [162654](https://sepolia.etherscan.io/tx/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a) | +| | **With 4337?** | **Account Creation** | **Account Creation + Native Transfer** | **Native Transfer** | **Account Creation + ERC20 Transfer** | **ERC20 Transfer** | **Account Creation + ERC721 Minting** | **ERC721 Minting** | +| :---------------------------------------------------: | :------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| **[Without Paymaster](../4337/test/gas/Gas.spec.ts)** | Yes | 359756 | 364687 | 88471 | 370661 | 94445 | 412448 | 136232 | +| **Gelato (4337 Compatible - 1Balance)** | Yes | 397421 ([TX](https://sepolia.etherscan.io/tx/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x21205216b55d0f48aa09ff4289ae982c3b16e7a9905494815cabd1fb01a0d505/gas-usage)) | 404828 ([TX](https://sepolia.etherscan.io/tx/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x7bb36c93d21c911a2c1bdc7e50f55030cc7f006a1f7e2e651251dca9651383e0/gas-usage)) | 114692 ([TX](https://sepolia.etherscan.io/tx/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xefa122224466e9f1d09d42683aaec2b37f9871f7f5569099f0cc066961b39f15/gas-usage)) | 408160 ([TX](https://sepolia.etherscan.io/tx/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x4f55488ecc542be4effc2d7a4743345db6790ef80e7ca94f3e939a290738fa2d/gas-usage)) | 118033 ([TX](https://sepolia.etherscan.io/tx/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x152c78871b6940215ba37cac5f5231fa2bd4bcf40742ebcf741903ce64adc405/gas-usage)) | 437372 ([TX](https://sepolia.etherscan.io/tx/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x4aa37845d5c9fc0ad0713caefbbc9931263040d1502f076a98c993282257e51d/gas-usage)) | 147232 ([TX](https://sepolia.etherscan.io/tx/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xfac73b16d0932ba97a93f12ddc230024b102e581a37a53625dfe8108ca581bb5/gas-usage)) | +| **Pimlico (USDC Paymaster)** | Yes | 506573 ([TX](https://mumbai.polygonscan.com/tx/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x3c6284e4df1686d699d2bc4cca04a25ecc76d68a73665ca53d466e6bd6bedf28/gas-usage)) | 511055 ([TX](https://mumbai.polygonscan.com/tx/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x8bc4e42b076d22e0fc3418eba40c65caab6e3a10c1fbb10cbeee4a7fbfa8b4b3/gas-usage)) | 199262 ([TX](https://mumbai.polygonscan.com/tx/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x46cdfc14649087609f69411fc41f5feb4dc23a6ea9255928b932841858e5f186/gas-usage)) | 514156 ([TX](https://mumbai.polygonscan.com/tx/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xa5cf461800341c2e9934608ff55aeda26d1a3e7da4f5bc9f3cce3fd185409623/gas-usage)) | 202387 ([TX](https://mumbai.polygonscan.com/tx/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xdc21ae13dc92eb48851fa62f57c74f3a0085acf81343d9aaaa14fcc3c6911f91/gas-usage)) | 543411 ([TX](https://mumbai.polygonscan.com/tx/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xcd6c137474be4f002822498e032ad9b78b0505bd4db495ee65fc602ec1a7f006/gas-usage)) | 231619 ([TX](https://mumbai.polygonscan.com/tx/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x31732175d3f3b35c9c2a38e841bcd485085edf79e7f3c532ec7997c4993c0192/gas-usage)) | +| **Pimlico (MATIC - Gas Policy)** | Yes | 448172 ([TX](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a/gas-usage)) | 455615 ([TX](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4/gas-usage)) | 123064 ([TX](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e/gas-usage)) | 459014 ([TX](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271/gas-usage)) | 126461 ([TX](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496/gas-usage)) | 488186 ([TX](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535/gas-usage)) | 155645 ([TX](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652/gas-usage)) | +| **Alchemy (ETH from Safe)** | Yes | 417074 ([TX](https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88/gas-usage)) | 424505 ([TX](https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2/gas-usage)) | 107057 ([TX](https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b/gas-usage)) | 427599 ([TX](https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143/gas-usage)) | 110174 ([TX](https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d/gas-usage)) | 456870 ([TX](https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6/gas-usage)) | 139420 ([TX](https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e/gas-usage)) | +| **Alchemy (ETH - Gas Policy)** | Yes | 411372 ([TX](https://sepolia.etherscan.io/tx/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1/gas-usage)) | 418779 ([TX](https://sepolia.etherscan.io/tx/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672/gas-usage)) | 130202 ([TX](https://sepolia.etherscan.io/tx/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a/gas-usage)) | 421926 ([TX](https://sepolia.etherscan.io/tx/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a/gas-usage)) | 133394 ([TX](https://sepolia.etherscan.io/tx/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1/gas-usage)) | 451200 ([TX](https://sepolia.etherscan.io/tx/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84/gas-usage)) | 162654 ([TX](https://sepolia.etherscan.io/tx/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a/gas-usage)) | From 2286ba09bbfeba10cd8dd584e53d87fd25fe43e6 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 21 Dec 2023 15:55:53 +0530 Subject: [PATCH 58/60] README Updated with Detailed Analysis --- 4337-gas-metering/README.md | 64 ++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/4337-gas-metering/README.md b/4337-gas-metering/README.md index 057a4d6f..a59c6099 100644 --- a/4337-gas-metering/README.md +++ b/4337-gas-metering/README.md @@ -1,4 +1,4 @@ -# Paymaster Analysis +# 4337 Gas Metering Analysis ## How to run? @@ -18,3 +18,65 @@ NOTE: If you run a paymaster analysis twice or more without changing the salt fo | **Pimlico (MATIC - Gas Policy)** | Yes | 448172 ([TX](https://mumbai.polygonscan.com/tx/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xd51d026ecfa6dbafa8aac8a138badc6e3b397683117878e360bae9051a3b733a/gas-usage)) | 455615 ([TX](https://mumbai.polygonscan.com/tx/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xdd966b95b6625be33ae37f6c5bb1ad33798afbbd899089acad1180005d0637c4/gas-usage)) | 123064 ([TX](https://mumbai.polygonscan.com/tx/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xca2e41e24c6206011fe0d932f27a2786c7d9486c93f63d96c131c5007e2b275e/gas-usage)) | 459014 ([TX](https://mumbai.polygonscan.com/tx/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xbd4c79d876ae928bbc721501029b01dbc5fc94d91d6299f548f19289f7c1c271/gas-usage)) | 126461 ([TX](https://mumbai.polygonscan.com/tx/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xd2b130bc2f26cfe43041f7102601425674e2cd22a6b74672b907b28e70686496/gas-usage)) | 488186 ([TX](https://mumbai.polygonscan.com/tx/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0x454a3a5a39432f7b01a70fcddfef948d20c70d2d719aea30d402d693447fa535/gas-usage)) | 155645 ([TX](https://mumbai.polygonscan.com/tx/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652)) ([Gas](https://dashboard.tenderly.co/tx/mumbai/0xa148a4938de9883b2fbcd512e3c7161e78ca695843b6e535fdb5054b88872652/gas-usage)) | | **Alchemy (ETH from Safe)** | Yes | 417074 ([TX](https://sepolia.etherscan.io/tx/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x03c507f5dc14c6b6af04c5ad722f0650d86925837d9889e4972cb087e34d7b88/gas-usage)) | 424505 ([TX](https://sepolia.etherscan.io/tx/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x0263331d8d4568c08d4a700385c08062ee0342fe6f65b2c7eb1a194ddec23ec2/gas-usage)) | 107057 ([TX](https://sepolia.etherscan.io/tx/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xf4e38d9f3535dcb9519ca3527734a5ea611a0d1bafb632051736537853eb502b/gas-usage)) | 427599 ([TX](https://sepolia.etherscan.io/tx/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x794b02531f14b6c432c0dcf08d1cb76a8693dd75b35c5dde0d4547754d208143/gas-usage)) | 110174 ([TX](https://sepolia.etherscan.io/tx/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xb56985ee07b1e7931aedc387698620d890c99992c4c688b8b3a150f355089e5d/gas-usage)) | 456870 ([TX](https://sepolia.etherscan.io/tx/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x2d2a0c8215821f0aa9cf8f88175aa8256cdca1a2928f2aa667916e5127f5dcb6/gas-usage)) | 139420 ([TX](https://sepolia.etherscan.io/tx/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x178d2c16a261dcb49e810bf39ce35cf96cbab8c7d3235709c7164ba6193c716e/gas-usage)) | | **Alchemy (ETH - Gas Policy)** | Yes | 411372 ([TX](https://sepolia.etherscan.io/tx/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xcbb2c3c49b9d72d9ecf692308d69a8ad797ab5b1c6603f4fad989f966d692af1/gas-usage)) | 418779 ([TX](https://sepolia.etherscan.io/tx/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x49fbedf833cfecf9db7de56c61d4227292723115520600dbc3711da5e6a85672/gas-usage)) | 130202 ([TX](https://sepolia.etherscan.io/tx/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x35f1e5b04d988e4614a17609190b3e21b0a9892f78da9f400248cfb3b5afde9a/gas-usage)) | 421926 ([TX](https://sepolia.etherscan.io/tx/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0x7dda913ae986d49c4322f414102ae374441a40adb4b33727e568ba140904d52a/gas-usage)) | 133394 ([TX](https://sepolia.etherscan.io/tx/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xe34902ebd5377cac04c47d142f6ca2de558df63a7e0c6541f704df651b7cfcb1/gas-usage)) | 451200 ([TX](https://sepolia.etherscan.io/tx/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xb1253508bc4ca5ce41222b15b0e7bf03b2273bcb09d93e1d6d6a5ea39b43ee84/gas-usage)) | 162654 ([TX](https://sepolia.etherscan.io/tx/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a)) ([Gas](https://dashboard.tenderly.co/tx/sepolia/0xd13fb70626a26aaa02e0389cd9347c1c0d8d8ed9ee794a61c5d3eea4b36e239a/gas-usage)) | + +## Detailed Individual Gas Usage Results + +### Gelato (4337 Compatible - 1Balance) + +| Type | Actual Gas | SponsoredCall | handleOps | \_createSenderIfNeeded | Safe Proxy Factory | ValidateUserOp (Safe) | \_executeUserOp | executeUserOp (Safe) | execTransactionFromModule | +| ---------------------------------- | ---------- | ------------- | --------- | ---------------------- | ------------------ | --------------------- | --------------- | -------------------- | ------------------------- | +| Account Creation | 397421 | 365997 | 351544 | 283735 | 267761 | 14931 | 14893 | 7015 | 4411 | +| Account Creation + Native Transfer | 404828 | 373368 | 358915 | 283735 | 267761 | 14931 | 22264 | 14386 | 11782 | +| Native Transfer | 114692 | 88636 | 76339 | NA | NA | 20512 | 24264 | 15795 | 13782 | +| Account Creation + ERC20 Transfer | 408160 | 380804 | 366101 | 283754 | 267761 | 14977 | 29339 | 21415 | 18550 | +| ERC20 Transfer | 118033 | 96069 | 83524 | NA | NA | 20558 | 31339 | 23415 | 20550 | +| Account Creation + ERC721 Minting | 437372 | 405404 | 390784 | 283748 | 267761 | 14962 | 54058 | 46150 | 43373 | +| ERC721 Minting | 147232 | 120668 | 108206 | NA | NA | 20542 | 56058 | 48150 | 45373 | + +### Pimlico (USDC Paymaster) + +| Type | Actual Gas | handleOps | \_createSenderIfNeeded | Safe Proxy Factory | ValidateUserOp (Safe) | \_executeUserOp | executeUserOp (Safe) | execTransactionFromModule | +| ---------------------------------- | ---------- | --------- | ---------------------- | ------------------ | --------------------- | --------------- | -------------------- | ------------------------- | +| Account Creation | 506573 | 475433 | 321191 | 307459 | 15039 | 65040 | 7015 | 4411 | +| Account Creation + Native Transfer | 511055 | 479867 | 321191 | 307459 | 15039 | 69474 | 14386 | 11782 | +| Native Transfer | 199262 | 175066 | NA | NA | 20527 | 71487 | 16386 | 13782 | +| Account Creation + ERC20 Transfer | 514156 | 487060 | 321210 | 307459 | 15085 | 76548 | 21415 | 18550 | +| ERC20 Transfer | 202387 | 182259 | NA | NA | 20573 | 78561 | 23415 | 20550 | +| Account Creation + ERC721 Minting | 543411 | 511739 | 321204 | 307459 | 15069 | 101268 | 46150 | 43373 | +| ERC721 Minting | 231619 | 206939 | NA | NA | 20558 | 103281 | 48150 | 45373 | + +### Pimlico (MATIC - Gas Policy) + +| Type | Actual Gas | handleOps | \_createSenderIfNeeded | Safe Proxy Factory | ValidateUserOp (Safe) | \_executeUserOp | executeUserOp (Safe) | execTransactionFromModule | +| ---------------------------------- | ---------- | --------- | ---------------------- | ------------------ | --------------------- | --------------- | -------------------- | ------------------------- | +| Account Creation | 448172 | 415716 | 321217 | 307459 | 15100 | 14932 | 7015 | 4411 | +| Account Creation + Native Transfer | 455615 | 423087 | 321217 | 307459 | 15100 | 22303 | 14386 | 11782 | +| Native Transfer | 123064 | 97540 | NA | NA | 20588 | 24306 | 16386 | 13782 | +| Account Creation + ERC20 Transfer | 459014 | 430566 | 321236 | 307459 | 15146 | 29378 | 21415 | 18550 | +| ERC20 Transfer | 126461 | 105017 | NA | NA | 20633 | 31382 | 23415 | 20550 | +| Account Creation + ERC721 Minting | 488186 | 455150 | 321229 | 307459 | 15130 | 54097 | 46150 | 43373 | +| ERC721 Minting | 155645 | 129601 | NA | NA | 20618 | 56100 | 48150 | 45373 | + +### Alchemy (ETH from Safe) + +| Type | Actual Gas | handleOps | \_createSenderIfNeeded | Safe Proxy Factory | ValidateUserOp (Safe) | \_executeUserOp | executeUserOp (Safe) | execTransactionFromModule | +| ---------------------------------- | ---------- | --------- | ---------------------- | ------------------ | --------------------- | --------------- | -------------------- | ------------------------- | +| Account Creation | 417074 | 407850 | 283735 | 267761 | 47159 | 34826 | 7015 | 4411 | +| Account Creation + Native Transfer | 424505 | 415221 | 283735 | 267761 | 47159 | 42197 | 14386 | 13795 | +| Native Transfer | 107057 | 83301 | NA | NA | 20512 | 24297 | 16386 | 15795 | +| Account Creation + ERC20 Transfer | 427599 | 422407 | 283754 | 267761 | 47205 | 49272 | 21415 | 20806 | +| ERC20 Transfer | 110174 | 90486 | NA | NA | 20558 | 31372 | 23415 | 20550 | +| Account Creation + ERC721 Minting | 456870 | 447090 | 283748 | 267761 | 47190 | 73991 | 46150 | 43373 | +| ERC721 Minting | 139420 | 115168 | NA | NA | 20542 | 56091 | 48150 | 45373 | + +### Alchemy (ETH - Gas Policy) + +| Type | Actual Gas | handleOps | \_createSenderIfNeeded | Safe Proxy Factory | ValidateUserOp (Safe) | \_executeUserOp | executeUserOp (Safe) | execTransactionFromModule | +| ---------------------------------- | ---------- | --------- | ---------------------- | ------------------ | --------------------- | --------------- | -------------------- | ------------------------- | +| Account Creation | 411372 | 380632 | 281451 | 267761 | 14992 | 14932 | 7015 | 4411 | +| Account Creation + Native Transfer | 418779 | 388003 | 281451 | 14992 | 22303 | 14386 | 11782 | | +| Native Transfer | 130202 | 104878 | NA | NA | 15320 | 24310 | 16386 | 15795 | +| Account Creation + ERC20 Transfer | 421926 | 395254 | 281471 | 15038 | 29378 | 21415 | 20806 | 18550 | +| ERC20 Transfer | 133394 | 112126 | NA | NA | 20618 | 31385 | 23415 | 20550 | +| Account Creation + ERC721 Minting | 451200 | 419916 | 281464 | 15023 | 54097 | 46150 | | 43373 | +| ERC721 Minting | 162654 | 136786 | NA | NA | 20603 | 56103 | 48150 | 45373 | From ce6abc745cb7f486b97df616239509b9aede2cd5 Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 21 Dec 2023 17:03:58 +0530 Subject: [PATCH 59/60] Github Workflow and Formatting Issues Rectified --- 4337-gas-metering/.eslintrc.cjs | 30 + 4337-gas-metering/.gitignore | 2 + 4337-gas-metering/alchemy/alchemy.ts | 6 +- 4337-gas-metering/gelato/gelato.ts | 17 +- 4337-gas-metering/package-lock.json | 4089 ++++++++++++++++++++++++-- 4337-gas-metering/package.json | 13 +- 4337-gas-metering/pimlico/pimlico.ts | 2 +- 4337-gas-metering/tsconfig.json | 7 +- 4337-gas-metering/utils/erc20.ts | 4 +- 4337-gas-metering/utils/safe.ts | 9 +- 4337-gas-metering/utils/userOps.ts | 15 +- 11 files changed, 3859 insertions(+), 335 deletions(-) create mode 100644 4337-gas-metering/.eslintrc.cjs diff --git a/4337-gas-metering/.eslintrc.cjs b/4337-gas-metering/.eslintrc.cjs new file mode 100644 index 00000000..9ced7b62 --- /dev/null +++ b/4337-gas-metering/.eslintrc.cjs @@ -0,0 +1,30 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + ], + overrides: [ + { + env: { + node: true, + }, + files: [".eslintrc.{js,cjs}"], + parserOptions: { + sourceType: "script", + }, + }, + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + rules: { + "@typescript-eslint/no-explicit-any": "warn", + }, +}; diff --git a/4337-gas-metering/.gitignore b/4337-gas-metering/.gitignore index 9722dbfa..96c1663d 100644 --- a/4337-gas-metering/.gitignore +++ b/4337-gas-metering/.gitignore @@ -12,3 +12,5 @@ node_modules # solidity-coverage files /coverage /coverage.json + +/dist \ No newline at end of file diff --git a/4337-gas-metering/alchemy/alchemy.ts b/4337-gas-metering/alchemy/alchemy.ts index 592ce36c..55a58566 100644 --- a/4337-gas-metering/alchemy/alchemy.ts +++ b/4337-gas-metering/alchemy/alchemy.ts @@ -173,7 +173,7 @@ const newNonce = await getAccountNonce(publicClient as Client, { }); console.log("\nNonce for the sender received from EntryPoint."); -let txCallData: `0x${string}` = await createCallData( +const txCallData: `0x${string}` = await createCallData( chain, publicClient, signer, @@ -208,7 +208,7 @@ sponsoredUserOperation.signature = await signUserOperation( console.log("\nSigned Dummy Data for Paymaster Data Creation from Alchemy."); if (usePaymaster) { - let rvGas = await getGasValuesFromAlchemyPaymaster( + const rvGas = await getGasValuesFromAlchemyPaymaster( policyID, entryPointAddress, sponsoredUserOperation, @@ -232,7 +232,7 @@ if (usePaymaster) { sponsoredUserOperation.maxPriorityFeePerGas, ); - let rvGas = await getGasValuesFromAlchemy( + const rvGas = await getGasValuesFromAlchemy( entryPointAddress, sponsoredUserOperation, chain, diff --git a/4337-gas-metering/gelato/gelato.ts b/4337-gas-metering/gelato/gelato.ts index f47fe817..aac197f5 100644 --- a/4337-gas-metering/gelato/gelato.ts +++ b/4337-gas-metering/gelato/gelato.ts @@ -1,15 +1,6 @@ import dotenv from "dotenv"; import { getAccountNonce } from "permissionless"; -import { setTimeout } from "timers/promises"; -import { - Client, - Hash, - createPublicClient, - formatEther, - http, - parseEther, - zeroAddress, -} from "viem"; +import { Client, Hash, createPublicClient, http, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { sepolia } from "viem/chains"; import { @@ -25,8 +16,6 @@ import { submitUserOperationGelato, createCallData, } from "../utils/userOps"; -import { transferETH } from "../utils/nativeTransfer"; -import { error } from "console"; dotenv.config(); const paymaster = "gelato"; @@ -154,7 +143,7 @@ const newNonce = await getAccountNonce(publicClient as Client, { }); console.log("\nNonce for the sender received from EntryPoint."); -let txCallData: `0x${string}` = await createCallData( +const txCallData: `0x${string}` = await createCallData( chain, publicClient, signer, @@ -194,7 +183,7 @@ if (usePaymaster) { sponsoredUserOperation.maxPriorityFeePerGas = 0n; // Gelato prefers to keep it to zero. sponsoredUserOperation.maxFeePerGas = 0n; - let rvGas = await getGasValuesFromGelato( + const rvGas = await getGasValuesFromGelato( entryPointAddress, sponsoredUserOperation, chainID, diff --git a/4337-gas-metering/package-lock.json b/4337-gas-metering/package-lock.json index 286c3905..469641ea 100644 --- a/4337-gas-metering/package-lock.json +++ b/4337-gas-metering/package-lock.json @@ -19,8 +19,25 @@ }, "devDependencies": { "@types/node": "20.10.2", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.5.0", + "eslint-plugin-prettier": "^5.1.0", + "eslint-plugin-promise": "^6.1.1", "prettier": "3.1.0", - "tsx": "3.13.0" + "tsx": "3.13.0", + "typescript": "^5.3.3" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@adraffy/ens-normalize": { @@ -412,6 +429,85 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -1060,6 +1156,62 @@ "@ethersproject/strings": "^5.7.0" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -1082,6 +1234,61 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@scure/base": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", @@ -1115,6 +1322,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.2.tgz", @@ -1124,405 +1343,3486 @@ "undici-types": "~5.26.4" } }, - "node_modules/abitype": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.8.11.tgz", - "integrity": "sha512-bM4v2dKvX08sZ9IU38IN5BKmN+ZkOSd2oI4a9f0ejHYZQYV6cDr7j+d95ga0z2XHG36Y4jzoG5Z7qDqxp7fi/A==", + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.19.1" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { - "zod": { + "typescript": { "optional": true } } }, - "node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" - }, - "node_modules/alchemy-sdk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.1.0.tgz", - "integrity": "sha512-KMzBo0Dq+cEqXegn4fh2sP74dhAngr9twIv2pBTyPq3/ZJs+aiXXlFzVrVUYaa6x9L7iQtqhz3YKFCuN5uvpAg==", - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/contracts": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/providers": "^5.7.0", - "@ethersproject/units": "^5.7.0", - "@ethersproject/wallet": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "axios": "^0.26.1", - "sturdy-websocket": "^0.2.1", - "websocket": "^1.0.34" - } - }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "follow-redirects": "^1.14.8" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/bufferutil": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", - "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", - "hasInstallScript": true, + "node_modules/@typescript-eslint/parser": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", + "dev": true, + "peer": true, "dependencies": { - "node-gyp-build": "^4.3.0" + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=6.14.2" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" + }, "engines": { - "node": ">=12" + "node": "^16.0.0 || >=18.0.0" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", + "dev": true, "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/abitype": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.8.11.tgz", + "integrity": "sha512-bM4v2dKvX08sZ9IU38IN5BKmN+ZkOSd2oI4a9f0ejHYZQYV6cDr7j+d95ga0z2XHG36Y4jzoG5Z7qDqxp7fi/A==", + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alchemy-sdk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.1.0.tgz", + "integrity": "sha512-KMzBo0Dq+cEqXegn4fh2sP74dhAngr9twIv2pBTyPq3/ZJs+aiXXlFzVrVUYaa6x9L7iQtqhz3YKFCuN5uvpAg==", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/providers": "^5.7.0", + "@ethersproject/units": "^5.7.0", + "@ethersproject/wallet": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "axios": "^0.26.1", + "sturdy-websocket": "^0.2.1", + "websocket": "^1.0.34" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.5.0.tgz", + "integrity": "sha512-Hw02Bj1QrZIlKyj471Tb1jSReTl4ghIMHGuBGiMVmw+s0jOPbI4CBuYpGbZr+tdQ+VAvSK6FDSta3J4ib/SKHQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.0.tgz", + "integrity": "sha512-hQc+2zbnMeXcIkg+pKZtVa+3Yqx4WY7SMkn1PLZ4VbBEU7jJIpVn9347P8BBhTbz6ne85aXvQf30kvexcqBeWw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" }, "engines": { "node": ">=0.10" } }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isows": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", + "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", + "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/permissionless": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.0.16.tgz", + "integrity": "sha512-tuGMf1l6X+WD8X2/WZAaF0Rqj7ysWX++eiUNgG/A+Qe1vP+e4Oik57jVUOW7Yo/VXQ9zU3luEC2GmwrFeo4b8w==", + "peerDependencies": { + "viem": "^1.19.4" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "hasInstallScript": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, "bin": { - "esbuild": "bin/esbuild" + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" }, "engines": { "node": ">=12" }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "dependencies": { - "type": "^2.7.2" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, "engines": { - "node": ">=4.0" + "node": ">=0.4" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/isows": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", - "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" - } - ], - "peerDependencies": { - "ws": "*" + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "node_modules/sturdy-websocket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz", + "integrity": "sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==" }, - "node_modules/node-gyp-build": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", - "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/permissionless": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.0.16.tgz", - "integrity": "sha512-tuGMf1l6X+WD8X2/WZAaF0Rqj7ysWX++eiUNgG/A+Qe1vP+e4Oik57jVUOW7Yo/VXQ9zU3luEC2GmwrFeo4b8w==", - "peerDependencies": { - "viem": "^1.19.4" + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "node_modules/synckit": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", + "integrity": "sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==", "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "@pkgr/utils": "^2.4.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://opencollective.com/unts" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", "dev": true, + "engines": { + "node": ">=12" + }, "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/sturdy-websocket": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz", - "integrity": "sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==" + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true }, "node_modules/tsx": { "version": "3.13.0", @@ -1546,6 +4846,95 @@ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -1555,10 +4944,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "peer": true, + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1567,12 +4955,45 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", @@ -1673,6 +5094,62 @@ "node": ">=4.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "node_modules/ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", @@ -1701,6 +5178,24 @@ "node": ">=0.10.32" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", diff --git a/4337-gas-metering/package.json b/4337-gas-metering/package.json index 86100b10..c3119523 100644 --- a/4337-gas-metering/package.json +++ b/4337-gas-metering/package.json @@ -16,7 +16,10 @@ "alchemy:erc721": "tsx ./alchemy/alchemy.ts erc721", "alchemy:erc721:paymaster": "tsx ./alchemy/alchemy.ts erc721 paymaster=true", "alchemy": "tsx ./alchemy/alchemy.ts", + "build": "npx rimraf dist && tsc", "fmt": "prettier --ignore-path .gitignore --write .", + "fmt:check": "prettier --check .", + "lint": "eslint ./alchemy && eslint ./gelato && eslint ./pimlico && eslint ./utils", "gelato:account:1balance": "tsx ./gelato/gelato.ts account", "gelato:native-transfer:1balance": "tsx ./gelato/gelato.ts native-transfer", "gelato:erc20:1balance": "tsx ./gelato/gelato.ts erc20", @@ -56,7 +59,15 @@ }, "devDependencies": { "@types/node": "20.10.2", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.5.0", + "eslint-plugin-prettier": "^5.1.0", + "eslint-plugin-promise": "^6.1.1", "prettier": "3.1.0", - "tsx": "3.13.0" + "tsx": "3.13.0", + "typescript": "^5.3.3" } } diff --git a/4337-gas-metering/pimlico/pimlico.ts b/4337-gas-metering/pimlico/pimlico.ts index f41026cd..f3af25ef 100644 --- a/4337-gas-metering/pimlico/pimlico.ts +++ b/4337-gas-metering/pimlico/pimlico.ts @@ -193,7 +193,7 @@ const newNonce = await getAccountNonce(publicClient as Client, { }); console.log("\nNonce for the sender received from EntryPoint."); -let txCallData: `0x${string}` = await createCallData( +const txCallData: `0x${string}` = await createCallData( chain, publicClient, signer, diff --git a/4337-gas-metering/tsconfig.json b/4337-gas-metering/tsconfig.json index aa4eb960..05e145c1 100644 --- a/4337-gas-metering/tsconfig.json +++ b/4337-gas-metering/tsconfig.json @@ -11,6 +11,9 @@ /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, /* Completeness */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "outDir": "./dist" /* Redirect output structure to the directory. */, + "noImplicitReturns": true /* Report error when not all code paths in function return a value. */ + }, + "exclude": ["node_modules"] } diff --git a/4337-gas-metering/utils/erc20.ts b/4337-gas-metering/utils/erc20.ts index 5433ab1a..2ba7f2a5 100644 --- a/4337-gas-metering/utils/erc20.ts +++ b/4337-gas-metering/utils/erc20.ts @@ -107,7 +107,7 @@ export const mintERC20Token = async ( publicClient: any, signer: PrivateKeyAccount, to: string, - amount: BigInt, + amount: bigint, chain: string, paymaster: string, ) => { @@ -191,7 +191,7 @@ export const transferERC20Token = async ( publicClient: any, signer: PrivateKeyAccount, to: string, - amount: BigInt, + amount: bigint, chain: string, paymaster: string, ) => { diff --git a/4337-gas-metering/utils/safe.ts b/4337-gas-metering/utils/safe.ts index 7e45fb30..b71b1845 100644 --- a/4337-gas-metering/utils/safe.ts +++ b/4337-gas-metering/utils/safe.ts @@ -1,9 +1,7 @@ import { Address, - Chain, Hex, PublicClient, - Transport, concatHex, encodeFunctionData, encodePacked, @@ -53,7 +51,7 @@ const getInitializerCode = async ({ erc20TokenAddress: Address; paymasterAddress: Address; }) => { - let setupTxs: InternalTx[] = [ + const setupTxs: InternalTx[] = [ { to: addModuleLibAddress, data: enableModuleCallData(safe4337ModuleAddress), @@ -289,10 +287,7 @@ export const encodeCallData = (params: { }); }; -export const getAccountAddress = async < - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, ->({ +export const getAccountAddress = async ({ client, owner, addModuleLibAddress, diff --git a/4337-gas-metering/utils/userOps.ts b/4337-gas-metering/utils/userOps.ts index 42ba9be2..786b69d2 100644 --- a/4337-gas-metering/utils/userOps.ts +++ b/4337-gas-metering/utils/userOps.ts @@ -20,7 +20,6 @@ import { generateMintingCallData } from "./erc721"; import { transferETH } from "./nativeTransfer"; dotenv.config(); -const safeVersion = process.env.SAFE_VERSION; export const txTypes = ["account", "erc20", "erc721", "native-transfer"]; @@ -226,10 +225,10 @@ export const getMaxFeePerGas = async ( let maxFeePerGas; // Get the latest Block Number - let latestBlockNum = await alchemy.core.getBlockNumber(); + const latestBlockNum = await alchemy.core.getBlockNumber(); // Get latest Block Details - let rvBlock = await alchemy.core.getBlock(latestBlockNum); + const rvBlock = await alchemy.core.getBlock(latestBlockNum); if (rvBlock && rvBlock.baseFeePerGas) { maxFeePerGas = ((BigInt(rvBlock.baseFeePerGas._hex) + BigInt(maxPriorityFeePerGas)) * @@ -380,11 +379,11 @@ export const submitUserOperationAlchemy = async ( ".etherscan.io/tx/" + responseValues["result"]["receipt"]["transactionHash"], ); - let actualGasUsed = fromHex( + const actualGasUsed = fromHex( responseValues["result"]["actualGasUsed"], "number", ); - let gasUsed = fromHex( + const gasUsed = fromHex( responseValues["result"]["receipt"]["gasUsed"], "number", ); @@ -646,7 +645,7 @@ export const submitUserOperationGelato = async ( ]["address"]; if (rvEntryPoint == entryPointAddress) { - let userOpHash = + const userOpHash = responseValues["result"]["logs"][ responseValues["result"]["logs"].length - 2 ]["topics"][1]; @@ -665,11 +664,11 @@ export const submitUserOperationGelato = async ( ".etherscan.io/tx/" + responseValues["result"]["receipt"]["transactionHash"], ); - let actualGasUsed = fromHex( + const actualGasUsed = fromHex( responseValues["result"]["actualGasUsed"], "number", ); - let gasUsed = fromHex( + const gasUsed = fromHex( responseValues["result"]["receipt"]["gasUsed"], "number", ); From cb0a1fd1009eca4a088fe3d70e460d54d218365d Mon Sep 17 00:00:00 2001 From: Shebin John Date: Thu, 21 Dec 2023 17:05:03 +0530 Subject: [PATCH 60/60] Github Workflow for Checks added --- .github/workflows/ci_4337_gas_metering.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ci_4337_gas_metering.yml diff --git a/.github/workflows/ci_4337_gas_metering.yml b/.github/workflows/ci_4337_gas_metering.yml new file mode 100644 index 00000000..5b2da0a1 --- /dev/null +++ b/.github/workflows/ci_4337_gas_metering.yml @@ -0,0 +1,20 @@ +name: safe-modules-4337-gas-metering +on: [push] + +jobs: + checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./4337-gas-metering + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: npm + cache-dependency-path: 4337-gas-metering/package-lock.json + - run: npm ci + - run: npm run fmt:check + - run: npm run lint + - run: npm run build