perf(extreme-fast-mode): use processed commitment + tight retry for BC refresh

Before: BC fetch in extreme_fast_mode used default (confirmed) commitment
and retried up to 10 times with growing backoff (~10s total). Geyser/logs
fire on processed, so the BC isn't yet at confirmed commitment when the
bot reads it — most fetches failed and burned the full retry budget,
adding ~10s to detect→confirmed.

After: read at processed commitment to match the listener event horizon,
and cap retries at 4 with 150ms gaps (~600ms ceiling). Most reads succeed
on the first attempt.

Mainnet benchmark (n=4 successful buys, geyser listener):
  buy_slot − create_slot: min=2  median=3  max=9  (~0.8s — 3.6s on chain)
  wall detect→confirmed:  median 2-3s (1s resolution)

Best case 2 slots = next slot after creation + tx propagation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anton Sauchyk
2026-04-28 22:11:37 +02:00
parent 20e3e163d8
commit 27929e97be
3 changed files with 25 additions and 12 deletions
+9 -4
View File
@@ -147,11 +147,15 @@ class SolanaClient:
return result["result"] return result["result"]
return None return None
async def get_account_info(self, pubkey: Pubkey) -> dict[str, Any]: async def get_account_info(
self, pubkey: Pubkey, commitment: str | None = None
) -> dict[str, Any]:
"""Get account info from the blockchain. """Get account info from the blockchain.
Args: Args:
pubkey: Public key of the account pubkey: Public key of the account
commitment: Optional commitment override (e.g., "processed" for
fresh state right after a geyser event; default "confirmed")
Returns: Returns:
Account info response Account info response
@@ -161,9 +165,10 @@ class SolanaClient:
""" """
await self._rate_limiter.acquire() await self._rate_limiter.acquire()
client = await self.get_client() client = await self.get_client()
response = await client.get_account_info( kwargs: dict[str, Any] = {"encoding": "base64"}
pubkey, encoding="base64" if commitment is not None:
) # base64 encoding for account data by default kwargs["commitment"] = commitment
response = await client.get_account_info(pubkey, **kwargs)
if not response.value: if not response.value:
raise ValueError(f"Account {pubkey} not found") raise ValueError(f"Account {pubkey} not found")
return response.value return response.value
+9 -2
View File
@@ -38,17 +38,24 @@ class PumpFunCurveManager(CurveManager):
"""Get the platform this manager serves.""" """Get the platform this manager serves."""
return Platform.PUMP_FUN return Platform.PUMP_FUN
async def get_pool_state(self, pool_address: Pubkey) -> dict[str, Any]: async def get_pool_state(
self, pool_address: Pubkey, commitment: str | None = None
) -> dict[str, Any]:
"""Get the current state of a pump.fun bonding curve. """Get the current state of a pump.fun bonding curve.
Args: Args:
pool_address: Address of the bonding curve pool_address: Address of the bonding curve
commitment: Optional commitment override. Pass "processed" right
after a geyser/logs event so the BC is readable in the same
slot it was created (default: confirmed, ~1-2 slot lag).
Returns: Returns:
Dictionary containing bonding curve state data Dictionary containing bonding curve state data
""" """
try: try:
account = await self.client.get_account_info(pool_address) account = await self.client.get_account_info(
pool_address, commitment=commitment
)
if not account.data: if not account.data:
raise ValueError(f"No data in bonding curve account {pool_address}") raise ValueError(f"No data in bonding curve account {pool_address}")
+7 -6
View File
@@ -76,18 +76,19 @@ class PlatformAwareBuyer(Trader):
) )
pool_state = None pool_state = None
last_err: Exception | None = None last_err: Exception | None = None
# Up to ~6s of retries — pumpportal frequently fires before # Use processed commitment — geyser/logs fire on processed
# the BC account is RPC-readable. Without a settled BC the # so the BC is typically readable in the same slot. Most
# bot can't pick the right fee_recipient or creator_vault. # listeners only need 1 attempt; pumpportal occasionally
for attempt in range(10): # races the on-chain commit, so allow a few quick retries.
for attempt in range(4):
try: try:
pool_state = await curve_manager.get_pool_state( pool_state = await curve_manager.get_pool_state(
pool_address pool_address, commitment="processed"
) )
break break
except Exception as inner: # noqa: BLE001 except Exception as inner: # noqa: BLE001
last_err = inner last_err = inner
await asyncio.sleep(min(0.3 + 0.3 * attempt, 1.5)) await asyncio.sleep(0.15)
if pool_state is None: if pool_state is None:
raise last_err or RuntimeError( raise last_err or RuntimeError(
"pool_state unavailable after retries" "pool_state unavailable after retries"