From f74687bc980e47c87c0deeac2b7f850bf5060bc9 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Tue, 30 May 2023 13:11:32 +0530 Subject: [PATCH 1/7] ERC20 Paymaster demo using Pimlico --- contracts/SimpleAccountWithPaymaster.sol | 19 ++++ .../SimpleAccountWithPaymasterFactory.sol | 47 +++++++++ deploy/deploy.ts | 5 +- package.json | 2 + src/exconfig.ts | 18 ++-- src/pages/Account/account-api/account-api.ts | 97 ++++++++++++++++++- .../pre-transaction-confirmation.tsx | 41 ++------ src/pages/Account/useAccountApi.ts | 30 +++--- .../Background/services/provider-bridge.ts | 10 +- yarn.lock | 43 +++++++- 10 files changed, 245 insertions(+), 67 deletions(-) create mode 100644 contracts/SimpleAccountWithPaymaster.sol create mode 100644 contracts/SimpleAccountWithPaymasterFactory.sol diff --git a/contracts/SimpleAccountWithPaymaster.sol b/contracts/SimpleAccountWithPaymaster.sol new file mode 100644 index 0000000..3d72675 --- /dev/null +++ b/contracts/SimpleAccountWithPaymaster.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +import '@account-abstraction/contracts/samples/SimpleAccount.sol'; + +contract SimpleAccountWithPaymaster is SimpleAccount { + constructor(IEntryPoint anEntryPoint) SimpleAccount(anEntryPoint) {} + + /** + * @dev The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint, + * a new implementation of SimpleAccount must be deployed with the new EntryPoint address, then upgrading + * the implementation by calling `upgradeTo()` + */ + function initialize(address anOwner, address dest, uint256 value, bytes calldata func) public virtual initializer { + initialize(anOwner); + _call(dest, value, func); + } + +} diff --git a/contracts/SimpleAccountWithPaymasterFactory.sol b/contracts/SimpleAccountWithPaymasterFactory.sol new file mode 100644 index 0000000..e225c10 --- /dev/null +++ b/contracts/SimpleAccountWithPaymasterFactory.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + + +import "@openzeppelin/contracts/utils/Create2.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "./SimpleAccountWithPaymaster.sol"; + +contract SimpleAccountWithPaymasterFactory { + SimpleAccountWithPaymaster public immutable accountImplementation; + + constructor(IEntryPoint _entryPoint) { + accountImplementation = new SimpleAccountWithPaymaster(_entryPoint); + } + + /** + * create an account, and return its address. + * returns the address even if the account is already deployed. + * Note that during UserOperation execution, this method is called only if the account is not deployed. + * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation + */ + function createAccount(address owner,uint256 salt, address dest, uint256 value, bytes calldata func) public returns (SimpleAccountWithPaymaster ret) { + address addr = getAddress(owner, salt, dest, value, func); + uint codeSize = addr.code.length; + if (codeSize > 0) { + return SimpleAccountWithPaymaster(payable(addr)); + } + ret = SimpleAccountWithPaymaster(payable(new ERC1967Proxy{salt : bytes32(salt)}( + address(accountImplementation), + abi.encodeCall(SimpleAccountWithPaymaster.initialize, (owner, dest, value, func)) + ))); + } + + /** + * calculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner,uint256 salt, address dest, uint256 value, bytes calldata func) public view returns (address) { + return Create2.computeAddress(bytes32(salt), keccak256(abi.encodePacked( + type(ERC1967Proxy).creationCode, + abi.encode( + address(accountImplementation), + abi.encodeCall(SimpleAccountWithPaymaster.initialize, (owner, dest,value, func)) + ) + ))); + } + +} \ No newline at end of file diff --git a/deploy/deploy.ts b/deploy/deploy.ts index 0d75e54..a28c87a 100644 --- a/deploy/deploy.ts +++ b/deploy/deploy.ts @@ -1,12 +1,13 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; +import config from '../src/exconfig'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const accounts = await hre.getUnnamedAccounts(); - await hre.deployments.deploy('Greeter', { + await hre.deployments.deploy('SimpleAccountWithPaymasterFactory', { from: accounts[0], deterministicDeployment: true, - args: ['Test'], + args: [config.network.entryPointAddress], log: true, }); }; diff --git a/package.json b/package.json index d06ec7e..8d45880 100755 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@account-abstraction/contracts": "^0.6.0", "@account-abstraction/sdk": "^0.6.0", + "@account-abstraction/utils": "^0.6.0", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@fontsource/roboto": "^4.5.8", @@ -28,6 +29,7 @@ "@nomicfoundation/hardhat-toolbox": "^2.0.1", "@peculiar/asn1-ecc": "^2.3.4", "@peculiar/asn1-schema": "^2.3.3", + "@pimlico/erc20-paymaster": "^0.0.12", "@redux-devtools/cli": "^2.0.0", "@redux-devtools/remote": "^0.8.0", "@reduxjs/toolkit": "^1.9.1", diff --git a/src/exconfig.ts b/src/exconfig.ts index 025d16f..f0de482 100644 --- a/src/exconfig.ts +++ b/src/exconfig.ts @@ -2,21 +2,21 @@ export default { enablePasswordEncryption: false, showTransactionConfirmationScreen: true, - factory_address: '0x9406Cc6185a346906296840746125a0E44976454', + factory_address: '0xb6f4fB799a085ef048c796F22a74B5c646BF77d4', stateVersion: '0.1', network: { - chainID: '11155111', + chainID: '80001', family: 'EVM', - name: 'Sepolia', - provider: 'https://sepolia.infura.io/v3/bdabe9d2f9244005af0f566398e648da', + name: 'Mumbai', + provider: + 'https://polygon-mumbai.g.alchemy.com/v2/YdsNfZkPMSAefI7wwCnBFfXK0ZRz2F-k', entryPointAddress: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', - bundler: 'https://sepolia.voltaire.candidewallet.com/rpc', + bundler: 'http://localhost:3000/rpc', baseAsset: { - symbol: 'ETH', - name: 'ETH', + symbol: 'MATIC', + name: 'MATIC', decimals: 18, - image: - 'https://ethereum.org/static/6b935ac0e6194247347855dc3d328e83/6ed5f/eth-diamond-black.webp', + image: 'https://cryptologos.cc/logos/polygon-matic-logo.png?v=025', }, }, }; diff --git a/src/pages/Account/account-api/account-api.ts b/src/pages/Account/account-api/account-api.ts index 26ecb65..e955098 100644 --- a/src/pages/Account/account-api/account-api.ts +++ b/src/pages/Account/account-api/account-api.ts @@ -1,4 +1,4 @@ -import { ethers, Wallet } from 'ethers'; +import { constants, ethers, Wallet } from 'ethers'; import { UserOperationStruct } from '@account-abstraction/contracts'; import { AccountApiParamsType, AccountApiType } from './types'; @@ -6,6 +6,16 @@ import { MessageSigningRequest } from '../../Background/redux-slices/signing'; import { TransactionDetailsForUserOp } from '@account-abstraction/sdk/dist/src/TransactionDetailsForUserOp'; import config from '../../../exconfig'; import { SimpleAccountAPI } from '@account-abstraction/sdk'; +import { hexConcat, resolveProperties } from 'ethers/lib/utils.js'; +import { + ERC20__factory, + ERC20Paymaster, + getERC20Paymaster, +} from '@pimlico/erc20-paymaster'; +import { + SimpleAccountWithPaymasterFactory, + SimpleAccountWithPaymasterFactory__factory, +} from './typechain-types'; const FACTORY_ADDRESS = config.factory_address; @@ -20,6 +30,9 @@ class SimpleAccountTrampolineAPI extends SimpleAccountAPI implements AccountApiType { + factoryWithPaymaster?: SimpleAccountWithPaymasterFactory; + erc20Paymaster?: ERC20Paymaster; + /** * * We create a new private key or use the one provided in the @@ -36,6 +49,11 @@ class SimpleAccountTrampolineAPI }); } + async init(): Promise { + this.erc20Paymaster = await getERC20Paymaster(this.provider, 'USDC'); + return super.init(); + } + /** * * @returns the serialized state of the account that is saved in @@ -48,6 +66,45 @@ class SimpleAccountTrampolineAPI }; }; + /** + * return the value to put into the "initCode" field, if the account is not yet deployed. + * this value holds the "factory" address, followed by this account's information + */ + async getAccountInitCode() { + if (this.factoryAddress === undefined) + throw new Error('no factory to get initCode'); + + if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); + + if (this.factoryWithPaymaster == null) { + this.factoryWithPaymaster = + SimpleAccountWithPaymasterFactory__factory.connect( + this.factoryAddress, + this.provider + ); + } + + const usdcTokenAddress = await this.erc20Paymaster.contract.token(); + const usdcToken = ERC20__factory.connect(usdcTokenAddress, this.owner); + const erc20PaymasterAddress = this.erc20Paymaster.contract.address; + + const approveData = usdcToken.interface.encodeFunctionData('approve', [ + erc20PaymasterAddress, + constants.MaxUint256, + ]); + + return hexConcat([ + this.factoryWithPaymaster.address, + this.factoryWithPaymaster.interface.encodeFunctionData('createAccount', [ + await this.owner.getAddress(), + this.index, + usdcToken.address, + 0, + approveData, + ]), + ]); + } + /** * Called when the Dapp requests eth_signTypedData */ @@ -58,6 +115,26 @@ class SimpleAccountTrampolineAPI throw new Error('signMessage method not implemented.'); }; + createUnsignedUserOp = async ( + info: TransactionDetailsForUserOp + ): Promise => { + const userOp = await resolveProperties( + await super.createUnsignedUserOp(info) + ); + if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); + const erc20PaymasterAndData = + await this.erc20Paymaster.generatePaymasterAndData(userOp); + return { + ...userOp, + preVerificationGas: ethers.BigNumber.from(userOp.preVerificationGas).gt( + 50000 + ) + ? userOp.preVerificationGas + : ethers.BigNumber.from(50000).toHexString(), + paymasterAndData: erc20PaymasterAndData ? erc20PaymasterAndData : '0x', + }; + }; + /** * Called after the user is presented with the pre-transaction confirmation screen * The context passed to this method is the same as the one passed to the @@ -67,11 +144,20 @@ class SimpleAccountTrampolineAPI info: TransactionDetailsForUserOp, preTransactionConfirmationContext?: any ): Promise { + if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); + + const userOp = await resolveProperties( + await this.createUnsignedUserOp(info) + ); + + // await this.erc20Paymaster.verifyTokenApproval(userOp); + + const erc20PaymasterAndData = + await this.erc20Paymaster.generatePaymasterAndData(userOp); + return { - ...(await this.createUnsignedUserOp(info)), - paymasterAndData: preTransactionConfirmationContext?.paymasterAndData - ? preTransactionConfirmationContext?.paymasterAndData - : '0x', + ...userOp, + paymasterAndData: erc20PaymasterAndData ? erc20PaymasterAndData : '0x', }; } @@ -84,6 +170,7 @@ class SimpleAccountTrampolineAPI userOp: UserOperationStruct, postTransactionConfirmationContext: any ): Promise => { + console.log('signUserOpWithContext', userOp); return this.signUserOp(userOp); }; } diff --git a/src/pages/Account/components/transaction/pre-transaction-confirmation.tsx b/src/pages/Account/components/transaction/pre-transaction-confirmation.tsx index 6c05bf7..79cac05 100644 --- a/src/pages/Account/components/transaction/pre-transaction-confirmation.tsx +++ b/src/pages/Account/components/transaction/pre-transaction-confirmation.tsx @@ -9,11 +9,12 @@ import { TextField, Typography, } from '@mui/material'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { PreTransactionConfirmation, PreTransactionConfirmationtProps, } from '../types'; +import useAccountApi from '../../useAccountApi'; const AddPaymasterAndData = ({ setPaymasterAndData, @@ -115,57 +116,27 @@ const PreTransactionConfirmationComponent: PreTransactionConfirmation = ({ transaction, onReject, }: PreTransactionConfirmationtProps) => { - const [loader, setLoader] = React.useState(false); - const [paymasterAndData, setPaymasterAndDataLocal] = useState(''); - return ( <> - Dummy Component + Paymaster Demo - You can show as many steps as you want in this dummy component. You - need to call the function onComplete passed as a props to this - component.
-
- The function takes a modifiedTransactions & context as a parameter, - the context will be passed to your AccountApi when creating a new - account. While modifiedTransactions will be agreed upon by the user. -
- This Component is defined in exported in{' '} -
- - trampoline/src/pages/Account/components/transaction/pre-transaction-confirmation.ts + We will be using docs.pimlico.io{' '} + as our paymaster for the purpose of this demo. - - -
diff --git a/src/pages/Account/useAccountApi.ts b/src/pages/Account/useAccountApi.ts index 7276f0a..3bd06ca 100644 --- a/src/pages/Account/useAccountApi.ts +++ b/src/pages/Account/useAccountApi.ts @@ -6,7 +6,14 @@ import { getActiveAccount, } from '../Background/redux-slices/selectors/accountSelectors'; -const useAccountApi = () => { +const useAccountApi = ({ + functionName, + args, +}: { + functionName: string; + args?: any[]; +}) => { + const [apiCalled, setApicalled] = useState(false); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const activeAccount = useBackgroundSelector(getActiveAccount); @@ -16,17 +23,16 @@ const useAccountApi = () => { const { accountApiCallResult, accountApiCallResultState } = useBackgroundSelector(getAccountApiCallResult); - const callAccountApi = useCallback( - async (functionName: string, args?: any[]) => { - setLoading(true); - if (activeAccount) { - await backgroundDispatch( - callAccountApiThunk({ address: activeAccount, functionName, args }) - ); - } - }, - [backgroundDispatch, activeAccount] - ); + const callAccountApi = useCallback(async () => { + if (apiCalled) return; + setLoading(true); + setApicalled(true); + if (activeAccount) { + await backgroundDispatch( + callAccountApiThunk({ address: activeAccount, functionName, args }) + ); + } + }, [apiCalled, activeAccount, backgroundDispatch, functionName, args]); useEffect(() => { if (accountApiCallResultState === 'set' && loading) { diff --git a/src/pages/Background/services/provider-bridge.ts b/src/pages/Background/services/provider-bridge.ts index 09b3db7..f28c992 100644 --- a/src/pages/Background/services/provider-bridge.ts +++ b/src/pages/Background/services/provider-bridge.ts @@ -261,6 +261,7 @@ export default class ProviderBridgeService extends BaseService { // Fetch the latest permission const persistedPermission = await this.checkPermission(origin); + console.log(persistedPermission); if (typeof persistedPermission !== 'undefined') { // if agrees then let's return the account data @@ -371,10 +372,10 @@ export default class ProviderBridgeService extends BaseService { ); await showExtensionPopup(AllowedQueryParamPage.dappPermission); - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.#pendingRequests[permissionRequest.origin] = { resolve, - reject, + reject: resolve, }; }); } @@ -680,7 +681,10 @@ export default class ProviderBridgeService extends BaseService { params, origin, showExtensionPopup(AllowedQueryParamPage.signTransaction) - ); + ).catch((error) => { + console.log(error); + new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest).toJSON(); + }); default: { return await this.routeSafeRPCRequest(method, params, origin); diff --git a/yarn.lock b/yarn.lock index 3dee794..c07ef06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@account-abstraction/contracts@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@account-abstraction/contracts/-/contracts-0.5.0.tgz#a089aee7b4c446251fbbce7df315bbf8f659e37f" + integrity sha512-CKyS9Zh5rcYUM+4B6TlaB9+THHzJ+6TY3tWF5QofqvFpqGNvIhF8ddy6wyCmqZw6TB74/yYv7cYD/RarVudfDg== + "@account-abstraction/contracts@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@account-abstraction/contracts/-/contracts-0.6.0.tgz#7188a01839999226e6b2796328af338329543b76" @@ -23,6 +28,18 @@ debug "^4.3.4" ethers "^5.7.0" +"@account-abstraction/utils@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@account-abstraction/utils/-/utils-0.5.0.tgz#aa7925741048b1657a71d7f98ccaf3c187f99b4a" + integrity sha512-dgXguTn5WgFMmr3wQMdLGEoIMDcIJgzAv74YlHeb2D3Nyy1pByPArSb3eLOOcgxCJSJeqTscpO9P57uhNkkC4A== + dependencies: + "@account-abstraction/contracts" "^0.5.0" + "@ethersproject/abi" "^5.7.0" + "@ethersproject/providers" "^5.7.0" + "@openzeppelin/contracts" "^4.7.3" + debug "^4.3.4" + ethers "^5.7.0" + "@account-abstraction/utils@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@account-abstraction/utils/-/utils-0.6.0.tgz#78682b3e2c0adf64668ae23ba7aeae0d31e79022" @@ -1602,7 +1619,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -2464,6 +2481,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ== +"@openzeppelin/contracts@^4.8.2": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.0.tgz#683f33b6598970051bc5f0806fd8660da9e018dd" + integrity sha512-DUP74AFGKlic2sQb/CmgrN2aUPMFGxRrmCTUxLHsiU2RzwWqVuMPZBxiAyvlff6Pea77uylAX6B5x9W6evEbhA== + "@peculiar/asn1-ecc@^2.3.4": version "2.3.4" resolved "https://registry.yarnpkg.com/@peculiar/asn1-ecc/-/asn1-ecc-2.3.4.tgz#f120091bde2ecd1e4f4691b46a2d18a74effce20" @@ -2499,6 +2521,20 @@ resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug== +"@pimlico/erc20-paymaster@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@pimlico/erc20-paymaster/-/erc20-paymaster-0.0.12.tgz#e83131ebe73558a96a1fb7a86c4709fed75f68d5" + integrity sha512-UG03UYI6JgOVtMLtDhZeqUUStY4YPvvL7mBqBptLAkDkeUrDW5FlrwSZEZAv++3syOT2wbw5vHYkCWr5hF3UbQ== + dependencies: + "@account-abstraction/contracts" "^0.6.0" + "@account-abstraction/utils" "^0.5.0" + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@openzeppelin/contracts" "^4.8.2" + ethers "^5.7.2" + solady "^0.0.90" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.10": version "0.5.10" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8" @@ -13331,6 +13367,11 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" +solady@^0.0.90: + version "0.0.90" + resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.90.tgz#2075349dfa6909b05ed04031f3b609ddbcd73598" + integrity sha512-NSc0mkfJo2CeKfFfyED1qbBQ8ofvDFxpBb0BLJ/wx2ZN7vCAYl8sp/j6TIIMdPb0BDvOKl2Rie9dVum+KIKeVA== + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" From 50a998265f6f43fb112f8ee664252dad6a98422c Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Tue, 30 May 2023 14:46:46 +0530 Subject: [PATCH 2/7] Remove redundnat code --- src/pages/Account/account-api/account-api.ts | 13 ++----------- src/pages/Background/services/provider-bridge.ts | 10 +++------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/pages/Account/account-api/account-api.ts b/src/pages/Account/account-api/account-api.ts index e955098..c55363c 100644 --- a/src/pages/Account/account-api/account-api.ts +++ b/src/pages/Account/account-api/account-api.ts @@ -126,6 +126,7 @@ class SimpleAccountTrampolineAPI await this.erc20Paymaster.generatePaymasterAndData(userOp); return { ...userOp, + // preVerificationGas predictions doesn't work properly on Mumbai network preVerificationGas: ethers.BigNumber.from(userOp.preVerificationGas).gt( 50000 ) @@ -144,20 +145,10 @@ class SimpleAccountTrampolineAPI info: TransactionDetailsForUserOp, preTransactionConfirmationContext?: any ): Promise { - if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); - - const userOp = await resolveProperties( - await this.createUnsignedUserOp(info) - ); - - // await this.erc20Paymaster.verifyTokenApproval(userOp); - - const erc20PaymasterAndData = - await this.erc20Paymaster.generatePaymasterAndData(userOp); + const userOp = await this.createUnsignedUserOp(info); return { ...userOp, - paymasterAndData: erc20PaymasterAndData ? erc20PaymasterAndData : '0x', }; } diff --git a/src/pages/Background/services/provider-bridge.ts b/src/pages/Background/services/provider-bridge.ts index f28c992..09b3db7 100644 --- a/src/pages/Background/services/provider-bridge.ts +++ b/src/pages/Background/services/provider-bridge.ts @@ -261,7 +261,6 @@ export default class ProviderBridgeService extends BaseService { // Fetch the latest permission const persistedPermission = await this.checkPermission(origin); - console.log(persistedPermission); if (typeof persistedPermission !== 'undefined') { // if agrees then let's return the account data @@ -372,10 +371,10 @@ export default class ProviderBridgeService extends BaseService { ); await showExtensionPopup(AllowedQueryParamPage.dappPermission); - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.#pendingRequests[permissionRequest.origin] = { resolve, - reject: resolve, + reject, }; }); } @@ -681,10 +680,7 @@ export default class ProviderBridgeService extends BaseService { params, origin, showExtensionPopup(AllowedQueryParamPage.signTransaction) - ).catch((error) => { - console.log(error); - new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest).toJSON(); - }); + ); default: { return await this.routeSafeRPCRequest(method, params, origin); From 612cd1f790ed5f16b7e4bb4fc33a426d179f0437 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Tue, 30 May 2023 14:48:15 +0530 Subject: [PATCH 3/7] Remove logs --- src/pages/Account/account-api/account-api.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/Account/account-api/account-api.ts b/src/pages/Account/account-api/account-api.ts index c55363c..019c043 100644 --- a/src/pages/Account/account-api/account-api.ts +++ b/src/pages/Account/account-api/account-api.ts @@ -145,11 +145,7 @@ class SimpleAccountTrampolineAPI info: TransactionDetailsForUserOp, preTransactionConfirmationContext?: any ): Promise { - const userOp = await this.createUnsignedUserOp(info); - - return { - ...userOp, - }; + return this.createUnsignedUserOp(info); } /** @@ -161,7 +157,6 @@ class SimpleAccountTrampolineAPI userOp: UserOperationStruct, postTransactionConfirmationContext: any ): Promise => { - console.log('signUserOpWithContext', userOp); return this.signUserOp(userOp); }; } From 0e143f37b5a94c9d45fb8003e82c014e0b5c60c6 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 15 Jun 2023 14:47:55 +0530 Subject: [PATCH 4/7] Get the correct gas before adding paymaster data --- src/pages/Account/account-api/account-api.ts | 82 ++++++++++++++++++-- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/src/pages/Account/account-api/account-api.ts b/src/pages/Account/account-api/account-api.ts index 019c043..3cc2afe 100644 --- a/src/pages/Account/account-api/account-api.ts +++ b/src/pages/Account/account-api/account-api.ts @@ -5,7 +5,7 @@ import { AccountApiParamsType, AccountApiType } from './types'; import { MessageSigningRequest } from '../../Background/redux-slices/signing'; import { TransactionDetailsForUserOp } from '@account-abstraction/sdk/dist/src/TransactionDetailsForUserOp'; import config from '../../../exconfig'; -import { SimpleAccountAPI } from '@account-abstraction/sdk'; +import { HttpRpcClient, SimpleAccountAPI } from '@account-abstraction/sdk'; import { hexConcat, resolveProperties } from 'ethers/lib/utils.js'; import { ERC20__factory, @@ -16,6 +16,7 @@ import { SimpleAccountWithPaymasterFactory, SimpleAccountWithPaymasterFactory__factory, } from './typechain-types'; +import { NotPromise } from '@account-abstraction/utils'; const FACTORY_ADDRESS = config.factory_address; @@ -32,6 +33,7 @@ class SimpleAccountTrampolineAPI { factoryWithPaymaster?: SimpleAccountWithPaymasterFactory; erc20Paymaster?: ERC20Paymaster; + bundler: HttpRpcClient; /** * @@ -47,6 +49,7 @@ class SimpleAccountTrampolineAPI : ethers.Wallet.createRandom(), factoryAddress: FACTORY_ADDRESS, }); + this.bundler = params.bundler; } async init(): Promise { @@ -115,23 +118,86 @@ class SimpleAccountTrampolineAPI throw new Error('signMessage method not implemented.'); }; + adjustGasParameters = async ( + userOp: NotPromise + ): Promise> => { + userOp.nonce = ethers.BigNumber.from(userOp.nonce).toHexString(); + userOp.callGasLimit = ethers.BigNumber.from( + userOp.callGasLimit + ).toHexString(); + userOp.verificationGasLimit = ethers.BigNumber.from( + userOp.verificationGasLimit + ).toHexString(); + userOp.preVerificationGas = ethers.BigNumber.from( + userOp.preVerificationGas + ).toHexString(); + userOp.maxFeePerGas = ethers.BigNumber.from( + userOp.maxFeePerGas + ).toHexString(); + userOp.maxPriorityFeePerGas = ethers.BigNumber.from( + userOp.maxPriorityFeePerGas + ) + .toHexString() + .toLowerCase(); + + console.log('yaha p to h'); + const gasParameters = await this.bundler.estimateUserOpGas( + await this.signUserOp(userOp) + ); + + console.log(this.bundler, gasParameters); + + const estimatedGasLimit = ethers.BigNumber.from( + gasParameters?.callGasLimit + ); + const estimateVerificationGasLimit = ethers.BigNumber.from( + gasParameters?.verificationGas + ); + const estimatePreVerificationGas = ethers.BigNumber.from( + gasParameters?.preVerificationGas + ); + + userOp.callGasLimit = estimatedGasLimit.gt( + ethers.BigNumber.from(userOp.callGasLimit) + ) + ? estimatedGasLimit.toHexString() + : userOp.callGasLimit; + + userOp.verificationGasLimit = estimateVerificationGasLimit.gt( + ethers.BigNumber.from(userOp.verificationGasLimit) + ) + ? estimateVerificationGasLimit.toHexString() + : userOp.verificationGasLimit; + + userOp.preVerificationGas = estimatePreVerificationGas.gt( + ethers.BigNumber.from(userOp.preVerificationGas) + ) + ? estimatePreVerificationGas.toHexString() + : userOp.preVerificationGas; + + return userOp; + }; + createUnsignedUserOp = async ( info: TransactionDetailsForUserOp ): Promise => { const userOp = await resolveProperties( await super.createUnsignedUserOp(info) ); + // preVerificationGas predictions doesn't work properly on Mumbai network + userOp.preVerificationGas = ethers.BigNumber.from( + userOp.preVerificationGas + ).gt(50000) + ? userOp.preVerificationGas + : ethers.BigNumber.from(50000).toHexString(); + if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); const erc20PaymasterAndData = - await this.erc20Paymaster.generatePaymasterAndData(userOp); + await this.erc20Paymaster.generatePaymasterAndData( + await this.adjustGasParameters(userOp) + ); return { ...userOp, - // preVerificationGas predictions doesn't work properly on Mumbai network - preVerificationGas: ethers.BigNumber.from(userOp.preVerificationGas).gt( - 50000 - ) - ? userOp.preVerificationGas - : ethers.BigNumber.from(50000).toHexString(), paymasterAndData: erc20PaymasterAndData ? erc20PaymasterAndData : '0x', }; }; From f5ff178178ccbf6ff5f57b6888b6afd288698c05 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Tue, 27 Jun 2023 06:02:23 +0530 Subject: [PATCH 5/7] Fix gas calculations and use Candide's bundlers --- src/exconfig.ts | 2 +- src/pages/Account/account-api/account-api.ts | 14 ++- src/pages/Background/services/keyring.ts | 98 +++++++++++--------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/src/exconfig.ts b/src/exconfig.ts index f0de482..4962e84 100644 --- a/src/exconfig.ts +++ b/src/exconfig.ts @@ -11,7 +11,7 @@ export default { provider: 'https://polygon-mumbai.g.alchemy.com/v2/YdsNfZkPMSAefI7wwCnBFfXK0ZRz2F-k', entryPointAddress: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', - bundler: 'http://localhost:3000/rpc', + bundler: 'https://mumbai.voltaire.candidewallet.com/rpc', baseAsset: { symbol: 'MATIC', name: 'MATIC', diff --git a/src/pages/Account/account-api/account-api.ts b/src/pages/Account/account-api/account-api.ts index 3cc2afe..acc508c 100644 --- a/src/pages/Account/account-api/account-api.ts +++ b/src/pages/Account/account-api/account-api.ts @@ -131,22 +131,20 @@ class SimpleAccountTrampolineAPI userOp.preVerificationGas = ethers.BigNumber.from( userOp.preVerificationGas ).toHexString(); - userOp.maxFeePerGas = ethers.BigNumber.from( - userOp.maxFeePerGas - ).toHexString(); + userOp.maxFeePerGas = ethers.BigNumber.from(userOp.maxFeePerGas) + .mul(3) // Alchemy vs Candide have different gas prices + .toHexString(); userOp.maxPriorityFeePerGas = ethers.BigNumber.from( userOp.maxPriorityFeePerGas ) + .mul(3) .toHexString() .toLowerCase(); - console.log('yaha p to h'); const gasParameters = await this.bundler.estimateUserOpGas( await this.signUserOp(userOp) ); - console.log(this.bundler, gasParameters); - const estimatedGasLimit = ethers.BigNumber.from( gasParameters?.callGasLimit ); @@ -187,9 +185,9 @@ class SimpleAccountTrampolineAPI // preVerificationGas predictions doesn't work properly on Mumbai network userOp.preVerificationGas = ethers.BigNumber.from( userOp.preVerificationGas - ).gt(50000) + ).gt(62660) ? userOp.preVerificationGas - : ethers.BigNumber.from(50000).toHexString(); + : ethers.BigNumber.from(62660).toHexString(); if (!this.erc20Paymaster) throw new Error('erc20Paymaster not initialized'); const erc20PaymasterAndData = diff --git a/src/pages/Background/services/keyring.ts b/src/pages/Background/services/keyring.ts index 09a0ef7..67c6f7e 100644 --- a/src/pages/Background/services/keyring.ts +++ b/src/pages/Background/services/keyring.ts @@ -407,53 +407,59 @@ export default class KeyringService extends BaseService { ); userOp.nonce = ethers.BigNumber.from(userOp.nonce).toHexString(); - userOp.callGasLimit = ethers.BigNumber.from( - userOp.callGasLimit - ).toHexString(); - userOp.verificationGasLimit = ethers.BigNumber.from( - userOp.verificationGasLimit - ).toHexString(); - userOp.preVerificationGas = ethers.BigNumber.from( - userOp.preVerificationGas - ).toHexString(); - userOp.maxFeePerGas = ethers.BigNumber.from( - userOp.maxFeePerGas - ).toHexString(); - userOp.maxPriorityFeePerGas = ethers.BigNumber.from( - userOp.maxPriorityFeePerGas - ).toHexString(); - - const gasParameters = await this.bundler?.estimateUserOpGas( - await keyring.signUserOp(userOp) - ); - - const estimatedGasLimit = ethers.BigNumber.from( - gasParameters?.callGasLimit - ); - const estimateVerificationGasLimit = ethers.BigNumber.from( - gasParameters?.verificationGas - ); - const estimatePreVerificationGas = ethers.BigNumber.from( - gasParameters?.preVerificationGas - ); - userOp.callGasLimit = estimatedGasLimit.gt( - ethers.BigNumber.from(userOp.callGasLimit) - ) - ? estimatedGasLimit.toHexString() - : userOp.callGasLimit; - - userOp.verificationGasLimit = estimateVerificationGasLimit.gt( - ethers.BigNumber.from(userOp.verificationGasLimit) - ) - ? estimateVerificationGasLimit.toHexString() - : userOp.verificationGasLimit; - - userOp.preVerificationGas = estimatePreVerificationGas.gt( - ethers.BigNumber.from(userOp.preVerificationGas) - ) - ? estimatePreVerificationGas.toHexString() - : userOp.preVerificationGas; + /** + * The following code was commented because the ERC20 paymaster uses these values to calculate the amount of USDC that will be required + * for the transaction to go through. So we must keep the gas values that we have calculated in the function createUnsignedUserOpWithContext + */ + + // userOp.callGasLimit = ethers.BigNumber.from( + // userOp.callGasLimit + // ).toHexString(); + // userOp.verificationGasLimit = ethers.BigNumber.from( + // userOp.verificationGasLimit + // ).toHexString(); + // userOp.preVerificationGas = ethers.BigNumber.from( + // userOp.preVerificationGas + // ).toHexString(); + // userOp.maxFeePerGas = ethers.BigNumber.from( + // userOp.maxFeePerGas + // ).toHexString(); + // userOp.maxPriorityFeePerGas = ethers.BigNumber.from( + // userOp.maxPriorityFeePerGas + // ).toHexString(); + + // const gasParameters = await this.bundler?.estimateUserOpGas( + // await keyring.signUserOp(userOp) + // ); + + // const estimatedGasLimit = ethers.BigNumber.from( + // gasParameters?.callGasLimit + // ); + // const estimateVerificationGasLimit = ethers.BigNumber.from( + // gasParameters?.verificationGas + // ); + // const estimatePreVerificationGas = ethers.BigNumber.from( + // gasParameters?.preVerificationGas + // ); + + // userOp.callGasLimit = estimatedGasLimit.gt( + // ethers.BigNumber.from(userOp.callGasLimit) + // ) + // ? estimatedGasLimit.toHexString() + // : userOp.callGasLimit; + + // userOp.verificationGasLimit = estimateVerificationGasLimit.gt( + // ethers.BigNumber.from(userOp.verificationGasLimit) + // ) + // ? estimateVerificationGasLimit.toHexString() + // : userOp.verificationGasLimit; + + // userOp.preVerificationGas = estimatePreVerificationGas.gt( + // ethers.BigNumber.from(userOp.preVerificationGas) + // ) + // ? estimatePreVerificationGas.toHexString() + // : userOp.preVerificationGas; return userOp; }; From c42e8158f31474f054eb4687a9dad18abe248f89 Mon Sep 17 00:00:00 2001 From: Garvit Khatri Date: Sat, 20 Jan 2024 16:20:36 +0530 Subject: [PATCH 6/7] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6b88a5b..20d104d 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ Trampoline is a chrome extension boilerplate code to showcase your own Smart Contract Wallets with React 18 and Webpack 5 support. + +> [!NOTE] +> This branch is the implementation of how to send a transaction using ERC20 Tokens. +> See [SimpleAccountWithPaymaster Contract](https://github.com/eth-infinitism/trampoline/blob/trampoline-demo-erc20-paymaster/contracts/SimpleAccountWithPaymaster.sol) for the changes we have done to allow ERC20 approval while in it's creation phase. NOTE: The override will not be allowed by every bundler as this access storage which is not allowed by ERC 4337 standard. + ## Installation and Running ### Steps: From 3f7ee52fa80732f6428ac5035fc9fa5476272fcc Mon Sep 17 00:00:00 2001 From: Garvit Khatri Date: Sat, 20 Jan 2024 16:24:55 +0530 Subject: [PATCH 7/7] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 20d104d..279d149 100755 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Trampoline is a chrome extension boilerplate code to showcase your own Smart Con > [!NOTE] > This branch is the implementation of how to send a transaction using ERC20 Tokens. > See [SimpleAccountWithPaymaster Contract](https://github.com/eth-infinitism/trampoline/blob/trampoline-demo-erc20-paymaster/contracts/SimpleAccountWithPaymaster.sol) for the changes we have done to allow ERC20 approval while in it's creation phase. NOTE: The override will not be allowed by every bundler as this access storage which is not allowed by ERC 4337 standard. +> +> Read more about it on this [blog](https://erc4337.mirror.xyz/7DUTUn2eNrjvum3tWAnRih8576IrX13E6WnZeADvAHQ) ## Installation and Running