Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for base paymaster #18

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-var-requires": "off"
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}
8 changes: 4 additions & 4 deletions src/gasless-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PimlicoBundlerClient } from 'permissionless/clients/pimlico';
import { UserOperation } from 'permissionless/types';
import { getSenderAddress, signUserOperationHashWithECDSA } from 'permissionless';
import * as constants from '../src/constants';
import { BasePaymaster } from './paymasters';
import { Paymaster } from './paymasters';
import { PartialBy } from 'viem/types/utils';
import { SponsorUserOperationReturnType } from 'permissionless/actions/pimlico';

Expand All @@ -24,7 +24,7 @@ export class GaslessProvider extends ProviderWrapper {
protected readonly _wrappedProvider: EIP1193Provider,
public readonly chain: string,
protected readonly bundlerClient: PimlicoBundlerClient,
protected readonly paymasterClient: BasePaymaster,
protected readonly paymasterClient: Paymaster,
protected readonly publicClient: ReturnType<typeof createPublicClient>,
protected readonly _initCode: `0x${string}`,
protected readonly senderAddress: `0x${string}`,
Expand All @@ -41,7 +41,7 @@ export class GaslessProvider extends ProviderWrapper {
_wrappedProvider: EIP1193Provider,
chain: string,
bundlerClient: PimlicoBundlerClient,
paymasterClient: BasePaymaster,
paymasterClient: Paymaster,
publicClient: ReturnType<typeof createPublicClient>,
) {
// NOTE: Bundlers can support many entry points, but currently they only support one, we use this method so if they ever add a new one the entry point will still work
Expand Down Expand Up @@ -146,7 +146,7 @@ export class GaslessProvider extends ProviderWrapper {
};

const paymasterAndData: `0x${string}` | SponsorUserOperationReturnType =
await this.paymasterClient.sponsorUserOperation(userOperation, this._entryPoint);
await this.paymasterClient.sponsorUserOperation(userOperation, this._entryPoint, this.bundlerClient);

let sponsoredUserOperation: UserOperation;

Expand Down
6 changes: 4 additions & 2 deletions src/paymaster.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { PaymasterType } from './types';
import { BasePaymaster } from './paymasters/BasePaymaster';
import { Paymaster } from './paymasters/Paymaster';
import * as Pm from './paymasters';

export function createPaymasterClient(paymasterType: PaymasterType, paymasterUrl: string): BasePaymaster {
export function createPaymasterClient(paymasterType: PaymasterType, paymasterUrl: string): Paymaster {
switch (paymasterType) {
case PaymasterType.Pimlico:
return new Pm.PimlicoPaymaster(paymasterUrl);
case PaymasterType.StackUp:
return new Pm.StackUpPaymaster(paymasterUrl);
case PaymasterType.Base:
return new Pm.BasePaymaster(paymasterUrl);

default:
throw new Error(`Unknown paymaster type ${paymasterType}`);
Expand Down
80 changes: 72 additions & 8 deletions src/paymasters/BasePaymaster.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,83 @@
import { SponsorUserOperationReturnType } from 'permissionless/actions/pimlico';
import { PartialUserOperation } from '../types';
import { Paymaster } from './Paymaster';
import { PimlicoBundlerClient } from 'permissionless/clients/pimlico';

export class BasePaymaster {
public endpoint: string;

export class BasePaymaster extends Paymaster {
constructor(endpoint: string) {
this.endpoint = endpoint;
super(endpoint);
}

// eslint-disable
public async sponsorUserOperation(
userOp: PartialUserOperation,
userOperation: PartialUserOperation,
entryPoint: `0x${string}`,
bundlerClient: PimlicoBundlerClient,
): Promise<`0x${string}` | SponsorUserOperationReturnType> {
throw new Error('This is a base class and should not be called directly.');
const userOp = this.convertBigIntsToString(userOperation);

const chainIdAsNumber = await bundlerClient.chainId();
const chainId = '0x' + chainIdAsNumber.toString(16);

const data = {
id: 1,
jsonrpc: '2.0',
method: 'eth_paymasterAndDataForEstimateGas',
params: [userOp, entryPoint, chainId],
};

const response = await fetch(this.endpoint, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});

const json = await response.json();

const paymasterAndData = json.result;
Comment on lines +21 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use something like client.request from viem library


const gasConfig = await bundlerClient.estimateUserOperationGas({
userOperation: Object.assign(userOperation, { paymasterAndData: paymasterAndData }),
entryPoint,
});

// Adding gas headroom for safety margin to ensure paymaster signs for enough gas based on estimations
gasConfig.preVerificationGas = gasConfig.preVerificationGas + 2000n;
gasConfig.verificationGasLimit = gasConfig.verificationGasLimit + 4000n;

const stringifyGasConfig = this.convertBigIntsToString(gasConfig);

const finalCallData = {
id: 1,
jsonrpc: '2.0',
method: 'eth_paymasterAndDataForUserOperation',
params: [Object.assign(userOp, stringifyGasConfig), entryPoint, chainId],
};

const finalResponse = await fetch(this.endpoint, {
method: 'POST',
body: JSON.stringify(finalCallData),
headers: { 'Content-Type': 'application/json' },
});

const finalJson = await finalResponse.json();

return {
...gasConfig,
paymasterAndData: finalJson.result,
};
}

// Helper function to convert bigints to hexadecimal strings, which is what base api expects
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private convertBigIntsToString(obj: any) {
for (const key in obj) {
if (typeof obj[key] === 'bigint') {
// Convert 0n to '0x', and other BigInts to their string representation
// NOTE: base expects gas values to be non-zero, but nonce might be zero so we need to be sure to exclude it
obj[key] = obj[key] === 0n && key !== 'nonce' ? '0x1' : '0x' + obj[key].toString(16);
}
}

return obj;
}
// eslint-enable
}
19 changes: 19 additions & 0 deletions src/paymasters/Paymaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { SponsorUserOperationReturnType } from 'permissionless/actions/pimlico';
import { PartialUserOperation } from '../types';
import { PimlicoBundlerClient } from 'permissionless/clients/pimlico';

export class Paymaster {
public endpoint: string;

constructor(endpoint: string) {
this.endpoint = endpoint;
}

public async sponsorUserOperation(
userOp: PartialUserOperation,
entryPoint: `0x${string}`,
bundlierClient: PimlicoBundlerClient,
): Promise<`0x${string}` | SponsorUserOperationReturnType> {
throw new Error('This is a base class and should not be called directly.');
}
}
6 changes: 4 additions & 2 deletions src/paymasters/PimlicoPaymaster.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { BasePaymaster } from './BasePaymaster';
import { Paymaster } from './Paymaster';
import { PartialUserOperation } from '../types';
import { http } from 'viem';
import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico';
import { SponsorUserOperationReturnType } from 'permissionless/actions/pimlico';
import { PimlicoBundlerClient } from 'permissionless/clients/pimlico';

export class PimlicoPaymaster extends BasePaymaster {
export class PimlicoPaymaster extends Paymaster {
public paymasterClient: ReturnType<typeof createPimlicoPaymasterClient>;

constructor(endpoint: string) {
Expand All @@ -17,6 +18,7 @@ export class PimlicoPaymaster extends BasePaymaster {
public async sponsorUserOperation(
userOperation: PartialUserOperation,
entryPoint: `0x${string}`,
bundlerClient: PimlicoBundlerClient,
): Promise<`0x${string}` | SponsorUserOperationReturnType> {
return await this.paymasterClient.sponsorUserOperation({
userOperation,
Expand Down
6 changes: 4 additions & 2 deletions src/paymasters/StackUpPaymaster.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { BasePaymaster } from './BasePaymaster';
import { Paymaster } from './Paymaster';
import { PartialUserOperation } from '../types';
import { http } from 'viem';
import { createStackupPaymasterClient } from 'permissionless/clients/stackup';
import { SponsorUserOperationReturnType } from 'permissionless/actions/stackup';
import { StackupPaymasterContext } from 'permissionless/types/stackup';
import { PimlicoBundlerClient } from 'permissionless/clients/pimlico';

export class StackUpPaymaster extends BasePaymaster {
export class StackUpPaymaster extends Paymaster {
public paymasterClient: ReturnType<typeof createStackupPaymasterClient>;

constructor(endpoint: string) {
Expand All @@ -18,6 +19,7 @@ export class StackUpPaymaster extends BasePaymaster {
public async sponsorUserOperation(
userOperation: PartialUserOperation,
entryPoint: `0x${string}`,
bundlerClient: PimlicoBundlerClient,
): Promise<`0x${string}` | SponsorUserOperationReturnType> {
return await this.paymasterClient.sponsorUserOperation({
userOperation,
Expand Down
3 changes: 2 additions & 1 deletion src/paymasters/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './BasePaymaster';
export * from './Paymaster';
export * from './PimlicoPaymaster';
export * from './StackUpPaymaster';
export * from './BasePaymaster';
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type PartialUserOperation = {

export enum PaymasterType {
Pimlico = 'pimlico',
Biconomy = 'biconomy',
Base = 'base',
StackUp = 'stackup',
}

Expand Down