Skip to content

Commit

Permalink
feat(alchemy): use normal paymaster when fee opts are set
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Aug 12, 2023
1 parent 4f1da98 commit 40be05f
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 28 deletions.
2 changes: 1 addition & 1 deletion examples/alchemy-daapp/src/configs/clientConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const daappConfigurations: Record<number, DAAppConfiguration> = {
},
[sepolia.id]: {
nftContractAddress: "0x5679b0de84bba361d31b2e7152ab20f0f8565245",
simpleAccountFactoryAddress: "0xc8c5736988F4Ea76B9f620dc678c23d5cBf3C83c",
simpleAccountFactoryAddress: "0x9406cc6185a346906296840746125a0e44976454",
gasManagerPolicyId: env.NEXT_PUBLIC_SEPOLIA_POLICY_ID,
rpcUrl: `/api/rpc/proxy?chainId=${sepolia.id}`,
chain: sepolia,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ const onboardingStepHandlers: Record<
let baseSigner = new SmartAccountProvider(
appConfig.rpcUrl,
context.entrypointAddress!,
context.chain!
context.chain!,
undefined,
{
txMaxRetries: 60,
}
).connect((provider: any) => {
if (!context.owner) {
throw new Error("No owner for account was found");
Expand Down
58 changes: 57 additions & 1 deletion packages/alchemy/e2e-tests/simple-account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ describe("Simple Account Tests", () => {

it("should successfully execute with alchemy paymaster info", async () => {
const newSigner = signer.withAlchemyGasManager({
provider: signer.rpcClient,
policyId: PAYMASTER_POLICY_ID,
entryPoint: ENTRYPOINT_ADDRESS,
});
Expand All @@ -101,4 +100,61 @@ describe("Simple Account Tests", () => {

await expect(txnHash).resolves.not.toThrowError();
}, 50000);

it("should successfully override fees in alchemy paymaster", async () => {
const newSigner = signer
.withAlchemyGasManager({
policyId: PAYMASTER_POLICY_ID,
entryPoint: ENTRYPOINT_ADDRESS,
})
.withFeeDataGetter(async () => ({
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
}));

// this should fail since we set super low fees
await expect(
async () =>
await newSigner.sendUserOperation({
target: await newSigner.getAddress(),
data: "0x",
})
).rejects.toThrow();
}, 50000);

it("should successfully use paymaster with fee opts", async () => {
const newSigner = new AlchemyProvider({
apiKey: API_KEY,
rpcUrl: RPC_URL,
chain,
entryPointAddress: ENTRYPOINT_ADDRESS,
feeOpts: {
baseFeeBufferPercent: 50n,
maxPriorityFeeBufferPercent: 50n,
preVerificationGasBufferPercent: 50n,
},
})
.connect(
(provider) =>
new SimpleSmartContractAccount({
entryPointAddress: ENTRYPOINT_ADDRESS,
chain,
owner,
factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS,
rpcClient: provider,
})
)
.withAlchemyGasManager({
policyId: PAYMASTER_POLICY_ID,
entryPoint: ENTRYPOINT_ADDRESS,
});

const result = await newSigner.sendUserOperation({
target: await newSigner.getAddress(),
data: "0x",
});
const txnHash = signer.waitForUserOperationTransaction(result.hash as Hash);

await expect(txnHash).resolves.not.toThrowError();
}, 50000);
});
4 changes: 4 additions & 0 deletions packages/alchemy/src/middleware/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export type ClientWithAlchemyMethods = PublicErc4337Client & {
entryPoint: Address;
userOperation: UserOperationRequest;
dummySignature: Hex;
feeOverride?: {
maxFeePerGas: Hex;
maxPriorityFeePerGas: Hex;
};
}
];
}): Promise<{
Expand Down
36 changes: 27 additions & 9 deletions packages/alchemy/src/middleware/gas-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import {
deepHexlify,
resolveProperties,
type ConnectedSmartAccountProvider,
type PublicErc4337Client,
type UserOperationRequest,
} from "@alchemy/aa-core";
import type { Address, Transport } from "viem";
import type { ClientWithAlchemyMethods } from "./client.js";

export interface AlchemyGasManagerConfig {
policyId: string;
entryPoint: Address;
provider: PublicErc4337Client;
}

/**
Expand All @@ -36,23 +35,37 @@ export const withAlchemyGasManager = <
preVerificationGas: 0n,
verificationGasLimit: 0n,
}))
// no-op gas manager because the alchemy api will do it
// no-op fee because the alchemy api will do it
// can set this after the fact to do fee overrides.
.withFeeDataGetter(async () => ({
maxFeePerGas: 0n,
maxPriorityFeePerGas: 0n,
}))
.withPaymasterMiddleware({
paymasterDataMiddleware: async (struct) => {
const userOperation: UserOperationRequest = deepHexlify(
await resolveProperties(struct)
);

let feeOverride = undefined;
if (BigInt(userOperation.maxFeePerGas) > 0n) {
feeOverride = {
maxFeePerGas: userOperation.maxFeePerGas,
maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas,
};
}

const result = await (
config.provider as ClientWithAlchemyMethods
provider.rpcClient as ClientWithAlchemyMethods
).request({
method: "alchemy_requestGasAndPaymasterAndData",
params: [
{
policyId: config.policyId,
entryPoint: config.entryPoint,
userOperation: deepHexlify(await resolveProperties(struct)),
userOperation: userOperation,
dummySignature: provider.account.getDummySignature(),
feeOverride: feeOverride,
},
],
});
Expand All @@ -65,18 +78,23 @@ export const withAlchemyGasManager = <

/**
* This is the middleware for calling the alchemy paymaster API which does not estimate gas. It's recommend to use
* {@link withAlchemyGasManager} instead which handles estimating gas + getting paymaster data in one go.
* this middleware if you want more customization over the gas and fee estimation middleware, including setting
* non-default buffer values for the fee/gas estimation.
*
* @param config {@link AlchemyPaymasterConfig}
* @returns middleware overrides for paymaster middlewares
*/
export const alchemyPaymasterAndDataMiddleware = (
export const alchemyPaymasterAndDataMiddleware = <
T extends Transport,
Provider extends ConnectedSmartAccountProvider<T>
>(
provider: Provider,
config: AlchemyGasManagerConfig
): Parameters<
ConnectedSmartAccountProvider["withPaymasterMiddleware"]
>["0"] => ({
dummyPaymasterDataMiddleware: async (_struct) => {
switch (config.provider.chain.id) {
switch (provider.rpcClient.chain.id) {
case 1:
case 10:
case 137:
Expand All @@ -94,7 +112,7 @@ export const alchemyPaymasterAndDataMiddleware = (
},
paymasterDataMiddleware: async (struct) => {
const { paymasterAndData } = await (
config.provider as ClientWithAlchemyMethods
provider.rpcClient as ClientWithAlchemyMethods
).request({
method: "alchemy_requestPaymasterAndData",
params: [
Expand Down
62 changes: 46 additions & 16 deletions packages/alchemy/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { SupportedChains } from "./chains.js";
import {
withAlchemyGasManager,
type AlchemyGasManagerConfig,
alchemyPaymasterAndDataMiddleware,
} from "./middleware/gas-manager.js";
import { withAlchemyGasFeeEstimator } from "./middleware/gas-fees.js";
import type { ClientWithAlchemyMethods } from "./middleware/client.js";
Expand All @@ -34,15 +35,33 @@ export type AlchemyProviderConfig = {
account?: BaseSmartContractAccount;
opts?: SmartAccountProviderOpts;
feeOpts?: {
/** this adds a percent buffer on top of the base fee estimated (default 25%) */
/** this adds a percent buffer on top of the base fee estimated (default 25%)
* NOTE: this is only applied if the default fee estimator is used.
*/
baseFeeBufferPercent?: bigint;
/** this adds a percent buffer on top of the priority fee estimated (default 5%) */
/** this adds a percent buffer on top of the priority fee estimated (default 5%)'
* * NOTE: this is only applied if the default fee estimator is used.
*/
maxPriorityFeeBufferPercent?: bigint;
/** this adds a percent buffer on top of the preVerificationGasEstimated
*
* Default 10% on Arbitrum and Optimism, 0% elsewhere
*
* This is only useful on Arbitrum and Optimism, where the preVerificationGas is
* dependent on the gas fee during the time of estimation. To improve chances of
* the UserOperation being mined, users can increase the preVerificationGas by
* a buffer. This buffer will always be charged, regardless of price at time of mine.
*
* NOTE: this is only applied if the defualt gas estimator is used.
*/
preVerificationGasBufferPercent?: bigint;
};
} & ConnectionConfig;

export class AlchemyProvider extends SmartAccountProvider<HttpTransport> {
alchemyClient: ClientWithAlchemyMethods;
private pvgBuffer: bigint;
private feeOptsSet: boolean;

constructor({
chain,
Expand Down Expand Up @@ -71,29 +90,34 @@ export class AlchemyProvider extends SmartAccountProvider<HttpTransport> {
feeOpts?.baseFeeBufferPercent ?? 25n,
feeOpts?.maxPriorityFeeBufferPercent ?? 5n
);
}

gasEstimator: AccountMiddlewareFn = async (struct) => {
const request = deepHexlify(await resolveProperties(struct));
const estimates = await this.rpcClient.estimateUserOperationGas(
request,
this.entryPointAddress
);

// On Arbitrum and Optimism, we need to increase the preVerificationGas by 10%
// to ensure the transaction is mined
if (
if (feeOpts?.preVerificationGasBufferPercent) {
this.pvgBuffer = feeOpts?.preVerificationGasBufferPercent;
} else if (
new Set<number>([
arbitrum.id,
arbitrumGoerli.id,
optimism.id,
optimismGoerli.id,
]).has(this.chain.id)
) {
estimates.preVerificationGas =
(BigInt(estimates.preVerificationGas) * 110n) / 100n;
this.pvgBuffer = 10n;
} else {
this.pvgBuffer = 0n;
}

this.feeOptsSet = !!feeOpts;
}

gasEstimator: AccountMiddlewareFn = async (struct) => {
const request = deepHexlify(await resolveProperties(struct));
const estimates = await this.rpcClient.estimateUserOperationGas(
request,
this.entryPointAddress
);
estimates.preVerificationGas =
(BigInt(estimates.preVerificationGas) * (100n + this.pvgBuffer)) / 100n;

return {
...struct,
...estimates,
Expand All @@ -107,6 +131,12 @@ export class AlchemyProvider extends SmartAccountProvider<HttpTransport> {
);
}

return withAlchemyGasManager(this, config);
if (this.feeOptsSet) {
return this.withPaymasterMiddleware(
alchemyPaymasterAndDataMiddleware(this, config)
);
} else {
return withAlchemyGasManager(this, config);
}
}
}

0 comments on commit 40be05f

Please sign in to comment.