Skip to content

Commit

Permalink
Merge pull request #14 from etherspot/PRO-2468-Paymaster_Changes
Browse files Browse the repository at this point in the history
PRO-2468 - Paymaster changes for EPv7
  • Loading branch information
vignesha22 authored Jun 26, 2024
2 parents bcd7fdb + a362af8 commit 07481ee
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 44 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## [1.0.3] - 2024-06-24
### Bug Fixes
- Added support for paymaster executions according to EPv7

## [1.0.2] - 2024-06-17
### New
- Added support for XDC Testnet.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@etherspot/modular-sdk",
"version": "1.0.2",
"version": "1.0.3",
"description": "Etherspot Modular SDK - build with ERC-7579 smart accounts modules",
"keywords": [
"ether",
Expand Down
32 changes: 17 additions & 15 deletions src/sdk/base/BaseAccountAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export abstract class BaseAccountAPI {

// entryPoint connected to "zero" address. allowed to make static calls (e.g. to getSenderAddress)
protected readonly entryPointView: IEntryPoint;
protected readonly nonceManager: INonceManager;

provider: Provider;
overheads?: Partial<GasOverheads>;
Expand Down Expand Up @@ -102,9 +101,6 @@ export abstract class BaseAccountAPI {
this.entryPointView = EntryPoint__factory.connect(params.entryPointAddress, params.provider).connect(
ethers.constants.AddressZero,
);
this.nonceManager = INonceManager__factory.connect(params.entryPointAddress, params.provider).connect(
ethers.constants.AddressZero
);
}

get state(): StateService {
Expand Down Expand Up @@ -440,19 +436,22 @@ export abstract class BaseAccountAPI {
};


let paymasterAndData: PaymasterResponse | undefined = null;
let paymasterData: PaymasterResponse | undefined = null;
if (this.paymasterAPI != null) {
// fill (partial) preVerificationGas (all except the cost of the generated paymasterAndData)
// fill (partial) preVerificationGas (all except the cost of the generated paymasterData)
const userOpForPm = {
...partialUserOp,
preVerificationGas: this.getPreVerificationGas(partialUserOp),
};
paymasterAndData = (await this.paymasterAPI.getPaymasterAndData(userOpForPm));
partialUserOp.verificationGasLimit = paymasterAndData.result.verificationGasLimit;
partialUserOp.preVerificationGas = paymasterAndData.result.preVerificationGas;
partialUserOp.callGasLimit = paymasterAndData.result.callGasLimit;
paymasterData = (await this.paymasterAPI.getPaymasterData(userOpForPm));
partialUserOp.verificationGasLimit = paymasterData.result.verificationGasLimit;
partialUserOp.preVerificationGas = paymasterData.result.preVerificationGas;
partialUserOp.callGasLimit = paymasterData.result.callGasLimit;
partialUserOp.paymaster = paymasterData.result.paymaster;
partialUserOp.paymasterVerificationGasLimit = paymasterData.result.paymasterVerificationGasLimit;
partialUserOp.paymasterPostOpGasLimit = paymasterData.result.paymasterPostOpGasLimit;
}
partialUserOp.paymasterAndData = paymasterAndData ? paymasterAndData.result.paymasterAndData : '0x';
partialUserOp.paymasterData = paymasterData ? paymasterData.result.paymasterData : '0x';
return {
...partialUserOp,
preVerificationGas: this.getPreVerificationGas(partialUserOp),
Expand All @@ -466,10 +465,13 @@ export abstract class BaseAccountAPI {
*/
async signUserOp(userOp: UserOperation): Promise<UserOperation> {
if (this.paymasterAPI != null) {
const paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOp);
userOp.verificationGasLimit = paymasterAndData.result.verificationGasLimit;
userOp.preVerificationGas = paymasterAndData.result.preVerificationGas;
userOp.callGasLimit = paymasterAndData.result.callGasLimit;
const paymasterData = await this.paymasterAPI.getPaymasterData(userOp);
userOp.verificationGasLimit = paymasterData.result.verificationGasLimit;
userOp.preVerificationGas = paymasterData.result.preVerificationGas;
userOp.callGasLimit = paymasterData.result.callGasLimit;
userOp.paymaster = paymasterData.result.paymaster;
userOp.paymasterVerificationGasLimit = paymasterData.result.paymasterVerificationGasLimit;
userOp.paymasterPostOpGasLimit = paymasterData.result.paymasterPostOpGasLimit;
}
const userOpHash = await this.getUserOpHash(userOp);
const signature = await this.signUserOpHash(userOpHash);
Expand Down
2 changes: 1 addition & 1 deletion src/sdk/base/EtherspotWalletAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
? ethers.utils.getAddress(this.multipleOwnerECDSAValidatorAddress) + "00000000"
: ethers.utils.getAddress(key.toHexString()) + "00000000";

return await this.nonceManager.getNonce(accountAddress, BigInt(dummyKey));
return await this.entryPointView.getNonce(accountAddress, BigInt(dummyKey));
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/sdk/base/PaymasterAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { PaymasterResponse } from './VerifyingPaymasterAPI';
*/
export class PaymasterAPI {
/**
* @param userOp a partially-filled UserOperation (without signature and paymasterAndData
* @param userOp a partially-filled UserOperation (without signature and paymasterData
* note that the "preVerificationGas" is incomplete: it can't account for the
* paymasterAndData value, which will only be returned by this method..
* @returns the value to put into the PaymasterAndData, undefined to leave it empty
* paymasterData value, which will only be returned by this method..
* @returns the value to put into the PaymasterData, undefined to leave it empty
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getPaymasterAndData(userOp: Partial<UserOperationStruct>): Promise<PaymasterResponse | undefined> {
return { result: {paymasterAndData: '0x', verificationGasLimit: '0x', preVerificationGas: '0x', callGasLimit: '0x' }};
async getPaymasterData(userOp: Partial<UserOperationStruct>): Promise<PaymasterResponse | undefined> {
return { result: { paymaster: '0x', paymasterData: '0x', paymasterPostOpGasLimit: '0x', paymasterVerificationGasLimit: '0x', preVerificationGas: '0x', verificationGasLimit: '0x', callGasLimit: '0x' }};
}
}
31 changes: 16 additions & 15 deletions src/sdk/base/VerifyingPaymasterAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import fetch from 'cross-fetch';
import { calcPreVerificationGas } from './calcPreVerificationGas';
import { PaymasterAPI } from './PaymasterAPI';
Expand All @@ -8,12 +8,16 @@ import { UserOperation } from '../common';
const DUMMY_PAYMASTER_AND_DATA =
'0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101';

// Expected EntryPoint v0.7 Paymaster Response
export interface PaymasterResponse {
result: {
paymasterAndData: string;
verificationGasLimit: string;
paymaster: string;
paymasterData: string;
preVerificationGas: string;
verificationGasLimit: string;
callGasLimit: string;
paymasterVerificationGasLimit: string;
paymasterPostOpGasLimit: string;
}
}

Expand All @@ -28,8 +32,8 @@ export class VerifyingPaymasterAPI extends PaymasterAPI {
this.context = context;
}

async getPaymasterAndData(userOp: Partial<UserOperation>): Promise<PaymasterResponse> {
// Hack: userOp includes empty paymasterAndData which calcPreVerificationGas requires.
async getPaymasterData(userOp: Partial<UserOperation>): Promise<PaymasterResponse> {
// Hack: userOp includes empty paymasterData which calcPreVerificationGas requires.
try {
// userOp.preVerificationGas contains a promise that will resolve to an error.
await ethers.utils.resolveProperties(userOp);
Expand All @@ -44,37 +48,34 @@ export class VerifyingPaymasterAPI extends PaymasterAPI {
verificationGasLimit: userOp.verificationGasLimit,
maxFeePerGas: userOp.maxFeePerGas,
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,
// A dummy value here is required in order to calculate a correct preVerificationGas value.
paymasterData: DUMMY_PAYMASTER_AND_DATA,
signature: userOp.signature ?? '0x',
paymaster: userOp.paymaster,
paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit,
paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit,
};
const op = await ethers.utils.resolveProperties(pmOp);
op.preVerificationGas = calcPreVerificationGas(op);

// Ask the paymaster to sign the transaction and return a valid paymasterAndData value.
const paymasterAndData = await fetch(this.paymasterUrl, {
// Ask the paymaster to sign the transaction and return a valid paymasterData value.
const paymasterData = await fetch(this.paymasterUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ params: [await toJSON(op), this.entryPoint, this.context], jsonrpc: '2', id: 2 }),
body: JSON.stringify({ method: 'pm_sponsorUserOperation', params: [await toJSON(op), this.entryPoint, this.context], jsonrpc: '2', id: 2 }),
})
.then(async (res) => {
const response = await await res.json();
if (response.error) {
throw new Error(response.error);
}
// Since the value of paymasterVerificationGasLimit is defined by the paymaster provider itself, it could be number in string
if (response.result && response.result.paymasterVerificationGasLimit)
response.result.paymasterVerificationGasLimit = BigNumber.from(response.result.paymasterVerificationGasLimit).toHexString()
return response
})
.catch((err) => {
throw new Error(err.message);
})

return paymasterAndData;
return paymasterData;
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/sdk/errorHandler/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ export const entryPointErrorMsg = {
'AA14 initCode must return sender': 'The initCode does not return the sender address. Check the initCode or the factory contract',
'AA15 initCode must create sender': 'The initCode in the user operation does not create an account. Check the initCode or the factory contract',
'AA20 account not deployed': 'The sender of the user operation is not deployed and there is no initCode specified. If this is the first transaction by this account make sure an initCode is included. Otherwise, check that the correct sender address is specified and is an ERC-4337 account',
"AA21 didn't pay prefund": `The sender did not have enough to prefund the EntryPoint for the user operation. If you are using a paymaster, the paymasterAndData field is likely not set. If you aren't using a paymaster, the address of the sender does not have enough gas token. After the user operation is executed, the remainder of the prefund is credited back to the sender`,
"AA21 didn't pay prefund": `The sender did not have enough to prefund the EntryPoint for the user operation. If you are using a paymaster, the paymasterData field is likely not set. If you aren't using a paymaster, the address of the sender does not have enough gas token. After the user operation is executed, the remainder of the prefund is credited back to the sender`,
'AA22 expired or not due': 'The signature is not valid because it is outside of the specified time range',
'AA23 reverted (or OOG)': `The sender does not have sufficient native tokens to cover the User Operation's gas costs. If you intended to use a Paymaster for sponsorship, ensure that the paymasterAndData field of the user operation is correctly set to enable proper handling of gas fees`,
'AA23 reverted (or OOG)': `The sender does not have sufficient native tokens to cover the User Operation's gas costs. If you intended to use a Paymaster for sponsorship, ensure that the paymasterData field of the user operation is correctly set to enable proper handling of gas fees`,
'AA24 signature error': `Check the signature field of the user operation. It may be in an incompatible format`,
'AA25 invalid account nonce': 'The nonce is invalid. The user operation may be re-using an old nonce, or formatted the nonce incorrectly',
'AA30 paymaster not deployed': 'The paymaster address specified by paymasterAndData contains no code. Check that the first characters of the paymasterAndData field are the paymaster address you intend to use',
'AA30 paymaster not deployed': 'The paymaster address specified by paymasterData contains no code. Check that the first characters of the paymasterData field are the paymaster address you intend to use',
'AA31 paymaster deposit too low': `The paymaster is out of funds. More gas tokens must be deposited into the EntryPoint for the paymaster. This is usually done by calling the paymaster contract's deposit function. If you are using a paymaster service, contact them immediately`,
'AA32 paymaster expired or not due': `The paymaster's signature is not valid because it is outside of the specified time range`,
'AA33 reverted (or OOG)': `The paymaster validation was rejected or ran out of gas. "OOG" is an abbreviation for Out-Of-Gas. First check the paymaster's signature in paymasterAndData. If the signature is correct, the verificationGasLimit may be too low`,
'AA34 signature error': `The paymaster's signature is invalid. Check the format of the signature in paymasterAndData`,
'AA33 reverted (or OOG)': `The paymaster validation was rejected or ran out of gas. "OOG" is an abbreviation for Out-Of-Gas. First check the paymaster's signature in paymasterData. If the signature is correct, the verificationGasLimit may be too low`,
'AA34 signature error': `The paymaster's signature is invalid. Check the format of the signature in paymasterData`,
'AA40 over verificationGasLimit': `The verification gas limit was exceeded. Check the verificationGasLimit in your user operation`,
'AA41 too little verificationGas': `Verifying the user operation took too much gas and did not complete. You may need to increase verificationGasLimit`,
'AA50 postOp reverted': `After the user operation was completed, the execution of additional logic by the EntryPoint reverted`,
Expand Down

0 comments on commit 07481ee

Please sign in to comment.