v4.0.11 switched the fast-submit (`wait_tx_confirmed = false`) result loop
from `wait_for_all_submitted` to `wait_for_first_submitted`. The new
function returns as soon as one SWQOS route's HTTP submit completes and
drains whatever signatures are in `ResultCollector.results` at that
moment — slower routes' signatures arrive after the caller has returned.
This is correct for fire-and-forget low-latency submits, but breaks
callers that do their own on-chain confirmation against a pinned durable
nonce. Only one of the submitted txs can land (nonce is consumed by
whichever lands first), but the caller has no way of knowing in advance
which route it will be. Their post-submit logic feeds every returned
signature to `getSignatureStatuses` to find the landed one — with only
the fastest HTTP responder returned (often not the one that lands), the
landed tx isn't in the polled set.
Adds an opt-in `SwapParams.wait_for_all_submits: bool` (default false,
plumbed through `TradeBuyParams` / `TradeSellParams`). When true and
`wait_tx_confirmed = false`, `execute_parallel` uses
`wait_for_all_submitted` (still in the codebase, was
`#[allow(dead_code)]`) so every submitted signature is returned. Zero
behaviour change for current users.
Sync supported DEX IDLs from bitquery/solana-idl-lib, upgrade protocol builders, and keep trade submission hot paths free of RPC query calls.
Include exact-output coverage, token-account setup fixes for WSOL and stable pairs, and low-latency transaction build improvements.
The patched solana-keypair in the main workspace adds try_from_base58_string,
but sol-safekey and other consumers use the standard crates.io version which
only provides from_base58_string (panics) and TryFrom<&[u8]> (fallible).
Use bs58::decode + Keypair::try_from to keep proper error handling while
remaining compatible with the standard solana-keypair crate.
BondingCurveAccount::try_from_slice requires the entire input slice to
be consumed, which fails with "Not all bytes read" when the on-chain
bonding curve account has been extended with new fields (e.g. after a
protocol upgrade). Switch to BorshDeserialize::deserialize which reads
the known fields and silently ignores any trailing bytes.
In build_sell_v1/v2, the creator_vault was previously derived via
effective_creator_for_trade() passed to resolve_creator_vault_for_ix_with_fee_sharing.
When observed_trade_creator differs from bonding_curve.creator (e.g. creator
updated post-deployment), the derived vault PDA mismatches the on-chain
seeds constraint, causing Anchor error 2006 (ConstraintSeeds) on sell.
The pump.fun sell instruction validates creator_vault seeds as
PDA("creator-vault", bonding_curve.creator), but buy does not check seeds,
so buy succeeds while sell fails with 2006.
Fix: when protocol_params.creator_vault is a valid non-default/non-phantom
value (observed from gRPC / update_fee_shares), use it directly without
re-derivation. Only fall back to resolve via bonding_curve.creator when
creator_vault is missing. This ensures the authoritative observed vault
is never overridden by a stale or mismatched creator.
The Pump.fun IDL explicitly states that user_volume_accumulator is
passed as remaining_accounts[0] for cashback coins during sell.
Using is_mayhem_mode incorrectly omitted the account for cashback
tokens, causing the program to misread bonding_curve_v2 as
user_volume_accumulator and resulting in Custom(6024) Overflow.
- Replace compile-time `pumpfun-v2` feature flag with runtime
`TradeConfig::use_pumpfun_v2(bool)` for flexible V1/V2 switching
- Fix BUYBACK_FEE_RECIPIENTS pool: replace wrong addresses (was reusing
standard pool + FEE_CONFIG) with official buyback pool from
FEE_RECIPIENTS.md
- Fix legacy buy/buy_exact_sol_in ix data encoding: use 2-byte
Option<bool> (option tag + value) matching official Pump SDK, 26 bytes
total instead of broken 25
- Add `SwapParams.use_pumpfun_v2` field and wire through TradingClient
buy/sell paths
- V2 instructions read `quote_mint` from `PumpFunParams` (not
BondingCurveAccount which lacks the field)
- Fix `fetch_bonding_curve_account` to use BorshDeserialize instead of
non-existent `decode_from_chain_account_data`
- Update all examples with `use_pumpfun_v2: false` field
- Update README with unified V1/V2 section showing both enabling methods
The refactored ix data encoder was producing 25-byte buy instructions
(1-byte track_volume), but the on-chain Pump program expects Anchor's
2-byte Option<bool> encoding (option tag + value), totaling 26 bytes.
The 1-byte shortfall caused instruction data under-read that manifested
as error 0xbbf (ConstraintOwner) for all legacy buy/buy_exact_sol_in.
Restore the 2-byte encoding: option tag (1u8) followed by the cashback
flag as the bool value, matching the pre-refactoring inline encoder.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The `effective_pump_mint_token_program` helper was defaulting to
TOKEN_PROGRAM_2022 when `token_program` was Pubkey::default(), causing
Anchor error 0xbbf (account owned by wrong program) for all legacy
PumpFun coins whose ATAs are owned by the original Token Program.
Fall back to TOKEN_PROGRAM (legacy) when no explicit token program is
provided. Token-2022 must be set explicitly for create_v2 coins.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- resolve_creator_vault_for_ix_with_fee_sharing uses non-placeholder ix vault as-is.
- When ix vault missing, fall back to fee_sharing_creator_vault_if_active then PDA(creator).
- Add Pump troubleshooting CN doc; extend PumpFunParams field documentation.
- Bump to v4.0.7.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds pumpfun_ix_data / pumpswap_ix_data; exports used by trading::core::params::PumpFunParams.
chore(release): bump version to 4.0.6
Co-authored-by: Cursor <cursoragent@cursor.com>
calculate_with_slippage_sell previously fell through to the branch that
returned 1 when amount <= basis_points/10_000, which is true for amount=0
and typical bps values—yielding a bogus minimum of 1. Short-circuit on
zero input so sell quotes stay correct.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Append pool-v2 in buy/sell remaining accounts only when pool coin_creator
is not default, matching @pump-fun/pump-swap-sdk. Always appending pool-v2
shifted buyback pubkey/ATA and caused BuybackFeeRecipientNotAuthorized (6053).
- Add coin_creator to PumpSwapParams (filled from decoded Pool / from_trade).
Update pumpswap_trading example to pass pool_data.coin_creator.
- Document buyback fee recipient constants against GlobalConfig.buyback_fee_recipients.
- Extend PumpSwap quote math to include cashback_fee_basis_points in creator-side fees.
Co-authored-by: Cursor <cursoragent@cursor.com>
fix(swqos): extend parallel-submit wait/grace windows for late HTTP responses
- Scale primary submit timeout with configured channel count (clamped 3–5s)
- After primary expiry, optionally poll longer so slow providers still land before drain
- Short settle sleeps to reduce counter vs TaskResult races and dropped signatures when
wait_transaction_confirmed is false
Co-authored-by: Cursor <cursoragent@cursor.com>
- PumpFun buy/sell: append bonding-curve-v2 and random extra fee recipient (writable)
- PumpSwap buy/sell: append pool-v2, protocol fee pubkey, and fee quote mint ATA
- Keep utils helpers and constants aligned with on-chain layout (2026-04 upgrade)
Made-with: Cursor
- Enable quinn feature runtime-tokio so QUIC clients (Speedlanding/Soyas) initialize instead of failing with "no async runtime found".
- Align Speedlanding with the official client: use solana_tls_utils new_dummy_x509_certificate and SkipServerVerification; use fixed TLS SNI "speed-landing" (not the PoP hostname).
- Raise per-SWQoS init timeout to 15s; always log init failures/timeouts; if every SWQoS client fails, fall back to Default RPC and print active channel labels.
- Improve SWQoS submission visibility (Default RPC path and related logging); adjust Soyas TLS credential handling where applicable.
Made-with: Cursor
- Pass token_a/token_b program based on swap direction (is_a_in) when
creating the output mint ATA in buy flow, matching ATA address derivation
for SPL Token vs Token-2022 mints.
- Resolve token_a_program and token_b_program from on-chain mint account
owners in MeteoraDammV2Params::from_pool_address_by_rpc instead of
hardcoding the classic SPL Token program.
Fixes incorrect ATA creation for Token-2022 output mints (issue #99).
Made-with: Cursor
- Extend SWQOS regional constants to 10 entries (Dublin, Singapore, Default)
so indices match SwqosRegion 0..9 everywhere
- Document geographic nearest-fill among published PoPs; call out Default and
single-PoP provider constraints in comments
- Fix Node1 QUIC Default endpoint to match HTTP; tune ZeroSlot/Blox/Speedlanding
regional mappings where a closer published PoP exists
- Add unit tests for table length and Astralane/Node1 cross-table consistency
- Update SwqosRegion rustdoc, README, and trading_client example accordingly
Made-with: Cursor
- Always derive bonding curve and associated bonding vault from mint (avoids stale cached PDAs / wrong pool).
- Use non-default fee_recipient from parser/stream as account #2 without extra authorization filtering (addresses Pump 6000 NotAuthorized when event fee was rejected).
- Rework resolve_creator_vault_for_ix: default-creator cases, phantom default PDA, fee-sharing vault; add unit tests.
- PumpFunParams: respect explicit mayhem_mode from events; fallback creator_vault via get_creator_vault_pda when resolve returns None.
- Remove unused lazy_static tip-cache stub and lazy_static dependency.
Made-with: Cursor
- Add get_fee_sharing_config_pda per @pump-fun/pump-sdk (sharing-config + mint under pump-fees)
- resolve_creator_vault_for_ix now takes mint: accept gRPC creator_vault when it matches either
PDA(creator) or PDA(fee_sharing_config(mint)) for migrated / fee-sharing bonding curves
- Mayhem uses the same Pump buy/sell account layout (IDL); Token-2022 + fee pool differ, not creator_vault seeds
Made-with: Cursor
- Add resolve_creator_vault_for_ix: use gRPC creator_vault only when it matches
PDA(['creator-vault', bonding_curve.creator]); on mismatch use derived PDA to
avoid ConstraintSeeds (2006) from stale/wrong stream vaults
- pump_fun_fee_recipient_meta: only use event fee_recipient if authorized for
is_mayhem_mode (avoids 6000 NotAuthorized when standard AMM fee is used on
Mayhem coins)
- Align PumpFunParams::from_trade/from_dev_trade cached creator_vault with the
same resolution logic
Made-with: Cursor
- Use protocol_params.creator_vault for buy/sell when non-default to avoid
ConstraintSeeds (2006) when gRPC/event creator drifts from on-chain bonding curve
- Preserve event creator_vault in PumpFunParams::from_trade/from_dev_trade instead
of always overwriting with PDA(creator)
- Fee recipient: prefer gRPC fee_recipient; else random from CURRENT_FEE_RECIPIENTS
pool (standard) or MAYHEM pool, matching @pump-fun/pump-sdk fees.ts
- Add cold-path helpers: extend_bonding_curve_account_instruction, PUMP_BONDING_CURVE_MIN_DATA_LEN
Made-with: Cursor
- PumpFunParams::from_trade/from_dev_trade: add mayhem_mode Option; infer Mayhem
via is_mayhem_fee_recipient when None (fixes fee recipient / NotAuthorized 6000
when AMM protocol fee pubkey is used).
- Add is_mayhem_fee_recipient and is_amm_fee_recipient helpers in pumpfun utils.
- Bump solana-* crates to 3.1.12; align tonic/prost; use solana-message for
AddressLookupTableAccount; add solana-system-interface 3.0.
- Update examples and latency script for new PumpFunParams signature.
- SWQoS and transaction builder adjustments for dependency changes.
Made-with: Cursor
- Add `mev_protection: bool` to `TradeConfig` and `InfrastructureConfig` (default: false)
- Astralane QUIC: switches to port 9000 (MEV-protected endpoint) when enabled
- BlockRazor HTTP: uses `mode=sandwichMitigation` query param when enabled
- BlockRazor gRPC: uses `mode=sandwichMitigation` when enabled
- Add `SWQOS_ENDPOINTS_ASTRALANE_QUIC_MEV` constants (port 9000) to `constants/swqos.rs`
- Fix `astralane_quic.rs` IP candidates to use the actual port from the address (supports both 7000 and 9000)
- Refactor `TradeConfig` to builder pattern via `TradeConfig::builder()`
- Introduce `TradeConfigBuilder` with all optional fields and clear defaults
- `TradeConfig::new()` kept as a shortcut (calls `builder().build()`) for backward compatibility
- Remove old `with_wsol_ata_config` / `with_check_min_tip` / `with_swqos_cores_from_end` / `with_mev_protection` chain methods
- Update all 16 examples to use `TradeConfig::builder()` with commented-out options so users can discover all available settings at a glance
- Update README.md and README_CN.md code snippets to use builder pattern
🤖 Generated with [Qoder][https://qoder.com]
Changes to src/utils/calc/common.rs:
- Add MAX_SLIPPAGE_BASIS_POINTS constant (9999 = 99.99%)
- Clamp basis_points in calculate_with_slippage_buy to prevent amount doubling
Changes to src/instruction/pumpswap.rs:
- Fix WSOL wrap amount calculation for exact vs non-exact input modes
- Use input_amount for exact mode, sol_amount for non-exact mode
🤖 Generated with [Qoder][https://qoder.com]
- Store gRPC client as Arc<ArcSwap<BlockRazorGrpcClient>> so BlockRazorBackend
can derive Clone (ArcSwap does not implement Clone).
- Cloning the outer Arc gives owned handles for tokio::spawn in start_ping_task,
fixing E0521 (borrowed self escaping to 'static future).
Made-with: Cursor
主要解决问题:
当 wait_tx_confirmed 参数为 false 时,SUBMIT_TIMEOUT_SECS 等待改为2秒,要么全部提交完成返回,要么不等待提交未返回的交易,方便快速创建交易订单。
#Let the SDK print submit_timing data (SwqosType and submission time) and return it to the buy and sell methods.
Main problems solved:
When the wait_tx_confirmed parameter is false, the SUBMIT_TIMEOUT_SECS wait is changed to 2 seconds. Either all submissions are completed and returned, or the unreturned transactions are not waited for submission, which facilitates the rapid creation of transaction orders.
Implement lock-free reconnection mechanism using ArcSwap to maintain high
performance for transaction sending while providing automatic recovery
from connection failures.
Key improvements:
- Add automatic reconnection with exponential backoff (1s -> 60s max)
- Use ArcSwap for lock-free atomic reference swapping
- Zero overhead for send_transaction path: only atomic load operation
- Background health check every 30s triggers reconnection on failure
- Reconnection success resets backoff delay to 1s
This ensures transaction sending remains fast (no lock contention) while
providing automatic recovery from network issues or server restarts.
Inspired by the proven reconnection pattern in sol-parser-sdk.
DataSize(252) filter rejects Token2022 PumpSwap pools which are 643 bytes.
This causes find_by_mint to miss pools that already exist on-chain, leading
to unnecessary 45s+ wait loops during migration detection.
Remove the DataSize filter from find_by_base_mint and find_by_quote_mint —
the Memcmp filter on program-owned accounts is specific enough.
Also add diagnostic logging on find_by_mint failure for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When using find_by_mint() followed by from_pool_address_by_rpc(), the pool
account is fetched twice via getAccount — once in find_by_mint (which returns
the decoded Pool) and again in from_pool_address_by_rpc.
from_pool_data() accepts a pre-decoded Pool reference and only fetches the
2 token balance RPC calls, saving 1 getAccount per trade.
from_pool_address_by_rpc() now delegates to from_pool_data() internally,
so existing callers are unaffected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prefer IPv4 candidates (including official Astralane direct IPs), bind local QUIC socket by remote address family, and rotate reconnect attempts across candidates for higher reliability under DNS/Cloudflare variance.
Made-with: Cursor
- Add SWQOS_ENDPOINTS_BLOCKRAZOR_GRPC with correct gRPC endpoints
- Add Grpc variant to SwqosTransport enum
- Add get_endpoint_with_transport() method for transport-aware endpoint selection
- Change BlockRazor default: transport=None now uses gRPC (was HTTP)
- Require explicit Some(SwqosTransport::Http) for HTTP fallback
This fixes the issue where BlockRazor was incorrectly using HTTP endpoints
when gRPC was intended. Users can now use gRPC by default or explicitly
specify HTTP transport when needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Major changes:
- Switch BlockRazor default transport from gRPC to HTTP to avoid FRAME_SIZE_ERROR
- gRPC mode still available via explicit SwqosTransport configuration
- Fix ZeroSlot Binary-Tx JSON-RPC 2.0 response parsing
- Properly handle success responses with "result" field
- Properly handle error responses with "error" field containing code and message
- Update version to 4.0.0
- Update README files to reflect new version
Technical details:
- BlockRazor: HTTP mode is now the default (SwqosTransport::Http or None)
- ZeroSlot: Parse JSON-RPC 2.0 format responses instead of plain text
- Proto code: Pre-generated gRPC code for BlockRazor (no protoc required)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add BlockRazorBackend enum to support both gRPC and HTTP transport
- Default to gRPC for better performance, HTTP available via explicit selection
- Update SwqosConfig::BlockRazor to accept optional SwqosTransport parameter
- Implement keep-alive ping task for both gRPC and HTTP backends
- Follow same backend pattern as Astralane (Quic/Http) and Node1 (Quic/Http)
- Manual gRPC client implementation to avoid proto compilation issues
Usage:
- Default: BlockRazorClient::new() uses gRPC
- HTTP: BlockRazorClient::new_http() for HTTP transport
- Config: SwqosConfig::BlockRazor(token, region, url, None) for gRPC
- Config: SwqosConfig::BlockRazor(token, region, url, Some(Http)) for HTTP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>