Skip to content

Commit 52a3de1

Browse files
authored
Simple smart account + 7702 (#413)
* wip toSimpleSmartAccount * rename * wip * wip * Use local alto prool instance * chore: format * Added changeset * wip prepareUserOperationForErc20Paymaster tests * wip --------- Co-authored-by: pavlovdog <5871170+pavlovdog@users.noreply.github.com>
1 parent 56fdf93 commit 52a3de1

File tree

12 files changed

+647
-341
lines changed

12 files changed

+647
-341
lines changed

.changeset/red-zebras-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"permissionless": patch
3+
---
4+
5+
Added to7702SimpleSmartAccount

bun.lockb

-48 Bytes
Binary file not shown.

packages/permissionless-test/mock-aa-infra/alto/constants/accounts/simple.ts

Lines changed: 3 additions & 0 deletions
Large diffs are not rendered by default.

packages/permissionless-test/mock-aa-infra/alto/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
SIMPLE_ACCOUNT_FACTORY_V06_CREATECALL,
6969
SIMPLE_ACCOUNT_FACTORY_V07_CREATECALL,
7070
SIMPLE_ACCOUNT_FACTORY_V08_CREATECALL,
71+
SIMPLE_ACCOUNT_IMPLEMENTATION_V08_CREATECALL,
7172
THIRDWEB_FACTORY_V06_CREATECALL,
7273
THIRDWEB_FACTORY_V07_CREATECALL,
7374
TRUST_ACCOUNT_FACET_CREATE_CALL,
@@ -137,6 +138,12 @@ export const setupContracts = async (rpc: string) => {
137138
gas: 15_000_000n,
138139
nonce: nonce++
139140
}),
141+
walletClient.sendTransaction({
142+
to: DETERMINISTIC_DEPLOYER,
143+
data: SIMPLE_ACCOUNT_IMPLEMENTATION_V08_CREATECALL,
144+
gas: 15_000_000n,
145+
nonce: nonce++
146+
}),
140147
walletClient.sendTransaction({
141148
to: DETERMINISTIC_DEPLOYER,
142149
data: ENTRY_POINT_V07_CREATECALL,
@@ -677,6 +684,7 @@ export const setupContracts = async (rpc: string) => {
677684
"0x4e59b44847b379578588920ca78fbf26c0b4956c", // Determinstic deployer
678685
"0x4337084d9e255ff0702461cf8895ce9e3b5ff108", // EntryPoint 0.8
679686
"0x13E9ed32155810FDbd067D4522C492D6f68E5944", // Simple Account Factory 0.8
687+
"0xe6Cae83BdE06E4c305530e199D7217f42808555B", // Simple Account V0.8 implementation
680688
"0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7", // Safe Singleton Factory
681689
"0x988C135a1049Ce61730724afD342fb7C56CD2776", // Biconomy Singleton Factory
682690
"0x0000000071727De22E5E9d8BAf0edAc6f37da032", // EntryPoint 0.7
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import { resolve } from "node:path"
2+
import { defineInstance } from "prool"
3+
import { toArgs } from "prool"
4+
import { execa } from "prool/processes"
5+
6+
export type AltoParameters = {
7+
/**
8+
* API version (used for internal Pimlico versioning compatibility).
9+
*/
10+
apiVersion?: readonly string[] | undefined
11+
/**
12+
* Override the sender native token balance during estimation
13+
*/
14+
balanceOverride?: boolean | undefined
15+
/**
16+
* Binary path of the `alto` executable.
17+
*/
18+
binary?: string | undefined
19+
/**
20+
* Address of the `BundleBulker` contract.
21+
*/
22+
bundleBulkerAddress?: `0x${string}` | undefined
23+
/**
24+
* Set if the bundler bundle user operations automatically or only when calling `debug_bundler_sendBundleNow`.
25+
* @default "auto"
26+
*/
27+
bundleMode?: "auto" | "manual" | undefined
28+
/**
29+
* Indicates weather the chain is a OP stack chain, arbitrum chain, or default EVM chain.
30+
*/
31+
chainType?: "default" | "op-stack" | "arbitrum" | undefined
32+
/**
33+
* Path to JSON config file.
34+
*/
35+
config?: string | undefined
36+
/**
37+
* Skip user operation validation, use with caution.
38+
*/
39+
dangerousSkipUserOperationValidation?: boolean | undefined
40+
/**
41+
* Default API version.
42+
*/
43+
defaultApiVersion?: string | undefined
44+
/**
45+
* Enable debug endpoints.
46+
* @default false
47+
*/
48+
enableDebugEndpoints?: boolean | undefined
49+
/**
50+
* Include user ops with the same sender in the single bundle.
51+
* @default true
52+
*/
53+
enforceUniqueSendersPerBundle?: boolean | undefined
54+
/**
55+
* EntryPoint contract addresses.
56+
*/
57+
entrypoints: readonly `0x${string}`[]
58+
/**
59+
* Address of the EntryPoint simulations contract.
60+
*/
61+
entrypointSimulationContract?: `0x${string}` | undefined
62+
/**
63+
* Private keys of the executor accounts.
64+
*/
65+
executorPrivateKeys?: readonly `0x${string}`[]
66+
/**
67+
* Interval to refill the signer balance (seconds).
68+
* @default 1200
69+
*/
70+
executorRefillInterval?: number | undefined
71+
/**
72+
* Should the node make expiration checks.
73+
* @default true
74+
*/
75+
expirationCheck?: boolean | undefined
76+
/**
77+
* Amount to multiply the gas prices fetched from the node.
78+
* @default "100"
79+
*/
80+
gasPriceBump?: string | undefined
81+
/**
82+
* Maximum that the gas prices fetched using pimlico_getUserOperationGasPrice will be accepted for (seconds).
83+
* @default 10
84+
*/
85+
gasPriceExpiry?: number | undefined
86+
/**
87+
* The minimum percentage of incoming user operation gas prices compared to the gas price used by the bundler to submit bundles.
88+
* @default 101
89+
*/
90+
gasPriceFloorPercent?: number | undefined
91+
/**
92+
* Amount to multiply the gas prices fetched using `pimlico_getUserOperationGasPrice`.
93+
* @default "105,110,115"
94+
*/
95+
gasPriceMultipliers?: readonly string[] | undefined
96+
/**
97+
* Use a fixed value for gas limits during bundle transaction gas limit estimations
98+
*/
99+
fixedGasLimitForEstimation?: string | undefined
100+
/**
101+
* Flush stuck transactions with old nonces during bundler startup.
102+
*/
103+
flushStuckTransactionsDuringStartup?: boolean | undefined
104+
/**
105+
* Log in JSON format.
106+
*/
107+
json?: boolean | undefined
108+
/**
109+
* Send legacy transactions instead of an EIP-1559 transactions.
110+
* @default false
111+
*/
112+
legacyTransactions?: boolean | undefined
113+
/**
114+
* Calculate the bundle transaction gas limits locally instead of using the RPC gas limit estimation.
115+
*/
116+
localGasLimitCalculation?: boolean | undefined
117+
/**
118+
* Default log level.
119+
*/
120+
logLevel?:
121+
| "trace"
122+
| "debug"
123+
| "info"
124+
| "warn"
125+
| "error"
126+
| "fatal"
127+
| undefined
128+
/**
129+
* Max block range for `eth_getLogs` calls.
130+
*/
131+
maxBlockRange?: number | undefined
132+
/**
133+
* Maximum number of operations allowed in the mempool before a bundle is submitted.
134+
* @default 10
135+
*/
136+
maxBundleSize?: number | undefined
137+
/**
138+
* Maximum time to wait for a bundle to be submitted (ms).
139+
* @default 1000
140+
*/
141+
maxBundleWait?: number | undefined
142+
/**
143+
* Maximum amount of gas per bundle.
144+
* @default "5000000"
145+
*/
146+
maxGasPerBundle?: string | undefined
147+
/**
148+
* Maximum number of executor accounts to use from the list of executor private keys.
149+
*/
150+
maxExecutors?: number | undefined
151+
/**
152+
* Maximum amount of parallel user ops to keep in the meempool (same sender, different nonce keys).
153+
* @default 10
154+
*/
155+
mempoolMaxParallelOps?: number | undefined
156+
/**
157+
* Maximum amount of sequential user ops to keep in the mempool (same sender and nonce key, different nonce values).
158+
* @default 0
159+
*/
160+
mempoolMaxQueuedOps?: number | undefined
161+
/**
162+
* Minimum stake required for a relay (in 10e18).
163+
* @default 1
164+
*/
165+
minEntityStake?: number | undefined
166+
/**
167+
* Minimum unstake delay (seconds).
168+
* @default 1
169+
*/
170+
minEntityUnstakeDelay?: number | undefined
171+
/**
172+
* Minimum balance required for each executor account (below which the utility account will refill).
173+
*/
174+
minExecutorBalance?: string | undefined
175+
/**
176+
* Name of the network (used for metrics).
177+
* @default "localhost"
178+
*/
179+
networkName?: string | undefined
180+
/**
181+
* Amount to multiply the paymaster gas limits fetched from simulations.
182+
*/
183+
paymasterGasLimitMultiplier?: string | undefined
184+
/**
185+
* Address of the `PerOpInflator` contract.
186+
*/
187+
perOpInflatorAddress?: `0x${string}` | undefined
188+
/**
189+
* Polling interval for querying for new blocks (ms).
190+
* @default 1000
191+
*/
192+
pollingInterval?: number | undefined
193+
/**
194+
* Port to listen on.
195+
* @default 3000
196+
*/
197+
port?: number | undefined
198+
/**
199+
* RPC url to connect to.
200+
*/
201+
rpcUrl: string
202+
/**
203+
* Enable safe mode (enforcing all ERC-4337 rules).
204+
* @default true
205+
*/
206+
safeMode?: boolean | undefined
207+
/**
208+
* RPC url to send transactions to (e.g. flashbots relay).
209+
*/
210+
sendTransactionRpcUrl?: string | undefined
211+
/**
212+
* Timeout for incoming requests (in ms).
213+
*/
214+
timeout?: number | undefined
215+
/**
216+
* Private key of the utility account.
217+
*/
218+
utilityPrivateKey?: string | undefined
219+
/**
220+
* Maximum payload size for websocket messages in bytes (default to 1MB).
221+
*/
222+
websocketMaxPayloadSize?: number | undefined
223+
/**
224+
* Enable websocket server.
225+
*/
226+
websocket?: boolean | undefined
227+
}
228+
229+
/**
230+
* Defines an Alto instance.
231+
*
232+
* @example
233+
* ```ts
234+
* const instance = alto({
235+
* entrypoints: ['0x0000000071727De22E5E9d8BAf0edAc6f37da032'],
236+
* rpcUrl: `http://localhost:8545`,
237+
* executorPrivateKeys: ['0x...'],
238+
* })
239+
* await instance.start()
240+
* // ...
241+
* await instance.stop()
242+
* ```
243+
*/
244+
export const alto = defineInstance((parameters?: AltoParameters) => {
245+
const { ...args } = (parameters || {}) as AltoParameters
246+
247+
const name = "alto"
248+
const process = execa({ name })
249+
250+
return {
251+
_internal: {
252+
args,
253+
get process() {
254+
return process._internal.process
255+
}
256+
},
257+
host: "localhost",
258+
name,
259+
port: args.port ?? 3000,
260+
async start({ port = args.port ?? 3000 }, options) {
261+
const binary = (() => {
262+
if (args.binary) return [args.binary]
263+
const libPath =
264+
"resolve" in import.meta
265+
? import.meta.resolve("@pimlico/alto").split("file:")[1]
266+
: require.resolve("@pimlico/alto")
267+
return ["node", resolve(libPath, "../cli/alto.js")]
268+
})()
269+
270+
const command = `${binary} ${toArgs({ port, ...args })}`
271+
272+
await process.start(
273+
($) => $`${binary} ${toArgs({ port, ...args })}`,
274+
{
275+
...options,
276+
// Resolve when the process is listening via a "Server listening at" message.
277+
resolver({ process, reject, resolve }) {
278+
process.stdout.on("data", (data) => {
279+
const message = data.toString()
280+
if (message.includes("Server listening at"))
281+
resolve()
282+
})
283+
// process.stderr.on('data', reject)
284+
}
285+
}
286+
)
287+
},
288+
async stop() {
289+
await process.stop()
290+
}
291+
}
292+
})

packages/permissionless-test/src/testWithRpc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { paymaster } from "@pimlico/mock-paymaster"
22
import getPort from "get-port"
3-
import { alto, anvil } from "prool/instances"
3+
import { anvil } from "prool/instances"
44
import {
55
entryPoint06Address,
66
entryPoint07Address,
@@ -9,6 +9,7 @@ import {
99
import { foundry } from "viem/chains"
1010
import { test } from "vitest"
1111
import { setupContracts } from "../mock-aa-infra/alto"
12+
import { alto } from "../mock-aa-infra/alto/instance"
1213

1314
export const getInstances = async ({
1415
anvilPort,

0 commit comments

Comments
 (0)