@gangfun/cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG, ADDR, listTokens, tokenInfo, quoteBuy, quoteSell, isAgent, balanceOf, allowance,
4
- calldata, decodeTx, parseEther, fmt, fmtTok, resolvePad, isAddress, client,
4
+ calldata, decodeTx, decodeLogs, parseEther, fmt, fmtTok, resolvePad, isAddress, client,
5
5
  } from './chain.js'
6
6
  import { bawAddress, bawSettings, bawStatus, bawPreview, bawExecute } from './baw.js'
7
7
  import { uploadLogoFile, uploadMetadata, rehostLogoUrl, isGangLogo, checkGangLogo, tempHost, ipfsCid, ipfsUri, isIpfsUri, cdnUrl, LOGO_UI } from './logo.js'
@@ -15,8 +15,11 @@ import { NOTE_SORTS, AGENT_WINDOWS, AGENT_SORTS, NOTE_EVENTS, noteRow, fetchNote
15
15
  import { NOTE_TX, MIN_FOLLOW_USD, REASONS, noteLink, checkFollow, amountUsd, localTime } from './follow.js'
16
16
  import { rememberPreview, previewFor } from './previews.js'
17
17
  import { UA, checkUpdate } from './version.js'
18
- import { runFair } from './fair.js'
19
- import { runV3 } from './v3.js'
18
+ import { runFair, FAIR_EVENT_ABIS } from './fair.js'
19
+ import { runV3, v3Ctx, V3_EVENT_ABIS } from './v3.js'
20
+ import { runBond } from './bond.js'
21
+ import { runBuyback } from './buyback.js'
22
+ import { FAIR_DEV_BUY, isLaunch, LAUNCH_FLAGS, runLaunch } from './launch.js'
20
23
  import { poolPayBnb, poolTrade } from './pool.js'
21
24
  import { parseAmount, ROUTER_ABI, ROUTER_HINTS, revertOf as revertIn, step as writeStep } from './steps.js'
22
25
  import { MERGED_ERRORS_ABI, kindOf, padNets } from './gangpad.js'
@@ -113,7 +116,7 @@ const fromArg = () => {
113
116
  return a
114
117
  }
115
118
  const FROM_CURVE = '--from only applies to a graduated token (a PancakeSwap trade through GangRouter); curve trades are previewed for the Agentic Wallet: leave --from out'
116
- const NOT_TESTNET = 'gang buy / gang sell trade the mainnet V2 / Soft / U / S pads only (no testnet deployment): leave --testnet out, or use gang v3 … / gang fair … --testnet'
119
+ const NOT_TESTNET = 'V2 / Soft / U / S tokens trade on mainnet only (no testnet deployment): leave --testnet out (--testnet is for GangLaunchpad V3 / 打新 tokens)'
117
120
 
118
121
  // --note is validated before any RPC / preview, so a bad note never reaches calldata
119
122
  const parseNote = () => {
@@ -149,7 +152,7 @@ const parseLinks = () => {
149
152
  const parseFollow = () => {
150
153
  if (!flag('follow')) return null
151
154
  if (cmd !== 'buy' && !(cmd === 'v3' && args[1] === 'buy')) {
152
- fail('--follow only applies to `gang buy` / `gang v3 buy` (or use `gang follow <note tx> --bnb <n>`)')
155
+ fail('--follow only applies to `gang buy` (or use `gang follow <note tx> --amount <n>`)')
153
156
  }
154
157
  const tx = opt('follow')
155
158
  if (!NOTE_TX.test(tx ?? '')) fail('--follow needs the tx hash of the note to follow (0x + 64 hex)', { got: tx ?? null })
@@ -195,8 +198,16 @@ const followSkipped = (follow) =>
195
198
 
196
199
  const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet) · new tokens on GangLaunchpad (V3 / 打新)
197
200
 
201
+ One command per action, whatever pad the token is on: buy / sell / info / status / claim / mint / withdraw / release
202
+ read the token's kind (GangLaunchpad kindOf: V3 curve or 打新 sale; otherwise the V2 / Soft / U / S pads) and run that
203
+ kind's trade. Results of V3 / 打新 tokens carry kind: "v3" | "fair". --testnet = BSC testnet GangLaunchpad (V3 / 打新
204
+ only); --calldata prints the raw tx instead of a baw preview; --from <addr> reads / builds for that wallet (V2 / Soft:
205
+ graduated tokens only).
206
+
198
207
  gang preflight check sign-in, Developer Mode, tradeAllTokens, Agent ID
199
- gang list tokens across V2 / Soft / U / S
208
+ gang list [--kind v3|fair [--status …]]
209
+ V2 / Soft / U / S tokens; --kind v3 (--status curve|graduated|all) / --kind fair
210
+ (--status minting|launched|all): GangLaunchpad tokens from gang.fun
200
211
  gang hot [--limit 10] [--track all|v2|soft] [--include-graduated]
201
212
  curve tokens ranked by 24h USD volume
202
213
  gang new [--limit 10] [--max-age 2h] [--track …] newest curve tokens first
@@ -211,87 +222,87 @@ const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet
211
222
  NDJSON: note (newly followable) / revealed (a sealed note's text went public)
212
223
  gang agents [--window 1d|7d|30d|all] [--sort score|hitRate|count|follow|followUsd] [--limit 10]
213
224
  note leaderboard: hit rate, calibration score, how their notes were followed
214
- gang token --token <addr> one token (auto-resolves pad)
215
- gang quote --token <addr> --bnb <n> Soft: BNB quote; V2/U/S: quote-token amount (same flag)
216
- gang buy --token <addr> --bnb <n> [--slippage 1] [--follow <note tx>] [--note "<why>" [--conf 0-100] [--reveal-after 1-72h]]
217
- Soft: payable BNB; V2: wrap BNB→WBNB if short, approve quote, then buy
218
- graduated: buys in the PancakeSwap pool through GangRouter (see POOL below;
219
- [--use-quote | --pay-bnb] [--from <addr>] [--calldata]; no --note / --follow)
220
- gang follow <note tx> --bnb <n> [--slippage 1] [--note …]
221
- buy the token that note is about, marked on-chain as following it (= buy --follow;
222
- a V3 token: = gang v3 buy --follow, --bnb = the quote to spend)
223
- gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--note … [--conf …] [--reveal-after …]]
224
- graduated: sells into the pool through GangRouter ([--to-bnb] [--from] [--calldata])
225
+
226
+ gang info --token <addr> one token (gang token is the older name). V3: curve, tax setting (taxNote: pool
227
+ only), recipient, what the router still owes it; graduated: swapBack (tax not sold
228
+ yet, the per-block chunk budget, the next chunk). 打新: the sale. V2 / Soft: the curve
229
+ gang status --token <addr> [--slippage 1]
230
+ my position. V3: balance and dividend (claimable now + tax credited, not sold yet,
231
+ estimate). 打新: my shares; after launch reward on / out, how much I can sell and stay
232
+ in, to claim. Graduated / launched: poolSellAll (what selling my balance into the pool
233
+ pays as of the next block: expectOut / minOut for amountOutMin). V2 / Soft: balance
234
+ gang quote --token <addr> --amount <n> V2 / Soft / U / S curve buy (Soft: BNB; V2 / U / S: the quote ERC-20). V3 / 打新:
235
+ gang buy simulates the trade and shows expectTokens (nothing sent before gang execute)
236
+ gang buy --token <addr> --amount <n> [--slippage 1] [--follow <note tx>] [--note "<why>" [--conf 0-100] [--reveal-after 1-72h]]
237
+ --amount = what to spend, in the token's quote (--bnb is the older name of the flag).
238
+ V3 curve: includes the 1% fee (no tax on the curve); WBNB-quoted pays native BNB via
239
+ GangRouter in one tx (--use-wbnb: from WBNB). 打新: after launch only (before it:
240
+ gang mint). Soft: payable BNB; V2: wraps BNB→WBNB if short, approves the quote, then
241
+ buys. Graduated / launched: the PancakeSwap pool through GangRouter (POOL below;
242
+ [--use-quote | --pay-bnb]; no --note / --follow)
243
+ gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--to-bnb] [--note … [--conf …] [--reveal-after …]]
244
+ on the curve, or (graduated / launched) into the pool through GangRouter. 打新: before
245
+ launch use gang withdraw; after it a sell past status.sellableBeforeOut (ends the
246
+ holder reward for good) is refused unless --confirm-out
247
+ gang mint --token <addr> --shares <n> [--min-shares <n>] [--use-wbnb]
248
+ 打新 before it fills: + the sale's mint fee (the platform's, not returned); WBNB
249
+ sales pay native BNB through GangRouter in one tx (--use-wbnb: from WBNB)
250
+ gang withdraw --token <addr> (--shares <n> | --all)
251
+ 打新: sell shares back before it fills: mint price − exit fee (none once stale)
252
+ gang claim --token <addr> V3: my dividend (the quote token); 打新: the holder reward
253
+ gang release --token <addr> V3: push a tax payment that failed earlier (anyone may; the recipient gets it)
254
+ gang follow <note tx> --amount <n> [--slippage 1] [--note …]
255
+ buy the token that note is about, marked on-chain as following it (= gang buy
256
+ --follow; curve buys only)
225
257
  gang upload-logo (--file <path> | --url <image URL>)
226
258
  copy an image to gang.fun → logoUrl (ipfs://<cid>, for --logo-uri) and url (CDN)
227
- gang create … = gang v3 create (below): new tokens go to GangLaunchpad. Untaxed unless
228
- --buy-tax / --sell-tax are given. LaunchpadV2 / Soft / U / S take no new tokens
229
- (their tokens still trade with gang buy / sell); --taxed (the old V2 track) is refused
230
- --logo-uri images are copied to gang.fun first (logoURI is permanent on-chain;
231
- other hosts expire). --keep-logo-uri puts a permanent URL (e.g. ipfs://) on-chain as is;
232
- gang.fun files (ipfs://<cid>, cdn.gang.fun/ipfs/<cid>) always go on as ipfs://<cid>
233
- NOTES on GangLaunchpad: gang create / gang v3 create|buy|sell / gang fair create|mint|withdraw take
234
- [--note "<why>" [--conf 0-100] [--reveal-after 1-72h]] as gang buy / sell (a create's
235
- note is its launch manifesto); gang v3 buy also --follow <note tx>. Mainnet only;
236
- pool trades (graduated / launched) carry no note
237
- gang fair create --name <n> --symbol <s> [--raise 15] [--cap 0.5] [--hold 60] [--mint-fee 1] [--exit-fee 1]
238
- [--shares 1] [--quote 0x… --ref-pool 0x…] [--logo <file> | --logo-uri URL] [--website URL]
239
- [--twitter @handle] [--description <text>]
240
- 打新 (fair mint): one price per share both ways, the last share launches on PancakeSwap at
241
- that price. No deadline, no refund: open until it fills. raise 5–50 BNB, cap 0.25–1% of
242
- supply per wallet (1% → at least 50 wallets), hold line 60–90%, mint fee 1–3%, withdraw fee 1–2% (none after 7 days without 10 new shares).
243
- Wraps / approves first when needed (re-run after each)
244
- --quote: WBNB (default) or any 18-decimal token; --ref-pool = a Pancake V2 pair or V3
245
- pool holding it, which prices the raise (as gang create; mainnet USDT defaults to the USDT/WBNB pool)
246
- --website / --twitter / --description and the logo go into the token's metadata JSON
247
- (uploaded to gang.fun; the token's contractURI / metaURI, permanent)
248
- gang fair mint --token <addr> --shares <n> [--min-shares <n>] [--use-wbnb]
249
- + the sale's mint fee (the platform's, not returned); WBNB sales pay native BNB
250
- through GangRouter in one tx (--use-wbnb: from WBNB, wrap / approve first)
251
- gang fair withdraw --token <addr> (--shares <n> | --all) sell back before it fills: mint price − exit fee (none once stale)
252
- gang fair buy --token <addr> --amount <n> [--slippage 1] [--use-quote | --pay-bnb]
253
- gang fair sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--to-bnb] [--confirm-out]
254
- after launch: trade in the PancakeSwap pool through GangRouter (POOL below);
255
- a sell past status.sellableBeforeOut (ends the holder reward for good) is refused
256
- unless --confirm-out
257
- gang fair status --token <addr> [--slippage 1]
258
- my shares; after launch: reward on / out, how much I can sell and stay in, to claim
259
- (claimable now + reward credited when tax was paid, not sold yet), poolSellAll
260
- (what selling my balance into the pool pays, as of the next block: expectOut /
261
- minOut for amountOutMin; also for a wallet that only bought in the pool)
262
- gang fair claim --token <addr> claim the holder reward
263
- gang fair info --token <addr> | gang fair list [--status minting|launched|all] | gang fair tx --hash <0x..>
264
- --testnet = BSC testnet (mainnet addresses are built in); --calldata prints the raw tx
265
- instead of a baw preview, --from <addr> reads / builds for that wallet
266
- gang v3 create --name <n> --symbol <s> [--buy-tax <1-5> --sell-tax <1-5> [--dividend 0-100] [--recipient <0x…>]]
267
- [--quote 0x… --ref-pool 0x…] [--logo <file> | --logo-uri URL] [--website URL] [--twitter @handle]
268
- [--description <text>]
269
- --website / --twitter / --description and the logo go into the token's metadata JSON
270
- (uploaded to gang.fun; the token's contractURI / metaURI, permanent)
271
- no --buy-tax / --sell-tax: untaxed (no pool tax, no dividend, no recipient)
272
- LaunchpadV3: own buy / sell tax in % (fixed at create), only in the pool after
273
- graduation: 0.5% of volume to the platform; of the rest --dividend % to holders, the
274
- remainder to --recipient (default: you, the creator; or a partner on the platform whitelist) in the quote. Curve
275
- trades pay only the 1% fee, no tax
276
- --quote: WBNB (default) or any 18-decimal token; --ref-pool = a Pancake V2 pair or V3
277
- pool holding it, which prices the 15 BNB graduation target (as gang create), unless
278
- gang set a fixed rate. The output shows graduationAt
279
- gang v3 buy --token <addr> --amount <quote> [--slippage 1] [--use-wbnb] [--follow <note tx>] [--note …]
280
- amount includes the 1% fee (no tax on the curve); WBNB-quoted: native BNB via GangRouter
281
- gang v3 sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--to-bnb] [--note …]
282
- on the curve; graduated: buy / sell trade in the PancakeSwap pool (POOL below)
283
- gang v3 info --token <addr> curve, tax setting (taxNote: pool only), recipient, what the router still owes it; graduated: swapBack
284
- (tax not sold yet, the per-block chunk budget, what the latest block has left of it
285
- and the next chunk)
286
- gang v3 status --token <addr> [--slippage 1]
287
- my balance and dividend: claimable now + tax credited to me, not sold yet (estimate);
288
- graduated: poolSellAll (expectOut / minOut for amountOutMin)
289
- gang v3 claim --token <addr> claim my dividend (the quote token)
290
- gang v3 release --token <addr> push a tax payment that failed earlier (anyone may; the recipient gets it)
291
- gang v3 income --recipient <addr> | gang v3 list [--status curve|graduated|all] | gang v3 tx --hash <0x..>
292
- V3 tokens and 打新 sales share one pad: a token of the other kind is pointed at the
293
- other command (kindOf). --testnet / mainnet, --calldata / --from as in gang fair
294
- POOL (graduated / launched token; gang v3 buy|sell, gang fair buy|sell, gang buy|sell): PancakeSwap through GangRouter.
259
+
260
+ gang create --name <n> --symbol <s> [--buy-tax <1-5> --sell-tax <1-5> [--dividend 0-100] [--recipient <0x…>]]
261
+ [--quote 0x… --ref-pool 0x…] [--logo <file> | --logo-uri URL] [--website URL] [--twitter @handle]
262
+ [--description <text>] [--testnet]
263
+ a V3 token on GangLaunchpad (LaunchpadV2 / Soft / U / S take no new tokens; --taxed,
264
+ the old V2 track, is refused). No --buy-tax / --sell-tax: untaxed (no pool tax, no
265
+ dividend, no recipient). Taxed: own buy / sell tax in % (fixed at create), only in the
266
+ pool after graduation: 0.5% of volume to the platform; of the rest --dividend % to
267
+ holders, the remainder to --recipient (default: you, the creator; or a partner on the
268
+ platform whitelist) in the quote. Curve trades pay only the 1% fee, no tax
269
+ gang create --fair --name <n> --symbol <s> [--raise 15] [--cap 0.5] [--hold 60] [--mint-fee 1] [--exit-fee 1]
270
+ [--shares 1] [--quote 0x… --ref-pool 0x…] [--logo …] [--website URL] [--twitter @handle] [--description …]
271
+ a 打新 (fair mint) sale: one price per share both ways, the last share launches on
272
+ PancakeSwap at that price. No deadline, no refund: open until it fills. raise 5–50
273
+ BNB, cap 0.25–1% of supply per wallet (1% → at least 50 wallets), hold line 60–90%,
274
+ mint fee 1–3%, withdraw fee 1–2% (none after 7 days without 10 new shares). Wraps /
275
+ approves first when needed (re-run after each)
276
+ both: --quote = WBNB (default) or any 18-decimal token; --ref-pool = a Pancake V2 pair
277
+ or V3 pool holding it, which prices the 15 BNB graduation target / the raise (mainnet
278
+ USDT defaults to the USDT/WBNB pool) unless gang set a fixed rate. --website /
279
+ --twitter / --description and the logo go into the token's metadata JSON (uploaded to
280
+ gang.fun; the token's contractURI / metaURI, permanent). --logo-uri images are copied
281
+ to gang.fun first (other hosts expire); --keep-logo-uri puts a permanent URL (e.g.
282
+ ipfs://) on-chain as is; gang.fun files always go on as ipfs://<cid>
283
+ LAUNCH in one command (one tx per run: execute it, then run the very same command again until launch.stage = done):
284
+ gang create … --dev-buy <quote> [--bond <quote> [--bond-hold <tokens|N%|all>] --bond-days <7-90>]
285
+ V3: create → the dev buys on the curve (--dev-buy, as gang buy) → bonds what it
286
+ holds (--bond-hold, default all; gang bond post). Wrap / approve steps come in between
287
+ gang create … --buy-tax <1-5> --sell-tax <1-5> --buyback <0-100> [--dev-buy … [--bond …]]
288
+ V3, taxed: the recipient's part of the tax is split, --buyback % bought back in the
289
+ pool and burned (0xdead), the rest to you. Steps: clone your buy-back splitter
290
+ (GangBuybackSplitterFactory; listed as a recipient in the same tx) → create with it as
291
+ the recipient → bind → the rest of the launch. Without --buyback the tax goes to you
292
+ gang create --fair … [--shares <n>] --bond <quote> [--bond-hold …] --bond-days <7-90>
293
+ 打新: create (the dev's --shares are bought in it) → bond. Progress is read from the
294
+ chain (a token this wallet created with this symbol since the plan); a run that finds
295
+ none never creates again by itself (the first may still be landing): --restart does.
296
+ Done stays done: running it again only reports. --note goes on the create only;
297
+ --bond needs GangBond on the network (testnet only for now), checked before the create
298
+ NOTES: gang create / buy / sell, and gang mint / withdraw on a 打新 sale, take [--note "<why>" [--conf 0-100]
299
+ [--reveal-after 1-72h]] (a create's note is its launch manifesto); gang buy also
300
+ --follow <note tx>. Mainnet only; pool trades (graduated / launched) carry no note
301
+ gang v3 income --recipient <addr> a tax recipient's income per token (gang.fun)
302
+ ALIASES: gang v3 <list|info|status|create|buy|sell|claim|release|tx> and gang fair <list|info|status|create|mint|
303
+ withdraw|buy|sell|claim|tx> run the same code as the commands above (their results
304
+ carry useInstead)
305
+ POOL (graduated / launched token; gang buy|sell): PancakeSwap through GangRouter.
295
306
  buy: WBNB quote → native BNB (poolBuyBNB), --use-quote pays WBNB; other quote → the
296
307
  quote (poolBuy), --pay-bnb pays BNB via WBNB → quote → token. minTokensOut =
297
308
  getAmountsOut − buy tax − --slippage. sell: poolSell → quote, --to-bnb poolSellForBNB
@@ -300,10 +311,53 @@ const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet
300
311
  --calldata: all txs in steps[] in order). A taxed sell needs about 700k gas (it also
301
312
  sells one chunk of the held tax): let the wallet estimate, or give at least 750k.
302
313
  The router is checked first (a GangRouter with pool trades, not Pancake / an old one);
303
- a reverted simulation with --calldata is doNotSend: true, no tx. gang buy|sell:
314
+ a reverted simulation with --calldata is doNotSend: true, no tx. V2 / Soft / U / S:
304
315
  --from only on graduated tokens, mainnet only
316
+ A creator selling below its bonded X (gang bond) is refused unless --break-bond;
317
+ past the deadline, --settle-first settles the bond before the sell.
318
+
319
+ BOND (押金承诺, GangBond; any GangLaunchpad token, from create on; GANG_BOND / GANG_TESTNET_BOND = its address):
320
+ gang bond post --token <addr> --hold <tokens|N%> --days <7-90> (--amount <quote> | --cover <coverage %>) [--use-wbnb]
321
+ creator only, any time after create (V3: buy on the curve first): bond in the token's
322
+ quote (WBNB: paid in BNB, --use-wbnb pays WBNB; also on topup) promising the creator
323
+ keeps at least --hold tokens (N% = of the 1B supply) from now on. The --days clock
324
+ starts when the token graduates / launches (at once if it already has). --cover 100 =
325
+ exactly what dumping X fetches now (only once on the pair). No early withdrawal;
326
+ below X anyone reports it, on the curve too
327
+ gang bond topup --token <addr> (--amount <quote> | --cover <target %>) | gang bond raise-hold --token <addr> --hold <tokens|N%>
328
+ gang bond extend --token <addr> --days <n>
329
+ creator only: the bond only grows, X only rises, the commitment only gets longer (90
330
+ days in all before the pair; a deadline ≤ 90 days from now after it)
331
+ gang bond status --token <addr> state, phase (pending: waiting for the pair / running), X and the creator's balance /
332
+ headroom, coverage + tier once on the pair (≥100% full cover, 50–100% bond, <50% no
333
+ badge), toFullCover, time left, buy-back progress, the creator's record
334
+ gang bond start --token <addr> anyone, once the token graduated / launched: start the clock (deadline = now + days)
335
+ gang bond report --token <addr> anyone, when the creator holds less than X: 1% of the bond to you, the rest buys
336
+ back and burns the token (simulated first; nothing is sent when not violated)
337
+ gang bond settle --token <addr> anyone, after the deadline while X is held: the bond goes back to the creator
338
+ gang bond refund --token <addr> creator, posted before the pair and still no pair 30 days later: run it once to ask
339
+ (a 3-day notice: X still binds, graduation / launch voids it), again after it to
340
+ get the bond back
341
+ gang bond burn --token <addr> [--slippage 1]
342
+ anyone: one buy-back chunk of a slashed bond (≤ 0.5% of the pair's quote side; waits
343
+ for a 10-minute average and skips when the price is > 2% above it). Slashed before the
344
+ pair: waits for it; none 30 days after the post → the rest goes to 0xdead as quote
345
+ gang bond tx --hash <0x..> decode GangBond events. --testnet, --calldata, --from as in gang v3. A bonded
346
+ creator's gang v3 sell (curve or pool), gang fair sell and gang fair withdraw are
347
+ checked against X (--break-bond / --settle-first)
348
+ BUYBACK (a V3 token whose recipient is a buy-back splitter; GANG_SPLITTER_FACTORY / GANG_TESTNET_SPLITTER_FACTORY):
349
+ gang buyback status --token <addr> ratio, a cut asked for (applies from / lapses after), quote waiting to be bought
350
+ back, owed to the dev, the next chunk (the keeper buys it back every few minutes)
351
+ gang buyback set --token <addr> --pct <0-100>
352
+ dev only: a higher share applies at once; a lower one after a 3-day notice
353
+ gang buyback apply --token <addr> anyone: apply a cut whose notice ran (within 7 days, or it lapses)
354
+ gang buyback bind --token <addr> | gang buyback release --token <addr>
355
+ anyone: bind the splitter to its token (the launch does it) / push the dev what a
356
+ failed payment left owed. --testnet, --calldata, --from as in gang v3
305
357
  gang execute --request-id <id> broadcast a previewed call
306
- gang tx --hash <0x..> wait for receipt and decode Gang events (publishes a queued --note)
358
+ gang tx --hash <0x..> [--testnet] wait for receipt and decode Gang events: V2 / Soft / U / S, GangLaunchpad (V3 /
359
+ 打新, tax router, vaults) (publishes a queued --note; mainnet). GangLaunchpad events
360
+ carry units: "raw" (amounts in wei, 18 decimals; bps / shares as is)
307
361
 
308
362
  --note is PUBLIC: shown on gang.fun next to the trade. Its hash goes into the tx calldata, so it is provably written
309
363
  before the trade and can't be edited later. 1–280 chars, no links. Sent by \`gang execute\` (or \`gang tx\`) within 24h.
@@ -407,7 +461,7 @@ function padNotes(kind, sub, pre = {}) {
407
461
  }
408
462
  const note = 'note' in pre ? pre.note : parseNote()
409
463
  const followTx = pre.checked ? pre.checked.noteTx : parseFollow()
410
- if (note && !PAD_NOTED[kind].includes(sub)) fail(`--note applies to gang ${kind} ${PAD_NOTED[kind].join(' / ')} only`)
464
+ if (note && !PAD_NOTED[kind].includes(sub)) fail(`--note applies to ${PAD_NOTED[kind].join(' / ')} on ${KIND_NAMES[kind]} only`)
411
465
  const rawOnly = flag('calldata')
412
466
  return {
413
467
  following: !!followTx,
@@ -610,6 +664,52 @@ async function buy(token, bnb, note, followTx, checked = null) {
610
664
  }, note, 'buy', priced))
611
665
  }
612
666
 
667
+ /** gang sell on a V2 / Soft / U / S token: the curve, or (graduated) the PancakeSwap pool through GangRouter. */
668
+ async function legacySell(token) {
669
+ const note = parseNote()
670
+ parseFollow() // refuses --follow: only buys follow a note
671
+ if (flag('testnet')) fail(NOT_TESTNET)
672
+ const from = fromArg()
673
+ const me = from || bawAddress()
674
+ const info = await tokenInfo(token, me)
675
+ if (from && !info.graduated) fail(FROM_CURVE)
676
+ if (!flag('all') && !opt('amount')) fail('pass --amount <tokens> or --all (full sell is no longer the default)')
677
+ if (flag('all') && opt('amount')) fail('use either --all or --amount, not both')
678
+ // graduated: sell into the PancakeSwap pool through GangRouter
679
+ if (info.graduated) {
680
+ return v2PoolTrade('sell', token, info, me, note, { amount: flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: flag('all') })
681
+ }
682
+ const bal = await balanceOf(token, me)
683
+ if (bal === 0n) fail('no balance to sell')
684
+ const amount = flag('all') ? bal : parseEther(opt('amount'))
685
+ if (amount > bal) fail('amount exceeds balance', { balance: fmtTok(bal) })
686
+ const pad = await resolvePad(token)
687
+ const allow = await allowance(token, me, pad.address)
688
+ if (allow < amount) {
689
+ const p = bawPreview({ from: me, to: token, data: calldata.approve(pad.address, amount) })
690
+ return out(summarizePreview(p, {
691
+ step: 'approve', token, symbol: info.symbol, pad: pad.id, amount: fmtTok(amount),
692
+ note: 'Approve first. After it confirms on-chain, run the same `gang sell` again to preview the sell.',
693
+ ...noteSkipped(note),
694
+ }))
695
+ }
696
+ const slippage = parseSlippage(opt('slippage', '1'))
697
+ const q = await quoteSell(token, amount)
698
+ const minOut = (q.out * BigInt(Math.round((100 - slippage) * 100))) / 10000n
699
+ if (pad.kind === 'soft') {
700
+ const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.softSell(token, amount, minOut), note) })
701
+ return out(summarizeWithNote(p, {
702
+ step: 'sell', kind: 'soft', token, symbol: info.symbol, pad: pad.id,
703
+ amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'BNB',
704
+ }, note, 'sell'))
705
+ }
706
+ const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.v2Sell(token, amount, minOut), note) })
707
+ return out(summarizeWithNote(p, {
708
+ step: 'sell', kind: 'v2', token, symbol: info.symbol, pad: pad.id, quote: info.quote,
709
+ amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'quote', quotePath: q.quotePath, deterministic: q.deterministic || false,
710
+ }, note, 'sell'))
711
+ }
712
+
613
713
  const TAXED_HINT = 'Set the pool tax with --buy-tax / --sell-tax (1–5% each; --buy-tax 1 --sell-tax 1 --dividend 100 is closest to the old 1% taxed track), or leave them out for an untaxed token.'
614
714
  const PAD_AGENT_ABI = parseAbi(['function isAgent(address account) view returns (bool)'])
615
715
  /**
@@ -621,26 +721,172 @@ const createGateAgent = (me) =>
621
721
 
622
722
  // what `gang v3` / `gang fair` (and `gang create`) get from here; each call adds its `notes` (padNotes)
623
723
  const PAD_IO = { opt, flag, out, fail, summarize: summarizePreview, resolveLogo: resolveLogoUri, checkMeta: parseMeta, resolveMeta: resolveMetaUri }
724
+ /** `gang create … --dev-buy … [--bond …]` / `gang fair create … --bond …`: one command, one tx per run (launch.js). */
725
+ const LAUNCH_DEPS = { io: PAD_IO, runV3, runFair, runBond, padNotes: (kind, sub, pre) => padNotes(kind, sub, pre) }
726
+
727
+ // ---------------------------------------------------------------- one command per action, whatever the pad
728
+ // gang buy|sell|info|status|claim|mint|withdraw|release --token <addr> ask GangLaunchpad's kindOf what the token is and
729
+ // run that kind's code: V3 curve (v3.js), 打新 sale (fair.js), else the mainnet V2 / Soft / U / S pads (here). A new pad
730
+ // is a new kind in this table, not new commands; gang v3 <sub> / gang fair <sub> stay as aliases of the same code.
731
+ const V3_VERBS = new Set(['buy', 'sell', 'info', 'status', 'claim', 'release'])
732
+ const FAIR_VERBS = new Set(['buy', 'sell', 'info', 'status', 'claim', 'mint', 'withdraw'])
733
+ const KIND_NAMES = { v3: 'a V3 (bonding-curve) token', fair: 'a 打新 (fair-mint) token', legacy: 'a V2 / Soft / U / S token' }
734
+ // what the V2 / Soft / U / S code runs: it looks the token up on those pads itself, so it can also take a token whose kindOf
735
+ // could not be read (a GangLaunchpad token then gets a pointer from resolvePad instead of a wrong answer)
736
+ const LEGACY_VERBS = new Set(['buy', 'sell', 'info', 'status', 'quote'])
737
+ /** Why a command does not apply to a kind, and what to run instead. */
738
+ const NOT_FOR = {
739
+ v3: {
740
+ mint: 'a V3 token has no shares: gang buy --token … --amount <quote>',
741
+ withdraw: 'a V3 token has no shares: gang sell --token … (--amount <tokens> | --all)',
742
+ },
743
+ fair: { release: '打新 tokens have no tax recipient: the pool tax goes to minters and the platform (gang claim for the reward)' },
744
+ legacy: {
745
+ claim: 'V2 / Soft / U / S tokens pay no dividend here',
746
+ release: 'V2 / Soft / U / S tokens have no tax recipient to pay',
747
+ mint: 'mint / withdraw are for 打新 sales: gang buy / gang sell trade this token',
748
+ withdraw: 'mint / withdraw are for 打新 sales: gang buy / gang sell trade this token',
749
+ },
750
+ }
751
+
752
+ /**
753
+ * 'v3' | 'fair' on GangLaunchpad (mainnet, or BSC testnet with --testnet); else 'legacy' (mainnet V2 / Soft / U / S).
754
+ * kindOf unreadable (RPC): 'legacy' only for a verb that code checks again (LEGACY_VERBS), a retry hint otherwise.
755
+ */
756
+ async function padKind(token, verb) {
757
+ const testnet = flag('testnet')
758
+ let ctx
759
+ try { ctx = v3Ctx(testnet) } catch (e) { fail(e.message) }
760
+ const kind = await kindOf(ctx, token)
761
+ if (kind === 'v3' || kind === 'fair') return kind
762
+ if (kind === null && (testnet || !LEGACY_VERBS.has(verb))) {
763
+ fail('could not read the token\'s kind from GangLaunchpad (kindOf; RPC error)', { token, retry: 'same command again' })
764
+ }
765
+ if (testnet) fail('not a GangLaunchpad token on BSC testnet (V2 / Soft / U / S have no testnet deployment)', { token })
766
+ return 'legacy'
767
+ }
768
+
769
+ /** --amount (or --bnb, its older name on buy / quote): what a buy spends, in the token's quote (BNB / WBNB for most). */
770
+ const spendArg = () => {
771
+ if (flag('amount') && flag('bnb')) fail('give the amount once: --amount <n> (--bnb is the older name of the same flag)')
772
+ return opt('amount') ?? opt('bnb')
773
+ }
774
+ // gang fair buy reads --amount only: --bnb is handed to it under that name
775
+ const amountIo = (io) => (flag('bnb') && !flag('amount')
776
+ ? { ...io, opt: (n, d) => (n === 'amount' ? opt('bnb') : opt(n, d)), flag: (n) => (n === 'amount' || flag(n)) }
777
+ : io)
778
+ // every result of a routed V3 / 打新 command says which kind ran (V2 / Soft results carry their own kind already)
779
+ const tagKind = (kind) => (o) => out(o && typeof o === 'object' && !('kind' in o) ? { kind, ...o } : o)
780
+
781
+ /** --token: missing and malformed told apart (viem refuses a mixed-case address whose checksum is wrong). */
782
+ const tokenArg = () => {
783
+ const token = opt('token')
784
+ if (!token || token.startsWith('--')) fail('--token <address> required')
785
+ if (!isAddress(token)) fail('--token must be a 0x address (40 hex; in mixed case the checksum must be right, or write it all in lower case)', { got: token })
786
+ return token
787
+ }
788
+
789
+ /** gang <verb> --token <addr>: the kind's code for it, or why it does not apply. */
790
+ async function routed(verb) {
791
+ const token = tokenArg()
792
+ // refused before any RPC: both --amount and --bnb, a malformed --note / --conf / --reveal-after, a bad --follow (the
793
+ // note itself is built again by the kind's code)
794
+ if (verb === 'buy') spendArg()
795
+ parseNote()
796
+ parseFollow()
797
+ const kind = await padKind(token, verb)
798
+ const io = { ...PAD_IO, out: tagKind(kind) }
799
+ if (kind === 'v3' && V3_VERBS.has(verb)) return runV3(verb, { ...io, notes: padNotes('v3', verb) })
800
+ if (kind === 'fair' && FAIR_VERBS.has(verb)) {
801
+ if (flag('follow')) fail('--follow only counts on curve buys (V3 / V2 tokens): a 打新 token has no curve', { token })
802
+ return runFair(verb, { ...amountIo(io), notes: padNotes('fair', verb) })
803
+ }
804
+ if (kind === 'legacy') {
805
+ if (verb === 'buy') {
806
+ const amount = spendArg()
807
+ if (!amount) fail('--amount <n> required (what to spend: BNB on Soft, the quote ERC-20 on V2 / U / S)')
808
+ return buy(token, amount, parseNote(), parseFollow())
809
+ }
810
+ if (verb === 'sell') return legacySell(token)
811
+ // a note goes on a trade only (as on V3 / 打新, where padNotes refuses it on info / status)
812
+ if (flag('note')) fail(`--note applies to buy / sell on ${KIND_NAMES.legacy} only`)
813
+ // no wallet needed (baw is optional here): with one, the result also shows its balance
814
+ if (verb === 'info') return out({ success: true, ...(await tokenInfo(token, myAddress() ?? undefined)) })
815
+ if (verb === 'status') {
816
+ const me = fromArg() || bawAddress()
817
+ return out({ success: true, wallet: me, ...(await tokenInfo(token, me)) })
818
+ }
819
+ }
820
+ // a V2 / Soft result names its pad's kind ('v2' | 'soft'); here the pad is not looked up, so no kind for them
821
+ return fail(`gang ${verb} does not apply to ${KIND_NAMES[kind]}`, { token, ...(kind !== 'legacy' && { kind }), hint: NOT_FOR[kind][verb] })
822
+ }
823
+
824
+ // what each gang v3 / gang fair subcommand is now: their results carry it as `useInstead` (the aliases keep working)
825
+ const UNIFIED = {
826
+ v3: { create: 'gang create', list: 'gang list --kind v3', tx: 'gang tx' },
827
+ fair: { create: 'gang create --fair', list: 'gang list --kind fair', tx: 'gang tx' },
828
+ }
829
+ function alias(kind, sub) {
830
+ const verbs = kind === 'v3' ? V3_VERBS : FAIR_VERBS
831
+ const use = UNIFIED[kind][sub] ?? (verbs.has(sub) ? `gang ${sub}` : null)
832
+ const io = { ...PAD_IO, notes: padNotes(kind, sub) }
833
+ if (!use) return io
834
+ return { ...io, out: (o) => out(o && typeof o === 'object' ? { ...o, useInstead: `${use} (same flags)` } : o) }
835
+ }
836
+
837
+ // gang create: V3 by default, a 打新 sale with --fair; a setting of the other kind is refused, not ignored
838
+ const V3_CREATE_FLAGS = ['buy-tax', 'sell-tax', 'dividend', 'recipient']
839
+ const FAIR_CREATE_FLAGS = ['raise', 'cap', 'hold', 'mint-fee', 'exit-fee', 'shares']
840
+ function create() {
841
+ if (flag('fair')) {
842
+ const wrong = V3_CREATE_FLAGS.find(flag)
843
+ if (wrong) fail(`--${wrong} is a V3 tax setting; a 打新 sale's pool tax is fixed (1%): leave it out, or drop --fair for a V3 token`)
844
+ if (isLaunch('fair', flag)) return runLaunch('fair', LAUNCH_DEPS)
845
+ return runFair('create', { ...PAD_IO, notes: padNotes('fair', 'create') })
846
+ }
847
+ const wrong = FAIR_CREATE_FLAGS.find(flag)
848
+ if (wrong) fail(`--${wrong} is a 打新 (fair-mint) setting: add --fair to create a sale (gang create --fair …)`)
849
+ if (isLaunch('v3', flag)) return runLaunch('v3', LAUNCH_DEPS)
850
+ return runV3('create', { ...PAD_IO, notes: padNotes('v3', 'create') })
851
+ }
624
852
 
625
853
  // commands whose JSON result carries `update`; `watch` streams NDJSON and help is plain text
626
- const UPDATE_CHECKED = new Set(['preflight', 'list', 'hot', 'new', 'near-grad', 'notes', 'agents', 'token', 'quote', 'buy',
627
- 'follow', 'sell', 'upload-logo', 'create', 'execute', 'tx', 'fair', 'v3'])
854
+ const UPDATE_CHECKED = new Set(['preflight', 'list', 'hot', 'new', 'near-grad', 'notes', 'agents', 'token', 'info', 'status', 'quote',
855
+ 'buy', 'follow', 'sell', 'claim', 'mint', 'withdraw', 'release', 'upload-logo', 'create', 'execute', 'tx', 'fair', 'v3', 'bond',
856
+ 'buyback'])
857
+ // gang tx decodes these beside the V2 / Soft / U / S pads' events (GangLaunchpad: both kinds; the tax router; the vaults)
858
+ const GANG_EVENT_ABIS = [...V3_EVENT_ABIS, ...FAIR_EVENT_ABIS]
628
859
 
629
860
  async function main() {
630
861
  if (UPDATE_CHECKED.has(cmd)) update = checkUpdate()
631
862
  // `gang create` (= `gang v3 create`) / `gang fair create` write the links into the token metadata
632
863
  const tokenCreate = cmd === 'create' || ((cmd === 'v3' || cmd === 'fair') && args[1] === 'create')
633
864
  for (const name of LINK_FLAGS) {
634
- if (!tokenCreate && flag(name)) fail(`--${name} only applies when creating a token (\`gang create\`, \`gang v3 create\`, \`gang fair create\`)`)
865
+ if (!tokenCreate && flag(name)) fail(`--${name} only applies when creating a token (\`gang create\`, \`gang create --fair\`)`)
635
866
  }
636
867
  if (flag('description') && !tokenCreate) {
637
- fail('--description only applies when creating a token (`gang create`, `gang v3 create`, `gang fair create`; it goes into the token metadata)')
868
+ fail('--description only applies when creating a token (`gang create`, `gang create --fair`; it goes into the token metadata)')
869
+ }
870
+ // --bond-hold / --bond-days / --restart only mean something in a chained launch (--dev-buy / --bond); never drop them silently
871
+ // gang fair create and gang create --fair are 打新 creates; the rest V3
872
+ const launchKind = cmd === 'fair' || (cmd === 'create' && flag('fair')) ? 'fair' : 'v3'
873
+ if (tokenCreate && !isLaunch(launchKind, flag)) {
874
+ // a 打新 dev buys with --shares in the create itself: say so here, not "add --bond" (which would only lead to that)
875
+ if (launchKind === 'fair' && flag('dev-buy')) fail(FAIR_DEV_BUY)
876
+ const lone = LAUNCH_FLAGS.find((f) => flag(f))
877
+ if (lone) fail(`--${lone} only applies to a launch in one command: add --bond <quote>${launchKind === 'fair' ? '' : ' (or --dev-buy <quote> / --buyback <pct>)'}`)
638
878
  }
639
879
  switch (cmd) {
640
880
  case 'fair':
641
- return runFair(args[1], { ...PAD_IO, notes: padNotes('fair', args[1]) })
881
+ if (args[1] === 'create' && isLaunch('fair', flag)) return runLaunch('fair', LAUNCH_DEPS)
882
+ return runFair(args[1], alias('fair', args[1]))
642
883
  case 'v3':
643
- return runV3(args[1], { ...PAD_IO, notes: padNotes('v3', args[1]) })
884
+ if (args[1] === 'create' && isLaunch('v3', flag)) return runLaunch('v3', LAUNCH_DEPS)
885
+ return runV3(args[1], alias('v3', args[1]))
886
+ case 'bond':
887
+ return runBond(args[1], { opt, flag, out, fail, summarize: summarizePreview })
888
+ case 'buyback':
889
+ return runBuyback(args[1], { opt, flag, out, fail, summarize: summarizePreview })
644
890
  case 'preflight': {
645
891
  const issues = []
646
892
  const status = bawStatus()
@@ -662,6 +908,13 @@ async function main() {
662
908
  })
663
909
  }
664
910
  case 'list': {
911
+ // --kind v3 / fair: GangLaunchpad tokens (from gang.fun); without it the V2 / Soft / U / S pads, as before
912
+ if (flag('kind')) {
913
+ const kind = opt('kind')
914
+ if (kind === 'v3') return runV3('list', { ...PAD_IO, notes: padNotes('v3', 'list') })
915
+ if (kind === 'fair') return runFair('list', { ...PAD_IO, notes: padNotes('fair', 'list') })
916
+ fail('--kind must be v3 or fair (leave it out for the V2 / Soft / U / S tokens)', { got: kind ?? null })
917
+ }
665
918
  const me = bawStatus() === 'CONNECTED' ? bawAddress() : undefined
666
919
  return out({ success: true, createPad: padNets('v3').mainnet.pad, tokens: await listTokens(me) })
667
920
  }
@@ -801,13 +1054,21 @@ async function main() {
801
1054
  await Promise.allSettled([...hooks])
802
1055
  return
803
1056
  }
804
- case 'token': {
805
- const token = opt('token'); if (!token) fail('--token required')
806
- const me = bawStatus() === 'CONNECTED' ? bawAddress() : undefined
807
- return out({ success: true, ...(await tokenInfo(token, me)) })
808
- }
1057
+ case 'info':
1058
+ case 'token': // the older name of gang info
1059
+ return routed('info')
1060
+ case 'status':
1061
+ case 'claim':
1062
+ case 'mint':
1063
+ case 'withdraw':
1064
+ case 'release':
1065
+ return routed(cmd)
809
1066
  case 'quote': {
810
- const token = opt('token'), bnb = opt('bnb'); if (!token || !bnb) fail('--token and --bnb required (Soft archive: BNB; V2: quote ERC-20 amount)')
1067
+ const token = tokenArg(), bnb = spendArg()
1068
+ if (!bnb) fail('--amount required (Soft archive: BNB; V2: quote ERC-20 amount; --bnb is the older name)')
1069
+ if ((await padKind(token, 'quote')) !== 'legacy') {
1070
+ fail('gang quote prices V2 / Soft / U / S curve buys; on GangLaunchpad, gang buy simulates the trade and shows expectTokens (nothing is sent before gang execute)', { token })
1071
+ }
811
1072
  const me = bawAddress()
812
1073
  const q = await quoteBuy(token, me, bnb)
813
1074
  return out({
@@ -819,23 +1080,21 @@ async function main() {
819
1080
  : (q.deterministic ? 'V2 quote = LaunchpadV2.sol curve math (unique path; no Soft/U/S fallback).' : undefined),
820
1081
  })
821
1082
  }
822
- case 'buy': {
823
- const token = opt('token'), bnb = opt('bnb'); if (!token || !bnb) fail('--token and --bnb required')
824
- return buy(token, bnb, parseNote(), parseFollow())
825
- }
1083
+ case 'buy':
1084
+ return routed('buy')
826
1085
  case 'follow': {
827
- // gang follow <note tx> --bnb <n>: the token comes from the note
1086
+ // gang follow <note tx> --amount <n>: the token comes from the note
828
1087
  const noteTx = args[1] && !args[1].startsWith('--') ? args[1].toLowerCase() : null
829
- if (!NOTE_TX.test(noteTx ?? '')) fail('usage: gang follow <note tx> --bnb <n> (the note tx is 0x + 64 hex)', { got: noteTx })
830
- if (flag('token')) fail('gang follow takes the token from the note; to name it yourself use `gang buy --token … --bnb … --follow <note tx>`')
831
- if (flag('follow')) fail('give the note tx once: `gang follow <note tx> --bnb <n>`')
832
- const bnb = opt('bnb'); if (!bnb) fail('--bnb required (the amount to buy, in the token\'s quote unit)')
1088
+ if (!NOTE_TX.test(noteTx ?? '')) fail('usage: gang follow <note tx> --amount <n> (the note tx is 0x + 64 hex)', { got: noteTx })
1089
+ if (flag('token')) fail('gang follow takes the token from the note; to name it yourself use `gang buy --token … --amount … --follow <note tx>`')
1090
+ if (flag('follow')) fail('give the note tx once: `gang follow <note tx> --amount <n>`')
1091
+ const bnb = spendArg(); if (!bnb) fail('--amount required (the amount to buy, in the token\'s quote unit; --bnb is the older name)')
833
1092
  const note = parseNote() // a bad --note fails before any API call, as on buy
834
1093
  // checked without a token: gang.fun answers with the note's token, and buy() reuses this check. The wallet is the
835
1094
  // one that buys: --from builds for another one (gang v3 buy takes it on the curve; buy() refuses it there)
836
1095
  const { token, follow } = await confirmFollow(noteTx, fromArg() || bawAddress())
837
1096
  if (!token) fail('gang.fun did not say which token this note is about', { noteTx })
838
- // a GangLaunchpad token: the same buy as `gang v3 buy` (--bnb = the quote to spend)
1097
+ // a GangLaunchpad token: the V3 curve buy (--amount / --bnb = the quote to spend)
839
1098
  const onPad = await kindOf(v2PoolCtx(), token)
840
1099
  if (onPad === 'fair') fail('this note is on a 打新 (fair-mint) token: only curve buys can follow a note', { noteTx, token })
841
1100
  if (onPad === 'v3') {
@@ -843,51 +1102,8 @@ async function main() {
843
1102
  }
844
1103
  return buy(token, bnb, note, noteTx, follow)
845
1104
  }
846
- case 'sell': {
847
- const token = opt('token'); if (!token) fail('--token required')
848
- const note = parseNote()
849
- parseFollow() // refuses --follow: only buys follow a note
850
- if (flag('testnet')) fail(NOT_TESTNET)
851
- const from = fromArg()
852
- const me = from || bawAddress()
853
- const info = await tokenInfo(token, me)
854
- if (from && !info.graduated) fail(FROM_CURVE)
855
- if (!flag('all') && !opt('amount')) fail('pass --amount <tokens> or --all (full sell is no longer the default)')
856
- if (flag('all') && opt('amount')) fail('use either --all or --amount, not both')
857
- // graduated: sell into the PancakeSwap pool through GangRouter
858
- if (info.graduated) {
859
- return v2PoolTrade('sell', token, info, me, note, { amount: flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: flag('all') })
860
- }
861
- const bal = await balanceOf(token, me)
862
- if (bal === 0n) fail('no balance to sell')
863
- const amount = flag('all') ? bal : parseEther(opt('amount'))
864
- if (amount > bal) fail('amount exceeds balance', { balance: fmtTok(bal) })
865
- const pad = await resolvePad(token)
866
- const allow = await allowance(token, me, pad.address)
867
- if (allow < amount) {
868
- const p = bawPreview({ from: me, to: token, data: calldata.approve(pad.address, amount) })
869
- return out(summarizePreview(p, {
870
- step: 'approve', token, symbol: info.symbol, pad: pad.id, amount: fmtTok(amount),
871
- note: 'Approve first. After it confirms on-chain, run the same `gang sell` again to preview the sell.',
872
- ...noteSkipped(note),
873
- }))
874
- }
875
- const slippage = parseSlippage(opt('slippage', '1'))
876
- const q = await quoteSell(token, amount)
877
- const minOut = (q.out * BigInt(Math.round((100 - slippage) * 100))) / 10000n
878
- if (pad.kind === 'soft') {
879
- const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.softSell(token, amount, minOut), note) })
880
- return out(summarizeWithNote(p, {
881
- step: 'sell', kind: 'soft', token, symbol: info.symbol, pad: pad.id,
882
- amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'BNB',
883
- }, note, 'sell'))
884
- }
885
- const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.v2Sell(token, amount, minOut), note) })
886
- return out(summarizeWithNote(p, {
887
- step: 'sell', kind: 'v2', token, symbol: info.symbol, pad: pad.id, quote: info.quote,
888
- amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'quote', quotePath: q.quotePath, deterministic: q.deterministic || false,
889
- }, note, 'sell'))
890
- }
1105
+ case 'sell':
1106
+ return routed('sell')
891
1107
  case 'upload-logo': {
892
1108
  const file = opt('file') || opt('path') || opt('logo')
893
1109
  const url = opt('url')
@@ -902,7 +1118,7 @@ async function main() {
902
1118
  if (flag('pair-or-quote') || flag('pair')) {
903
1119
  fail('--pair-or-quote was removed (CLI ≤0.3.0 encoded a create the contract does not have). Use --quote <ERC-20> --ref-pool <Pancake pair/pool holding it>; omit both for WBNB.')
904
1120
  }
905
- return runV3('create', { ...PAD_IO, notes: padNotes('v3', 'create') })
1121
+ return create()
906
1122
  }
907
1123
  case 'execute': {
908
1124
  const id = opt('request-id'); if (!id) fail('--request-id required')
@@ -934,8 +1150,18 @@ async function main() {
934
1150
  })
935
1151
  }
936
1152
  case 'tx': {
937
- const hash = opt('hash'); if (!hash) fail('--hash required')
938
- const t = await decodeTx(hash)
1153
+ const hash = opt('hash'); if (!/^0x[0-9a-fA-F]{64}$/.test(hash ?? '')) fail('--hash must be a tx hash (0x + 64 hex)')
1154
+ if (flag('testnet')) {
1155
+ // BSC testnet: GangLaunchpad only (no V2 / Soft there, and gang.fun indexes no testnet notes)
1156
+ let ctx
1157
+ try { ctx = v3Ctx(true) } catch (e) { fail(e.message) }
1158
+ const r = await ctx.client.waitForTransactionReceipt({ hash, timeout: 120_000 })
1159
+ return out({
1160
+ success: true, chainId: Number(ctx.chainId), hash, status: r.status, block: Number(r.blockNumber),
1161
+ events: decodeLogs(r.logs, GANG_EVENT_ABIS), explorer: `${ctx.explorer}/tx/${hash}`,
1162
+ })
1163
+ }
1164
+ const t = await decodeTx(hash, GANG_EVENT_ABIS)
939
1165
  // the input may carry a follow ref and a note commitment: a committed note is sent here if it's still in the
940
1166
  // local queue (e.g. after PENDING_CONFIRMATION), or its state reported / refreshed if already sent
941
1167
  const queued = hasQueued()