Skip to content

Commit

Permalink
feat: support one-off percentage overrides for user operations (#289)
Browse files Browse the repository at this point in the history
* feat: support one-off percentage overrides for user operations

* feat: apply user op override or fee option utils in aa-core
  • Loading branch information
denniswon authored and moldy530 committed Dec 1, 2023
1 parent bfae0f5 commit e76c4b3
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 123 deletions.
51 changes: 23 additions & 28 deletions packages/alchemy/src/middleware/gas-fees.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
import { applyFeeOption, type BigNumberish } from "@alchemy/aa-core";
import { applyUserOpOverrideOrFeeOption } from "@alchemy/aa-core";
import type { AlchemyProvider } from "../provider.js";
import type { ClientWithAlchemyMethods } from "./client.js";

export const withAlchemyGasFeeEstimator = (
provider: AlchemyProvider
): AlchemyProvider => {
provider.withFeeDataGetter(async (struct, overrides, feeOptions) => {
const maxPriorityFeePerGas =
overrides?.maxPriorityFeePerGas != null
? overrides?.maxPriorityFeePerGas
: // it's a fair assumption that if someone is using this Alchemy Middleware, then they are using Alchemy RPC
applyFeeOption(
await (provider.rpcClient as ClientWithAlchemyMethods).request({
method: "rundler_maxPriorityFeePerGas",
params: [],
}),
feeOptions?.maxPriorityFeePerGas
);
let [block, maxPriorityFeePerGasEstimate] = await Promise.all([
provider.rpcClient.getBlock({ blockTag: "latest" }),
// it's a fair assumption that if someone is using this Alchemy Middleware, then they are using Alchemy RPC
(provider.rpcClient as ClientWithAlchemyMethods).request({
method: "rundler_maxPriorityFeePerGas",
params: [],
}),
]);
const baseFeePerGas = block.baseFeePerGas;
if (baseFeePerGas == null) {
throw new Error("baseFeePerGas is null");
}

const estimateMaxFeePerGas = async (priorityFeePerGas: BigNumberish) => {
const block = await provider.rpcClient.getBlock({ blockTag: "latest" });
const baseFeePerGas = block.baseFeePerGas;
if (baseFeePerGas == null) {
throw new Error("baseFeePerGas is null");
}
return applyFeeOption(
baseFeePerGas + BigInt(priorityFeePerGas),
feeOptions?.maxFeePerGas
);
};

const maxFeePerGas =
overrides?.maxFeePerGas != null
? overrides?.maxFeePerGas
: await estimateMaxFeePerGas(maxPriorityFeePerGas);
const maxPriorityFeePerGas = applyUserOpOverrideOrFeeOption(
maxPriorityFeePerGasEstimate,
overrides?.maxPriorityFeePerGas,
feeOptions?.maxPriorityFeePerGas
);
const maxFeePerGas = applyUserOpOverrideOrFeeOption(
baseFeePerGas + BigInt(maxPriorityFeePerGas),
overrides?.maxFeePerGas,
feeOptions?.maxFeePerGas
);

return {
...struct,
Expand Down
17 changes: 14 additions & 3 deletions packages/alchemy/src/middleware/gas-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
deepHexlify,
filterUndefined,
isBigNumberish,
isPercentage,
resolveProperties,
type AccountMiddlewareFn,
Expand Down Expand Up @@ -169,15 +170,25 @@ const withAlchemyGasAndPaymasterAndDataMiddleware = <P extends AlchemyProvider>(
const overrideField = (
field: keyof UserOperationFeeOptions
): Hex | Percentage | undefined => {
// one-off absolute override
if (overrides?.[field] != null) {
return deepHexlify(overrides[field]);
// one-off absolute override
if (isBigNumberish(overrides[field])) {
return deepHexlify(overrides[field]);
}
// one-off percentage overrides
else {
return {
percentage:
100 + Number((overrides[field] as Percentage).percentage),
};
}
}

// provider level fee options with percentage
if (isPercentage(feeOptions?.[field])) {
return {
percentage: 100 + Number(feeOptions?.[field]?.percentage),
percentage:
100 + Number((feeOptions![field] as Percentage).percentage),
};
}

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export type * from "./types.js";
export type * from "./utils/index.js";
export {
ChainSchema,
applyFeeOption,
applyUserOpFeeOption,
applyUserOpOverride,
applyUserOpOverrideOrFeeOption,
asyncPipe,
bigIntMax,
bigIntPercent,
Expand Down
102 changes: 46 additions & 56 deletions packages/core/src/provider/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
type UserOperationStruct,
} from "../types.js";
import {
applyFeeOption,
applyUserOpOverrideOrFeeOption,
asyncPipe,
bigIntMax,
bigIntPercent,
Expand Down Expand Up @@ -521,41 +521,31 @@ export class SmartAccountProvider<
overrides,
feeOptions
) => {
let { callGasLimit, verificationGasLimit, preVerificationGas } =
overrides ?? {};

if (
callGasLimit == null ||
verificationGasLimit == null ||
preVerificationGas == null
) {
const request = deepHexlify(await resolveProperties(struct));
const estimates = await this.rpcClient.estimateUserOperationGas(
request,
this.getEntryPointAddress()
);
const request = deepHexlify(await resolveProperties(struct));
const estimates = await this.rpcClient.estimateUserOperationGas(
request,
this.getEntryPointAddress()
);

callGasLimit =
callGasLimit ??
applyFeeOption(estimates.callGasLimit, feeOptions?.callGasLimit);
verificationGasLimit =
verificationGasLimit ??
applyFeeOption(
estimates.verificationGasLimit,
feeOptions?.verificationGasLimit
);
preVerificationGas =
preVerificationGas ??
applyFeeOption(
estimates.preVerificationGas,
feeOptions?.preVerificationGas
);
}
const callGasLimit = applyUserOpOverrideOrFeeOption(
estimates.callGasLimit,
overrides?.callGasLimit,
feeOptions?.callGasLimit
);
const verificationGasLimit = applyUserOpOverrideOrFeeOption(
estimates.verificationGasLimit,
overrides?.verificationGasLimit,
feeOptions?.verificationGasLimit
);
const preVerificationGas = applyUserOpOverrideOrFeeOption(
estimates.preVerificationGas,
overrides?.preVerificationGas,
feeOptions?.preVerificationGas
);

struct.callGasLimit = callGasLimit;
struct.verificationGasLimit = verificationGasLimit;
struct.preVerificationGas = preVerificationGas;

return struct;
};

Expand All @@ -564,11 +554,6 @@ export class SmartAccountProvider<
overrides,
feeOptions
) => {
const estimateMaxPriorityFeePerGas = async () => {
const estimate = await this.rpcClient.estimateMaxPriorityFeePerGas();
return applyFeeOption(estimate, feeOptions?.maxPriorityFeePerGas);
};

// maxFeePerGas must be at least the sum of maxPriorityFeePerGas and baseFee
// so we need to accommodate for the fee option applied maxPriorityFeePerGas for the maxFeePerGas
//
Expand All @@ -577,30 +562,35 @@ export class SmartAccountProvider<
//
// Refer to https://docs.alchemy.com/docs/maxpriorityfeepergas-vs-maxfeepergas
// for more information about maxFeePerGas and maxPriorityFeePerGas
const estimateMaxFeePerGas = async (maxPriorityFeePerGas: BigNumberish) => {
const feeData = await this.rpcClient.estimateFeesPerGas();
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) {
throw new Error(
"feeData is missing maxFeePerGas or maxPriorityFeePerGas"
);
}
const baseFee = applyFeeOption(
feeData.maxFeePerGas - feeData.maxPriorityFeePerGas,
feeOptions?.maxFeePerGas

const feeData = await this.rpcClient.estimateFeesPerGas();
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) {
throw new Error(
"feeData is missing maxFeePerGas or maxPriorityFeePerGas"
);
}

return BigInt(baseFee) + BigInt(maxPriorityFeePerGas);
};
let maxPriorityFeePerGas: BigNumberish =
await this.rpcClient.estimateMaxPriorityFeePerGas();
maxPriorityFeePerGas = applyUserOpOverrideOrFeeOption(
maxPriorityFeePerGas,
overrides?.maxPriorityFeePerGas,
feeOptions?.maxPriorityFeePerGas
);

struct.maxPriorityFeePerGas =
overrides?.maxPriorityFeePerGas != null
? overrides?.maxPriorityFeePerGas
: await estimateMaxPriorityFeePerGas();
struct.maxFeePerGas =
overrides?.maxFeePerGas != null
? overrides?.maxFeePerGas
: await estimateMaxFeePerGas(struct.maxPriorityFeePerGas);
let maxFeePerGas: BigNumberish =
feeData.maxFeePerGas -
feeData.maxPriorityFeePerGas +
BigInt(maxPriorityFeePerGas);

maxFeePerGas = applyUserOpOverrideOrFeeOption(
maxFeePerGas,
overrides?.maxFeePerGas,
feeOptions?.maxFeePerGas
);

struct.maxFeePerGas = maxFeePerGas;
struct.maxPriorityFeePerGas = maxPriorityFeePerGas;
return struct;
};

Expand Down
1 change: 0 additions & 1 deletion packages/core/src/provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ export interface ISmartAccountProvider<
* prior to execution.
*
* @param override - a function for overriding the default feeDataGetter middleware
* @param feeOptions - optional FeeDataFeeOptions to set at the global level of the provider.
* @returns
*/
withFeeDataGetter: (override: FeeDataMiddleware) => this;
Expand Down
23 changes: 12 additions & 11 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,18 @@ export type UserOperationFeeOptions = z.infer<
typeof UserOperationFeeOptionsSchema
>;

export type UserOperationOverrides = Partial<
Pick<
UserOperationStruct,
| "callGasLimit"
| "maxFeePerGas"
| "maxPriorityFeePerGas"
| "paymasterAndData"
| "preVerificationGas"
| "verificationGasLimit"
>
>;
export type UserOperationOverrides = Partial<{
callGasLimit: UserOperationStruct["callGasLimit"] | Percentage;
maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Percentage;
maxPriorityFeePerGas:
| UserOperationStruct["maxPriorityFeePerGas"]
| Percentage;
preVerificationGas: UserOperationStruct["preVerificationGas"] | Percentage;
verificationGasLimit:
| UserOperationStruct["verificationGasLimit"]
| Percentage;
paymasterAndData: UserOperationStruct["paymasterAndData"];
}>;

// represents the request as it needs to be formatted for RPC requests
export interface UserOperationRequest {
Expand Down
24 changes: 2 additions & 22 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import type {
BigNumberish,
Percentage,
PromiseOrValue,
UserOperationFeeOptionsField,
UserOperationRequest,
} from "../types.js";
import { bigIntClamp, bigIntPercent } from "./bigint.js";
import { BigNumberishSchema, PercentageSchema } from "./schema.js";

/**
Expand Down Expand Up @@ -97,24 +95,6 @@ export function deepHexlify(obj: any): any {
);
}

export function applyFeeOption(
value: BigNumberish | undefined,
feeOption?: UserOperationFeeOptionsField
): BigNumberish {
if (feeOption == null) {
return value ?? 0n;
}
return value
? bigIntClamp(
feeOption.percentage
? bigIntPercent(value, BigInt(100 + feeOption.percentage))
: value,
feeOption.min,
feeOption.max
)
: feeOption.min ?? 0n;
}

/**
* Generates a hash for a UserOperation valid from entrypoint version 0.6 onwards
*
Expand Down Expand Up @@ -183,11 +163,11 @@ export function defineReadOnly<T, K extends keyof T>(
}

export function isBigNumberish(x: any): x is BigNumberish {
return BigNumberishSchema.safeParse(x).success;
return x != null && BigNumberishSchema.safeParse(x).success;
}

export function isPercentage(x: any): x is Percentage {
return PercentageSchema.safeParse(x).success;
return x != null && PercentageSchema.safeParse(x).success;
}

export function filterUndefined(
Expand Down
Loading

0 comments on commit e76c4b3

Please sign in to comment.