Commit Graph

92 Commits

Author SHA1 Message Date
Anton Sauchyk 836d873d27 fix: report on-chain trade outcomes correctly, and repair every broken learning example (#177)
* fix(tx-status): read meta.err before reporting a trade succeeded

confirm_transaction answers "did this signature land in a block?", never
"did it succeed". A landed transaction can have reverted, and RPC reports
that only in meta.err. Reading whether a call threw instead of reading
meta.err produced false results in both directions.

False success — a reverted trade reported as confirmed:

- ten examples (pump.fun, pumpswap, letsbonk) printed "Transaction
  confirmed" without checking meta.err
- src/cleanup/manager.py discarded confirm_transaction's boolean and
  logged "Closed successfully" unconditionally, so a reverted close
  reported rent as reclaimed while the account stayed open
- learning-examples/cleanup_accounts.py did the same

False failure — a good trade reported as unconfirmed:

- _get_transaction_result omitted maxSupportedTransactionVersion, so the
  RPC answered -32015 for every versioned (v0) transaction. meta.err was
  unreadable and a successful trade read back as failed. The bot sends
  legacy transactions, which is the only reason this was survivable.
- confirm_transaction raised TypeError on a base58 str (solana-py wants a
  Signature) while _get_transaction_result raised on a Signature (not
  JSON serializable). Both were swallowed by a broad except into "not
  confirmed". The annotations pointed the wrong way too:
  build_and_send_transaction returns Signature, not str.

Changes:

- add learning-examples/tx_status.py — assert_transaction_succeeded and
  confirm_and_assert, replacing the copy duplicated in mint_and_buy{,_v2}
- wire it into the ten examples that confirmed without checking
- read the boolean in both cleanup paths
- normalize str/Signature at the client boundary; correct the annotations
- send maxSupportedTransactionVersion: 0 on getTransaction
- split verify_transaction_succeeded out of confirm_transaction so the
  meta.err check can run against a transaction that landed earlier

Two reporting bugs found while testing the above:

- live_v2_round_trip read balances at solana-py's default (finalized)
  commitment while confirming trades at "confirmed", so the end read saw
  pre-trade state and it printed "net change: +0.000000000 SOL" after a
  real round trip. Verified: on a busy account finalized trails confirmed
  by ~263k lamports.
- cleanup_accounts produced no output at all, success or failure, because
  get_logger attaches no handler and only the bot installs one. httpx is
  pinned to WARNING alongside it — the RPC endpoint carries an API key.

Adds learning-examples/verify_tx_status_checks.py: offline stub checks, a
scan that fails if an example confirms without checking meta.err, an AST
check that nothing in src/ discards the boolean, a guard that
getTransaction opts into v0, and --live, which replays issue #175's three
signatures against mainnet and requires both layers to reject them on
meta.err (Custom: 6062) rather than on a failed fetch. The two src/ guards
were mutation-tested: reintroducing each bug makes them fail.

The BuybackFeeRecipientMissing (6062) half of #175 was already fixed by
the buy_v2/sell_v2 migration in 02343b7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(client): retry RPC timeouts instead of letting them escape post_rpc

aiohttp signals a request timeout with asyncio.TimeoutError, which is not
an aiohttp.ClientError. post_rpc caught only the latter, so every RPC
timeout propagated to the caller unretried — and str() on it is empty, so
whatever logged it printed a blank reason.

Found while running learning-examples/live_listener_matrix.py: three of
the four listeners died mid-run with "CRASHED: " and no message. The
endpoint was answering getHealth in ~100ms while getAccountInfo hung past
60s, and every caller that touches it (sol_balance, ata_is_closed,
AccountCleanupManager.cleanup_ata) went down with it. With the retry in
place the same run degrades to a logged failure and completes.

Also makes the verifier report a raising check as a failure rather than
aborting the run — several checks assert that a call does NOT raise, so
the raise is the finding and the remaining checks still need to report.

Covered by "RPC timeouts are retried, not raised" in
verify_tx_status_checks.py, mutation-tested against the one-exception
version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(examples): raise websocket frame limit, load .env, drop dead code

Running every learning example turned up three separate reasons a shipped
script could not work at all.

blockSubscribe examples were completely non-functional. websockets
defaults max_size to 1 MiB and Solana's blockSubscribe frames run well
past that, so the connection died with a 1009 close on the first real
block and the retry loop spun. listen_blocksubscribe.py logged 158,613
"message too big" errors in 35s and decoded zero tokens; compare_listeners
produced 10.1M error lines and its block column never reported anything.
Pass max_size=WEBSOCKET_MAX_MESSAGE_BYTES (32 MiB, the value the bot's own
listeners already use) at all 12 example connect sites — logsSubscribe and
programSubscribe included, since they have the same latent ceiling.

After: listen_blocksubscribe decodes tokens with 0 errors, and
compare_listeners reports provider_1_block alongside geyser and logs.

Seven examples read SOLANA_* from the environment but never called
load_dotenv(), so they only ran with variables already exported —
manual_buy, manual_buy_cu_optimized, manual_buy_geyser, manual_sell,
fetch_price, blockSubscribe_extract_transactions and
sample_cashback_pumpswap. manual_buy died on
"None isn't a valid URI: scheme isn't ws or wss" against a normal .env
checkout.

Dead code and artifacts:

- unused datetime import in listen_pumpportal
- initial_real_token_reserves computed and never read in mint_and_buy and
  mint_and_buy_v2
- gitignore blockSubscribe-transactions/, which a shipped example writes
  into 1200+ files deep and which nothing ignored
- stop tracking trades/trades.log; .gitignore has listed it all along, but
  a tracked file ignores .gitignore

ruff over src/ and learning-examples/ goes 2396 -> 2393 findings: the three
removals, nothing new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix: read balances at confirmed, derive letsbonk platform_config per pool

Running every example against mainnet surfaced four more bugs, three of
them the same root cause as the commitment bug already in this branch:
solana-py defaults to "finalized", but trades confirm at "confirmed", and
finalization lags far enough behind that anything read in between is
pre-trade state.

get_token_account_balance defaulted to finalized. Cleanup reads it to
decide whether to burn before closing, so right after a sell it saw the
pre-sell amount and built a burn for tokens the account no longer held —
the burn + close reverted with InsufficientFunds and the rent stayed
locked. Observed live: "Burning 35766666 tokens" on an account the sell
had already emptied, then Custom(1). Defaults to confirmed now, which is
also what confirm_transaction uses.

manual_sell_pumpswap read the user's base balance at finalized, which
failed two ways in one session: "could not find account" when the ATA had
been created by a buy moments earlier, and a stale non-zero balance whose
transfer then reverted with insufficient funds. Pool vault reads move to
confirmed too, so quotes are not priced off stale reserves.

The letsbonk examples hardcoded platform_config. LaunchLab pools do not
share one — partner launches carry their own — so every buy/sell against
such a pool failed with ConstraintAddress (2012):

  AnchorError caused by account: platform_config
  Left:  5thqcDwKp5QQ8US4XRMoseGeGbmLKMmoKZmS6zHrQAsA
  Right: FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1

PoolState carries platform_config at field 18 and these scripts already
parse the pool, so they now take the pool's own value and keep the
constant only as a fallback. src/platforms/letsbonk already documented
this; only the examples were stale. All four scripts now simulate and land
(buy_exact_in, sell_exact_in, buy_exact_out, sell_exact_out).

They also discarded simulation logs on failure, printing an error number
with no indication of which account or constraint broke. They print the
program logs now — that is how the above was diagnosed.

Runnability and credentials:

- fetch_price, cleanup_accounts and the four letsbonk scripts hardcoded a
  placeholder ("...", "YOUR_TOKEN_MINT_ADDRESS_HERE") and could not run at
  all without editing the source. They take argv[1] now, matching
  manual_sell and the pumpswap scripts. cleanup_accounts takes "2022" as
  argv[2] for Token-2022 mints, which every pump.fun coin is.
- six scripts printed the RPC/WSS endpoint, which carries an API key, into
  stdout. They print only the host now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(cleanup): never burn wrapped SOL, and label what actually happened

learning-examples/cleanup_accounts.py burned any non-zero balance before
closing. The token program rejects a burn of native (wrapped) SOL with
NativeNotSupported (error 10), so the burn + close transaction reverted and
a WSOL account could never be cleaned up — the pumpswap examples leave one
behind on every sell. Closing a WSOL account already returns both the
wrapped lamports and the rent, so there is nothing to burn first.

src/cleanup/manager.py already had this guard; only the example was
missing it. Observed live: "Burning 2206381 tokens" then Custom(10), with
2.2m lamports stranded until the guard went in.

The success line also claimed "Burned and closed" for the unwrap path,
which burns nothing. It now reports Unwrapped/Burned/Closed to match the
instructions actually built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* chore: drop captured-output artifacts and a spent one-off investigation script

learning-examples/decoded_buy_tx_from_getTransaction.json and
decoded_create_tx_from_getTransaction.json are not JSON — they are captured
stdout from the decode scripts, several objects concatenated, so json.load
raises "Extra data" on both. Nothing reads them and nothing can. The
raw_*.json fixtures stay: all three are valid input and were re-checked
through decode_from_getTransaction / decode_from_blockSubscribe /
decode_from_getAccountInfo.

learning-examples/pumpswap/sample_cashback_pumpswap.py was added by #168 to
find the position of the extra account that cashback PumpSwap pools
require. That question is answered — the layouts are in
platforms/pumpfun/instruction_builder.py and machine-checked against the
IDL by verify_v2_account_layout.py. It is undocumented, unlike every other
pumpswap example in the README, and nothing imports it.

Not touched: logs/ (run history), .cursor/.kiro/.windsurf (deliberate
mirrors of the same rules for other editors), and the raw_*.json fixtures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(tx-status): fail closed on missing metadata, stop retrying landed reverts

Addresses the must-fix findings from review. Two of them were wrong in code
this branch added.

assert_transaction_succeeded folded meta=None into err=None and returned
success. Missing execution metadata means the outcome is unknown, not that
it succeeded — which is the exact mistake this module exists to prevent. It
raises now.

It also always read at "confirmed" while confirm_and_assert accepted a
commitment argument, so confirming at "finalized" and then reading status at
"confirmed" could report success before the finalization the caller asked
for. The commitment is threaded through.

The manual buy/sell retry loops built one message and blockhash before the
loop, so a landed revert was retried by resubmitting byte-identical signed
bytes — three attempts with backoff that could never succeed. Reverts now
raise TransactionRevertedError (a RuntimeError subclass, so existing
handlers keep working) and the loops treat it as terminal. Rebuilding and
re-signing per attempt is the fuller fix but a bigger change to these
scripts than this branch should carry.

cleanup_accounts guessed the mint's token program, defaulting to legacy SPL
unless argv[2] was "2022". The ATA address differs between programs, so a
wrong guess derives an address that does not exist and the script reports
"already closed" for an account it never looked at. It reads the owner off
the mint account instead, which is authoritative, and rejects anything not
owned by a token program. Verified live: pump.fun coins resolve to
Token-2022, letsbonk and USDC to legacy. argv[2] is gone — nothing to guess.

urlsplit(...).netloc keeps any user:pass@ userinfo, so the endpoint
redaction added earlier still printed credentials for providers that put the
key there. Uses .hostname in all five sites.

Four checks added to verify_tx_status_checks.py, each mutation-tested:
missing metadata, revert-is-terminal, commitment propagation, and a scan
that fails if any example goes back to netloc.

Skipped, both minor and pre-existing repo-wide rather than introduced here:
validating that RPC/WSS env vars are non-None before connecting (every
example has this shape; fixing two of them would just make it inconsistent),
and the cryptic base58 error when a letsbonk script runs with no argument,
which matches the "..." placeholder convention the pumpswap and manual_sell
examples already use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 13:44:35 +02:00
Anton Sauchyk 02343b775b feat(pumpfun): migrate to buy_v2/sell_v2 and support non-SOL quote assets (#176)
Refresh the vendored IDLs from pump-fun/pump-public-docs @ 9c82f61 and move all
pump.fun trading onto the v2 instruction interface. This is required, not
optional: legacy buy/sell cannot trade coins paired against a quote asset other
than SOL, and USDC is already whitelisted in the on-chain Global account.

Protocol changes absorbed:

- buy_v2 (27 accounts) / sell_v2 (26 accounts) replace the legacy instructions.
  Every account is mandatory and the order is identical for all coins, so the
  conditional cashback/mayhem account lists are gone. Legacy remains available
  via PumpFunInstructionBuilder(use_legacy_instructions=True).
- BondingCurve is 151 bytes: virtual_sol_reserves -> virtual_quote_reserves,
  real_sol_reserves -> real_quote_reserves, plus quote_mint at offset 83. Old
  field names are kept as aliases so existing callers keep working.
- v2 instruction data drops the track_volume OptionBool; amounts are in the
  quote mint's raw units rather than always lamports.
- create_v2 carries a non-SOL quote mint as optional remaining accounts 17-19,
  and CreateEvent gained quote_mint, so extreme_fast_mode can resolve the quote
  asset without an extra fetch.

USDC support: new trade.quote_amounts and filters.allowed_quote_mints config,
accepting "sol"/"usdc" aliases or raw mints. Amounts are per-quote-mint because
1 USDC and 1 SOL are not interchangeable. A coin whose quote mint has no
configured amount is skipped rather than traded at the wrong size, so SOL-only
configs are unaffected.

Bug fixes found while verifying:

- The logs and blocks listeners set no websocket max_size, so any frame over
  1 MiB closed the connection with 1009 and the token in it was lost. Raised
  to 32 MiB.
- PumpSwap priced against the raw quote vault balance, ignoring the new
  Pool.virtual_quote_reserves (i128 at offset 245; live pools are 301 bytes).
  Upstream's note that this field is 0 everywhere is out of date: a live pool
  carries 17.58 SOL against a 148 SOL vault, a 10.15% price error.
- The seller read curve state once at confirmed commitment and silently fell
  back to create-time values, risking a stale creator_vault and ConstraintSeeds.
  It now retries at processed, matching the buyer.
- Account cleanup would burn wrapped SOL when force_burn was set, destroying
  value that closing the account returns. WSOL is now closed without burning.
- The mint scripts treated a landed transaction as a successful one, so a
  reverted buy printed as success. They now assert the on-chain result.

Compute unit limits retuned from mainnet measurements: buy 100k -> 180k,
sell 60k -> 120k. Mint-and-buy is no longer atomic, because create_v2 plus
buy_v2 exceeds the 1232-byte transaction limit; both mint scripts send two
transactions.

Adds learning-examples/pump_v2.py as one shared, standalone v2 toolkit for the
example scripts, and three verification scripts: an offline layout check
against the IDL, a no-funds mainnet simulation, and a live listener matrix that
buys, sells and closes the ATA per listener.

Verified on mainnet: all four listeners (geyser, logs, blocks, pumpportal) and
all eight example scripts completed a real buy, sell and ATA close, each
confirmed by reading the transaction result back rather than trusting
confirmation alone. The USDC path is verified structurally only; no USDC-paired
coin could be found on-chain to exercise it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:58:33 +02:00
Anton Sauchyk 27929e97be 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>
2026-04-28 22:11:37 +02:00
Anton Sauchyk 20e3e163d8 fix(trading): refresh BC state on extreme_fast_mode buy + letsbonk pumpportal token-program default
Two related fixes from mainnet validation of geyser/logs/blocks/pumpportal:

1. extreme_fast_mode buys previously skipped the BC fetch entirely. After
   the 2026-04-28 cutover, fee_recipient (mayhem mode) and creator_vault
   (PFEE-delegated BC.creator) cannot be derived from listener-side data
   alone — the program rejects with NotAuthorized (0x1770) /
   ConstraintSeeds (0x7d6). Add a short retry loop that fetches the curve
   state once before submitting the buy, so the instruction builder picks
   the correct fee_recipient and creator_vault. The ~150ms cost is small
   relative to a failed tx and 5s sell wait.

2. LetsBonkPumpPortalProcessor returned TokenInfo without token_program_id.
   The universal builder then defaulted to Token-2022 and the buy failed
   with IncorrectProgramId at GetAccountDataSize on the user-ATA create
   ix. LetsBonk tokens are predominantly regular SPL Token, so default to
   TOKEN_PROGRAM in the processor.

Note: the pumpportal listener still has unrelated data-race issues (the
PumpPortal stream sometimes fires before the on-chain BC is RPC-readable,
and bondingCurveKey occasionally references a different mint than the
detected token). These predate the 2026-04-28 cutover and need a separate
fix (e.g., verifying mint+BC consistency on the tx side before submit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:12:37 +02:00
Anton Sauchyk 2ece1c9a2d fix(trading): refresh creator_vault from on-chain BC state before sell
Post-2026-04-28, BC.creator can be delegated to a PFEE-program-owned PDA
after the initial creator buy. The create-time creator_vault cached on
TokenInfo goes stale before the sell lands, producing ConstraintSeeds
(0x7d6) on the Sell instruction.

The sell flow already re-fetches pool_state to refresh is_mayhem_mode and
is_cashback_coin; extend it to also refresh token_info.creator and re-derive
creator_vault from the current BC.creator.

Mainnet validation: geyser listener buy+sell on BEE — buy 2TSqZHZh…k7uN,
sell 3TevcP5g…f8nQ, err=None on both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:51:56 +02:00
Anton Sauchyk d0dea6d4ac fix(blocks-listener): skip failed txs + use CreateEvent log for canonical creator
Two bugs found by mainnet validation post-2026-04-28 cutover:

1. Blocks listener processed failed txs, including failed create_v2s. The
   bot then tried to buy a non-existent mint (InvalidMint at ATA creation).
   Fix: filter `meta.err is not None` in _process_block_transactions.

2. Blocks listener parsed `args.creator` from the create_v2 ix payload to
   derive creator_vault. Post-cutover, BC.creator is sometimes set to a
   PFEE-program-owned PDA distinct from args.creator (e.g., Companions:
   args.creator=DdZG8dw, BC.creator=3hPZm9). The Anchor seeds constraint
   on creator_vault then fails with ConstraintSeeds (0x7d6). Fix: in
   parse_token_creation_from_block, prefer parsing the CreateEvent log
   (which carries the canonical creator the program wrote to BC.creator)
   over args.creator from the ix payload.

Mainnet validation:
- buy 2uBrNqqW…bPDG, sell 32EKJjrK…9fXv on PUMP (Pumpcoin), err=None.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:44:42 +02:00
Anton Sauchyk 22a0c23450 feat(pumpfun): align src/ with 2026-04-28 program upgrade + cashback path (#167)
Mirrors the four learning-examples commits (9b48418, c25e2ed, 57ffe8b,
ad6d2cc) into the production code paths under src/. Affects only pump_fun;
lets_bonk untouched. PumpSwap is not implemented under src/ so the
pool-v2 + breaking-fee-recipient pumpswap accounts are out of scope here.

Changes
-------
src/platforms/pumpfun/address_provider.py
  - Adds BREAKING_FEE_RECIPIENTS: ClassVar[list[Pubkey]] with the 8
    addresses pump.fun published for the 2026-04-28 upgrade.
  - Adds pick_breaking_fee_recipient() (random.choice across the 8 to
    spread program-tx throughput per pump.fun's recommendation).
  - get_buy_instruction_accounts and get_sell_instruction_accounts now
    expose "breaking_fee_recipient" alongside the existing keys.

src/platforms/pumpfun/instruction_builder.py
  - BC buy ix: appends breaking_fee_recipient (mutable) AFTER
    bonding_curve_v2 → 18 accounts total (was 17).
  - BC sell ix: appends breaking_fee_recipient (mutable) AFTER
    bonding_curve_v2 for both cashback and non-cashback paths
    → 16 accounts non-cashback / 17 cashback (was 15 / 16).
  - get_required_accounts_for_{buy,sell} updated to include the new
    account so priority-fee scraping covers it.

src/platforms/pumpfun/event_parser.py
  - is_mayhem_mode now extracted into TokenInfo on both paths
    (CreateEvent logs path and create / create_v2 instruction path);
    previously only is_cashback_coin was carried forward, leaving
    extreme_fast_mode buys to use the wrong fee_recipient on
    Mayhem-Mode tokens.

src/trading/platform_aware.py
  - Buy non-extreme path now also refreshes is_cashback_coin from
    pool_state (was only refreshing is_mayhem_mode).
  - Sell path now refreshes both flags from curve state before building
    the sell ix, so a coin that flips cashback after buy still gets the
    correct 16-vs-17-account layout. Wrapped in try/except so a transient
    RPC failure logs a warning and falls back to token_info defaults
    rather than failing the sell outright.

Live-validation (mainnet, all four bots/*.yaml configs)
-------------------------------------------------------
  bot-sniper-2-logs.yaml      buy 4GVJJwYtGixSPAQimgUhNYjEUBG6TB9XeQjKxs9EWpw2QfWuq7LHmUivPrsjhESgaPsPJQ9N6CCxECqxm9csqrH9 (18 accts) → sell 2ijgoPgxPmcSX4mFqz1PVkZ3yAvu36vj3Jy1yWZofxrK7jR92j2DWP3tRNy9PmUd1kiTaoYzVsrpGr6286KCnTp7 (16 accts) ✓
  bot-sniper-3-blocks.yaml    buy 4siRLG7tYk9iHyXAvnp3DH54vQXVEDHmCqZRmFgYhnRB9mVGjhh43ywohKjg8CpWctt5TyCtDkLRTRWT6VDwVyZi → sell 46jShWJ3sfuCP4YgTcpXWZdyqVmw4ULQSTC3Kwvbc9hE2y4yDosBww8Un8anXZpR1Xf96rN9QusV1G92oNcbp1o3 ✓
  bot-sniper-1-geyser.yaml    buy 4UEZy7LPm7Rxgw22d1bduQHTWHHHYJSpuaoq2iCyPFpnBPGt4C3QUCu9x77vwWugXnyGv4pQQeTbEZEjvyxxiDH2 → sell 21hSHVPKtqse25XybxhdGrpqK1fSxKKeNZGDCD5j568mzRPfyYR1TQs6mAc2moHKT7kaAAfdMC8DPt8VXmu2rviF ✓
  bot-sniper-4-pp.yaml        buy 4P91y4iPK6y1gPjz65VL6XAcVkT4DbqjHE5vHfX8PLnzqViCy1qZQ44eWiNDUPzoWCejibB7UJPKYAEj4K3Z3c3S ✓ ; sell hit slippage protection (Custom 6003 — program-side, the ix passed deserialization)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:39:17 +02:00
Anton Sauchyk 4a48f545c1 feat(pumpfun): update IDLs and add bonding_curve_v2 + cashback support (#159)
Update all 3 pump.fun IDL files (pump_fun, pump_swap, pump_fees) with
new cashback rewards and fee-sharing features. Fix IDL parser to handle
tuple struct types (OptionBool). Add bonding_curve_v2 remaining account
to all buy/sell instructions as required by the pump.fun program upgrade.

Key changes:
- Fix IDL parser crash on tuple struct fields (string vs dict)
- Add bonding_curve_v2 PDA derivation and append as remaining account
- Add is_cashback_coin field to TokenInfo and BondingCurve decoding
- Propagate is_cashback_enabled from CreateEvent/create_v2 to TokenInfo
- Conditionally include user_volume_accumulator for cashback sell txs

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:39:52 +01:00
Anton Sauchyk 0b6779ca34 Fix buy/sell transactions silently failing on-chain (#157)
* fix(core): disable account_data_size limit and add meta.err checking

The 12.5MB setLoadedAccountsDataSizeLimit was causing all buy transactions
to fail with MaxLoadedAccountsDataSizeExceeded since pump.fun migrated to
Token-2022. Transactions landed on-chain (fees paid) but inner instructions
were rejected — the bot paid gas for nothing.

Changes:
- Disable account_data_size in all bot configs (Token-2022 needs >12.5MB)
- Add meta.err check in get_buy_transaction_details() for clear error
  reporting when transactions fail on-chain
- Include tx signature in platform_aware.py error messages for debugging

Tested: full buy→sell→cleanup cycle works on pump_fun with geyser listener.


* fix(core): check meta.err in confirm_transaction to detect failed txs

Solana transactions can be "confirmed" (included in a block) but still
fail execution if inner program instructions are rejected. Previously,
confirm_transaction only checked that the tx landed on-chain, causing
silent failures in buy, sell, and cleanup operations.

Now fetches the transaction result after confirmation and checks
meta.err, returning False when the transaction failed. This fixes
the ATA cleanup issue where sells were silently failing with
Custom: 6003 errors, leaving non-zero token balances.


* fix(core): treat failed tx fetch as unconfirmed in confirm_transaction

When _get_transaction_result returns None (RPC failure, timeout, etc.),
the function was falling through to return True — silently treating an
unknown state as success. Now returns False with a warning log.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:37:04 +01:00
Alex Kuligowski 40ad32b8d0 feat(core): add RPC rate limiting and retry handling (#154)
* feat(core): add RPC rate limiting, retry logic, and 429 handling

Addresses #44 — users on free-tier RPC endpoints hit HTTP 429 errors
during buy transactions due to no rate limiting or retry handling.

- Add TokenBucketRateLimiter (new file: src/core/rpc_rate_limiter.py)
- Gate all RPC methods through rate limiter (both post_rpc and solana-py calls)
- Rewrite post_rpc() with retry loop, exponential backoff, jitter, and
  specific 429 detection with Retry-After header support
- Replace per-call aiohttp session with shared persistent session
- Wire node.max_rps from YAML bot config through to SolanaClient
- Fix cleanup manager and learning example to use SolanaClient abstraction
  instead of bypassing it via get_client()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(core): address CodeRabbitAI review feedback on rate limiting PR

Validate max_rps > 0 in TokenBucketRateLimiter to prevent ZeroDivisionError
and infinite loops with fractional values. Add asyncio.Lock to _get_session
to fix race condition, handle non-numeric Retry-After headers gracefully,
replace dead json.JSONDecodeError with aiohttp.ContentTypeError, and combine
burn+close into a single transaction in cleanup example.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: document RPC rate limiting feature in README

- Add section on built-in RPC rate limiting with token bucket algorithm
- Document configurable max RPS and automatic retry logic
- Update roadmap to mark "Configurable RPS" as completed
- Clarify benefits of rate limiting for provider compliance

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: use math.ceil for burst_size to handle fractional max_rps

- Replace int(max_rps) with math.ceil(max_rps) in burst_size calculation
- Prevents infinite loop when max_rps < 1.0 (e.g., 0.5 RPS would result in burst_size=0)
- Ensures burst_size is always at least 1 for valid fractional rates
- Addresses CodeRabbit feedback on rpc_rate_limiter.py:27

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(core): validate burst_size and fix table pipe consistency

Address remaining CodeRabbitAI review feedback: add burst_size
validation guard, fix TRY003 lint (use msg variable for ValueError),
break long line under 88 chars, and fix MD055 table pipe style in README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(core): separate 429 retry budget from error retries in post_rpc

429 responses no longer count against max_retries — they use a dedicated
max_429_retries counter (default 10) so free-tier users hitting rate
limits won't exhaust retries prematurely. Also refresh the aiohttp
session inside the retry loop to avoid stale references after network
failures, and fix cleanup log message accuracy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Anton Sauchyk <antonsauchyk@gmail.com>
2026-02-17 10:02:11 +01:00
Anton Sauchyk 31955103dc feat: add Windows compatibility for uvloop dependency (#155)
Fixes chainstacklabs/pumpfun-bonkfun-bot#152

Changes:
- Make uvloop optional with platform-specific markers (Unix only)
- Add winloop as Windows alternative for performance optimization
- Update code to gracefully fall back to standard asyncio when event loop
  libraries are unavailable
- Both bot_runner.py and universal_trader.py now detect platform and use
  appropriate event loop implementation

This resolves the blocking installation issue for Windows users while
maintaining performance benefits on all platforms. Windows users can now
install and run the bot with winloop for improved performance, or use
standard asyncio as fallback.

Related: https://github.com/smypmsa/gh-triage-reports/blob/main/issues/chainstacklabs-pumpfun-bonkfun-bot/issue-152-2026-02-08.md

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 16:58:24 +04:00
Anton Sauchyk 9d93d859a8 Reduce RPC delays when selling (#150)
* feat(trading): enhance sell execution with token amount and price parameters to reduce RPC delays

* feat(trading): add method to fetch actual token balance after transaction to improve accuracy

* feat(trading): update account data size limit and enhance price calculations
2025-11-23 12:02:29 +01:00
Anton Sauchyk 03a4e7bcbc Add mayhem mode support and Token2022 integration (#149)
* feat: mayhem update in idl

* feat(examples): update bonding curve scripts

* feat(example): update listen_blocksubscribe

* feat(examples): update geyser listener

* feat(examples): update all new token listeners

* feat(examples): add comments, fix printing, formatting

* feat(examples): pumpswap buy and sell update with mayhem mode

* fix(examples): sell pump amm fee recipient

* feat(examples): update decode scripts

* feat(examples): update fetch price

* feat(examples): buy and sell bonding curve scripts

* feat(examples): add mint with mayhem mode enabled

* feat(examples): improve listening to wallet txs

* feat(examples): migration listener improvements

* feat(examples): global vol accumulator is not writable

* feat(examples): support token/token2022 programs in buy instructions

* feat(examples): token/token2022 for pumpswap buy

* feat(examples): token/token2022 supprot for sell instructions

* feat(bot): support create_v2 with token2022, mayhem mode, other fixes

* fix(bot): support only token2022 in logs and pumportal listeners

* feat(bot): token2022 support in cleanup flow

* fix(bot): update token program handling and improve price validation in trading logic

* feat(bot): enhance token program handling for LetsBonk integration
2025-11-18 13:09:37 +01:00
Anton Sauchyk b6928d1f5f feat(core): integrate account data size limit (#144)
* feat(core): integrate account data size limit in buyer and seller transaction

* refactor: format code for improved readability and consistency
2025-10-28 21:21:21 +01:00
Anton 68cac87aad Fix/update letsbonk fun instructions in the bot (#142)
* feat(core): support multiple initialize instruction variants in LetsBonkEventParser

* feat(core): add creator and platform fee vault derivation methods and update account handling in buy/sell instructions

* feat(letsbonk): add global_config and platform_config to TokenInfo and update address provider logic
2025-10-26 16:21:58 +01:00
smypmsa 9731b179cc feat(core): add configurable cu limits 2025-09-03 15:05:12 +00:00
smypmsa e1e15dd539 feat(pumpfun): add fee_config and fee_program, close #132 2025-08-29 15:21:36 +00:00
smypmsa f8e0414ee7 fix(core): handle block listener errors 2025-08-14 09:49:01 +00:00
smypmsa 8ab8932168 docs(claude): add claude code rules 2025-08-11 05:35:25 +00:00
smypmsa a9c58faf9e fix(pump): derive associated bonding curve 2025-08-05 20:30:41 +00:00
smypmsa 329266611c fix(core): minor fixes in hardcoded values 2025-08-05 20:19:00 +00:00
smypmsa 874d148777 fix(core): use logger exception 2025-08-05 16:09:05 +00:00
smypmsa 53763b7123 fix(core): remove unused args, improve logging 2025-08-05 14:56:59 +00:00
smypmsa 7e644286a8 fix(core): remove redundant kwargs 2025-08-05 14:53:53 +00:00
smypmsa 2c2306e80b fix(core): add missing method to event parser interface 2025-08-05 14:53:07 +00:00
smypmsa 3e82cfa128 fix(core): replace os with pathlib 2025-08-05 14:51:48 +00:00
smypmsa 80b03748b0 fix(core): refactor dataclass to use class vars 2025-08-05 14:44:06 +00:00
smypmsa a831cafaf4 fix(core) import level 2025-08-05 14:41:36 +00:00
smypmsa 49c5085764 fix(core): remove silent failures 2025-08-05 14:40:43 +00:00
smypmsa ea1d6b469a feat(core): pump portal fully integrated 2025-08-04 05:40:02 +00:00
smypmsa 2c6da58eb7 fix(core): fix event idl parser for logs 2025-08-04 05:02:53 +00:00
smypmsa 9b6563cecc feat(core): add event idl parser 2025-08-02 19:19:20 +00:00
smypmsa bae03c3fab fix(core): block event processor account keys bytes 2025-08-02 16:41:23 +00:00
smypmsa 26c048c86a fix(core): platform listener check 2025-08-02 16:12:45 +00:00
smypmsa e7107751f5 fix(letsbonk): buy/sell instructions 2025-08-02 15:38:53 +00:00
smypmsa 93332794a3 feat(pump): integrate idl parser manager 2025-08-02 15:10:24 +00:00
smypmsa 9a09862f0d feat(letsbonk): add idl manager 2025-08-02 14:52:40 +00:00
smypmsa 1ddadcd0af feat(letsbonk): integrate idl parser 2025-08-02 14:38:54 +00:00
smypmsa 4acabfb1af wip(letsbonk): idl parser in parser, builder 2025-08-02 14:33:44 +00:00
smypmsa f92f68fc41 wip(core): dynamic idl parser 2025-08-02 14:21:16 +00:00
smypmsa 04342fee5a fix(pump): fix curve management 2025-08-02 14:17:29 +00:00
smypmsa 8ee6b95d2d feat(core): cleanup address management system 2025-08-02 14:03:41 +00:00
smypmsa 227ef0b3bd wip(core): platform aware trading 2025-08-02 13:27:07 +00:00
smypmsa 9ec9eeb793 wip(core): platform aware trading 2025-08-02 11:30:30 +00:00
smypmsa bb35101a8f wip(core): platform aware trading 2025-08-02 09:25:39 +00:00
smypmsa 60c452da0f fix(core): increase cu limit 2025-08-01 12:00:17 +00:00
smypmsa 3faeb1338e feat(core): update buy instruction, add volume_accumulator accounts 2025-08-01 05:25:54 +00:00
smypmsa 536a5dd6a5 docs(cursor): add uv rules, formatting 2025-07-18 12:03:00 +00:00
Bukhtiiarov b74d8df0a9 chore: update generated protobuf files 2025-06-30 15:10:59 +03:00
smypmsa 984f43b757 fix: remove coderabbit syntax bugs 2025-06-15 20:12:54 +00:00