Skip to content

HyperFx Custom Bundler Tracking issue #1363

Closed
6 of 6 issues completed
Closed

Description

@Wizdave97

This issue tracks the the things needed to get a custom Hyperfx bundler running.

Why our own bundler

What we run

rundler, forked at polytope-labs/rundler. One process per chain: Ethereum, Arbitrum, Base, Polygon and BNB Chain.

From reading the source at 2dc1788:

  • EntryPoint versions. It supports v0.6 to v0.9 and can serve several at once. Only v0.7 is on by default, so v0.9 has to be enabled with --enabled_entry_points.
  • Unsafe mode. We run without traced validation. [simplex] Bundler whitelists in paymaster #1364 needs that anyway, because it reads tx.origin during validation.
  • BNB Chain. No chain spec is bundled, so we write one.
  • Submission. The default sender is the public mempool. Private senders are built in for Ethereum (flashbots) and Polygon (polygonprivate, bloxroute). There is none for BNB Chain.
  • Signer wallets. Each signer is one funded wallet per chain. On v0.9 they must be plain EOAs, and with [simplex] Bundler whitelists in paymaster #1364 each one has to be on the paymaster's list.
  • RPC usage. The chain watcher polls every 100 ms and makes several calls per block whether or not there is anything to bundle. On a metered provider that is most of the cost at low traffic. Raising the poll interval, turning flashblocks off on Base, and pausing the watcher while the pool is empty bring it close to zero when idle. The pause is a small change in the fork. It is scoped but not built.

Work

Contract and off-chain changes are the sub-issues of this issue, all in the EntryPoint v0.9 milestone.

Bundler work that is not filed as issues yet:

  • Chain spec for BNB Chain
  • One instance per chain with v0.9 enabled, and funded signer wallets
  • Decide how bundles are submitted on Polygon and BNB Chain
  • Idle pause in the fork
  • Point the app and simplex at these endpoints

Activity

  1. added this to the EntryPoint v0.9 milestone on Oct 1, 2026
  2. royvardhan commented on Oct 2, 2026

    @royvardhan
    Collaborator

    On "Decide how bundles are submitted on Polygon and BNB Chain": I think we can use the default public sender (raw) on every chain and leave the private senders out.

    The only reason to submit privately is to stop a pending bundle from being copied and front-run, and #1364 already covers that. The paymaster only sponsors when tx.origin is one of our bundler wallets, so operations copied out of the public mempool fail paymaster validation for anyone else and our bundle still lands. That means there's no BNB sender to write, and no need to set up flashbots, polygonprivate or bloxroute.

    This holds only while the whitelist is set on that chain. If #1364 ends up treating an empty list as "any bundler", we should make sure our wallets are always listed.

  3. Wizdave97 commented on Oct 2, 2026

    @Wizdave97
    MemberAuthor

    On "Decide how bundles are submitted on Polygon and BNB Chain": I think we can use the default public sender (raw) on every chain and leave the private senders out.

    The only reason to submit privately is to stop a pending bundle from being copied and front-run, and #1364 already covers that. The paymaster only sponsors when tx.origin is one of our bundler wallets, so operations copied out of the public mempool fail paymaster validation for anyone else and our bundle still lands. That means there's no BNB sender to write, and no need to set up flashbots, polygonprivate or bloxroute.

    This holds only while the whitelist is set on that chain. If #1364 ends up treating an empty list as "any bundler", we should make sure our wallets are always listed.

    With bundler whitelists on paymaster we can use the public mempool on all bnb and polygon. That's what alchemy already does on polygon. Private mempool would be a nice to have if possible for faster transaction inclusion.

  4. royvardhan commented on Oct 8, 2026

    @royvardhan
    Collaborator

    Live testnet E2E

    Run on 2026-10-08 on BSC Chapel (97) and Polygon Amoy (80002), governed from Gargantua. The simplex testnet harness from #1375 ran against the HyperFX testnet orderbook. Our rundler (polytope-labs/rundler#3) bundled on each chain, with one solver.

    Setup on testnet

    Gargantua sudo:

    • registered the SimplexPaymaster proxies with addPaymasterDeployment: Chapel 0x9f4a6F1254f27373C2aca5bDd0e48FbA45EA3aeC, Amoy 0x6085a14078B7d26259145a894140f7C8361e02ae;
    • upgraded the IntentGatewayV2 proxy on both chains to 0x38a82f8283a0fAdB763888707aD18fCd34AF719D through executeOnGateway(upgradeToAndCall).

    Both upgrades landed at 12:38 UTC. The new SELECT_SOLVER_TYPEHASH is 0xe706bdab…e09e.

    Results

    All five scenarios were filled:

    Scenario Fill txs
    same-chain (Chapel) https://testnet.bscscan.com/tx/0x7e20b4cd0e2ab826dcb12ee8907cd24642d7dc4ae58db3e69be9237a39062f31
    cross-chain (Chapel to Amoy) https://amoy.polygonscan.com/tx/0x7b37dd0a24f11373081d1cc66ee147095d071b0a27fc68bf10ad4294cbb171d9
    same-solver-levels (Chapel) https://testnet.bscscan.com/tx/0x241e4fec96fc2a095c38628ccbb44a4fb120b8b1a8205960143539e33a7481de
    https://testnet.bscscan.com/tx/0xb136288f2f0cbffa50fa7b4738a40477732b4d09b9f310a873dd2a1ccfb5a1b7
    multi-leg-same-input (Chapel) https://testnet.bscscan.com/tx/0xc44af90dfebe073f4acf13ba668524005828448c6d2891eae6a0837d38cc0612
    https://testnet.bscscan.com/tx/0x2cacb0bf2cf4b5922a185360ebdb1fc806026646df56624d9b0e0e1511104d6c
    https://testnet.bscscan.com/tx/0x4df98330a46c393239e1f4d9a4725387bf1816783c16f3eb27aec74276710326
    onchain-limit (Chapel) https://testnet.bscscan.com/tx/0xf71c5d76f47bd9108914becf2cb775ede7d40e31bbb6486a65077428b04169af

    Every one of the 8 fills was checked on chain:

    • it was sent by our rundler signer to EntryPoint v0.9;
    • its UserOperationEvent names that chain's SimplexPaymaster with success = 1;
    • the gateway's OrderFilled or PartialFill event is in the same transaction.

    What each PR got from the run

    Not covered live yet

    Mainnet rollout

    1. Merge [evm, pallet-intents-coprocessor]: bundler allowlist and EntryPoint v0.9 for SimplexPaymaster and SolverAccount #1371.

    2. Merge feat: pause idle chain polling and add a paymaster allowlist rundler#3 and run one rundler per mainnet chain. Each instance needs:

      • --enabled_entry_points v0.9 --unsafe and a funded signer;
      • --pool.paymaster_allowlist set to that chain's paymaster proxy, with idle pause on;
      • a per-chain --builder.max_blocks_to_wait_for_mine;
      • the default public-mempool sender on every chain, including Polygon and BNB Chain. The bundler list in step 8 makes a copied bundle revert, so no private sender is needed.

      Check that eth_supportedEntryPoints returns only v0.9 and that an operation with no paymaster gets -32602.

    3. Deploy the mainnet contracts on each chain: SolverAccount v0.9, the new IntentGatewayV2 implementation and the new SimplexPaymaster implementation.

    4. Upgrade the Nexus runtime to include the [evm, pallet-intents-coprocessor]: bundler allowlist and EntryPoint v0.9 for SimplexPaymaster and SolverAccount #1371 pallet change (set_paymaster_bundlers). Check that the call is in the runtime metadata.

    5. Merge [evm, sdk, simplex, indexer]: EntryPoint v0.9 end to end with userOpHash solver selection #1375, release the SDK, and deploy the mainnet indexer and the HyperFX mainnet orderbook (with [DOCS] ISMP #74).

      • Check that the app on the new SDK still fills against the old gateways, since the SDK reads the selection format on chain.
      • Check that the indexer lists the new SolverAccount.
      • Hold back the simplex release to solvers, because it delegates to the new SolverAccount, which only works on an upgraded gateway.
    6. Cut over all chains in one governance window.

      • For each chain, send executeOnGateway(upgradeToAndCall(newImpl, "")) and upgrade_paymaster(newImpl, migrate()).
      • Polkadot Hub has no v0.9 EntryPoint, so turn solver selection off there or leave it out.
      • Check on each chain:
        • the gateway implementation and typehash;
        • the paymaster's version() is 3;
        • its v0.9 deposit equals the old v0.8 deposit;
        • the v0.8 stake is unlocking.

      Fills on a chain stop until step 7, so keep the gap short.

    7. Solvers move to the new simplex. It re-delegates to the new SolverAccount at boot, and they point at our bundlers.

      • Solvers need USDC or USDT on every chain, because an allowlisted bundler refuses self-paid bids.
      • Check that a small swap on each chain fills through our rundler signer with the paymaster.
    8. Set the bundler list on each chain with set_paymaster_bundlers, right after the cutover. It must include every rundler signer plus the simulation origin 0x0643866dA50efE0b055Cd15aF95191968c8411b5. Until it is set, someone can copy a bundle from the public mempool and land it first.

      • Check getBundlers() and run another swap.
      • Only remove a signer after it has stopped bundling.
    9. After the 24 h unstake delay, call withdrawStakeV08() on chains that had a v0.8 stake (Ethereum and BSC; Base had none). Check that the v0.9 deposit grew by the stake.

  5. royvardhan commented on Oct 8, 2026

    @royvardhan
    Collaborator

    Live testnet E2E: multiple solvers

    These are the scenarios the earlier run couldn't cover. Three solvers ran on BSC Chapel and Polygon Amoy, and every fill went through our rundler (polytope-labs/rundler#3), with SimplexPaymaster sponsoring it.

    The deployed testnet indexer doesn't know the new SolverAccount yet, so these runs used a local HyperFX orderbook (hyperfx-orderbook main, including #74). That orderbook read a local indexer built from #1375. Everything else was live testnet.

    Scenario Result Fill txs (Chapel)
    partial Two solvers filled one order: solver1 paid 9.78 USDC and solver2 the remaining 2.44 USDC, exactly the order's 12.22 USDC minimum https://testnet.bscscan.com/tx/0xd508c548f96aa9ad3d160e2215354d0f70d2475bde6a26aa38023d20764d1ba6
    https://testnet.bscscan.com/tx/0x015acd2d5f66a3f99290ae4436b59a9d856dfb6fed2adb0a5d077a093c4ef662
    onchain-limit-refusal Solver1's best bid was refused on chain with LimitOrderExceeded, and solver2 filled the order instead https://testnet.bscscan.com/tx/0x73b5d5b61cbc7bf326928dc50895a8fe5e8706668664a349780270b7a192cc2d
  6. seunlanlege commented on Oct 9, 2026

    @seunlanlege
    Member

    closed in #1371

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions