Skip to content

Commit a68eb17

Browse files
committed
feat(entrykit): add wiresaw transport
1 parent 5f0b4d1 commit a68eb17

File tree

14 files changed

+587
-45
lines changed

14 files changed

+587
-45
lines changed

.changeset/tender-kangaroos-build.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/entrykit": patch
3+
---
4+
5+
Added experimental support for fast user operations on wiresaw-enabled chains.

packages/common/src/exports/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "../deploy/ensureContractsDeployed";
44
export * from "../deploy/ensureDeployer";
55
export * from "../deploy/getContractAddress";
66
export * from "../deploy/getDeployer";
7+
export * from "../transports/wiresaw";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { RpcSchema, UnionToTuple } from "viem";
2+
3+
export type getRpcMethod<rpcSchema extends RpcSchema, method extends rpcSchema[number]["Method"]> = Extract<
4+
rpcSchema[number],
5+
{ Method: method }
6+
>;
7+
8+
export type getRpcSchema<rpcSchema extends RpcSchema, method extends rpcSchema[number]["Method"]> = UnionToTuple<
9+
getRpcMethod<rpcSchema, method>
10+
>;
11+
12+
export type getRpcReturnType<rpcSchema extends RpcSchema, method extends rpcSchema[number]["Method"]> = {
13+
[k in keyof rpcSchema & number as rpcSchema[k]["Method"]]: rpcSchema[k]["ReturnType"];
14+
}[method];
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
export const entryPointSimulationsAbi = [
2+
{
3+
inputs: [
4+
{
5+
components: [
6+
{
7+
internalType: "address",
8+
name: "sender",
9+
type: "address",
10+
},
11+
{
12+
internalType: "uint256",
13+
name: "nonce",
14+
type: "uint256",
15+
},
16+
{
17+
internalType: "bytes",
18+
name: "initCode",
19+
type: "bytes",
20+
},
21+
{
22+
internalType: "bytes",
23+
name: "callData",
24+
type: "bytes",
25+
},
26+
{
27+
internalType: "bytes32",
28+
name: "accountGasLimits",
29+
type: "bytes32",
30+
},
31+
{
32+
internalType: "uint256",
33+
name: "preVerificationGas",
34+
type: "uint256",
35+
},
36+
{
37+
internalType: "bytes32",
38+
name: "gasFees",
39+
type: "bytes32",
40+
},
41+
{
42+
internalType: "bytes",
43+
name: "paymasterAndData",
44+
type: "bytes",
45+
},
46+
{
47+
internalType: "bytes",
48+
name: "signature",
49+
type: "bytes",
50+
},
51+
],
52+
internalType: "struct PackedUserOperation",
53+
name: "op",
54+
type: "tuple",
55+
},
56+
{
57+
internalType: "address",
58+
name: "target",
59+
type: "address",
60+
},
61+
{
62+
internalType: "bytes",
63+
name: "targetCallData",
64+
type: "bytes",
65+
},
66+
],
67+
name: "simulateHandleOp",
68+
outputs: [
69+
{
70+
components: [
71+
{
72+
internalType: "uint256",
73+
name: "preOpGas",
74+
type: "uint256",
75+
},
76+
{
77+
internalType: "uint256",
78+
name: "paid",
79+
type: "uint256",
80+
},
81+
{
82+
internalType: "uint256",
83+
name: "accountValidationData",
84+
type: "uint256",
85+
},
86+
{
87+
internalType: "uint256",
88+
name: "paymasterValidationData",
89+
type: "uint256",
90+
},
91+
{
92+
internalType: "bool",
93+
name: "targetSuccess",
94+
type: "bool",
95+
},
96+
{
97+
internalType: "bytes",
98+
name: "targetResult",
99+
type: "bytes",
100+
},
101+
],
102+
internalType: "struct IEntryPointSimulations.ExecutionResult",
103+
name: "",
104+
type: "tuple",
105+
},
106+
],
107+
stateMutability: "nonpayable",
108+
type: "function",
109+
},
110+
] as const;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import {
2+
BundlerRpcSchema,
3+
decodeFunctionResult,
4+
DecodeFunctionResultReturnType,
5+
EIP1193RequestFn,
6+
encodeFunctionData,
7+
Hex,
8+
zeroAddress,
9+
} from "viem";
10+
import { getRpcMethod } from "../common";
11+
import {
12+
entryPoint07Address,
13+
formatUserOperation,
14+
formatUserOperationRequest,
15+
toPackedUserOperation,
16+
UserOperation,
17+
} from "viem/account-abstraction";
18+
import { bigIntMax } from "../../utils";
19+
import { entryPointSimulationsAbi } from "../entryPointSimulationsAbi";
20+
21+
type rpcMethod = getRpcMethod<BundlerRpcSchema, "eth_estimateUserOperationGas">;
22+
23+
type EstimateUserOperationGasOptions = {
24+
request: EIP1193RequestFn;
25+
params: rpcMethod["Parameters"];
26+
};
27+
28+
export async function estimateUserOperationGas({
29+
request,
30+
params,
31+
}: EstimateUserOperationGasOptions): Promise<rpcMethod["ReturnType"]> {
32+
const userOp = formatUserOperation(params[0]);
33+
const hasPaymaster = userOp.paymaster != null && userOp.paymaster !== zeroAddress;
34+
35+
const [simulationResult, simulationResultWithPaymaster] = await Promise.all([
36+
simulateHandleOp({ userOp, removePaymaster: hasPaymaster, request }),
37+
hasPaymaster ? simulateHandleOp({ userOp, request }) : undefined,
38+
]);
39+
40+
const gasEstimates = getGasEstimates({ userOp, simulationResult, simulationResultWithPaymaster });
41+
42+
return formatUserOperationRequest({
43+
...gasEstimates,
44+
});
45+
}
46+
47+
type SimulateHandleOpOptions = {
48+
request: EIP1193RequestFn;
49+
userOp: UserOperation<"0.7">;
50+
removePaymaster?: boolean;
51+
};
52+
53+
type SimulationResult = DecodeFunctionResultReturnType<typeof entryPointSimulationsAbi>;
54+
55+
async function simulateHandleOp({
56+
userOp,
57+
removePaymaster,
58+
request,
59+
}: SimulateHandleOpOptions): Promise<SimulationResult> {
60+
if (removePaymaster) {
61+
const {
62+
/* eslint-disable */
63+
paymaster,
64+
paymasterData,
65+
paymasterPostOpGasLimit,
66+
paymasterVerificationGasLimit,
67+
/* eslint-enable */
68+
...userOpWithoutPaymaster
69+
} = userOp;
70+
userOp = userOpWithoutPaymaster;
71+
}
72+
73+
// Prepare user operation for simulation
74+
const paymasterGasLimits =
75+
userOp.paymaster && !removePaymaster
76+
? {
77+
paymasterPostOpGasLimit: 2_000_000n,
78+
paymasterVerificationGasLimit: 5_000_000n,
79+
}
80+
: {};
81+
const simulationUserOp = {
82+
...userOp,
83+
preVerificationGas: 0n,
84+
callGasLimit: 10_000_000n,
85+
verificationGasLimit: 10_000_000n,
86+
// https://github.com/pimlicolabs/alto/blob/471998695e5ec75ef88dda3f8a534f47c24bcd1a/src/rpc/methods/eth_estimateUserOperationGas.ts#L117
87+
maxPriorityFeePerGas: userOp.maxFeePerGas,
88+
...paymasterGasLimits,
89+
} satisfies UserOperation<"0.7">;
90+
91+
const packedUserOp = toPackedUserOperation(simulationUserOp);
92+
const simulationData = encodeFunctionData({
93+
abi: entryPointSimulationsAbi,
94+
functionName: "simulateHandleOp",
95+
args: [packedUserOp, zeroAddress, "0x"],
96+
});
97+
98+
const senderBalanceOverride = removePaymaster ? { [userOp.sender]: { balance: "0xFFFFFFFFFFFFFFFFFFFF" } } : {};
99+
const simulationParams = [
100+
{
101+
to: entryPoint07Address,
102+
data: simulationData,
103+
},
104+
"pending",
105+
{
106+
...senderBalanceOverride,
107+
},
108+
];
109+
const encodedSimulationResult: Hex = await request({
110+
method: "wiresaw_callEntryPointSimulations",
111+
params: simulationParams,
112+
});
113+
114+
return decodeFunctionResult({
115+
abi: entryPointSimulationsAbi,
116+
functionName: "simulateHandleOp",
117+
data: encodedSimulationResult,
118+
});
119+
}
120+
121+
type GetGasEstimatesOptions = {
122+
userOp: UserOperation<"0.7">;
123+
simulationResult: SimulationResult;
124+
simulationResultWithPaymaster?: SimulationResult;
125+
};
126+
127+
type GasEstimates = {
128+
verificationGasLimit: bigint;
129+
callGasLimit: bigint;
130+
paymasterVerificationGasLimit: bigint;
131+
paymasterPostOpGasLimit: bigint;
132+
preVerificationGas: bigint;
133+
};
134+
135+
function getGasEstimates({
136+
userOp,
137+
simulationResult,
138+
simulationResultWithPaymaster,
139+
}: GetGasEstimatesOptions): GasEstimates {
140+
const hasPaymaster = simulationResultWithPaymaster != null;
141+
142+
// The verification gas is the total gas available during the validation phase, including the gas used by the paymaster
143+
const verificationGas = hasPaymaster ? simulationResultWithPaymaster.preOpGas : simulationResult.preOpGas;
144+
145+
// The paymaster verification gas is the difference between verification gas with and without paymaster
146+
const paymasterVerificationGas = hasPaymaster
147+
? simulationResultWithPaymaster.preOpGas - simulationResult.preOpGas
148+
: 0n;
149+
150+
// The call gas is only the gas used by the user operation, not the paymaster
151+
const callGas = simulationResult.paid / userOp.maxFeePerGas - simulationResult.preOpGas;
152+
153+
// The paymaster post-op gas is the difference between non-verification gas with and without paymaster
154+
const paymasterPostOpGas = hasPaymaster
155+
? simulationResultWithPaymaster.paid / userOp.maxFeePerGas - simulationResultWithPaymaster.preOpGas - callGas
156+
: 0n;
157+
158+
// Apply a 2x buffer to the calculated limits
159+
const verificationGasLimit = verificationGas * 2n;
160+
const callGasLimit = bigIntMax(callGas * 2n, 9000n);
161+
const paymasterVerificationGasLimit = paymasterVerificationGas * 2n;
162+
const paymasterPostOpGasLimit = paymasterPostOpGas * 2n;
163+
164+
return {
165+
verificationGasLimit,
166+
callGasLimit,
167+
paymasterVerificationGasLimit,
168+
paymasterPostOpGasLimit,
169+
preVerificationGas: 20_000n,
170+
};
171+
}

packages/entrykit/src/quarry/transports/methods/getUserOperationReceipt.ts renamed to packages/common/src/transports/methods/getUserOperationReceipt.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import {
1212
} from "viem";
1313
import { entryPoint07Abi } from "viem/account-abstraction";
1414

15-
// TODO: move to common package?
16-
1715
const userOperationRevertReasonAbi = [
1816
entryPoint07Abi.find(
1917
(item): item is ExtractAbiItem<typeof entryPoint07Abi, "UserOperationRevertReason"> =>

0 commit comments

Comments
 (0)