@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/README.md +59 -43
- package/README.zh-CN.md +59 -43
- package/package.json +1 -1
- package/src/bond.js +564 -0
- package/src/buyback.js +183 -0
- package/src/chain.js +27 -4
- package/src/fair.js +18 -8
- package/src/gangpad.js +3 -3
- package/src/index.js +384 -158
- package/src/launch.js +284 -0
- package/src/pool.js +11 -2
- package/src/steps.js +1 -1
- package/src/v3.js +18 -6
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 = '
|
|
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`
|
|
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
|
|
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
|
-
|
|
215
|
-
gang
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
gang
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
gang
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
--quote
|
|
245
|
-
pool holding it, which prices the
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
gang
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
gang
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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.
|
|
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..>
|
|
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
|
|
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', '
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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 =
|
|
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
|
-
|
|
824
|
-
return buy(token, bnb, parseNote(), parseFollow())
|
|
825
|
-
}
|
|
1083
|
+
case 'buy':
|
|
1084
|
+
return routed('buy')
|
|
826
1085
|
case 'follow': {
|
|
827
|
-
// gang follow <note tx> --
|
|
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> --
|
|
830
|
-
if (flag('token')) fail('gang follow takes the token from the note; to name it yourself use `gang buy --token … --
|
|
831
|
-
if (flag('follow')) fail('give the note tx once: `gang follow <note tx> --
|
|
832
|
-
const bnb =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
938
|
-
|
|
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()
|