.env, an in-process queue, and console output. Read Before you adapt this before pointing it at real funds.Why one account is one queue
An EVM account’s transaction nonces are strictly sequential. If noncen has not executed, nonce n + 1 cannot execute first. Two failures that look similar behave differently:
- Strict nonce admission. Under Giga, the Autobahn producer mempool admits EVM transactions in per-sender nonce order and rejects a gap with a
bad nonceerror instead of holding it for later. See Giga mode behavior. What you observe through an RPC endpoint can differ, because the node in front of the producer may hold a gapped transaction and release it later, or accept it and then drop it. The repository’snpm run baselinecommand probes the RPC path you configure; in one session it returned two different verdicts for two Atlantic-2 endpoints. - No dependable pending view. Sei does not expose Ethereum-style pending state, and Finality and block tags tells you not to rely on a pending nonce differing from the confirmed nonce.
txpool_contentis also truncated and collapses the pending/queued distinction. Even where a node answers a pending-nonce query, the value cannot be used to rebuild an in-flight queue.
How it works
The design combines four mechanisms. Each solves one part of the problem; none is sufficient alone.ERC-4337 nonce lanes
EntryPoint v0.8 stores a UserOperation nonce as a 192-bit key plus a 64-bit sequence:sequence counter for each key. The specification calls this a two-dimensional nonce; the repository calls a key a lane.
Four rules follow from that layout:
- Operations on different lanes have no ordering relationship.
- Operations on the same lane stay strictly sequential, so the implementation allows at most one in-flight operation per lane.
- An operation that executes and reverts still consumes its lane sequence.
- An operation that never reaches a successful
handleOpstransaction consumes nothing.
LaneAccount rejects lane 0. Most SDKs pick key 0 when you do not pass one, and work that lands entirely on key 0 is a single queue again. Rejecting it converts a silent fallback into an explicit validation failure. ADMIN_LANE (the maximum uint192) is reserved for integrations that need one explicitly ordered lane for administrative calls.
EIP-7702 keeps the funded address
An EIP-7702 authorization writes a delegation designator into the EOA’s code slot:LaneAccount’s code in the EOA’s context, so LaneAccount.execute reaches the target with the funded EOA as msg.sender.
LaneAccount inherits the reference Simple7702Account from the eth-infinitism account-abstraction repository and adds a single policy check:
nonce + 1 and applying it increments the account a second time. A self-sponsored delegation advances the account’s nonce by two; npm run delegate takes this path. A delegation sponsored by a different sender advances the funded account by one, the increment the authorization itself performs. In both cases, once the designator is in place, the submission path signs UserOperations only and the funded account’s EVM nonce stops moving.
Gas-only relayers carry what is left of the queue
UserOperations are not transactions. Something still has to wrap them inEntryPoint.handleOps transactions and pay for them. The repository uses a pool of gas-only relayers fed by an in-process bundling queue.
The sequential constraint moves rather than disappears. Each relayer has one sequential EVM nonce and keeps one outer transaction in flight at a time. The custody boundary is what changes: relayers hold native SEI for gas and nothing else. A compromised relayer key can lose its own gas balance or rebroadcast operations the funded account already signed. It cannot create a new operation, because every UserOperation carries an EIP-712 signature from the funded account over the EntryPoint’s PackedUserOperation digest.
The bundling queue matters for a different reason. It is an in-process queue, not a mempool: nothing is gossiped, nothing arrives from another party, and it is gone when the process exits. It therefore never enters the canonical ERC-4337 alt-mempool, and the ERC-7562 validation rules that govern that mempool, including SAME_SENDER_MEMPOOL_COUNT = 4 for an unstaked sender, do not apply to it. A cap of four pending operations per sender is sized for wallets, not for a submission pipeline. Those rules exist so competing bundlers can safely pack operations from unrelated senders into one bundle. Here every operation comes from one account you control, so that threat does not arise. The EntryPoint still enforces everything that protects funds: the signature, per-lane nonce uniqueness, and prefund solvency.
One bundle, start to finish
Each relayer runs one asynchronous worker. The worker takes a bundle of up toMAX_OPS_PER_BUNDLE operations (never two from the same lane), and then works through a fixed sequence.
Two rules provide most of the safety:
- Write-ahead ordering. The signed outer transaction is written to the journal before it is broadcast. Each journal snapshot is fsynced before it is renamed into place, and the directory is fsynced after it, so a written record survives a kernel panic or host loss, not only a process crash. On restart, the exact raw bytes are rebroadcast first, so a crash between signing and sending cannot lose or duplicate work. The journal is still one file on one disk, not a replicated database.
- Same-nonce replacement. If no receipt arrives within
BUNDLE_RECEIPT_TIMEOUT_MS, the worker checks earlier attempts for a receipt, bumps fees byREPLACEMENT_FEE_BUMP_PERCENT, and signs a replacement at the same relayer nonce. It never sends noncen + 1while a transaction atnmight still land, which is the gap this design exists to avoid.
RECEIPT_POLLING_INTERVAL_MS, 250 ms by default. Sei produces blocks in well under a second. With viem’s 4-second default, most of the timeout passes idle between polls, and a bundle that has already landed can be reported as timed out, which triggers an unnecessary fee-bumped replacement.
Two further guards cover the simulation and the journal. The eth_estimateGas call passes the relayer’s address string as account, not the viem Account object. Given a local account object, viem prepares a full transaction request before estimating, which adds a chain ID read, a fee lookup, and an eth_getTransactionCount(address, "pending") call; passing the address skips all three, including the pending-nonce read this hot path is built to avoid. A file lock keeps a second process off the same account. The lock identifies its holder by inode rather than by the existence of a file, and the process re-checks that it still holds the lock immediately before it signs and before every journal write, so a process that lost a lock race stops instead of signing.
Before any new work is created, a restarted process reconciles every incomplete journal entry against the EntryPoint. If the chain sequence is ahead of the journal, the operation was consumed. If they match, the lane is reserved and the operation is recovered or requeued. If the chain is behind the journal, the state is inconsistent and the process stops rather than guessing.
What fails alone and what fails together
The EntryPoint treats an account execution failure as a per-operation result: it emits a failedUserOperationEvent, charges gas, advances that lane, and continues with the next operation. A validation failure (bad signature, stale sequence, insufficient prefund) is different: it reverts the whole handleOps transaction and nothing in it is consumed.
eth_estimateGas before broadcast, so validation failures are normally caught before any gas is spent. MAX_OPS_PER_BUNDLE sets the size of the shared validation domain; keep it small when isolation matters more than amortized cost.
Why this is hard to replicate
Faster hardware and better RPC routing help every submission strategy. The difference here is structural: one funded address holds many independent operations in flight while keeping the custody surface of a single wallet. The relevant comparison is a fleet of hot wallets, which is what most teams run. A fleet can match the width. It cannot do so from one balance, one approval set, and one key. The baseline is not one transaction at a time. A single address can sign and broadcast noncesn, n+1, n+2, and onward and have all of them unresolved, and the producer mempool admits them as long as they arrive in order. The constraint is ordering: the outstanding transactions form one queue, and one that never lands strands every later nonce behind it.
LANE_POOL_SIZE caps how many operations can be signed and unresolved at once; RELAYER_COUNT caps how many outer transactions are broadcast at once. Lanes provide a large pool of independent intents rather than a large number of simultaneous transactions. The approximate per-block submission width is RELAYER_COUNT × MAX_OPS_PER_BUNDLE.- Capital, approvals, and protocol state stay on one address. Width comes from lanes, not from splitting funds. A new lane costs nothing to open and is valid at sequence
0immediately. - The only key that can create a valid operation is the funded account’s. Relayer keys can be rotated, replaced, or lost at a cost bounded by their gas balances.
- After the one-time delegation, the funded account’s EVM nonce does not move while submitting. Nothing an RPC drops or a producer rejects can strand the account.
- The in-process bundling queue removes the ERC-7562
SAME_SENDER_MEMPOOL_COUNTlimit and the dependency on a third-party bundler’s inclusion policy, while every EntryPoint check that protects funds still runs. - Signed operations and signed outer transactions are journaled and fsynced before broadcast, replacement reuses the relayer nonce, and restart reconciliation is deterministic: it refuses to create new work when the recorded state is ambiguous.
- The signing path performs no nonce reads, so an unreliable pending view costs nothing. Fast blocks and instant finality keep each relayer’s receipt wait short, which is what makes
RELAYER_COUNT × MAX_OPS_PER_BUNDLEa usable per-block width.
Tutorial: run the reference implementation
The walkthrough below deploys the demo contracts, delegates a throwaway account, funds a relayer pool, and submits 24 operations across 32 lanes. One order is deliberately given an unfillable limit price so you can watch a revert land without disturbing its neighbors.Prerequisites
- Git with submodule support
- Foundry with
forge,anvil, andcast - Node.js 22 or newer, and npm
- For the Atlantic-2 path: a fresh throwaway key funded from the Sei faucet
Clone and verify
account-abstraction, OpenZeppelin, and forge-std dependencies are checked out:git submodule update --init --recursive.Install the Node dependencies and run every local check:0 is rejected, and that a 50-lane bundle fits in one outer transaction.Choose a target network
1328, so an authorization signed against it is replayable on Atlantic-2 itself whenever the account’s nonce lines up. Regenerate every key and mnemonic before you point a fork’s .env at a public network.- Local Anvil fork (recommended first)
- Atlantic-2 testnet
TRADER_PRIVATE_KEY to account 0’s private key from the Anvil startup output, and RELAYER_MNEMONIC to the mnemonic printed by that same Anvil process. Starting relayers at index 1 keeps the funded account and relayer identities distinct; the application rejects overlapping identities.Those are Anvil’s published credentials, so this .env must only ever point at the fork.Deploy the demo contracts from the repository root using Anvil’s unlocked account:.env:ALLOW_MAINNET=1 is set explicitly, and they refuse any remote-chain write when TRADER_PRIVATE_KEY is Anvil account 0 or RELAYER_MNEMONIC is the Anvil mnemonic. Those guards catch only an accidental SEI_CHAIN_ID=1329 run and the two most common leaked credentials. They do not cover the separate Forge deployment command, and they do not make the demo production-ready.Check the preflight
status is read-only. Run it before anything that writes:0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108, the account’s current delegation, balances, its EntryPoint deposit, each relayer’s confirmed nonce and gas balance, the lane sequences, and the venue state.Delegate the account
LANE_ACCOUNT_IMPL, signed and paid for by the account itself, so the account’s EVM nonce advances by two: once for the transaction, once for the authorization. The command reads the account’s confirmed nonce once and pins both the transaction nonce and the authorization nonce (nonce + 1) to that read, instead of letting the client fill them from a pending-nonce lookup. That matters because a wrong authorization nonce is not rejected: the EVM skips the authorization, the transaction still succeeds, and nothing is installed. The command therefore re-reads the designator after the receipt and fails if the delegation did not take effect.The command is idempotent: if the account already delegates to the configured implementation, it does nothing. If the account delegates to something else, it tells you before replacing it.Fund the relayers and the EntryPoint deposit
fund uses ordinary transactions to top each relayer up to RELAYER_FUNDING SEI and to bring the account’s EntryPoint.depositTo balance up to ENTRYPOINT_DEPOSIT SEI. The deposit is what the EntryPoint draws prefund from when it validates each operation. On a public network you can instead send SEI to relayer 0 and run npm run dispense, which waits for the balance and splits it across the pool. Use one bootstrapping path or the other, not both.Submit the run
submit process performs the complete run and exits. It runs the preflight, estimates the delegated call’s gas, reads each lane’s sequence once, signs 24 operations concurrently with no nonce RPCs, journals them, bundles them, drains the bundles through the relayer pool, and prints a report. By default, order 2 receives a limit price below the mark, so its operation reverts during execution while the neighboring lanes continue.Read the report
The output below is illustrative; your addresses, blocks, and timings will differ.trader noncebefore andtrader EVM nonceafter must be identical. The tool calls the funded account the trader. Its key never entered a queue.call gasis the measured delegated-call estimate plus 25 percent. ACALL_GAS_LIMIT floorappears in that line only when you set one; leave it unset unless you have a reason, because the EntryPoint reserves the declared limit before running each operation.execcombines the bundle receipt with the venue’sisFilledread.revertedmeans the outer transaction landed, so the operation consumed its lane sequence, but the call failed inside the venue.not minedwould mean the outer transaction never landed and nothing was consumed.land#is the venue’s global landing counter. It shows the order in which operations actually executed, which has nothing to do with lane number. Lane acquisition is last-in, first-out, so a fresh 32-lane pool starts at lane32; lane numbers carry no priority.hash checkconfirms that the locally computed EIP-712 digest matchesEntryPoint.getUserOpHashfor the first operation, so client-side hashing matches consensus.
PENDING or FAILED, the command exits non-zero and leaves the journal intact. Run npm run submit again once the RPC can answer receipt and nonce queries; the process recovers or replaces at the same relayer nonce before it creates new work. Do not delete the journal and do not send the relayer’s next nonce by hand.
Optional: real swaps on Atlantic-2
The repository includes a real-target path that routes tiny native SEI and native USDC swaps through the documented DragonSwap V1 deployment on Atlantic-2. It is hard-blocked on every other chain. Get testnet USDC from the Circle faucet, then:swap:setup uses ordinary transactions to approve a limited amount of USDC and to create and seed the WSEI/USDC pair if the factory has no live pair. swap:submit alternates SEI to USDC and USDC to SEI swaps through independent lanes and reports outcomes from EntryPoint events rather than one RPC read per swap. The same knobs apply:
REVERT_ORDER_INDEX=-1 when you want to measure maximum throughput.
Optional: see the baseline you are escaping
baseline measures the constraint directly. It sends nonce n + 1 while deliberately skipping n, waits to see whether that transaction can be included above a gap, fills the gap with nonce n, then checks whether the skipped-ahead transaction lands once the gap is closed. The verdict is one of four outcomes:
latest and pending nonce tags before, during, and after, and prints any rejection message verbatim, because the exact message is the finding. If the two tags already disagree before the probe starts, it warns you: either the account has work in flight or the node reports a mempool-derived pending nonce, and in both cases the gap it is about to create may not be a gap at all, so the verdict cannot be attributed to nonce ordering. Compare the result with a submit run, where 24 operations from one account are mutually independent and one failure strands nothing.
The verdict depends on the path. In one session, two Atlantic-2 RPC endpoints answered differently: a dedicated provider queued the gapped transaction and released it once the gap filled, while the public endpoint returned a hash and then dropped it, so it never landed even after the gap was filled. Neither rejected it at admission. Probe the path you will actually use.
Tuning
Three knobs shape a run. They interact, so change one at a time and measure.Lane pool size: the ceiling on in-flight operations
Lane pool size: the ceiling on in-flight operations
LANE_POOL_SIZE is the hard ceiling on unresolved UserOperations in a process. Larger pools permit more concurrently unresolved intents, add startup getNonce reads (batched with bounded concurrency so a public RPC does not rate-limit you), and increase the recovery state you must understand after a failure. ORDERS must not exceed LANE_POOL_SIZE; the application rejects that configuration instead of silently submitting fewer operations.Bundle width: gas efficiency against isolation
Bundle width: gas efficiency against isolation
MAX_OPS_PER_BUNDLE trades amortized outer-transaction overhead for the size of the shared validation failure domain. A width of 1 gives maximum isolation and the highest overhead. Execution reverts stay per-operation at any width. The relayer caps signed transaction gas below the block gas limit it read at startup and rejects a bundle whose estimate cannot fit.A bundle does not need the sum of its operations’ declared call gas. The EntryPoint checks before each operation that enough gas remains to honor that operation’s callGasLimit, and whatever an operation leaves unspent passes to the next one, so the outer transaction needs the gas the bundle actually consumes plus about one operation’s declared limit in reserve. Over-declaring still has a cost: v0.8 charges 10 percent of unused call gas beyond a 40,000-gas threshold, and each operation reserves prefund from the deposit against its declared limits rather than its measured cost, so an inflated limit ties up deposit that is only refunded afterwards.That is why CALL_GAS_LIMIT is unset by default. Both submit paths measure the real call gas for the chain and call shape and add 25 percent; the variable is only a floor, and it has an effect only when you set it above that measurement. Because the EntryPoint reserves each operation’s declared call gas before running it, a floor above the measurement costs block gas, and therefore operations per block, without changing the gas actually used.As a reference point, on Atlantic-2 with CALL_GAS_LIMIT=500000, the default in an earlier revision of the repository, the real-swap path sustained 77 operations per bundle; 78 no longer fit the 12,500,000 block gas limit in effect during that run and failed safely during simulation. Width 76 produced the best observed submission rate for that call shape, 47.7 landed swaps per second. These are measurements for one call shape, one configuration, and one network state, not protocol limits.The mock venue is a heavier call. In the benchmark session, with CALL_GAS_LIMIT unset, each place operation used about 331,000 gas of outer transaction gas, so 36 operations was the widest bundle that fit and 37 failed safely in simulation. Widths 8, 16, and 36 packed 4, 2, and 1 bundles into a 12,500,000-gas block; width 9, which should fit 4, landed 3. Wide bundles also queue behind each other, because only one 11,700,000-gas bundle fits a block. With more than about 8 relayers at width 32 or 36, receipt waits exceed the default BUNDLE_RECEIPT_TIMEOUT_MS; raise the timeout for those shapes instead of paying for fee-bumped replacements of bundles that land anyway.Relayer count: concurrent outer transaction streams
Relayer count: concurrent outer transaction streams
RELAYER_COUNT × MAX_OPS_PER_BUNDLE per block. That is a planning heuristic, not a throughput guarantee: RPC latency, block limits, state contention, gas, and producer policy still apply. Public RPC endpoints have rate limits; use a dedicated provider or your own node for anything beyond a demo.One relayer’s bundle cycle is five sequential RPC round trips (gas estimate, fee estimate, block number, broadcast, receipt poll) plus inclusion, about 1.5 seconds on a 120 ms endpoint. Below roughly 120 operations in flight the pool is the bottleneck and throughput scales with RELAYER_COUNT; above that, block gas is. In the benchmark session, 4 relayers landed 2.7, 8.9, 15.3, 24.3, 45.4, and 50.5 operations per second at widths 1, 4, 8, 16, 32, and 36, and width 4 landed 2.2, 8.9, 15.4, 27.9, and 47.2 per second with 1, 4, 8, 16, and 32 relayers.Configuration reference
The application always loads.env from the repository root. The variables you are most likely to change:
Benchmarks
app/bench/ measures the lane path against the baseline it replaces: one EOA sending ordinary transactions with sequential nonces. Every mode calls the same MockPerpVenue.place on the configured VENUE, so the numbers differ only in how the calls were submitted. The commands read the root .env, and the shell environment overrides it:
bench:baseline modes are the ways one address can drive a sequential queue. serial sends one transaction and waits for its receipt before the next. pipelined signs everything up front and broadcasts in nonce order without waiting. pipelined-gap does the same but never broadcasts the transaction at BENCH_GAP_INDEX, then repairs the gap. batch sends everything in one JSON-RPC batch request. fleet derives BENCH_FLEET_SIZE wallets from RELAYER_MNEMONIC, funds them from the account, and runs pipelined on each at once.
bench:lanes shares the account-wide run lock with submit and swap:submit but keeps its own journal under app/.state/bench/, so a benchmark never replays the tutorial’s pending operations. If a submit run was interrupted, recover it with npm run submit first. The remaining BENCH_* and REPORT_* knobs are documented in the repository README.
Measured against the alternatives
Every row below comes from one measurement session on Atlantic-2. Conditions during the session: block gas limit 12,500,000; base fee 50 gwei plus a 1 gwei tip; about 2.0 blocks per second under load; 120 ms warm request latency to the configured endpoint. Each directplace transaction used 328,425 gas and each lane operation about 331,000 gas of outer transaction gas, so the block admits 36 to 38 operations however they are submitted: roughly 73 landed operations per second for this call shape. Chain-side rates divide landed operations by the block-timestamp span, which Sei stamps in whole seconds. Client-side rates divide by wall time from first broadcast to last receipt.
- Block gas, not the nonce model, set the ceiling. Lanes came closest to it, with 28 of 31 blocks at least 90 percent full in the 16 × 36 run, from one address whose EVM nonce never moved. A single ordered queue matched the fleet only when every transaction left in one JSON-RPC batch; one request at a time, it is bounded by the round trip, about 7 per second.
- Small relayer pools are client-bound. Throughput scales with
RELAYER_COUNTuntil roughly 120 operations are in flight, then block gas takes over. The relayer and width series are in Tuning. - A gap costs the whole queue. One lost transaction in the sequential queue (
pipelined-gap, nonce 50 of 100) left the 49 transactions behind it accepted but unmined until the client resent it 15 seconds later. In the lane runs, the deliberately reverting order consumed only its own lane while the rest of its bundle landed. - RPC paths differ.
npm run baselinegave two different verdicts for two Atlantic-2 endpoints in the same session.
app/.state/bench/ include a journal for bench:lanes, which you should treat like pending-ops.json.Adapting it to your contract
The demo’sMockPerpVenue is a stand-in that reverts on slippage so a failure is observable. Swapping it for a real target means encoding a different call. LaneAccount.execute(target, value, data) forwards any call, and the target sees the funded EOA as msg.sender:
app/src/swap-submit.ts is a complete example of this against a live router, including forwarding native SEI as value.
Check these before you trust a new target:
msg.senderandtx.origin. At the target,msg.senderis the funded EOA andtx.originis the gas-paying relayer. Contracts that requiretx.origin == msg.senderare incompatible. Audit each router, approval path, callback, reentrancy assumption, and authorization rule.- Gas on Sei. Storage writes cost materially more than on Ethereum. The application estimates the delegated call live and declares that estimate plus 25 percent, raised to
CALL_GAS_LIMITonly when you set that floor higher. Do not copy Ethereum-sized static limits in either direction: too low runs out of gas, and too high reserves block gas your operations never use. See Gas and fees. - Storage contention. Lanes remove submission ordering, not execution conflicts. Analyze which storage slots your calls touch; see Optimizing for parallelization.
- Lane policy. One lane per in-flight intent is the simplest correct policy. If some calls must stay ordered relative to each other, put them on one lane (or on
ADMIN_LANE) rather than falling back to lane0.
Before you adapt this
The repository is explicit about what it leaves out. Before this design touches real funds, add at least:- audited account and integration contracts;
- hardware-backed or remote signing;
- a real risk engine with an idempotent intent model;
- durable, replicated queue and reconciliation storage;
- metrics, tracing, alerting, and structured logs;
- controlled deployment and delegation procedures;
- RPC redundancy and chain-specific fee policy;
- graceful shutdown and operator runbooks; and
- load, fault-injection, and live-chain recovery testing.
.env, app/.state/, signed raw transactions, and RPC URLs containing credentials as sensitive. The journal does not contain private keys, but it contains signed UserOperations and replayable raw transactions until their nonces are consumed. Clone the repository for a teammate and create fresh keys; do not copy a working directory.
Troubleshooting
RPC chain ID does not match
RPC chain ID does not match
SEI_CHAIN_ID and SEI_RPC_URL. For a local fork, pass --chain-id 1328 to Anvil. The configured chain ID is part of the EIP-712 signature domain and cannot be guessed safely.EntryPoint v0.8 … MISSING
EntryPoint v0.8 … MISSING
0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108. Confirm the chain and the fork source before deploying anything. The preflight checks for code presence, not byte-for-byte identity; verify canonical addresses independently before a real deployment.Delegation mismatch
Delegation mismatch
npm run status and compare delegated to with LANE_ACCOUNT_IMPL. Do not blindly replace an unexpected designator. Confirm the account, chain, and implementation first, then run npm run delegate deliberately.Relayer has no gas
Relayer has no gas
npm run fund, or send SEI to relayer 0 and run npm run dispense.simulation failed: AA…
simulation failed: AA…
AA24 is an invalid signature or the wrong EIP-712 chain or domain; AA25 is a stale or incorrect lane sequence; an insufficient EntryPoint prefund; or delegation to the wrong account implementation. Run npm run status and resolve the cause before widening bundles or retrying.AA95 out of gas while widening bundles
AA95 out of gas while widening bundles
AA95 is the EntryPoint’s report that the outer transaction had less gas left than an operation’s declared limits require. In general it points to an outer gas limit or estimation headroom that is too low, not to network capacity. While widening bundles, the usual cause is the block gas limit: simulation cannot find an outer gas amount under that limit that satisfies the check, and the relayer never signs above the limit it read at startup. Nothing in a bundle that fails simulation is broadcast or consumed. Rerun with a smaller MAX_OPS_PER_BUNDLE; the durable queue is repacked at the smaller width.Journal lock is owned by another process
Journal lock is owned by another process
submit and swap:submit use different journals. Stop the other process. A lock whose recorded PID is no longer alive is removed automatically on the next run.could not acquire the lock … within 2000ms is different. The lock file exists but its contents could not be parsed, so the process cannot tell whether a live holder owns it, and it refuses to remove a lock it cannot attribute. A torn or truncated file left by a dead process and a live holder caught mid-publish produce the same message, and the message repeating across runs does not distinguish between them. Removing a lock that a live process holds lets two processes sign for the same account, which produces duplicate operations on the same lane sequences and conflicting relayer nonces.Before deleting a lock file by hand, confirm that no submit, swap:submit, or bench:lanes process is running against this account. Read the pid from the file if it is legible, and check the process table on every host that shares the state directory. Both locks live under app/.state/: the account-wide run lock (sender-<chainId>-<address>.lock, or SENDER_RUN_LOCK_PATH) and one .lock file next to each journal. Delete the file only after that check.A bundle remains in pending recovery
A bundle remains in pending recovery
npm run submit again once the RPC can answer receipt and nonce queries. If the application reports partial lane consumption or a state it cannot reconcile, stop and inspect the EntryPoint events, every attempted transaction hash, the relayer’s confirmed nonce, and each lane sequence.