Skip to content

Commit

Permalink
feat: support one-off percentage overrides for user operations
Browse files Browse the repository at this point in the history
  • Loading branch information
denniswon committed Nov 29, 2023
1 parent eac55b4 commit 5dd80e2
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 63 deletions.
41 changes: 24 additions & 17 deletions packages/alchemy/src/middleware/gas-fees.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { applyFeeOption, type BigNumberish } from "@alchemy/aa-core";
import {
applyFeeOption,
isBigNumberish,
isPercentage,
type BigNumberish,
} 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
);
const maxPriorityFeePerGas = isBigNumberish(overrides?.maxPriorityFeePerGas)
? 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: [],
}),
isPercentage(overrides?.maxPriorityFeePerGas)
? overrides?.maxPriorityFeePerGas
: feeOptions?.maxPriorityFeePerGas
);

const estimateMaxFeePerGas = async (priorityFeePerGas: BigNumberish) => {
const block = await provider.rpcClient.getBlock({ blockTag: "latest" });
Expand All @@ -26,14 +32,15 @@ export const withAlchemyGasFeeEstimator = (
}
return applyFeeOption(
baseFeePerGas + BigInt(priorityFeePerGas),
feeOptions?.maxFeePerGas
isPercentage(overrides?.maxFeePerGas)
? overrides?.maxFeePerGas
: feeOptions?.maxFeePerGas
);
};

const maxFeePerGas =
overrides?.maxFeePerGas != null
? overrides?.maxFeePerGas
: await estimateMaxFeePerGas(maxPriorityFeePerGas);
const maxFeePerGas = isBigNumberish(overrides?.maxFeePerGas)
? overrides?.maxFeePerGas!
: await estimateMaxFeePerGas(maxPriorityFeePerGas!);

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
82 changes: 52 additions & 30 deletions packages/core/src/provider/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Logger } from "../logger.js";
import {
type BatchUserOperationCallData,
type BigNumberish,
type Percentage,
type UserOperationCallData,
type UserOperationFeeOptions,
type UserOperationOverrides,
Expand All @@ -42,6 +43,7 @@ import {
getDefaultEntryPointAddress,
getDefaultUserOperationFeeOptions,
getUserOperationHash,
isBigNumberish,
isValidRequest,
resolveProperties,
type Deferrable,
Expand Down Expand Up @@ -534,32 +536,40 @@ export class SmartAccountProvider<
overrides ?? {};

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

callGasLimit =
callGasLimit ??
applyFeeOption(estimates.callGasLimit, feeOptions?.callGasLimit);
verificationGasLimit =
verificationGasLimit ??
applyFeeOption(
const request = deepHexlify(await resolveProperties(struct));
const estimates = await this.rpcClient.estimateUserOperationGas(
request,
this.getEntryPointAddress()
);

callGasLimit = isBigNumberish(callGasLimit)
? callGasLimit
: applyFeeOption(
estimates.callGasLimit,
callGasLimit || feeOptions?.callGasLimit
);
verificationGasLimit = isBigNumberish(verificationGasLimit)
? verificationGasLimit
: applyFeeOption(
estimates.verificationGasLimit,
feeOptions?.verificationGasLimit
verificationGasLimit || feeOptions?.verificationGasLimit
);
preVerificationGas =
preVerificationGas ??
applyFeeOption(
preVerificationGas = isBigNumberish(preVerificationGas)
? preVerificationGas
: applyFeeOption(
estimates.preVerificationGas,
feeOptions?.preVerificationGas
preVerificationGas || feeOptions?.preVerificationGas
);
}

struct.callGasLimit = callGasLimit;
struct.verificationGasLimit = verificationGasLimit;
Expand All @@ -575,7 +585,14 @@ export class SmartAccountProvider<
) => {
const estimateMaxPriorityFeePerGas = async () => {
const estimate = await this.rpcClient.estimateMaxPriorityFeePerGas();
return applyFeeOption(estimate, feeOptions?.maxPriorityFeePerGas);
const _overridesMaxPriorityFeePerGas =
overrides?.maxPriorityFeePerGas != null
? (overrides?.maxPriorityFeePerGas as Percentage)
: null;
return applyFeeOption(
estimate,
_overridesMaxPriorityFeePerGas || feeOptions?.maxPriorityFeePerGas
);
};

// maxFeePerGas must be at least the sum of maxPriorityFeePerGas and baseFee
Expand All @@ -593,22 +610,27 @@ export class SmartAccountProvider<
"feeData is missing maxFeePerGas or maxPriorityFeePerGas"
);
}
const _overridesMaxFeePerGas =
overrides?.maxFeePerGas != null
? (overrides?.maxFeePerGas as Percentage)
: null;

const baseFee = applyFeeOption(
feeData.maxFeePerGas - feeData.maxPriorityFeePerGas,
feeOptions?.maxFeePerGas
_overridesMaxFeePerGas || feeOptions?.maxFeePerGas
);

return BigInt(baseFee) + BigInt(maxPriorityFeePerGas);
};

struct.maxPriorityFeePerGas =
overrides?.maxPriorityFeePerGas != null
? overrides?.maxPriorityFeePerGas
: await estimateMaxPriorityFeePerGas();
struct.maxFeePerGas =
overrides?.maxFeePerGas != null
? overrides?.maxFeePerGas
: await estimateMaxFeePerGas(struct.maxPriorityFeePerGas);
struct.maxPriorityFeePerGas = isBigNumberish(
overrides?.maxPriorityFeePerGas
)
? overrides?.maxPriorityFeePerGas
: await estimateMaxPriorityFeePerGas();
struct.maxFeePerGas = isBigNumberish(overrides?.maxFeePerGas)
? overrides?.maxFeePerGas
: await estimateMaxFeePerGas(struct.maxPriorityFeePerGas!);

return struct;
};
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
4 changes: 2 additions & 2 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,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

0 comments on commit 5dd80e2

Please sign in to comment.