From ef1a52c40b7366be098c080e243a3ae0898d01ee Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 11:51:28 +0800 Subject: [PATCH 1/9] docs: add CHANGELOG.md Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..85bfdad --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,89 @@ +# Changelog + +All notable changes to `@bankofai/sun-cli` are documented in this file. Format +loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this +project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.0] — 2026-05-22 + +End-to-end SunPump support: read-only discovery for the SunPump meme-token +launchpad, plus on-chain trading against the SunPump bonding-curve contract +via `sun-kit`. Mainnet only. + +### Added + +#### `sun sunpump` — read-only data (no wallet required) + +- `token list` — paginated token list with filters and sort +- `token get ` — token detail (price, market cap, holders, social links, listed CEXes); human mode prints a labelled key/value view, `--json` returns the raw object +- `token search ` / `token search-v2 ` — fuzzy search +- `token by-owner ` — tokens created by a wallet +- `token holders ` / `token holders-v2 ` — top holders with a `Type` column distinguishing pools from users +- `token favors` — signed-message favourites lookup +- `token ranking --type MARKET_CAP|VOLUME_24H|PRICE_CHANGE_24H` +- `token king-of-hill` +- `token pump-list` — raw SunSwap-compatible token list +- `tx token ` / `tx user ` — swap history with filters +- `portfolio ` — wallet's SunPump positions with TRX value + +#### `sun sunpump` — on-chain trading (wallet required) + +- `state ` — on-chain state with named label: `0 NOT_EXIST` / `1 TRADING` / `2 READY_TO_LAUNCH` / `3 LAUNCHED` +- `quote-buy --trx ` — read-only buy preview +- `quote-sell --amount [--decimals 18]` — read-only sell preview +- `buy --trx [--slippage 0.05] [--min-out ]` — spend TRX, receive tokens +- `sell --amount [--decimals 18] [--slippage 0.05] [--min-out ]` — sell tokens for TRX (auto-handles first-time TRC20 approval) + +All write commands go through `writeAction`: wallet check → signed summary → confirmation prompt → broadcast → Tronscan link. `--dry-run` and `--yes` work as expected. Decimal inputs (`--trx 10`, `--amount 1000`) are scaled internally by `1e6` (TRX → Sun) and `10^decimals` (tokens → raw uint256). Buy/sell summaries pre-fetch a quote so the user sees expected output and fee before confirming. + +#### Output & formatting improvements + +- New table configs: `tokenTable` with a `tokenPriceUsd` fallback (no more `$0` rows when the API omits the TRX/USD rate — falls back to `marketCap / totalSupply`); `holderTable` reading the correct `percentage` field with auto-detected fraction/percent units; `portfolioTable`; key/value detail view for `token get`. +- `extractList` recognises `tokens` (alongside the existing `swaps`/`holders`); `readPagination` descends into `pageData` / `metadata` and treats `size` as a `pageSize` alias. +- HTTP errors from SunPump now surface the API's `msg` field, e.g. `SunPump request failed: 400 Bad Request (/token/getRanking) — Validation error: No enum constant ...`. + +### Breaking + +- **Nile testnet removed** for SunPump. The host (`tn-api.sunpump.meme`) is internal-only and the test deployment is being retired. Every `sunpump` subcommand throws on non-mainnet: + + ``` + SunPump is only available on mainnet (got "nile"). + Drop --network or pass --network mainnet. + ``` + + `sun swap`, `sun price`, `sun pool …` and other non-SunPump commands continue to support nile / shasta. + +- **Trimmed API surface.** The following were intentionally removed (not core to trading/discovery): + + | Removed | Reason | + |---|---| + | `sunpump home` (`stats` / `data` / `banners`) | Site-chrome data | + | `sunpump tx ticker` | Server hard-capped at ~15 rows | + | `sunpump kline` (`v1` / `v2` / `v3`) | Three near-identical OHLCV variants | + | `sunpump red-packet` (`get` / `remain` / `by-user` / `summary`) | Sun Agent campaign feature | + | `sunpump campaign` (`list` / `banners`) | Marketing banners | + | `sunpump referral` (`rewards` / `invites`) | Back-office reporting | + | `sunpump admin-summary` | Requires an admin password | + | `sunpump quota` | Third-platform integration, internal | + +### Notes & gotchas + +- **State enum off-by-one.** `sun-kit`'s exported `SunPumpTokenState` lists `LAUNCHED = 2`, but the on-chain contract returns `3` for tokens that have migrated to SunSwap. The CLI re-labels: state `3` prints as `LAUNCHED (3)`. Trust the printed label, not the raw int. +- **Quotes ignore on-chain state.** `quote-buy` returns a price even for `LAUNCHED` tokens (and `quote-sell` may revert with `REVERT opcode executed`). The actual `buy` / `sell` pre-checks state and throws `SUNPUMP_LAUNCHED` cleanly — call `sunpump state` first if you're routing logic. +- **First sell ≠ one transaction.** When the wallet has zero allowance, the SDK auto-sends `approve(SunPump, 2^256-1)` before the sell tx. Only the final sell tx hash is returned in `tronscanUrl`. +- **Default slippage** for bonding-curve trading is `0.05` (5%) — meme tokens are volatile. Tighten with `--slippage 0.005` or pass `--min-out ` for an exact floor. + +### Companion release + +[`sunpump-agent-skill`](https://github.com/BofAI/skills/tree/main/sunpump-agent-skill) +**v1.2.0** ships in parallel — pins this CLI version, documents the new +`buy/sell/quote-*/state` commands as the pre-launch trade path with `sun swap` +as the post-launch path, and updates pre-validation checklists to enforce +`--network mainnet`. + +Install: + +```bash +npm install -g @bankofai/sun-cli@^1.2.0 +npx skills add BofAI/skills +``` From 41b8e2b8eac4c67da8c8acfab54b7c16301c1518 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 14:08:10 +0800 Subject: [PATCH 2/9] Revert "refactor(sunpump): drop nile testnet (mainnet only)" This reverts commit adc1177a03d43d915b423c2ff442321fbc9e1fac. The nile host tn-api.sunpump.meme is publicly reachable now (verified: HTTP 200 with valid token data; `sun --network nile sunpump token list` works end-to-end), so the nile/mainnet switch is back. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 10 +++++----- src/commands/sunpump.ts | 21 ++++++++------------- src/lib/sunpump.ts | 32 +++++++++++++++++++++++++------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6c58c91..20151f3 100644 --- a/README.md +++ b/README.md @@ -361,9 +361,8 @@ holder portfolios) plus on-chain trade commands (`buy`/`sell`/`quote-buy`/`quote-sell`/`state`) that talk to the bonding-curve contract through `sun-kit`. Read-only API calls need no wallet; trade commands do. -SunPump is **mainnet only** — both the API host (`https://api-v2.sunpump.meme/pump-api`) -and the on-chain bonding-curve contract. Passing `--network nile` (or any non-mainnet -value) to a `sunpump` subcommand will fail fast. +- Mainnet (default): `https://api-v2.sunpump.meme/pump-api` +- Nile testnet: `https://tn-api.sunpump.meme/pump-api` — use the global `--network nile` flag ```bash sun sunpump token king-of-hill # current king-of-the-hill token @@ -400,8 +399,9 @@ move fast); pass `--slippage 0.005` for 0.5% or `--min-out ` for an exact f base units. Endpoints requiring a signed message (`favors`) accept `--user-address`, -`--signature`, `--signed-message` flags. Override the base URL with -`SUNPUMP_API_BASE_URL` only when you have a custom mainnet-compatible host. +`--signature`, `--signed-message` flags. Switch to nile testnet with +`sun --network nile sunpump ...`, or override the base URL with `SUNPUMP_API_BASE_URL` +for a custom host. --- diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 6b42a62..0e22ece 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -13,7 +13,7 @@ import { formatAmount, formatPct, } from '../lib/output' -import { getSunPump, SunPump } from '../lib/sunpump' +import { getSunPump, SunPump, SunPumpNetwork } from '../lib/sunpump' // --------------------------------------------------------------------------- // pumpAction — local mirror of readApiAction but for the SunPump client. @@ -31,19 +31,11 @@ interface PumpActionOpts { detailFor?: (data: any) => Record | null } -function assertMainnet(): void { - const n = getNetwork() - if (n && n !== 'mainnet') { - throw new Error( - `SunPump is only available on mainnet (got "${n}"). Drop --network or pass --network mainnet.`, - ) - } -} +let currentNetwork: SunPumpNetwork = 'mainnet' async function pumpAction(opts: PumpActionOpts): Promise { try { - assertMainnet() - const client = getSunPump() + const client = getSunPump(currentNetwork) const raw = await withSpinner(opts.spinnerLabel, () => opts.execute(client)) let data: unknown = raw @@ -278,9 +270,12 @@ const portfolioTable = { export function registerSunpumpCommands(program: Command) { const sp = program .command('sunpump') - .description('SunPump endpoints (mainnet only: api-v2.sunpump.meme).') + .description( + 'SunPump read-only endpoints (mainnet: api-v2.sunpump.meme, nile: tn-api.sunpump.meme). Use global --network nile for testnet.', + ) .hook('preAction', () => { - assertMainnet() + const n = program.opts().network ?? process.env.TRON_NETWORK + currentNetwork = n === 'nile' ? 'nile' : 'mainnet' }) // -------------------------- token group ---------------------------------- diff --git a/src/lib/sunpump.ts b/src/lib/sunpump.ts index a063c5c..11c0c46 100644 --- a/src/lib/sunpump.ts +++ b/src/lib/sunpump.ts @@ -1,7 +1,8 @@ /** * SunPump API client — read-only GET endpoints. * - * Mainnet only: https://api-v2.sunpump.meme/pump-api + * - Mainnet: https://api-v2.sunpump.meme/pump-api + * - Nile testnet: https://tn-api.sunpump.meme/pump-api * * Standalone from sun-kit's SunAPI: SunPump is a separate service with its own * base URL and schema. Uses Node's global fetch (>=18). @@ -9,10 +10,19 @@ * Methods mirror the OpenAPI operationIds documented in docs/sunpump-api.md. */ -export const SUNPUMP_DEFAULT_BASE_URL = 'https://api-v2.sunpump.meme/pump-api' +export const SUNPUMP_MAINNET_BASE_URL = 'https://api-v2.sunpump.meme/pump-api' +export const SUNPUMP_NILE_BASE_URL = 'https://tn-api.sunpump.meme/pump-api' +export const SUNPUMP_DEFAULT_BASE_URL = SUNPUMP_MAINNET_BASE_URL + +export type SunPumpNetwork = 'mainnet' | 'nile' + +export function sunPumpBaseUrlFor(network: SunPumpNetwork): string { + return network === 'nile' ? SUNPUMP_NILE_BASE_URL : SUNPUMP_MAINNET_BASE_URL +} export interface SunPumpClientOptions { baseUrl?: string + network?: SunPumpNetwork fetchImpl?: typeof fetch } @@ -47,7 +57,11 @@ export class SunPump { private readonly fetchImpl: typeof fetch constructor(opts: SunPumpClientOptions = {}) { - const base = opts.baseUrl ?? process.env.SUNPUMP_API_BASE_URL ?? SUNPUMP_DEFAULT_BASE_URL + const base = + opts.baseUrl ?? + (opts.network ? sunPumpBaseUrlFor(opts.network) : undefined) ?? + process.env.SUNPUMP_API_BASE_URL ?? + SUNPUMP_DEFAULT_BASE_URL this.baseUrl = base.replace(/\/+$/, '') this.fetchImpl = opts.fetchImpl ?? fetch } @@ -235,9 +249,13 @@ export class SunPump { } } -let _client: SunPump | null = null +const _clients = new Map() -export function getSunPump(): SunPump { - if (!_client) _client = new SunPump() - return _client +export function getSunPump(network: SunPumpNetwork = 'mainnet'): SunPump { + let c = _clients.get(network) + if (!c) { + c = new SunPump({ network }) + _clients.set(network, c) + } + return c } From af27ec9f82bc9d2f310a2b0af47eb68282bb2ffa Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 14:08:29 +0800 Subject: [PATCH 3/9] feat(sunpump): add launch command (agent token launch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New `sun sunpump launch` creates a token through the SunPump agent endpoint (POST /ai/agentTokenLaunch) — server-side creation, no local wallet or signing: - lib/sunpump.ts: add post() (shares send() error handling with GET) and agentTokenLaunch(); export AgentTokenLaunchParams. - commands/sunpump.ts: `launch` with required --name/--symbol, optional --description, --image (read + base64-encoded) or --image-base64, social URLs, --tweet-username. Irreversible action: prints a summary and asks for confirmation (--yes skips), honours --dry-run. Success view reuses tokenDetail plus Create Tx / Logo. The launch response serializes *Instant fields as epoch-millis/1e6 (unlike GET endpoints' epoch seconds) — normalized before display. - README: document the launch command. Verified on nile: launched TFAC (TXsGnUGNRoZ1AqyHfak1vvbayxo9JqZg3J, tx 8fecad1b…) and queried it back via `token get`. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 13 ++++++ src/commands/sunpump.ts | 96 ++++++++++++++++++++++++++++++++++++++++- src/lib/sunpump.ts | 41 +++++++++++++++++- 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20151f3..79a4ff8 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,19 @@ sun sunpump tx user --size 20 # swap history for a wallet sun sunpump portfolio --include-zero ``` +Launch a new token through the SunPump agent endpoint (server-side creation — +no wallet needed; asks for confirmation, `--yes` to skip, `--dry-run` to preview): + +```bash +sun sunpump launch --name MyToken --symbol MTK \ + --description "my meme token" --image ./logo.png \ + --twitter-url https://x.com/mytoken --website-url https://mytoken.xyz +``` + +`--image ` reads a local file and sends it as base64; pass `--image-base64` +to supply the encoded string directly. On success the CLI prints the new token's +contract address and creation tx hash. + Trade on the bonding curve (requires a wallet; pre-launch tokens only — once a token migrates to SunSwap, use `sun swap` instead): diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 0e22ece..22e4ee1 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -1,5 +1,6 @@ import { Command } from 'commander' -import { parseApiResponse, writeAction } from '../lib/command' +import { isDryRun, parseApiResponse, writeAction } from '../lib/command' +import { confirm, printSummary } from '../lib/confirm' import { getNetwork, getKit } from '../lib/context' import { output, @@ -595,6 +596,99 @@ export function registerSunpumpCommands(program: Command) { }) + // -------------------------- launch (agent token launch) ------------------ + sp.command('launch') + .description( + 'Launch a new token via the SunPump agent endpoint (server-side creation, no wallet needed)', + ) + .requiredOption('--name ', 'Token name') + .requiredOption('--symbol ', 'Token symbol') + .option('--description ', 'Token description') + .option('--image ', 'Logo image file (read and sent as base64)') + .option('--image-base64 ', 'Logo image as a raw base64 string (overrides --image)') + .option('--twitter-url ', 'Twitter URL') + .option('--telegram-url ', 'Telegram URL') + .option('--website-url ', 'Website URL') + .option('--tweet-username ', 'Tweet username to associate with the launch') + .action(async (opts) => { + let imageBase64: string | undefined = opts.imageBase64 + let imageLabel = imageBase64 ? `base64 (${imageBase64.length} chars)` : undefined + if (!imageBase64 && opts.image) { + try { + const { readFile } = await import('fs/promises') + const buf = await readFile(opts.image) + imageBase64 = buf.toString('base64') + imageLabel = `${opts.image} (${buf.length} bytes)` + } catch (err: any) { + outputError('Failed to read --image file', err) + return + } + } + + const params = { + name: opts.name, + symbol: opts.symbol, + description: opts.description ?? '', + imageBase64, + twitterUrl: opts.twitterUrl ?? '', + telegramUrl: opts.telegramUrl ?? '', + websiteUrl: opts.websiteUrl ?? '', + tweetUsername: opts.tweetUsername ?? '', + } + + if (isDryRun()) { + output({ + dryRun: true, + action: 'SunPump Agent Token Launch', + params: { ...params, imageBase64: imageLabel }, + }) + return + } + + printSummary('SunPump Agent Token Launch', { + Name: params.name, + Symbol: params.symbol, + Description: params.description, + Image: imageLabel, + Twitter: params.twitterUrl, + Telegram: params.telegramUrl, + Website: params.websiteUrl, + 'Tweet User': params.tweetUsername, + Network: currentNetwork, + }) + const confirmed = await confirm('Launch this token?') + if (!confirmed) { + if (!isJsonMode()) console.log('Cancelled.') + return + } + + try { + const client = getSunPump(currentNetwork) + const raw = await withSpinner('Launching token...', () => client.agentTokenLaunch(params)) + const { data } = parseApiResponse(raw) + if (isJsonMode()) { + output(data) + return + } + // Unlike the GET endpoints (plain epoch seconds), the launch endpoint + // serializes *Instant fields as epoch-millis / 1e6 (e.g. 1780476.327). + // Normalize to epoch seconds so tokenDetail/formatTime render correctly. + for (const k of ['tokenCreatedInstant', 'tokenLaunchedInstant', 'firstReachHillInstant']) { + const v = Number(data?.[k]) + if (Number.isFinite(v) && v > 0 && v < 1e8) data[k] = v * 1000 + } + const pairs = tokenDetail(data) ?? {} + if (data?.createTxHash) pairs['Create Tx'] = data.createTxHash + if (data?.logoUrl) pairs['Logo'] = data.logoUrl + const chalk = (await import('chalk')).default + console.log() + console.log(chalk.green('Token launched')) + printKeyValue(pairs) + } catch (err: any) { + outputError('Launch failed', err) + } + }) + // -------------------------- trade (buy/sell/quote/state) ----------------- sp.command('state ') .description( diff --git a/src/lib/sunpump.ts b/src/lib/sunpump.ts index 11c0c46..5560b3c 100644 --- a/src/lib/sunpump.ts +++ b/src/lib/sunpump.ts @@ -29,6 +29,18 @@ export interface SunPumpClientOptions { type QueryValue = string | number | boolean | null | undefined export type Query = Record +export interface AgentTokenLaunchParams { + name: string + symbol: string + description?: string + /** Logo image content as a base64 string (no data-URI prefix). */ + imageBase64?: string + twitterUrl?: string + telegramUrl?: string + websiteUrl?: string + tweetUsername?: string +} + export class SunPumpHttpError extends Error { readonly code = 'SUNPUMP_HTTP_ERROR' constructor( @@ -67,11 +79,23 @@ export class SunPump { } async request(path: string, query?: Query): Promise { - const url = `${this.baseUrl}${path}${buildQueryString(query)}` - const res = await this.fetchImpl(url, { + return this.send(path, buildQueryString(query), { method: 'GET', headers: { Accept: 'application/json' }, }) + } + + async post(path: string, body: unknown): Promise { + return this.send(path, '', { + method: 'POST', + headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + } + + private async send(path: string, queryString: string, init: RequestInit): Promise { + const url = `${this.baseUrl}${path}${queryString}` + const res = await this.fetchImpl(url, init) const text = await res.text() if (!res.ok) { const excerpt = text.length > 500 ? text.slice(0, 500) + '…' : text @@ -231,6 +255,19 @@ export class SunPump { return this.request(`/transactions/holder/${encodeURIComponent(ownerAddress)}`, query) } + // --------------------------------------------------------------------------- + // AI agent — token launch + // --------------------------------------------------------------------------- + + /** + * Launch a new token through the SunPump agent endpoint. The server creates + * the token on-chain itself — no local wallet or signing involved. Returns + * the full token object (contractAddress, createTxHash, …) in the envelope. + */ + agentTokenLaunch(params: AgentTokenLaunchParams) { + return this.post('/ai/agentTokenLaunch', params) + } + // --------------------------------------------------------------------------- // Holder portfolio // --------------------------------------------------------------------------- From 69040302a8274e2622efa520033592ba039c3534 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 14:18:16 +0800 Subject: [PATCH 4/9] docs: changelog entries for sunpump launch and nile re-enable Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85bfdad..1851a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to `@bankofai/sun-cli` are documented in this file. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- `sun sunpump launch` — create a token through the SunPump agent endpoint + (`POST /ai/agentTokenLaunch`). Server-side creation: the platform signs and + broadcasts the creation transaction, so no local wallet is needed. Required + `--name`/`--symbol`; optional `--description`, `--image ` (read and + sent as base64) or `--image-base64`, social URLs, `--tweet-username`. + Prints a summary and asks for confirmation (`--yes` skips); honours + `--dry-run`. On success prints the new token's contract address, creation + tx hash and logo URL. +- Nile testnet support is back for all `sunpump` commands (the + `tn-api.sunpump.meme` host is publicly reachable again) — switch with the + global `--network nile` flag. Reverts the 1.2.0 mainnet-only restriction. + ## [1.2.0] — 2026-05-22 End-to-end SunPump support: read-only discovery for the SunPump meme-token From c0c67ff904273fc1a65bbbcb0f0dc21e439ab556 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 14:21:13 +0800 Subject: [PATCH 5/9] docs(readme): mention sunpump launch in highlights and section intro Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79a4ff8..73f28d7 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ - **Read anything** — token prices, pools, farms, positions, transaction history, and protocol metrics - **Quote and route** — best-route quotes across SUNSwap V1/V2/V3/V4 - **Execute on-chain** — swaps, liquidity management (V2/V3/V4), and arbitrary contract writes +- **Meme tokens** — SunPump discovery, one-command token launching, and bonding-curve trading - **Automate** — JSON output, field filters, `--dry-run`, and `--yes` for non-interactive use - **Read-only out of the box** — no wallet required for queries and quotes @@ -357,9 +358,10 @@ sun contract send transfer --args '["TRecipient","1000000"]' ### SunPump Access to SunPump — read-only API for discovery (token launches, trending lists, -holder portfolios) plus on-chain trade commands -(`buy`/`sell`/`quote-buy`/`quote-sell`/`state`) that talk to the bonding-curve -contract through `sun-kit`. Read-only API calls need no wallet; trade commands do. +holder portfolios), token creation via the agent endpoint (`launch`), and +on-chain trade commands (`buy`/`sell`/`quote-buy`/`quote-sell`/`state`) that talk +to the bonding-curve contract through `sun-kit`. Read-only API calls and `launch` +need no wallet; trade commands do. - Mainnet (default): `https://api-v2.sunpump.meme/pump-api` - Nile testnet: `https://tn-api.sunpump.meme/pump-api` — use the global `--network nile` flag From 81d8013ce477b840acc78c44d009e6a3a5277cc2 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 14:22:57 +0800 Subject: [PATCH 6/9] fix(sunpump): make --description required for launch name/symbol/description are all required by the agentTokenLaunch endpoint; image and social fields stay optional. Docs updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- src/commands/sunpump.ts | 4 ++-- src/lib/sunpump.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1851a64..34f8916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `sun sunpump launch` — create a token through the SunPump agent endpoint (`POST /ai/agentTokenLaunch`). Server-side creation: the platform signs and broadcasts the creation transaction, so no local wallet is needed. Required - `--name`/`--symbol`; optional `--description`, `--image ` (read and + `--name`/`--symbol`/`--description`; optional `--image ` (read and sent as base64) or `--image-base64`, social URLs, `--tweet-username`. Prints a summary and asks for confirmation (`--yes` skips); honours `--dry-run`. On success prints the new token's contract address, creation diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 22e4ee1..85c36e9 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -603,7 +603,7 @@ export function registerSunpumpCommands(program: Command) { ) .requiredOption('--name ', 'Token name') .requiredOption('--symbol ', 'Token symbol') - .option('--description ', 'Token description') + .requiredOption('--description ', 'Token description') .option('--image ', 'Logo image file (read and sent as base64)') .option('--image-base64 ', 'Logo image as a raw base64 string (overrides --image)') .option('--twitter-url ', 'Twitter URL') @@ -628,7 +628,7 @@ export function registerSunpumpCommands(program: Command) { const params = { name: opts.name, symbol: opts.symbol, - description: opts.description ?? '', + description: opts.description, imageBase64, twitterUrl: opts.twitterUrl ?? '', telegramUrl: opts.telegramUrl ?? '', diff --git a/src/lib/sunpump.ts b/src/lib/sunpump.ts index 5560b3c..3a1dc47 100644 --- a/src/lib/sunpump.ts +++ b/src/lib/sunpump.ts @@ -32,7 +32,7 @@ export type Query = Record export interface AgentTokenLaunchParams { name: string symbol: string - description?: string + description: string /** Logo image content as a base64 string (no data-URI prefix). */ imageBase64?: string twitterUrl?: string From 7188ecb94bb0a97975a88b6251c122a3872fe7f9 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Thu, 4 Jun 2026 18:06:20 +0800 Subject: [PATCH 7/9] fix(sunpump): hint at missing logo on opaque launch 500 The agentTokenLaunch endpoint currently fails with "500 Invoke third part error" when no imageBase64 accompanies the request (verified against the nile API: same payload succeeds once an image is attached). The image stays optional per the API contract, but when that specific error comes back on an image-less launch, print a stderr hint pointing at --image so users aren't left guessing. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/sunpump.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 85c36e9..439f294 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -9,6 +9,7 @@ import { printPaginationFooter, printKeyValue, isJsonMode, + info, formatUsd, formatTime, formatAmount, @@ -686,6 +687,11 @@ export function registerSunpumpCommands(program: Command) { printKeyValue(pairs) } catch (err: any) { outputError('Launch failed', err) + // The server has been seen to return this opaque error when no logo + // image accompanies the launch — point at the likely fix. + if (!imageBase64 && String(err?.message ?? '').includes('Invoke third part error')) { + info('Hint: this error often means the launch lacked a logo — retry with --image .') + } } }) From 1ab92fbac1741a9687ff4e7a412e02b6705bb108 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Mon, 8 Jun 2026 19:28:23 +0800 Subject: [PATCH 8/9] refactor(sunpump): drop nile testnet (mainnet only) The nile host tn-api.sunpump.meme is an internal-only service, not publicly reachable, so the nile/mainnet switch never worked end-to-end for external users. Removing nile from every SunPump path: - lib/sunpump.ts: drop SUNPUMP_NILE_BASE_URL/SUNPUMP_MAINNET_BASE_URL, the SunPumpNetwork type, sunPumpBaseUrlFor(), and the network-keyed client cache. getSunPump() is back to a plain singleton; the base URL resolves from baseUrl arg -> SUNPUMP_API_BASE_URL env -> mainnet. - commands/sunpump.ts: replace the currentNetwork machinery with assertMainnet(), called from the sp preAction hook (and the launch action) so every sunpump subcommand bails fast on non-mainnet. - README + CHANGELOG: reflect mainnet-only behaviour. Verified: token list works on mainnet; --network nile fails fast with the new error. tsc clean, 106 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 5 +---- README.md | 10 +++++----- src/commands/sunpump.ts | 25 +++++++++++++++---------- src/lib/sunpump.ts | 32 +++++++------------------------- 4 files changed, 28 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34f8916..a04936f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,7 @@ project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). sent as base64) or `--image-base64`, social URLs, `--tweet-username`. Prints a summary and asks for confirmation (`--yes` skips); honours `--dry-run`. On success prints the new token's contract address, creation - tx hash and logo URL. -- Nile testnet support is back for all `sunpump` commands (the - `tn-api.sunpump.meme` host is publicly reachable again) — switch with the - global `--network nile` flag. Reverts the 1.2.0 mainnet-only restriction. + tx hash and logo URL. Mainnet only. ## [1.2.0] — 2026-05-22 diff --git a/README.md b/README.md index 73f28d7..0bafd3d 100644 --- a/README.md +++ b/README.md @@ -363,8 +363,9 @@ on-chain trade commands (`buy`/`sell`/`quote-buy`/`quote-sell`/`state`) that tal to the bonding-curve contract through `sun-kit`. Read-only API calls and `launch` need no wallet; trade commands do. -- Mainnet (default): `https://api-v2.sunpump.meme/pump-api` -- Nile testnet: `https://tn-api.sunpump.meme/pump-api` — use the global `--network nile` flag +SunPump is **mainnet only** — both the API host (`https://api-v2.sunpump.meme/pump-api`) +and the on-chain bonding-curve contract. Passing `--network nile` (or any non-mainnet +value) to a `sunpump` subcommand will fail fast. ```bash sun sunpump token king-of-hill # current king-of-the-hill token @@ -414,9 +415,8 @@ move fast); pass `--slippage 0.005` for 0.5% or `--min-out ` for an exact f base units. Endpoints requiring a signed message (`favors`) accept `--user-address`, -`--signature`, `--signed-message` flags. Switch to nile testnet with -`sun --network nile sunpump ...`, or override the base URL with `SUNPUMP_API_BASE_URL` -for a custom host. +`--signature`, `--signed-message` flags. Override the base URL with +`SUNPUMP_API_BASE_URL` only when you have a custom mainnet-compatible host. --- diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 439f294..ec97261 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -15,7 +15,7 @@ import { formatAmount, formatPct, } from '../lib/output' -import { getSunPump, SunPump, SunPumpNetwork } from '../lib/sunpump' +import { getSunPump, SunPump } from '../lib/sunpump' // --------------------------------------------------------------------------- // pumpAction — local mirror of readApiAction but for the SunPump client. @@ -33,11 +33,19 @@ interface PumpActionOpts { detailFor?: (data: any) => Record | null } -let currentNetwork: SunPumpNetwork = 'mainnet' +function assertMainnet(): void { + const n = getNetwork() + if (n && n !== 'mainnet') { + throw new Error( + `SunPump is only available on mainnet (got "${n}"). Drop --network or pass --network mainnet.`, + ) + } +} async function pumpAction(opts: PumpActionOpts): Promise { try { - const client = getSunPump(currentNetwork) + assertMainnet() + const client = getSunPump() const raw = await withSpinner(opts.spinnerLabel, () => opts.execute(client)) let data: unknown = raw @@ -272,12 +280,9 @@ const portfolioTable = { export function registerSunpumpCommands(program: Command) { const sp = program .command('sunpump') - .description( - 'SunPump read-only endpoints (mainnet: api-v2.sunpump.meme, nile: tn-api.sunpump.meme). Use global --network nile for testnet.', - ) + .description('SunPump endpoints (mainnet only: api-v2.sunpump.meme).') .hook('preAction', () => { - const n = program.opts().network ?? process.env.TRON_NETWORK - currentNetwork = n === 'nile' ? 'nile' : 'mainnet' + assertMainnet() }) // -------------------------- token group ---------------------------------- @@ -655,7 +660,6 @@ export function registerSunpumpCommands(program: Command) { Telegram: params.telegramUrl, Website: params.websiteUrl, 'Tweet User': params.tweetUsername, - Network: currentNetwork, }) const confirmed = await confirm('Launch this token?') if (!confirmed) { @@ -664,7 +668,8 @@ export function registerSunpumpCommands(program: Command) { } try { - const client = getSunPump(currentNetwork) + assertMainnet() + const client = getSunPump() const raw = await withSpinner('Launching token...', () => client.agentTokenLaunch(params)) const { data } = parseApiResponse(raw) if (isJsonMode()) { diff --git a/src/lib/sunpump.ts b/src/lib/sunpump.ts index 3a1dc47..7044c58 100644 --- a/src/lib/sunpump.ts +++ b/src/lib/sunpump.ts @@ -1,8 +1,7 @@ /** * SunPump API client — read-only GET endpoints. * - * - Mainnet: https://api-v2.sunpump.meme/pump-api - * - Nile testnet: https://tn-api.sunpump.meme/pump-api + * Mainnet only: https://api-v2.sunpump.meme/pump-api * * Standalone from sun-kit's SunAPI: SunPump is a separate service with its own * base URL and schema. Uses Node's global fetch (>=18). @@ -10,19 +9,10 @@ * Methods mirror the OpenAPI operationIds documented in docs/sunpump-api.md. */ -export const SUNPUMP_MAINNET_BASE_URL = 'https://api-v2.sunpump.meme/pump-api' -export const SUNPUMP_NILE_BASE_URL = 'https://tn-api.sunpump.meme/pump-api' -export const SUNPUMP_DEFAULT_BASE_URL = SUNPUMP_MAINNET_BASE_URL - -export type SunPumpNetwork = 'mainnet' | 'nile' - -export function sunPumpBaseUrlFor(network: SunPumpNetwork): string { - return network === 'nile' ? SUNPUMP_NILE_BASE_URL : SUNPUMP_MAINNET_BASE_URL -} +export const SUNPUMP_DEFAULT_BASE_URL = 'https://api-v2.sunpump.meme/pump-api' export interface SunPumpClientOptions { baseUrl?: string - network?: SunPumpNetwork fetchImpl?: typeof fetch } @@ -69,11 +59,7 @@ export class SunPump { private readonly fetchImpl: typeof fetch constructor(opts: SunPumpClientOptions = {}) { - const base = - opts.baseUrl ?? - (opts.network ? sunPumpBaseUrlFor(opts.network) : undefined) ?? - process.env.SUNPUMP_API_BASE_URL ?? - SUNPUMP_DEFAULT_BASE_URL + const base = opts.baseUrl ?? process.env.SUNPUMP_API_BASE_URL ?? SUNPUMP_DEFAULT_BASE_URL this.baseUrl = base.replace(/\/+$/, '') this.fetchImpl = opts.fetchImpl ?? fetch } @@ -286,13 +272,9 @@ export class SunPump { } } -const _clients = new Map() +let _client: SunPump | null = null -export function getSunPump(network: SunPumpNetwork = 'mainnet'): SunPump { - let c = _clients.get(network) - if (!c) { - c = new SunPump({ network }) - _clients.set(network, c) - } - return c +export function getSunPump(): SunPump { + if (!_client) _client = new SunPump() + return _client } From b5a4a51ca05dc3cc0ed6e30f253f6e62c31de527 Mon Sep 17 00:00:00 2001 From: "Leon.Zhang" Date: Tue, 9 Jun 2026 11:35:27 +0800 Subject: [PATCH 9/9] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba25f32..ae1d325 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bankofai/sun-cli", - "version": "1.2.0", + "version": "1.2.1", "description": "CLI tool for SUN.IO / SUNSWAP on TRON — for humans and AI agents", "main": "dist/bin.js", "type": "commonjs",