@gangfun/cli 0.10.1 → 0.12.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 +43 -42
- package/README.zh-CN.md +43 -42
- package/package.json +1 -1
- package/src/chain.js +27 -4
- package/src/fair.js +23 -23
- package/src/follow.js +10 -7
- package/src/gangpad.js +3 -13
- package/src/index.js +384 -174
- package/src/pool.js +1 -1
- package/src/steps.js +1 -1
- package/src/v3.js +42 -25
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,11 +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
20
|
import { poolPayBnb, poolTrade } from './pool.js'
|
|
21
|
-
import { parseAmount, ROUTER_ABI, ROUTER_HINTS, revertOf as revertIn } from './steps.js'
|
|
22
|
-
import { MERGED_ERRORS_ABI, padNets } from './gangpad.js'
|
|
21
|
+
import { parseAmount, ROUTER_ABI, ROUTER_HINTS, revertOf as revertIn, step as writeStep } from './steps.js'
|
|
22
|
+
import { MERGED_ERRORS_ABI, kindOf, padNets } from './gangpad.js'
|
|
23
23
|
import { V2_ABI } from './abi.js'
|
|
24
24
|
import { parseAbi } from 'viem'
|
|
25
25
|
|
|
@@ -98,7 +98,7 @@ async function v2PoolTrade(side, token, info, me, note, { amount, all = false })
|
|
|
98
98
|
handler: null, swapBackGas: false, payBnb: side === 'buy' && poolPayBnb(ctx, info.quote, flag), toBnb: flag('to-bnb'),
|
|
99
99
|
base: {
|
|
100
100
|
kind: info.kind, pad: info.pad, symbol: info.symbol, pair: info.pair,
|
|
101
|
-
...(note && { agentNote:
|
|
101
|
+
...(note && { agentNote: POOL_NO_NOTE }),
|
|
102
102
|
},
|
|
103
103
|
}, {
|
|
104
104
|
rawOnly: flag('calldata'), summarize: summarizePreview,
|
|
@@ -113,7 +113,7 @@ const fromArg = () => {
|
|
|
113
113
|
return a
|
|
114
114
|
}
|
|
115
115
|
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 = '
|
|
116
|
+
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
117
|
|
|
118
118
|
// --note is validated before any RPC / preview, so a bad note never reaches calldata
|
|
119
119
|
const parseNote = () => {
|
|
@@ -145,10 +145,12 @@ const parseLinks = () => {
|
|
|
145
145
|
} catch (e) { fail(e.message) }
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
/** --follow <note tx> (
|
|
148
|
+
/** --follow <note tx> (buys only). Syntax here; whether the note can be followed is asked from gang.fun before preview. */
|
|
149
149
|
const parseFollow = () => {
|
|
150
150
|
if (!flag('follow')) return null
|
|
151
|
-
if (cmd !== 'buy'
|
|
151
|
+
if (cmd !== 'buy' && !(cmd === 'v3' && args[1] === 'buy')) {
|
|
152
|
+
fail('--follow only applies to `gang buy` (or use `gang follow <note tx> --amount <n>`)')
|
|
153
|
+
}
|
|
152
154
|
const tx = opt('follow')
|
|
153
155
|
if (!NOTE_TX.test(tx ?? '')) fail('--follow needs the tx hash of the note to follow (0x + 64 hex)', { got: tx ?? null })
|
|
154
156
|
return tx.toLowerCase()
|
|
@@ -179,8 +181,8 @@ async function confirmFollow(noteTx, me, token) {
|
|
|
179
181
|
* Adds the rough USD value of what the buy pays (after any cap refund: gang.fun counts the payment); under $1 the follow
|
|
180
182
|
* won't count, so say so (the buy itself is still fine).
|
|
181
183
|
*/
|
|
182
|
-
async function withUsd(follow, token,
|
|
183
|
-
const usd = await amountUsd(token,
|
|
184
|
+
async function withUsd(follow, token, track, spent, decimals) {
|
|
185
|
+
const usd = await amountUsd(token, track, spent, decimals)
|
|
184
186
|
return {
|
|
185
187
|
...follow, usd,
|
|
186
188
|
...(usd != null && usd < MIN_FOLLOW_USD && { warning: `this buy is about $${usd}; buys under $${MIN_FOLLOW_USD} don't count as a follow` }),
|
|
@@ -193,8 +195,16 @@ const followSkipped = (follow) =>
|
|
|
193
195
|
|
|
194
196
|
const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet) · new tokens on GangLaunchpad (V3 / 打新)
|
|
195
197
|
|
|
198
|
+
One command per action, whatever pad the token is on: buy / sell / info / status / claim / mint / withdraw / release
|
|
199
|
+
read the token's kind (GangLaunchpad kindOf: V3 curve or 打新 sale; otherwise the V2 / Soft / U / S pads) and run that
|
|
200
|
+
kind's trade. Results of V3 / 打新 tokens carry kind: "v3" | "fair". --testnet = BSC testnet GangLaunchpad (V3 / 打新
|
|
201
|
+
only); --calldata prints the raw tx instead of a baw preview; --from <addr> reads / builds for that wallet (V2 / Soft:
|
|
202
|
+
graduated tokens only).
|
|
203
|
+
|
|
196
204
|
gang preflight check sign-in, Developer Mode, tradeAllTokens, Agent ID
|
|
197
|
-
gang list
|
|
205
|
+
gang list [--kind v3|fair [--status …]]
|
|
206
|
+
V2 / Soft / U / S tokens; --kind v3 (--status curve|graduated|all) / --kind fair
|
|
207
|
+
(--status minting|launched|all): GangLaunchpad tokens from gang.fun
|
|
198
208
|
gang hot [--limit 10] [--track all|v2|soft] [--include-graduated]
|
|
199
209
|
curve tokens ranked by 24h USD volume
|
|
200
210
|
gang new [--limit 10] [--max-age 2h] [--track …] newest curve tokens first
|
|
@@ -209,82 +219,72 @@ const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet
|
|
|
209
219
|
NDJSON: note (newly followable) / revealed (a sealed note's text went public)
|
|
210
220
|
gang agents [--window 1d|7d|30d|all] [--sort score|hitRate|count|follow|followUsd] [--limit 10]
|
|
211
221
|
note leaderboard: hit rate, calibration score, how their notes were followed
|
|
212
|
-
|
|
213
|
-
gang
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
|
|
223
|
+
gang info --token <addr> one token (gang token is the older name). V3: curve, tax setting (taxNote: pool
|
|
224
|
+
only), recipient, what the router still owes it; graduated: swapBack (tax not sold
|
|
225
|
+
yet, the per-block chunk budget, the next chunk). 打新: the sale. V2 / Soft: the curve
|
|
226
|
+
gang status --token <addr> [--slippage 1]
|
|
227
|
+
my position. V3: balance and dividend (claimable now + tax credited, not sold yet,
|
|
228
|
+
estimate). 打新: my shares; after launch reward on / out, how much I can sell and stay
|
|
229
|
+
in, to claim. Graduated / launched: poolSellAll (what selling my balance into the pool
|
|
230
|
+
pays as of the next block: expectOut / minOut for amountOutMin). V2 / Soft: balance
|
|
231
|
+
gang quote --token <addr> --amount <n> V2 / Soft / U / S curve buy (Soft: BNB; V2 / U / S: the quote ERC-20). V3 / 打新:
|
|
232
|
+
gang buy simulates the trade and shows expectTokens (nothing sent before gang execute)
|
|
233
|
+
gang buy --token <addr> --amount <n> [--slippage 1] [--follow <note tx>] [--note "<why>" [--conf 0-100] [--reveal-after 1-72h]]
|
|
234
|
+
--amount = what to spend, in the token's quote (--bnb is the older name of the flag).
|
|
235
|
+
V3 curve: includes the 1% fee (no tax on the curve); WBNB-quoted pays native BNB via
|
|
236
|
+
GangRouter in one tx (--use-wbnb: from WBNB). 打新: after launch only (before it:
|
|
237
|
+
gang mint). Soft: payable BNB; V2: wraps BNB→WBNB if short, approves the quote, then
|
|
238
|
+
buys. Graduated / launched: the PancakeSwap pool through GangRouter (POOL below;
|
|
239
|
+
[--use-quote | --pay-bnb]; no --note / --follow)
|
|
240
|
+
gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--to-bnb] [--note … [--conf …] [--reveal-after …]]
|
|
241
|
+
on the curve, or (graduated / launched) into the pool through GangRouter. 打新: before
|
|
242
|
+
launch use gang withdraw; after it a sell past status.sellableBeforeOut (ends the
|
|
243
|
+
holder reward for good) is refused unless --confirm-out
|
|
244
|
+
gang mint --token <addr> --shares <n> [--min-shares <n>] [--use-wbnb]
|
|
245
|
+
打新 before it fills: + the sale's mint fee (the platform's, not returned); WBNB
|
|
246
|
+
sales pay native BNB through GangRouter in one tx (--use-wbnb: from WBNB)
|
|
247
|
+
gang withdraw --token <addr> (--shares <n> | --all)
|
|
248
|
+
打新: sell shares back before it fills: mint price − exit fee (none once stale)
|
|
249
|
+
gang claim --token <addr> V3: my dividend (the quote token); 打新: the holder reward
|
|
250
|
+
gang release --token <addr> V3: push a tax payment that failed earlier (anyone may; the recipient gets it)
|
|
251
|
+
gang follow <note tx> --amount <n> [--slippage 1] [--note …]
|
|
252
|
+
buy the token that note is about, marked on-chain as following it (= gang buy
|
|
253
|
+
--follow; curve buys only)
|
|
222
254
|
gang upload-logo (--file <path> | --url <image URL>)
|
|
223
255
|
copy an image to gang.fun → logoUrl (ipfs://<cid>, for --logo-uri) and url (CDN)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
gang fair info --token <addr> | gang fair list [--status minting|launched|all] | gang fair tx --hash <0x..>
|
|
257
|
-
--testnet = BSC testnet (mainnet addresses are built in); --calldata prints the raw tx
|
|
258
|
-
instead of a baw preview, --from <addr> reads / builds for that wallet
|
|
259
|
-
gang v3 create --name <n> --symbol <s> [--buy-tax <1-5> --sell-tax <1-5> [--dividend 0-100] [--recipient <0x…>]]
|
|
260
|
-
[--quote 0x… --ref-pool 0x…] [--logo <file> | --logo-uri URL] [--website URL] [--twitter @handle]
|
|
261
|
-
[--description <text>]
|
|
262
|
-
--website / --twitter / --description and the logo go into the token's metadata JSON
|
|
263
|
-
(uploaded to gang.fun; the token's contractURI / metaURI, permanent)
|
|
264
|
-
no --buy-tax / --sell-tax: untaxed (no pool tax, no dividend, no recipient)
|
|
265
|
-
LaunchpadV3: own buy / sell tax in % (fixed at create), only in the pool after
|
|
266
|
-
graduation: 0.5% of volume to the platform; of the rest --dividend % to holders, the
|
|
267
|
-
remainder to --recipient (default: you, the creator; or a partner on the platform whitelist) in the quote. Curve
|
|
268
|
-
trades pay only the 1% fee, no tax
|
|
269
|
-
--quote: WBNB (default) or any 18-decimal token; --ref-pool = a Pancake V2 pair or V3
|
|
270
|
-
pool holding it, which prices the 15 BNB graduation target (as gang create), unless
|
|
271
|
-
gang set a fixed rate. The output shows graduationAt
|
|
272
|
-
gang v3 buy --token <addr> --amount <quote> [--slippage 1] [--use-wbnb]
|
|
273
|
-
amount includes the 1% fee (no tax on the curve); WBNB-quoted: native BNB via GangRouter
|
|
274
|
-
gang v3 sell --token <addr> (--amount <tokens> | --all) [--slippage 1] [--to-bnb]
|
|
275
|
-
on the curve; graduated: buy / sell trade in the PancakeSwap pool (POOL below)
|
|
276
|
-
gang v3 info --token <addr> curve, tax setting (taxNote: pool only), recipient, what the router still owes it; graduated: swapBack
|
|
277
|
-
(tax not sold yet, the per-block chunk budget, what the latest block has left of it
|
|
278
|
-
and the next chunk)
|
|
279
|
-
gang v3 status --token <addr> [--slippage 1]
|
|
280
|
-
my balance and dividend: claimable now + tax credited to me, not sold yet (estimate);
|
|
281
|
-
graduated: poolSellAll (expectOut / minOut for amountOutMin)
|
|
282
|
-
gang v3 claim --token <addr> claim my dividend (the quote token)
|
|
283
|
-
gang v3 release --token <addr> push a tax payment that failed earlier (anyone may; the recipient gets it)
|
|
284
|
-
gang v3 income --recipient <addr> | gang v3 list [--status curve|graduated|all] | gang v3 tx --hash <0x..>
|
|
285
|
-
V3 tokens and 打新 sales share one pad: a token of the other kind is pointed at the
|
|
286
|
-
other command (kindOf). --testnet / mainnet, --calldata / --from as in gang fair
|
|
287
|
-
POOL (graduated / launched token; gang v3 buy|sell, gang fair buy|sell, gang buy|sell): PancakeSwap through GangRouter.
|
|
256
|
+
|
|
257
|
+
gang create --name <n> --symbol <s> [--buy-tax <1-5> --sell-tax <1-5> [--dividend 0-100] [--recipient <0x…>]]
|
|
258
|
+
[--quote 0x… --ref-pool 0x…] [--logo <file> | --logo-uri URL] [--website URL] [--twitter @handle]
|
|
259
|
+
[--description <text>] [--testnet]
|
|
260
|
+
a V3 token on GangLaunchpad (LaunchpadV2 / Soft / U / S take no new tokens; --taxed,
|
|
261
|
+
the old V2 track, is refused). No --buy-tax / --sell-tax: untaxed (no pool tax, no
|
|
262
|
+
dividend, no recipient). Taxed: own buy / sell tax in % (fixed at create), only in the
|
|
263
|
+
pool after graduation: 0.5% of volume to the platform; of the rest --dividend % to
|
|
264
|
+
holders, the remainder to --recipient (default: you, the creator; or a partner on the
|
|
265
|
+
platform whitelist) in the quote. Curve trades pay only the 1% fee, no tax
|
|
266
|
+
gang create --fair --name <n> --symbol <s> [--raise 15] [--cap 0.5] [--hold 60] [--mint-fee 1] [--exit-fee 1]
|
|
267
|
+
[--shares 1] [--quote 0x… --ref-pool 0x…] [--logo …] [--website URL] [--twitter @handle] [--description …]
|
|
268
|
+
a 打新 (fair mint) sale: one price per share both ways, the last share launches on
|
|
269
|
+
PancakeSwap at that price. No deadline, no refund: open until it fills. raise 5–50
|
|
270
|
+
BNB, cap 0.25–1% of supply per wallet (1% → at least 50 wallets), hold line 60–90%,
|
|
271
|
+
mint fee 1–3%, withdraw fee 1–2% (none after 7 days without 10 new shares). Wraps /
|
|
272
|
+
approves first when needed (re-run after each)
|
|
273
|
+
both: --quote = WBNB (default) or any 18-decimal token; --ref-pool = a Pancake V2 pair
|
|
274
|
+
or V3 pool holding it, which prices the 15 BNB graduation target / the raise (mainnet
|
|
275
|
+
USDT defaults to the USDT/WBNB pool) unless gang set a fixed rate. --website /
|
|
276
|
+
--twitter / --description and the logo go into the token's metadata JSON (uploaded to
|
|
277
|
+
gang.fun; the token's contractURI / metaURI, permanent). --logo-uri images are copied
|
|
278
|
+
to gang.fun first (other hosts expire); --keep-logo-uri puts a permanent URL (e.g.
|
|
279
|
+
ipfs://) on-chain as is; gang.fun files always go on as ipfs://<cid>
|
|
280
|
+
NOTES: gang create / buy / sell, and gang mint / withdraw on a 打新 sale, take [--note "<why>" [--conf 0-100]
|
|
281
|
+
[--reveal-after 1-72h]] (a create's note is its launch manifesto); gang buy also
|
|
282
|
+
--follow <note tx>. Mainnet only; pool trades (graduated / launched) carry no note
|
|
283
|
+
gang v3 income --recipient <addr> a tax recipient's income per token (gang.fun)
|
|
284
|
+
ALIASES: gang v3 <list|info|status|create|buy|sell|claim|release|tx> and gang fair <list|info|status|create|mint|
|
|
285
|
+
withdraw|buy|sell|claim|tx> run the same code as the commands above (their results
|
|
286
|
+
carry useInstead)
|
|
287
|
+
POOL (graduated / launched token; gang buy|sell): PancakeSwap through GangRouter.
|
|
288
288
|
buy: WBNB quote → native BNB (poolBuyBNB), --use-quote pays WBNB; other quote → the
|
|
289
289
|
quote (poolBuy), --pay-bnb pays BNB via WBNB → quote → token. minTokensOut =
|
|
290
290
|
getAmountsOut − buy tax − --slippage. sell: poolSell → quote, --to-bnb poolSellForBNB
|
|
@@ -293,10 +293,12 @@ const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet
|
|
|
293
293
|
--calldata: all txs in steps[] in order). A taxed sell needs about 700k gas (it also
|
|
294
294
|
sells one chunk of the held tax): let the wallet estimate, or give at least 750k.
|
|
295
295
|
The router is checked first (a GangRouter with pool trades, not Pancake / an old one);
|
|
296
|
-
a reverted simulation with --calldata is doNotSend: true, no tx.
|
|
296
|
+
a reverted simulation with --calldata is doNotSend: true, no tx. V2 / Soft / U / S:
|
|
297
297
|
--from only on graduated tokens, mainnet only
|
|
298
298
|
gang execute --request-id <id> broadcast a previewed call
|
|
299
|
-
gang tx --hash <0x..>
|
|
299
|
+
gang tx --hash <0x..> [--testnet] wait for receipt and decode Gang events: V2 / Soft / U / S, GangLaunchpad (V3 /
|
|
300
|
+
打新, tax router, vaults) (publishes a queued --note; mainnet). GangLaunchpad events
|
|
301
|
+
carry units: "raw" (amounts in wei, 18 decimals; bps / shares as is)
|
|
300
302
|
|
|
301
303
|
--note is PUBLIC: shown on gang.fun next to the trade. Its hash goes into the tx calldata, so it is provably written
|
|
302
304
|
before the trade and can't be edited later. 1–280 chars, no links. Sent by \`gang execute\` (or \`gang tx\`) within 24h.
|
|
@@ -312,7 +314,7 @@ To find notes to follow: \`gang notes\` (what can be followed now, with each aut
|
|
|
312
314
|
|
|
313
315
|
Logo UI (browser): ${LOGO_UI}
|
|
314
316
|
|
|
315
|
-
A buy / sell preview without --note says so in agentNote.hint (gang
|
|
317
|
+
A buy / sell / create preview without --note says so in agentNote.hint (also gang v3 / gang fair). When npm has a newer CLI, results carry
|
|
316
318
|
update: { current, latest, cmd, skill } (npm is asked at most once a day in the background; GANG_UPDATE_CHECK=0 turns it
|
|
317
319
|
off, GANG_UPDATE_URL points it at an npm mirror).
|
|
318
320
|
|
|
@@ -337,7 +339,8 @@ function summarizePreview(p, extra = {}) {
|
|
|
337
339
|
}
|
|
338
340
|
}
|
|
339
341
|
|
|
340
|
-
// shown on every buy / sell / create without --note: agents with an old local skill
|
|
342
|
+
// shown on every noted step (buy / sell / create, fair mint / withdraw) without --note: agents with an old local skill
|
|
343
|
+
// don't know to write one.
|
|
341
344
|
// The CLI can't tell whether the user asked for no note, so the agent decides.
|
|
342
345
|
const NO_NOTE = {
|
|
343
346
|
attached: false,
|
|
@@ -348,6 +351,13 @@ const NO_NOTE_SENT = {
|
|
|
348
351
|
hint: 'This trade went out without a note. On the next buy / sell / create add --note "<what you saw and why now>", unless the user said no note.',
|
|
349
352
|
}
|
|
350
353
|
|
|
354
|
+
const attachedNote = (note) => ({
|
|
355
|
+
attached: true, hash: note.hash, text: note.text, conf: note.conf,
|
|
356
|
+
...(note.revealAfter
|
|
357
|
+
? { revealAfterHours: note.revealAfter / 3600, visibility: `sealed: public ${note.revealAfter / 3600}h after the trade` }
|
|
358
|
+
: { visibility: 'public after execute' }),
|
|
359
|
+
})
|
|
360
|
+
|
|
351
361
|
/**
|
|
352
362
|
* A trade preview with the note's commitment (and follow ref) already in `data`: queue the reveal under the preview's
|
|
353
363
|
* requestId, and remember what the preview carried so `gang execute` can report on it.
|
|
@@ -357,12 +367,7 @@ function summarizeWithNote(p, extra, note, kind, follow = null) {
|
|
|
357
367
|
if (!s.success) return s
|
|
358
368
|
if (note) {
|
|
359
369
|
queueNote(note, { kind, token: extra.token ?? null, requestId: s.requestId })
|
|
360
|
-
s.agentNote =
|
|
361
|
-
attached: true, hash: note.hash, text: note.text, conf: note.conf,
|
|
362
|
-
...(note.revealAfter
|
|
363
|
-
? { revealAfterHours: note.revealAfter / 3600, visibility: `sealed: public ${note.revealAfter / 3600}h after the trade` }
|
|
364
|
-
: { visibility: 'public after execute' }),
|
|
365
|
-
}
|
|
370
|
+
s.agentNote = attachedNote(note)
|
|
366
371
|
} else {
|
|
367
372
|
s.agentNote = NO_NOTE
|
|
368
373
|
}
|
|
@@ -371,10 +376,59 @@ function summarizeWithNote(p, extra, note, kind, follow = null) {
|
|
|
371
376
|
return s
|
|
372
377
|
}
|
|
373
378
|
/** The buy step itself: price the follow (setup steps skip this API call). */
|
|
374
|
-
const pricedFollow = (follow, token, pad, q) => follow && withUsd(follow, token, pad.kind === 'soft', q.spent)
|
|
379
|
+
const pricedFollow = (follow, token, pad, q) => follow && withUsd(follow, token, pad.kind === 'soft' ? 'soft' : 'v2', q.spent)
|
|
375
380
|
const noteSkipped = (note) =>
|
|
376
381
|
note ? { agentNote: { attached: false, reason: 'this setup step carries no note; pass --note again on the re-run' } } : {}
|
|
377
382
|
|
|
383
|
+
// GangLaunchpad steps gang.fun matches a note to (mainnet): curve trades, creates and fair-mint txs. buy / sell of a
|
|
384
|
+
// graduated or launched token are PancakeSwap trades: they take --note but carry none, as `gang buy` / `gang sell`.
|
|
385
|
+
const PAD_NOTED = { v3: ['create', 'buy', 'sell'], fair: ['create', 'mint', 'withdraw', 'buy', 'sell'] }
|
|
386
|
+
const POOL_NO_NOTE = { attached: false, reason: 'notes are matched to curve trades only: this PancakeSwap trade carries none' }
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* --note / --follow for `gang v3 <sub>` / `gang fair <sub>` (`gang create` = gang v3 create). `pre`: the note and the
|
|
390
|
+
* follow `gang follow` already parsed / confirmed. The pad modules use:
|
|
391
|
+
* following → a follow was asked for (--follow, or `gang follow`)
|
|
392
|
+
* follow(token, me) → the follow, checked with gang.fun (null without one; fails if it can't count)
|
|
393
|
+
* priced(follow, token, spent, decimals) → it with the buy's USD value (only on the buy step: setup steps skip the call)
|
|
394
|
+
* step(ctx, tx, extra, kind, follow) → the write step with the follow ref and note commitment in its calldata
|
|
395
|
+
* skipped(follow) → the fields a wrap / approve step shows instead
|
|
396
|
+
* pool → the fields a PancakeSwap trade shows
|
|
397
|
+
*/
|
|
398
|
+
function padNotes(kind, sub, pre = {}) {
|
|
399
|
+
const testnet = flag('testnet')
|
|
400
|
+
if (testnet && (flag('note') || flag('follow') || pre.checked)) {
|
|
401
|
+
fail('--note / --follow are mainnet only: gang.fun does not index testnet txs; leave them out with --testnet')
|
|
402
|
+
}
|
|
403
|
+
const note = 'note' in pre ? pre.note : parseNote()
|
|
404
|
+
const followTx = pre.checked ? pre.checked.noteTx : parseFollow()
|
|
405
|
+
if (note && !PAD_NOTED[kind].includes(sub)) fail(`--note applies to ${PAD_NOTED[kind].join(' / ')} on ${KIND_NAMES[kind]} only`)
|
|
406
|
+
const rawOnly = flag('calldata')
|
|
407
|
+
return {
|
|
408
|
+
following: !!followTx,
|
|
409
|
+
async follow(token, me) {
|
|
410
|
+
if (!followTx) return null
|
|
411
|
+
return pre.checked ?? (await confirmFollow(followTx, me, token)).follow
|
|
412
|
+
},
|
|
413
|
+
priced: (follow, token, spent, decimals) => follow && withUsd(follow, token, 'v3', spent, decimals),
|
|
414
|
+
step(ctx, { data, ...tx }, extra, noteKind, follow = null) {
|
|
415
|
+
if (testnet) return writeStep(ctx, { data, ...tx }, extra, rawOnly, summarizePreview)
|
|
416
|
+
const noted = { ...tx, data: withNote(withFollow(data, follow), note) }
|
|
417
|
+
if (!rawOnly) return writeStep(ctx, noted, extra, false, (p, e) => summarizeWithNote(p, e, note, noteKind, follow))
|
|
418
|
+
// --calldata: signed elsewhere, so the note waits in the local queue for `gang tx --hash` once the tx lands
|
|
419
|
+
const r = writeStep(ctx, noted, extra, true)
|
|
420
|
+
if (note) queueNote(note, { kind: noteKind, token: extra.token ?? null, requestId: null })
|
|
421
|
+
return {
|
|
422
|
+
...r,
|
|
423
|
+
agentNote: note ? { ...attachedNote(note), publish: 'after the tx lands, `gang tx --hash <hash>` on this machine publishes it' } : NO_NOTE,
|
|
424
|
+
...(follow && { follow: { attached: true, ...follow } }),
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
skipped: (follow) => ({ ...noteSkipped(note), ...followSkipped(follow) }),
|
|
428
|
+
pool: note ? { agentNote: POOL_NO_NOTE } : {},
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
378
432
|
/** Reveal after the tx is mined (the server checks receipt status). Failures don't affect the trade. */
|
|
379
433
|
async function revealNote(entry, hash) {
|
|
380
434
|
let receipt
|
|
@@ -551,6 +605,52 @@ async function buy(token, bnb, note, followTx, checked = null) {
|
|
|
551
605
|
}, note, 'buy', priced))
|
|
552
606
|
}
|
|
553
607
|
|
|
608
|
+
/** gang sell on a V2 / Soft / U / S token: the curve, or (graduated) the PancakeSwap pool through GangRouter. */
|
|
609
|
+
async function legacySell(token) {
|
|
610
|
+
const note = parseNote()
|
|
611
|
+
parseFollow() // refuses --follow: only buys follow a note
|
|
612
|
+
if (flag('testnet')) fail(NOT_TESTNET)
|
|
613
|
+
const from = fromArg()
|
|
614
|
+
const me = from || bawAddress()
|
|
615
|
+
const info = await tokenInfo(token, me)
|
|
616
|
+
if (from && !info.graduated) fail(FROM_CURVE)
|
|
617
|
+
if (!flag('all') && !opt('amount')) fail('pass --amount <tokens> or --all (full sell is no longer the default)')
|
|
618
|
+
if (flag('all') && opt('amount')) fail('use either --all or --amount, not both')
|
|
619
|
+
// graduated: sell into the PancakeSwap pool through GangRouter
|
|
620
|
+
if (info.graduated) {
|
|
621
|
+
return v2PoolTrade('sell', token, info, me, note, { amount: flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: flag('all') })
|
|
622
|
+
}
|
|
623
|
+
const bal = await balanceOf(token, me)
|
|
624
|
+
if (bal === 0n) fail('no balance to sell')
|
|
625
|
+
const amount = flag('all') ? bal : parseEther(opt('amount'))
|
|
626
|
+
if (amount > bal) fail('amount exceeds balance', { balance: fmtTok(bal) })
|
|
627
|
+
const pad = await resolvePad(token)
|
|
628
|
+
const allow = await allowance(token, me, pad.address)
|
|
629
|
+
if (allow < amount) {
|
|
630
|
+
const p = bawPreview({ from: me, to: token, data: calldata.approve(pad.address, amount) })
|
|
631
|
+
return out(summarizePreview(p, {
|
|
632
|
+
step: 'approve', token, symbol: info.symbol, pad: pad.id, amount: fmtTok(amount),
|
|
633
|
+
note: 'Approve first. After it confirms on-chain, run the same `gang sell` again to preview the sell.',
|
|
634
|
+
...noteSkipped(note),
|
|
635
|
+
}))
|
|
636
|
+
}
|
|
637
|
+
const slippage = parseSlippage(opt('slippage', '1'))
|
|
638
|
+
const q = await quoteSell(token, amount)
|
|
639
|
+
const minOut = (q.out * BigInt(Math.round((100 - slippage) * 100))) / 10000n
|
|
640
|
+
if (pad.kind === 'soft') {
|
|
641
|
+
const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.softSell(token, amount, minOut), note) })
|
|
642
|
+
return out(summarizeWithNote(p, {
|
|
643
|
+
step: 'sell', kind: 'soft', token, symbol: info.symbol, pad: pad.id,
|
|
644
|
+
amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'BNB',
|
|
645
|
+
}, note, 'sell'))
|
|
646
|
+
}
|
|
647
|
+
const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.v2Sell(token, amount, minOut), note) })
|
|
648
|
+
return out(summarizeWithNote(p, {
|
|
649
|
+
step: 'sell', kind: 'v2', token, symbol: info.symbol, pad: pad.id, quote: info.quote,
|
|
650
|
+
amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'quote', quotePath: q.quotePath, deterministic: q.deterministic || false,
|
|
651
|
+
}, note, 'sell'))
|
|
652
|
+
}
|
|
653
|
+
|
|
554
654
|
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.'
|
|
555
655
|
const PAD_AGENT_ABI = parseAbi(['function isAgent(address account) view returns (bool)'])
|
|
556
656
|
/**
|
|
@@ -560,29 +660,154 @@ const PAD_AGENT_ABI = parseAbi(['function isAgent(address account) view returns
|
|
|
560
660
|
const createGateAgent = (me) =>
|
|
561
661
|
client.readContract({ address: padNets('v3').mainnet.pad, abi: PAD_AGENT_ABI, functionName: 'isAgent', args: [me] }).catch(() => isAgent(me))
|
|
562
662
|
|
|
663
|
+
// what `gang v3` / `gang fair` (and `gang create`) get from here; each call adds its `notes` (padNotes)
|
|
664
|
+
const PAD_IO = { opt, flag, out, fail, summarize: summarizePreview, resolveLogo: resolveLogoUri, checkMeta: parseMeta, resolveMeta: resolveMetaUri }
|
|
665
|
+
|
|
666
|
+
// ---------------------------------------------------------------- one command per action, whatever the pad
|
|
667
|
+
// gang buy|sell|info|status|claim|mint|withdraw|release --token <addr> ask GangLaunchpad's kindOf what the token is and
|
|
668
|
+
// run that kind's code: V3 curve (v3.js), 打新 sale (fair.js), else the mainnet V2 / Soft / U / S pads (here). A new pad
|
|
669
|
+
// is a new kind in this table, not new commands; gang v3 <sub> / gang fair <sub> stay as aliases of the same code.
|
|
670
|
+
const V3_VERBS = new Set(['buy', 'sell', 'info', 'status', 'claim', 'release'])
|
|
671
|
+
const FAIR_VERBS = new Set(['buy', 'sell', 'info', 'status', 'claim', 'mint', 'withdraw'])
|
|
672
|
+
const KIND_NAMES = { v3: 'a V3 (bonding-curve) token', fair: 'a 打新 (fair-mint) token', legacy: 'a V2 / Soft / U / S token' }
|
|
673
|
+
// 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
|
|
674
|
+
// could not be read (a GangLaunchpad token then gets a pointer from resolvePad instead of a wrong answer)
|
|
675
|
+
const LEGACY_VERBS = new Set(['buy', 'sell', 'info', 'status', 'quote'])
|
|
676
|
+
/** Why a command does not apply to a kind, and what to run instead. */
|
|
677
|
+
const NOT_FOR = {
|
|
678
|
+
v3: {
|
|
679
|
+
mint: 'a V3 token has no shares: gang buy --token … --amount <quote>',
|
|
680
|
+
withdraw: 'a V3 token has no shares: gang sell --token … (--amount <tokens> | --all)',
|
|
681
|
+
},
|
|
682
|
+
fair: { release: '打新 tokens have no tax recipient: the pool tax goes to minters and the platform (gang claim for the reward)' },
|
|
683
|
+
legacy: {
|
|
684
|
+
claim: 'V2 / Soft / U / S tokens pay no dividend here',
|
|
685
|
+
release: 'V2 / Soft / U / S tokens have no tax recipient to pay',
|
|
686
|
+
mint: 'mint / withdraw are for 打新 sales: gang buy / gang sell trade this token',
|
|
687
|
+
withdraw: 'mint / withdraw are for 打新 sales: gang buy / gang sell trade this token',
|
|
688
|
+
},
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* 'v3' | 'fair' on GangLaunchpad (mainnet, or BSC testnet with --testnet); else 'legacy' (mainnet V2 / Soft / U / S).
|
|
693
|
+
* kindOf unreadable (RPC): 'legacy' only for a verb that code checks again (LEGACY_VERBS), a retry hint otherwise.
|
|
694
|
+
*/
|
|
695
|
+
async function padKind(token, verb) {
|
|
696
|
+
const testnet = flag('testnet')
|
|
697
|
+
let ctx
|
|
698
|
+
try { ctx = v3Ctx(testnet) } catch (e) { fail(e.message) }
|
|
699
|
+
const kind = await kindOf(ctx, token)
|
|
700
|
+
if (kind === 'v3' || kind === 'fair') return kind
|
|
701
|
+
if (kind === null && (testnet || !LEGACY_VERBS.has(verb))) {
|
|
702
|
+
fail('could not read the token\'s kind from GangLaunchpad (kindOf; RPC error)', { token, retry: 'same command again' })
|
|
703
|
+
}
|
|
704
|
+
if (testnet) fail('not a GangLaunchpad token on BSC testnet (V2 / Soft / U / S have no testnet deployment)', { token })
|
|
705
|
+
return 'legacy'
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** --amount (or --bnb, its older name on buy / quote): what a buy spends, in the token's quote (BNB / WBNB for most). */
|
|
709
|
+
const spendArg = () => {
|
|
710
|
+
if (flag('amount') && flag('bnb')) fail('give the amount once: --amount <n> (--bnb is the older name of the same flag)')
|
|
711
|
+
return opt('amount') ?? opt('bnb')
|
|
712
|
+
}
|
|
713
|
+
// gang fair buy reads --amount only: --bnb is handed to it under that name
|
|
714
|
+
const amountIo = (io) => (flag('bnb') && !flag('amount')
|
|
715
|
+
? { ...io, opt: (n, d) => (n === 'amount' ? opt('bnb') : opt(n, d)), flag: (n) => (n === 'amount' || flag(n)) }
|
|
716
|
+
: io)
|
|
717
|
+
// every result of a routed V3 / 打新 command says which kind ran (V2 / Soft results carry their own kind already)
|
|
718
|
+
const tagKind = (kind) => (o) => out(o && typeof o === 'object' && !('kind' in o) ? { kind, ...o } : o)
|
|
719
|
+
|
|
720
|
+
/** --token: missing and malformed told apart (viem refuses a mixed-case address whose checksum is wrong). */
|
|
721
|
+
const tokenArg = () => {
|
|
722
|
+
const token = opt('token')
|
|
723
|
+
if (!token || token.startsWith('--')) fail('--token <address> required')
|
|
724
|
+
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 })
|
|
725
|
+
return token
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** gang <verb> --token <addr>: the kind's code for it, or why it does not apply. */
|
|
729
|
+
async function routed(verb) {
|
|
730
|
+
const token = tokenArg()
|
|
731
|
+
// refused before any RPC: both --amount and --bnb, a malformed --note / --conf / --reveal-after, a bad --follow (the
|
|
732
|
+
// note itself is built again by the kind's code)
|
|
733
|
+
if (verb === 'buy') spendArg()
|
|
734
|
+
parseNote()
|
|
735
|
+
parseFollow()
|
|
736
|
+
const kind = await padKind(token, verb)
|
|
737
|
+
const io = { ...PAD_IO, out: tagKind(kind) }
|
|
738
|
+
if (kind === 'v3' && V3_VERBS.has(verb)) return runV3(verb, { ...io, notes: padNotes('v3', verb) })
|
|
739
|
+
if (kind === 'fair' && FAIR_VERBS.has(verb)) {
|
|
740
|
+
if (flag('follow')) fail('--follow only counts on curve buys (V3 / V2 tokens): a 打新 token has no curve', { token })
|
|
741
|
+
return runFair(verb, { ...amountIo(io), notes: padNotes('fair', verb) })
|
|
742
|
+
}
|
|
743
|
+
if (kind === 'legacy') {
|
|
744
|
+
if (verb === 'buy') {
|
|
745
|
+
const amount = spendArg()
|
|
746
|
+
if (!amount) fail('--amount <n> required (what to spend: BNB on Soft, the quote ERC-20 on V2 / U / S)')
|
|
747
|
+
return buy(token, amount, parseNote(), parseFollow())
|
|
748
|
+
}
|
|
749
|
+
if (verb === 'sell') return legacySell(token)
|
|
750
|
+
// a note goes on a trade only (as on V3 / 打新, where padNotes refuses it on info / status)
|
|
751
|
+
if (flag('note')) fail(`--note applies to buy / sell on ${KIND_NAMES.legacy} only`)
|
|
752
|
+
// no wallet needed (baw is optional here): with one, the result also shows its balance
|
|
753
|
+
if (verb === 'info') return out({ success: true, ...(await tokenInfo(token, myAddress() ?? undefined)) })
|
|
754
|
+
if (verb === 'status') {
|
|
755
|
+
const me = fromArg() || bawAddress()
|
|
756
|
+
return out({ success: true, wallet: me, ...(await tokenInfo(token, me)) })
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// a V2 / Soft result names its pad's kind ('v2' | 'soft'); here the pad is not looked up, so no kind for them
|
|
760
|
+
return fail(`gang ${verb} does not apply to ${KIND_NAMES[kind]}`, { token, ...(kind !== 'legacy' && { kind }), hint: NOT_FOR[kind][verb] })
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// what each gang v3 / gang fair subcommand is now: their results carry it as `useInstead` (the aliases keep working)
|
|
764
|
+
const UNIFIED = {
|
|
765
|
+
v3: { create: 'gang create', list: 'gang list --kind v3', tx: 'gang tx' },
|
|
766
|
+
fair: { create: 'gang create --fair', list: 'gang list --kind fair', tx: 'gang tx' },
|
|
767
|
+
}
|
|
768
|
+
function alias(kind, sub) {
|
|
769
|
+
const verbs = kind === 'v3' ? V3_VERBS : FAIR_VERBS
|
|
770
|
+
const use = UNIFIED[kind][sub] ?? (verbs.has(sub) ? `gang ${sub}` : null)
|
|
771
|
+
const io = { ...PAD_IO, notes: padNotes(kind, sub) }
|
|
772
|
+
if (!use) return io
|
|
773
|
+
return { ...io, out: (o) => out(o && typeof o === 'object' ? { ...o, useInstead: `${use} (same flags)` } : o) }
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// gang create: V3 by default, a 打新 sale with --fair; a setting of the other kind is refused, not ignored
|
|
777
|
+
const V3_CREATE_FLAGS = ['buy-tax', 'sell-tax', 'dividend', 'recipient']
|
|
778
|
+
const FAIR_CREATE_FLAGS = ['raise', 'cap', 'hold', 'mint-fee', 'exit-fee', 'shares']
|
|
779
|
+
function create() {
|
|
780
|
+
if (flag('fair')) {
|
|
781
|
+
const wrong = V3_CREATE_FLAGS.find(flag)
|
|
782
|
+
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`)
|
|
783
|
+
return runFair('create', { ...PAD_IO, notes: padNotes('fair', 'create') })
|
|
784
|
+
}
|
|
785
|
+
const wrong = FAIR_CREATE_FLAGS.find(flag)
|
|
786
|
+
if (wrong) fail(`--${wrong} is a 打新 (fair-mint) setting: add --fair to create a sale (gang create --fair …)`)
|
|
787
|
+
return runV3('create', { ...PAD_IO, notes: padNotes('v3', 'create') })
|
|
788
|
+
}
|
|
789
|
+
|
|
563
790
|
// commands whose JSON result carries `update`; `watch` streams NDJSON and help is plain text
|
|
564
|
-
const UPDATE_CHECKED = new Set(['preflight', 'list', 'hot', 'new', 'near-grad', 'notes', 'agents', 'token', '
|
|
565
|
-
'follow', 'sell', 'upload-logo', 'create', 'execute', 'tx', 'fair', 'v3'])
|
|
791
|
+
const UPDATE_CHECKED = new Set(['preflight', 'list', 'hot', 'new', 'near-grad', 'notes', 'agents', 'token', 'info', 'status', 'quote',
|
|
792
|
+
'buy', 'follow', 'sell', 'claim', 'mint', 'withdraw', 'release', 'upload-logo', 'create', 'execute', 'tx', 'fair', 'v3'])
|
|
793
|
+
// gang tx decodes these beside the V2 / Soft / U / S pads' events (GangLaunchpad: both kinds; the tax router; the vaults)
|
|
794
|
+
const GANG_EVENT_ABIS = [...V3_EVENT_ABIS, ...FAIR_EVENT_ABIS]
|
|
566
795
|
|
|
567
796
|
async function main() {
|
|
568
797
|
if (UPDATE_CHECKED.has(cmd)) update = checkUpdate()
|
|
569
798
|
// `gang create` (= `gang v3 create`) / `gang fair create` write the links into the token metadata
|
|
570
799
|
const tokenCreate = cmd === 'create' || ((cmd === 'v3' || cmd === 'fair') && args[1] === 'create')
|
|
571
800
|
for (const name of LINK_FLAGS) {
|
|
572
|
-
if (!tokenCreate && flag(name)) fail(`--${name} only applies when creating a token (\`gang create\`, \`gang
|
|
801
|
+
if (!tokenCreate && flag(name)) fail(`--${name} only applies when creating a token (\`gang create\`, \`gang create --fair\`)`)
|
|
573
802
|
}
|
|
574
803
|
if (flag('description') && !tokenCreate) {
|
|
575
|
-
fail('--description only applies when creating a token (`gang create`, `gang
|
|
804
|
+
fail('--description only applies when creating a token (`gang create`, `gang create --fair`; it goes into the token metadata)')
|
|
576
805
|
}
|
|
577
806
|
switch (cmd) {
|
|
578
807
|
case 'fair':
|
|
579
|
-
return runFair(args[1],
|
|
580
|
-
opt, flag, out, fail, summarize: summarizePreview, resolveLogo: resolveLogoUri, checkMeta: parseMeta, resolveMeta: resolveMetaUri,
|
|
581
|
-
})
|
|
808
|
+
return runFair(args[1], alias('fair', args[1]))
|
|
582
809
|
case 'v3':
|
|
583
|
-
return runV3(args[1],
|
|
584
|
-
opt, flag, out, fail, summarize: summarizePreview, resolveLogo: resolveLogoUri, checkMeta: parseMeta, resolveMeta: resolveMetaUri,
|
|
585
|
-
})
|
|
810
|
+
return runV3(args[1], alias('v3', args[1]))
|
|
586
811
|
case 'preflight': {
|
|
587
812
|
const issues = []
|
|
588
813
|
const status = bawStatus()
|
|
@@ -604,6 +829,13 @@ async function main() {
|
|
|
604
829
|
})
|
|
605
830
|
}
|
|
606
831
|
case 'list': {
|
|
832
|
+
// --kind v3 / fair: GangLaunchpad tokens (from gang.fun); without it the V2 / Soft / U / S pads, as before
|
|
833
|
+
if (flag('kind')) {
|
|
834
|
+
const kind = opt('kind')
|
|
835
|
+
if (kind === 'v3') return runV3('list', { ...PAD_IO, notes: padNotes('v3', 'list') })
|
|
836
|
+
if (kind === 'fair') return runFair('list', { ...PAD_IO, notes: padNotes('fair', 'list') })
|
|
837
|
+
fail('--kind must be v3 or fair (leave it out for the V2 / Soft / U / S tokens)', { got: kind ?? null })
|
|
838
|
+
}
|
|
607
839
|
const me = bawStatus() === 'CONNECTED' ? bawAddress() : undefined
|
|
608
840
|
return out({ success: true, createPad: padNets('v3').mainnet.pad, tokens: await listTokens(me) })
|
|
609
841
|
}
|
|
@@ -743,13 +975,21 @@ async function main() {
|
|
|
743
975
|
await Promise.allSettled([...hooks])
|
|
744
976
|
return
|
|
745
977
|
}
|
|
746
|
-
case '
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
978
|
+
case 'info':
|
|
979
|
+
case 'token': // the older name of gang info
|
|
980
|
+
return routed('info')
|
|
981
|
+
case 'status':
|
|
982
|
+
case 'claim':
|
|
983
|
+
case 'mint':
|
|
984
|
+
case 'withdraw':
|
|
985
|
+
case 'release':
|
|
986
|
+
return routed(cmd)
|
|
751
987
|
case 'quote': {
|
|
752
|
-
const token =
|
|
988
|
+
const token = tokenArg(), bnb = spendArg()
|
|
989
|
+
if (!bnb) fail('--amount required (Soft archive: BNB; V2: quote ERC-20 amount; --bnb is the older name)')
|
|
990
|
+
if ((await padKind(token, 'quote')) !== 'legacy') {
|
|
991
|
+
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 })
|
|
992
|
+
}
|
|
753
993
|
const me = bawAddress()
|
|
754
994
|
const q = await quoteBuy(token, me, bnb)
|
|
755
995
|
return out({
|
|
@@ -761,68 +1001,30 @@ async function main() {
|
|
|
761
1001
|
: (q.deterministic ? 'V2 quote = LaunchpadV2.sol curve math (unique path; no Soft/U/S fallback).' : undefined),
|
|
762
1002
|
})
|
|
763
1003
|
}
|
|
764
|
-
case 'buy':
|
|
765
|
-
|
|
766
|
-
return buy(token, bnb, parseNote(), parseFollow())
|
|
767
|
-
}
|
|
1004
|
+
case 'buy':
|
|
1005
|
+
return routed('buy')
|
|
768
1006
|
case 'follow': {
|
|
769
|
-
// gang follow <note tx> --
|
|
1007
|
+
// gang follow <note tx> --amount <n>: the token comes from the note
|
|
770
1008
|
const noteTx = args[1] && !args[1].startsWith('--') ? args[1].toLowerCase() : null
|
|
771
|
-
if (!NOTE_TX.test(noteTx ?? '')) fail('usage: gang follow <note tx> --
|
|
772
|
-
if (flag('token')) fail('gang follow takes the token from the note; to name it yourself use `gang buy --token … --
|
|
773
|
-
if (flag('follow')) fail('give the note tx once: `gang follow <note tx> --
|
|
774
|
-
const bnb =
|
|
1009
|
+
if (!NOTE_TX.test(noteTx ?? '')) fail('usage: gang follow <note tx> --amount <n> (the note tx is 0x + 64 hex)', { got: noteTx })
|
|
1010
|
+
if (flag('token')) fail('gang follow takes the token from the note; to name it yourself use `gang buy --token … --amount … --follow <note tx>`')
|
|
1011
|
+
if (flag('follow')) fail('give the note tx once: `gang follow <note tx> --amount <n>`')
|
|
1012
|
+
const bnb = spendArg(); if (!bnb) fail('--amount required (the amount to buy, in the token\'s quote unit; --bnb is the older name)')
|
|
775
1013
|
const note = parseNote() // a bad --note fails before any API call, as on buy
|
|
776
|
-
// checked without a token: gang.fun answers with the note's token, and buy() reuses this check
|
|
777
|
-
|
|
1014
|
+
// checked without a token: gang.fun answers with the note's token, and buy() reuses this check. The wallet is the
|
|
1015
|
+
// one that buys: --from builds for another one (gang v3 buy takes it on the curve; buy() refuses it there)
|
|
1016
|
+
const { token, follow } = await confirmFollow(noteTx, fromArg() || bawAddress())
|
|
778
1017
|
if (!token) fail('gang.fun did not say which token this note is about', { noteTx })
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
parseFollow() // refuses --follow: only buys follow a note
|
|
785
|
-
if (flag('testnet')) fail(NOT_TESTNET)
|
|
786
|
-
const from = fromArg()
|
|
787
|
-
const me = from || bawAddress()
|
|
788
|
-
const info = await tokenInfo(token, me)
|
|
789
|
-
if (from && !info.graduated) fail(FROM_CURVE)
|
|
790
|
-
if (!flag('all') && !opt('amount')) fail('pass --amount <tokens> or --all (full sell is no longer the default)')
|
|
791
|
-
if (flag('all') && opt('amount')) fail('use either --all or --amount, not both')
|
|
792
|
-
// graduated: sell into the PancakeSwap pool through GangRouter
|
|
793
|
-
if (info.graduated) {
|
|
794
|
-
return v2PoolTrade('sell', token, info, me, note, { amount: flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: flag('all') })
|
|
795
|
-
}
|
|
796
|
-
const bal = await balanceOf(token, me)
|
|
797
|
-
if (bal === 0n) fail('no balance to sell')
|
|
798
|
-
const amount = flag('all') ? bal : parseEther(opt('amount'))
|
|
799
|
-
if (amount > bal) fail('amount exceeds balance', { balance: fmtTok(bal) })
|
|
800
|
-
const pad = await resolvePad(token)
|
|
801
|
-
const allow = await allowance(token, me, pad.address)
|
|
802
|
-
if (allow < amount) {
|
|
803
|
-
const p = bawPreview({ from: me, to: token, data: calldata.approve(pad.address, amount) })
|
|
804
|
-
return out(summarizePreview(p, {
|
|
805
|
-
step: 'approve', token, symbol: info.symbol, pad: pad.id, amount: fmtTok(amount),
|
|
806
|
-
note: 'Approve first. After it confirms on-chain, run the same `gang sell` again to preview the sell.',
|
|
807
|
-
...noteSkipped(note),
|
|
808
|
-
}))
|
|
1018
|
+
// a GangLaunchpad token: the V3 curve buy (--amount / --bnb = the quote to spend)
|
|
1019
|
+
const onPad = await kindOf(v2PoolCtx(), token)
|
|
1020
|
+
if (onPad === 'fair') fail('this note is on a 打新 (fair-mint) token: only curve buys can follow a note', { noteTx, token })
|
|
1021
|
+
if (onPad === 'v3') {
|
|
1022
|
+
return runV3('buy', { ...PAD_IO, opt: (n, d) => (n === 'token' ? token : opt(n, d)), notes: padNotes('v3', 'buy', { note, checked: follow }) })
|
|
809
1023
|
}
|
|
810
|
-
|
|
811
|
-
const q = await quoteSell(token, amount)
|
|
812
|
-
const minOut = (q.out * BigInt(Math.round((100 - slippage) * 100))) / 10000n
|
|
813
|
-
if (pad.kind === 'soft') {
|
|
814
|
-
const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.softSell(token, amount, minOut), note) })
|
|
815
|
-
return out(summarizeWithNote(p, {
|
|
816
|
-
step: 'sell', kind: 'soft', token, symbol: info.symbol, pad: pad.id,
|
|
817
|
-
amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'BNB',
|
|
818
|
-
}, note, 'sell'))
|
|
819
|
-
}
|
|
820
|
-
const p = bawPreview({ from: me, to: pad.address, data: withNote(calldata.v2Sell(token, amount, minOut), note) })
|
|
821
|
-
return out(summarizeWithNote(p, {
|
|
822
|
-
step: 'sell', kind: 'v2', token, symbol: info.symbol, pad: pad.id, quote: info.quote,
|
|
823
|
-
amount: fmtTok(amount), expectOut: q.outFmt, fee: q.feeFmt, minOut: fmt(minOut), unit: 'quote', quotePath: q.quotePath, deterministic: q.deterministic || false,
|
|
824
|
-
}, note, 'sell'))
|
|
1024
|
+
return buy(token, bnb, note, noteTx, follow)
|
|
825
1025
|
}
|
|
1026
|
+
case 'sell':
|
|
1027
|
+
return routed('sell')
|
|
826
1028
|
case 'upload-logo': {
|
|
827
1029
|
const file = opt('file') || opt('path') || opt('logo')
|
|
828
1030
|
const url = opt('url')
|
|
@@ -837,9 +1039,7 @@ async function main() {
|
|
|
837
1039
|
if (flag('pair-or-quote') || flag('pair')) {
|
|
838
1040
|
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.')
|
|
839
1041
|
}
|
|
840
|
-
return
|
|
841
|
-
opt, flag, out, fail, summarize: summarizePreview, resolveLogo: resolveLogoUri, checkMeta: parseMeta, resolveMeta: resolveMetaUri,
|
|
842
|
-
})
|
|
1042
|
+
return create()
|
|
843
1043
|
}
|
|
844
1044
|
case 'execute': {
|
|
845
1045
|
const id = opt('request-id'); if (!id) fail('--request-id required')
|
|
@@ -871,8 +1071,18 @@ async function main() {
|
|
|
871
1071
|
})
|
|
872
1072
|
}
|
|
873
1073
|
case 'tx': {
|
|
874
|
-
const hash = opt('hash'); if (
|
|
875
|
-
|
|
1074
|
+
const hash = opt('hash'); if (!/^0x[0-9a-fA-F]{64}$/.test(hash ?? '')) fail('--hash must be a tx hash (0x + 64 hex)')
|
|
1075
|
+
if (flag('testnet')) {
|
|
1076
|
+
// BSC testnet: GangLaunchpad only (no V2 / Soft there, and gang.fun indexes no testnet notes)
|
|
1077
|
+
let ctx
|
|
1078
|
+
try { ctx = v3Ctx(true) } catch (e) { fail(e.message) }
|
|
1079
|
+
const r = await ctx.client.waitForTransactionReceipt({ hash, timeout: 120_000 })
|
|
1080
|
+
return out({
|
|
1081
|
+
success: true, chainId: Number(ctx.chainId), hash, status: r.status, block: Number(r.blockNumber),
|
|
1082
|
+
events: decodeLogs(r.logs, GANG_EVENT_ABIS), explorer: `${ctx.explorer}/tx/${hash}`,
|
|
1083
|
+
})
|
|
1084
|
+
}
|
|
1085
|
+
const t = await decodeTx(hash, GANG_EVENT_ABIS)
|
|
876
1086
|
// the input may carry a follow ref and a note commitment: a committed note is sent here if it's still in the
|
|
877
1087
|
// local queue (e.g. after PENDING_CONFIRMATION), or its state reported / refreshed if already sent
|
|
878
1088
|
const queued = hasQueued()
|