Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ src/pumpfun_cli/
├── crypto.py # AES-256-GCM wallet encryption (scrypt KDF)
├── output.py # render() + error() — TTY-aware output
├── commands/ # Thin CLI layer (config, info, launch, tokens, trade, tx_status, wallet)
├── core/ # Business logic (config, info, launch, pumpswap, tokens, trade, tx_status, validate, wallet)
└── protocol/ # Solana primitives (address, client, contracts, curve, idl_parser, instructions, pumpswap, quote)
├── core/ # Business logic (config, cpmm, info, launch, launchlab, pumpswap, tokens, trade, tx_status, validate, wallet)
└── protocol/ # Solana primitives (address, client, contracts, cpmm, curve, idl_parser, instructions, launchlab, pumpswap, quote)

tests/
├── test_commands/ # CLI smoke tests
Expand All @@ -68,7 +68,7 @@ tests/

**Error handling:** `error()` raises `SystemExit` — code after it is unreachable. Core functions return `dict` with `"error"` key for expected failures (graduated tokens, not found, slippage exceeded, insufficient_balance). Catch `ValueError` for wrong wallet password in every command that decrypts. Buy/sell functions perform pre-trade balance validation — SOL for fees and rent, the quote asset for the spend, token balance for sells with specific amounts.

**Auto-routing:** When `buy_token` / `sell_token` returns `{"error": "graduated"}`, the command layer in `commands/trade.py` automatically retries via `buy_pumpswap` / `sell_pumpswap` (PumpSwap AMM). Never suppress or swallow the `"graduated"` error in core — the routing decision belongs in the command layer.
**Auto-routing:** `_route` in `commands/trade.py` picks the venue from the errors core returns: `{"error": "graduated"}` from `buy_token` / `sell_token` retries on PumpSwap, `{"error": "not_found"}` retries on LaunchLab (`buy_launchlab` / `sell_launchlab`), and LaunchLab's `graduated` retries on Raydium CPMM (`buy_cpmm` / `sell_cpmm`). `--force-amm` tries PumpSwap and falls back to CPMM only when PumpSwap reports no pool (`pool_missing`); PumpSwap's error stands unless CPMM recognises the coin as LaunchLab's. Never suppress or swallow those errors in core — the routing decision belongs in the command layer. `info` falls back to LaunchLab, and to its CPMM pool, the same way in `core/info.py`.

**Imports:** stdlib → third-party → local. Example:
```python
Expand Down Expand Up @@ -130,6 +130,21 @@ lists; these are the ones this CLI depends on.
pool, then `pool-v2` **only on a canonical pool** (`coin_creator` set), then the
buyback recipient and its quote ATA last. The canonical pool is derived from the
mint and the curve's quote mint.
- `migrate_v2` also carries accounts the IDL omits: after its 27, the new pool's
boost vault authority (`["boost_vault", pool]` on PumpSwap) and that
authority's quote token account. Without them PumpSwap reverts with 6027.
- LaunchLab (`protocol/launchlab.py`) pools are keyed by base and quote mint.
The SOL pool is derived; any other quote is found by `getProgramAccounts`
on the base mint (PoolState offset 205). Swaps are the IDL's 15 accounts plus
the system program and the platform and creator fee vaults
(`[platform_config, quote]`, `[creator, quote]`). The curve fee is
GlobalConfig's trade fee plus PlatformConfig's fee and creator fee, read
live; a Token-2022 coin's transfer fee comes off what arrives.
- A graduated LaunchLab coin's CPMM pool (`protocol/cpmm.py`) is derived from
its PlatformConfig's `cpswap_config` and the two mints sorted by bytes.
Reserves are the vault balances less the protocol, fund and creator fees the
pool still owes. Both sides are `swap_base_input`, input token's accounts
first. `migrate_type` 0 (AMM v4) is refused.
- `send_tx` sends v0 and falls back to v1 when the transaction exceeds 1232
bytes (a cashback-pool buy, a launch with a buy). v1 carries the compute budget
in its header.
Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pumpfun-cli

A standalone command-line tool for launching and trading tokens with the [pump.fun](https://pump.fun).
A standalone command-line tool for launching and trading tokens on [pump.fun](https://pump.fun), and for trading coins launched on Raydium LaunchLab (letsbonk.fun, StonkFun and other launchpads).

Built from experience developing [pumpfun-bot](https://github.com/chainstacklabs/pumpfun-bonkfun-bot), distilled into a stateless CLI designed for humans, scripts, and LLM agents alike.

Expand Down Expand Up @@ -73,6 +73,17 @@ pump.fun no longer accepts anonymous metadata uploads, so `launch` takes the
URI of a metadata JSON you host (`name`, `symbol`, `description`, `image`).
Without `--uri` it still tries the upload and says so when it is refused.

`buy`, `sell` and `info` also work on coins launched on Raydium LaunchLab, the
program behind letsbonk.fun, StonkFun and other launchpads: a mint with no
pump.fun bonding curve is looked up there, and the output says `"venue":
"launchlab"` and which launchpad (`platform`). A LaunchLab coin priced in
anything but SOL is found with `getProgramAccounts`, so it needs an RPC that
serves it. Token-2022 transfer fees are taken into account in the amounts.
Once a LaunchLab coin graduates it trades on Raydium CPMM (`"venue": "cpmm"`),
the same way a graduated pump.fun coin trades on PumpSwap; `--force-amm` goes
straight to whichever pool the coin has. A coin that graduated to Raydium AMM
v4 instead is refused.

Amounts are in the coin's quote asset. Most pump.fun coins are priced in SOL,
but a coin can be priced in USDC or another admitted mint; `buy`, `sell` and
`info` read it from the coin and label their output with it. Buying such a coin
Expand All @@ -89,6 +100,12 @@ without `tokens_received_raw`, so check for the field. Dry-run fields
(`expected_tokens_raw`, `expected_quote_raw`, `min_*_out_raw`) are exact
integers of the quote the transaction would be built from, not executed amounts.

A bonding curve pays a sell out of its real reserves, which on some coins hold
less than the price promises. `sell` then sells the most the curve can pay for
and reports the rest as `tokens_kept`; when it can pay nothing it sends nothing
(`curve_short`). A buy bigger than what is left on the curve fails with
`exceeds_curve` and the tokens left in `curve_tokens_left`.

## Commands

```
Expand All @@ -100,7 +117,7 @@ Token discovery (zero config; price and progress added when an RPC is set):
pumpfun tokens search <query> Search by name or ticker

Token info (requires RPC):
pumpfun info <mint> Price, bonding progress, reserves
pumpfun info <mint> Price, bonding progress, reserves (pump.fun or LaunchLab, and their pools)

Trading (requires RPC + wallet):
pumpfun buy <mint> <amount> [--slippage N] [--force-amm] [--dry-run] [--confirm]
Expand Down Expand Up @@ -151,9 +168,9 @@ Global options:
| `tokens recommended` | `frontend-api-v3.pump.fun/coins/recommendations` | HTTPS |
| `tokens search` | `frontend-api-v3.pump.fun/coins/search-unrestricted` | HTTPS |
| `tokens *` with an RPC set | + Solana RPC (`getMultipleAccounts` for the bonding curves) | JSON-RPC |
| `info` | Solana RPC (`getAccountInfo` for bonding curve PDA) | JSON-RPC |
| `buy` | Solana RPC (`getAccountInfo`, `getLatestBlockhash`, `sendTransaction`) | JSON-RPC |
| `sell` | Solana RPC (`getAccountInfo`, `getTokenAccountBalance`, `getLatestBlockhash`, `sendTransaction`) | JSON-RPC |
| `info` | Solana RPC (`getAccountInfo` for bonding curve PDA; LaunchLab: `getMultipleAccounts`, `getProgramAccounts`) | JSON-RPC |
| `buy` | Solana RPC (`getAccountInfo`, `getLatestBlockhash`, `sendTransaction`; LaunchLab adds `getMultipleAccounts`, `getProgramAccounts`) | JSON-RPC |
| `sell` | Solana RPC (`getAccountInfo`, `getTokenAccountBalance`, `getLatestBlockhash`, `sendTransaction`; LaunchLab as `buy`) | JSON-RPC |
| `launch` | Solana RPC (`sendTransaction`); `pump.fun/api/ipfs` only without `--uri` | JSON-RPC (+ HTTPS) |
| `tx-status` | Solana RPC (`getTransaction`) | JSON-RPC |
| `migrate` | Solana RPC (`sendTransaction`) | JSON-RPC |
Expand Down
Loading
Loading