@gangfun/cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/buyback.js ADDED
@@ -0,0 +1,183 @@
1
+ import { createPublicClient, http, parseAbi, encodeFunctionData, formatUnits, hexToString, isAddress, zeroAddress } from 'viem'
2
+ import { padNets, NOT_ON_MAINNET } from './gangpad.js'
3
+ import { walletFor } from './fair.js'
4
+ import { parseNum, revertOf as revertIn, step } from './steps.js'
5
+
6
+ /**
7
+ * `gang buyback …` and `gang create … --buyback <pct>` — GangBuybackSplitter (dev 税按比例回购销毁): a V3 token's tax
8
+ * recipient that splits each payment between a buy-back in the token's PancakeSwap pool, sent to 0xdead, and the dev.
9
+ * GangBuybackSplitterFactory clones one per token for the dev (`create(bps, salt)`, at `predict(dev, salt)`) and has
10
+ * GangHandlerAdmin list it, so the dev names it as the recipient at create (launch.js does the three steps: clone →
11
+ * create → bind). A dev who doesn't
12
+ * want it leaves --buyback out: the tax goes to their own address as before.
13
+ * The ratio: a raise applies at once, a cut 3 days after it is asked for (then anyone applies it within 7 days, or it
14
+ * lapses). The keeper buys back in chunks (≤ 0.5% of the pool's quote side, never > 2% above the 10-minute average).
15
+ * Env: GANG_SPLITTER_FACTORY / GANG_TESTNET_SPLITTER_FACTORY override the built-in factory addresses.
16
+ */
17
+
18
+ // BSC 56, 2026-10-28 (docs/MAINNET-BUYBACK-SPLITTER-2026-10-28.md); lists clones once the Safe ran its batch
19
+ export const MAINNET_SPLITTER_FACTORY = '0x6bcd79206da85Aa0643Db2164Ab887b468618546'
20
+ export const TESTNET_SPLITTER_FACTORY = '0x0Ff5C8801BdeaDac374fC14fEd6e7B31977c9DA1'
21
+ export const BUYBACK_TYPE = 'buyback-burn-v1'
22
+
23
+ export const FACTORY_ABI = parseAbi([
24
+ 'function create(uint16 buybackBps, bytes32 salt) returns (address splitter)',
25
+ 'function predict(address dev, bytes32 salt) view returns (address)',
26
+ 'function pad() view returns (address)',
27
+ 'error NotAgent()',
28
+ 'error NotFactory()',
29
+ 'error BadBps()',
30
+ ])
31
+
32
+ export const SPLITTER_ABI = parseAbi([
33
+ 'function moduleType() view returns (bytes32)',
34
+ 'function dev() view returns (address)',
35
+ 'function token() view returns (address)',
36
+ 'function quote() view returns (address)',
37
+ 'function buybackBps() view returns (uint16)',
38
+ 'function pendingCut() view returns (bool live, uint16 bps, uint64 at)',
39
+ 'function buybackLeft() view returns (uint256)',
40
+ 'function unsplit() view returns (uint256)',
41
+ 'function devOwed() view returns (uint256)',
42
+ 'function previewBurn() view returns (bool ready, uint256 quoteIn, uint256 tokensOut)',
43
+ 'function bind(address token)',
44
+ 'function setBuybackBps(uint16 newBps)',
45
+ 'function applyBuybackBps()',
46
+ 'function releaseDev() returns (uint256)',
47
+ 'error NotDev()',
48
+ 'error BadBps()',
49
+ 'error AlreadyBound()',
50
+ 'error NotCurve()',
51
+ 'error NotCreator()',
52
+ 'error NotRecipient()',
53
+ 'error NoProposal()',
54
+ 'error NotDue()',
55
+ 'error CutLapsed()',
56
+ 'error NothingOwed()',
57
+ ])
58
+
59
+ const PAD_ABI = parseAbi([
60
+ 'function taxConfigOf(address token) view returns (uint16 buyTaxBps, uint16 sellTaxBps, uint16 dividendBps, address recipient)',
61
+ ])
62
+
63
+ const HINTS = {
64
+ NotAgent: 'only an Agent can create a V3 token, so only an Agent gets a buy-back splitter',
65
+ NotFactory: 'this factory is not allowed on the platform\'s GangHandlerAdmin (any more): it cannot list a splitter',
66
+ NotDev: 'only the dev this splitter was made for can change its ratio',
67
+ NotCreator: 'the token was not created by this splitter\'s dev',
68
+ NotRecipient: 'the token does not pay its tax to this splitter',
69
+ AlreadyBound: 'the splitter is bound to a token already',
70
+ NotDue: 'the 3-day notice of this cut has not run yet',
71
+ CutLapsed: 'the cut was not applied within 7 days after its notice and has lapsed: ask for it again',
72
+ NoProposal: 'no cut asked for',
73
+ NothingOwed: 'nothing is owed to the dev',
74
+ }
75
+ const revertOf = (e) => revertIn(e, [SPLITTER_ABI, FACTORY_ABI], HINTS)
76
+
77
+ export function splitterFactoryAddress(testnet) {
78
+ return (testnet ? process.env.GANG_TESTNET_SPLITTER_FACTORY || TESTNET_SPLITTER_FACTORY
79
+ : process.env.GANG_SPLITTER_FACTORY || MAINNET_SPLITTER_FACTORY) || null
80
+ }
81
+
82
+ export const notDeployed = (testnet) =>
83
+ `GangBuybackSplitterFactory is not deployed on ${testnet ? 'the BSC testnet' : 'BSC mainnet'} in this CLI version: set ${testnet ? 'GANG_TESTNET_SPLITTER_FACTORY' : 'GANG_SPLITTER_FACTORY'} to its address`
84
+
85
+ /** --buyback <pct> (0–100, the share of the recipient's part bought back) in bps. */
86
+ export const buybackBpsOf = (fail, raw) => Math.round(parseNum(fail, 'buyback', raw, 0, 100) * 100)
87
+
88
+ const pct = (bps) => Number(bps) / 100
89
+ const iso = (s) => new Date(Number(s) * 1000).toISOString()
90
+
91
+ /** The splitter's state when `addr` is a GangBuybackSplitter; null when it is not (an EOA, a partner, another module). */
92
+ export async function splitterOf(read, addr) {
93
+ if (!addr || addr === zeroAddress) return null
94
+ let type
95
+ try { type = await read(addr, SPLITTER_ABI, 'moduleType') } catch { return null }
96
+ if (hexToString(type, { size: 32 }) !== BUYBACK_TYPE) return null
97
+ const [dev, token, bps, cut, left, unsplit, owed, burn] = await Promise.all(
98
+ ['dev', 'token', 'buybackBps', 'pendingCut', 'buybackLeft', 'unsplit', 'devOwed', 'previewBurn'].map((f) => read(addr, SPLITTER_ABI, f)))
99
+ // the pad takes 18-decimal quotes only
100
+ const q = (x) => formatUnits(x, 18)
101
+ return {
102
+ splitter: addr, moduleType: BUYBACK_TYPE, dev, token: token === zeroAddress ? null : token, buybackPct: pct(bps), buybackBps: Number(bps),
103
+ pendingCut: cut[0] ? { toPct: pct(cut[1]), appliesFrom: iso(cut[2]), lapsesAfter: iso(Number(cut[2]) + 7 * 86400) } : null,
104
+ buybackLeft: q(left), unsplit: q(unsplit), devOwed: q(owed),
105
+ nextChunk: burn[0] ? { quoteIn: q(burn[1]), tokensBurned: formatUnits(burn[2], 18) } : null,
106
+ note: `${pct(bps)}% of each tax payment to this recipient is bought back in the pool and burned (0xdead), the rest goes to the dev; a raise applies at once, a cut only 3 days after it is asked for`,
107
+ }
108
+ }
109
+
110
+ function buybackCtx(testnet) {
111
+ const net = padNets('v3')[testnet ? 'testnet' : 'mainnet']
112
+ if (!net.pad) throw new Error(NOT_ON_MAINNET)
113
+ const client = createPublicClient({ chain: net.chain, transport: http(net.rpc) })
114
+ return { ...net, client, read: (address, abi, functionName, args = []) => client.readContract({ address, abi, functionName, args }) }
115
+ }
116
+
117
+ /** Dry-run a call; { failed } with the revert when it would not pass. */
118
+ export async function simulateCall(client, from, address, abi, functionName, args = []) {
119
+ try {
120
+ const { result } = await client.simulateContract({ account: from, address, abi, functionName, args })
121
+ return { result }
122
+ } catch (e) {
123
+ const r = revertOf(e)
124
+ return { failed: r ? { reverted: true, ...r } : { reverted: false, error: e.shortMessage || e.message.split('\n')[0] } }
125
+ }
126
+ }
127
+
128
+ const SUBS = ['status', 'set', 'apply', 'bind', 'release']
129
+ const USAGE = 'usage: gang buyback <status|set|apply|bind|release> --token <addr> [--pct <0-100>] [--testnet] — see gang help'
130
+
131
+ /** `gang buyback <sub>`: the splitter of a V3 token (its tax recipient), found from --token. */
132
+ export async function runBuyback(sub, { opt, flag, out, fail, summarize }) {
133
+ if (!SUBS.includes(sub)) return fail(USAGE, { got: sub ?? null })
134
+ const testnet = flag('testnet')
135
+ const rawOnly = flag('calldata')
136
+ let ctx
137
+ try { ctx = buybackCtx(testnet) } catch (e) { return fail(e.message) }
138
+ const token = opt('token')
139
+ if (!isAddress(token ?? '')) fail('--token must be a V3 token address (0x…)', { got: token ?? null })
140
+ let recipient
141
+ try { recipient = (await ctx.read(ctx.pad, PAD_ABI, 'taxConfigOf', [token]))[3] } catch {
142
+ return fail('not a V3 token on this pad', { token, chainId: Number(ctx.chainId) })
143
+ }
144
+ const s = await splitterOf(ctx.read, recipient)
145
+ if (!s) return fail('this token\'s tax recipient is not a buy-back splitter', { token, recipient: recipient === zeroAddress ? null : recipient })
146
+ const chainId = Number(ctx.chainId)
147
+ const me = () => walletFor(ctx, opt('from'))
148
+ const send = async (functionName, args, extra) => {
149
+ const sim = await simulateCall(ctx.client, me(), s.splitter, SPLITTER_ABI, functionName, args)
150
+ if (sim.failed) return out({ success: false, step: functionName, ...sim.failed })
151
+ const data = encodeFunctionData({ abi: SPLITTER_ABI, functionName, args })
152
+ return out(step(ctx, { from: me(), to: s.splitter, data }, { chainId, token, splitter: s.splitter, ...extra }, rawOnly, summarize))
153
+ }
154
+
155
+ if (sub === 'status') return out({ success: true, chainId, ...s, ...(s.token === null && { bound: false }) })
156
+
157
+ if (sub === 'bind') {
158
+ if (s.token) return fail('already bound', { boundTo: s.token })
159
+ return send('bind', [token], { step: 'bind', note: 'anyone may bind; the token must be the dev\'s and pay its tax to this splitter' })
160
+ }
161
+
162
+ if (sub === 'set') {
163
+ if (me().toLowerCase() !== s.dev.toLowerCase()) return fail(HINTS.NotDev, { wallet: me(), dev: s.dev })
164
+ if (!flag('pct')) fail('--pct <0-100> required: the new buy-back share')
165
+ const bps = Math.round(parseNum(fail, 'pct', opt('pct'), 0, 100) * 100)
166
+ return send('setBuybackBps', [bps], bps >= s.buybackBps
167
+ ? { step: 'set-buyback', fromPct: s.buybackPct, toPct: bps / 100, note: `applies at once${s.pendingCut ? ' and cancels the pending cut' : ''}` }
168
+ : {
169
+ step: 'ask-cut', fromPct: s.buybackPct, toPct: bps / 100, appliesFromAbout: new Date(Date.now() + 3 * 86400_000).toISOString(),
170
+ note: `a cut waits 3 days (holders see it coming), then anyone runs gang buyback apply --token ${token}${testnet ? ' --testnet' : ''} within 7 days or it lapses${s.pendingCut ? '; this replaces the cut already asked for' : ''}`,
171
+ })
172
+ }
173
+
174
+ if (sub === 'apply') {
175
+ if (!s.pendingCut) return fail(HINTS.NoProposal, { buybackPct: s.buybackPct })
176
+ if (Date.parse(s.pendingCut.appliesFrom) > Date.now()) return fail(HINTS.NotDue, { appliesFrom: s.pendingCut.appliesFrom })
177
+ return send('applyBuybackBps', [], { step: 'apply-cut', fromPct: s.buybackPct, toPct: s.pendingCut.toPct, note: 'anyone may apply a cut once its notice ran' })
178
+ }
179
+
180
+ // release
181
+ if (!(Number(s.devOwed) > 0)) return fail(HINTS.NothingOwed)
182
+ return send('releaseDev', [], { step: 'release', amount: s.devOwed, note: 'anyone may; the dev gets what a failed push left owed' })
183
+ }
package/src/chain.js CHANGED
@@ -32,8 +32,8 @@ const PADS = [
32
32
  ]
33
33
 
34
34
  const GANG_POINTER = {
35
- v3: (t) => `a GangLaunchpad V3 (bonding-curve) token: use gang v3 (gang v3 info|buy|sell|status --token ${t})`,
36
- fair: (t) => `a GangLaunchpad 打新 (fair-mint) token: use gang fair (gang fair info|status --token ${t}; before launch mint / withdraw, after it buy / sell)`,
35
+ v3: (t) => `a GangLaunchpad V3 (bonding-curve) token: gang info|buy|sell|status --token ${t} trade it (re-run if this was an RPC hiccup)`,
36
+ fair: (t) => `a GangLaunchpad 打新 (fair-mint) token: gang info|status --token ${t}; before launch gang mint / withdraw, after it gang buy / sell (re-run if this was an RPC hiccup)`,
37
37
  }
38
38
 
39
39
  /** Resolve which pad hosts `token` by probing curves(token).creator != 0; a GangLaunchpad token is pointed at its command. */
@@ -261,13 +261,36 @@ export const calldata = {
261
261
  wrap: () => encodeFunctionData({ abi: WBNB_ABI, functionName: 'deposit' }),
262
262
  }
263
263
 
264
- export async function decodeTx(hash) {
264
+ /**
265
+ * Events of the logs that decode with one of `abis`, each with the emitting contract: GangLaunchpad logs, which `gang tx`
266
+ * reads beside the V2 / Soft / U / S pads'. Integers stay as on chain (strings), marked `units: 'raw'`: amounts are in wei
267
+ * (18 decimals), bps / shares / counts as they are; the V2 / Soft events are formatted (no `units`).
268
+ */
269
+ export function decodeLogs(logs, abis) {
270
+ const events = []
271
+ for (const log of logs) {
272
+ for (const abi of abis) {
273
+ try {
274
+ const ev = decodeEventLog({ abi, data: log.data, topics: log.topics })
275
+ events.push({ event: ev.eventName, contract: log.address, units: 'raw', ...Object.fromEntries(Object.entries(ev.args).map(([k, v]) => [k, typeof v === 'bigint' ? v.toString() : v])) })
276
+ break
277
+ } catch { /* other contract / event */ }
278
+ }
279
+ }
280
+ return events
281
+ }
282
+
283
+ /** `extraAbis`: decoders for logs not from the V2 / Soft / U / S pads (see decodeLogs). */
284
+ export async function decodeTx(hash, extraAbis = []) {
265
285
  const r = await client.waitForTransactionReceipt({ hash, timeout: 120_000 })
266
286
  const events = []
267
287
  const known = new Map(PADS.map((p) => [p.address.toLowerCase(), p]))
268
288
  for (const log of r.logs) {
269
289
  const pad = known.get(log.address.toLowerCase())
270
- if (!pad) continue
290
+ if (!pad) {
291
+ events.push(...decodeLogs([log], extraAbis))
292
+ continue
293
+ }
271
294
  try {
272
295
  const ev = decodeEventLog({ abi: pad.abi, data: log.data, topics: log.topics })
273
296
  const a = Object.fromEntries(Object.entries(ev.args).map(([k, v]) => {
package/src/fair.js CHANGED
@@ -5,6 +5,7 @@ import { API } from './discover.js'
5
5
  import { UA } from './version.js'
6
6
  import { MERGED_ERRORS_ABI, MERGED_HINTS, NOT_ON_MAINNET, notThisKind, padNets } from './gangpad.js'
7
7
  import { poolPayBnb, poolTrade } from './pool.js'
8
+ import { bondAddress, bondGuard } from './bond.js'
8
9
 
9
10
  /**
10
11
  * `gang fair …` — 打新 (fair mint): one fixed price per share both ways (mint buys at it, withdraw sells back at it);
@@ -81,7 +82,7 @@ const STATUS = ['none', 'minting', 'launched']
81
82
  const HINTS = {
82
83
  BadConfig: 'a rule is out of bounds: see reason',
83
84
  NotAgent: 'this wallet holds no Agent ID; fair mint is Agent-only',
84
- NotMinting: 'the sale already launched: trade the token in the pool with gang fair buy / gang fair sell',
85
+ NotMinting: 'the sale already launched: trade the token in the pool with gang buy / gang sell',
85
86
  WalletCapReached: 'this wallet already holds the per-wallet cap of shares',
86
87
  Slippage: 'fewer shares are left than --min-shares (someone filled first)',
87
88
  BadAmount: '--shares is more than this wallet holds in the sale',
@@ -104,6 +105,9 @@ const HINTS = {
104
105
  VaultFloorTooHigh: 'the platform\'s dividend vault requires a larger holding than fair-mint rewards allow, so no sale can be created right now; ask gang to fix the vault setting',
105
106
  }
106
107
 
108
+ /** What `gang tx` decodes a 打新 trade's logs with (the pad, the holder-reward vault). */
109
+ export const FAIR_EVENT_ABIS = [PAD_ABI, VAULT_ABI]
110
+
107
111
  export function fairCtx(testnet) {
108
112
  const net = FAIR_NETS[testnet ? 'testnet' : 'mainnet']
109
113
  if (!net.pad) throw new Error(NOT_ON_MAINNET)
@@ -244,7 +248,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
244
248
  if (!['minting', 'launched', 'all'].includes(status)) fail('--status must be minting, launched or all')
245
249
  const r = await fetch(`${API}/fair/sales?status=${status}&limit=30`, { headers: { 'user-agent': UA }, signal: AbortSignal.timeout(10_000) })
246
250
  .then((x) => (x.ok ? x.json() : { error: x.status })).catch((e) => ({ error: e.message }))
247
- if (r.error) return out({ success: false, error: `gang.fun fair list unavailable (${r.error})`, hint: `use gang fair info --token <addr>${testnet ? ' --testnet' : ''} for one sale` })
251
+ if (r.error) return out({ success: false, error: `gang.fun fair list unavailable (${r.error})`, hint: `use gang info --token <addr>${testnet ? ' --testnet' : ''} for one sale` })
248
252
  return out({ success: true, status, items: (r.items || []).map((x) => ({
249
253
  token: x.address, symbol: x.symbol, status: x.status, sold: x.sold, participants: x.participants,
250
254
  sharePrice: x.sharePrice && formatUnits(BigInt(x.sharePrice), 18), quote: x.quoteSymbol, chainId: x.chainId,
@@ -336,7 +340,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
336
340
  const sale = await saleOf(ctx, token)
337
341
  if (!sale) return notFair(token)
338
342
  if (sale.status !== 'minting') {
339
- return fail(`sale is ${sale.status}, not minting`, { hint: `it launched: buy in the pool with gang fair buy --token ${token} --amount <quote>${testnet ? ' --testnet' : ''}` })
343
+ return fail(`sale is ${sale.status}, not minting`, { hint: `it launched: buy in the pool with gang buy --token ${token} --amount <quote>${testnet ? ' --testnet' : ''}` })
340
344
  }
341
345
  const me = walletFor(ctx, opt('from'))
342
346
  if (sale.gate !== zeroAddress && (await ctx.read(sale.gate, ERC721_ABI, 'balanceOf', [me])) === 0n) {
@@ -382,7 +386,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
382
386
  if (sale.status !== 'minting') {
383
387
  return fail(`sale is ${sale.status}: withdraw only while minting`, sale.launched ? {
384
388
  pair: sale.pair,
385
- hint: `it launched: sell in the pool with \`gang fair sell --token ${token} (--amount <tokens> | --all)${testnet ? ' --testnet' : ''}${opt('from') ? ` --from ${opt('from')}` : ''} [--slippage 1] [--to-bnb]\``
389
+ hint: `it launched: sell in the pool with \`gang sell --token ${token} (--amount <tokens> | --all)${testnet ? ' --testnet' : ''}${opt('from') ? ` --from ${opt('from')}` : ''} [--slippage 1] [--to-bnb]\``
386
390
  + ' (through GangRouter; minOut from the tax handler\'s previewSellNextBlock)',
387
391
  } : {})
388
392
  }
@@ -390,6 +394,11 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
390
394
  const held = await ctx.read(ctx.pad, PAD_ABI, 'positions', [token, me])
391
395
  if (!held) return fail('this wallet has no shares in this sale')
392
396
  const shares = flag('all') ? Number(held) : parseIntIn(fail, 'shares', opt('shares'), 1, Number(held))
397
+ // a bond posted during the sale already binds: selling shares back takes their tokens out of the creator's wallet
398
+ const guard = bondAddress(testnet) && await bondGuard(ctx, {
399
+ token, me, amount: BigInt(shares) * (await ctx.read(ctx.pad, PAD_ABI, 'TOKENS_PER_SHARE')), testnet, breakBond: flag('break-bond'), settleFirst: flag('settle-first'),
400
+ }, rawOnly, summarize)
401
+ if (guard?.refuse) return out({ success: false, step: 'withdraw', wallet: me, ...guard.refuse })
393
402
  const [payout, fee] = await ctx.read(ctx.pad, PAD_ABI, 'previewWithdraw', [token, me, BigInt(shares)])
394
403
  // the price is fixed, so only the exit fee can move the payout: a mint landing first can restart the 7-day clock
395
404
  // and bring a 1–2% fee back. 0.1% below the preview refuses that and nothing else.
@@ -401,7 +410,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
401
410
  step: 'withdraw', chainId: Number(ctx.chainId), token, shares,
402
411
  receive: await units(ctx, sale.quote, payout), minReceive: await units(ctx, sale.quote, minPayout),
403
412
  exitFee: await units(ctx, sale.quote, fee), quote: sale.quoteSymbol,
404
- exitFeePct: sale.exitFeeNowPct,
413
+ exitFeePct: sale.exitFeeNowPct, ...guard?.info,
405
414
  note: sale.stale
406
415
  ? `no progress for 7 days: no exit fee, the full mint price back; the ${sale.mintFeePct}% paid at mint is not returned`
407
416
  : `mint price − ${sale.exitFeeNowPct}% exit fee (none after 7 days without 10 new shares); the ${sale.mintFeePct}% paid at mint is not returned`,
@@ -414,8 +423,8 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
414
423
  const sale = await saleOf(ctx, token)
415
424
  if (!sale) return notFair(token)
416
425
  if (!sale.launched || !sale.pair) {
417
- return fail(`sale is ${sale.status}: gang fair ${sub} trades the token in its pool after launch`, {
418
- hint: sub === 'buy' ? 'while minting, buy shares with gang fair mint --shares <n>' : 'while minting, sell shares back with gang fair withdraw (--shares <n> | --all)',
426
+ return fail(`sale is ${sale.status}: gang ${sub} trades the token in its pool after launch`, {
427
+ hint: sub === 'buy' ? `while minting, buy shares with gang mint --token ${token} --shares <n>` : `while minting, sell shares back with gang withdraw --token ${token} (--shares <n> | --all)`,
419
428
  })
420
429
  }
421
430
  const slippage = parseNum(fail, 'slippage', opt('slippage', '1'), 0, 50)
@@ -459,6 +468,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
459
468
  amount: sub === 'sell' && flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: sub === 'sell' && flag('all'),
460
469
  swapBackGas: Number(sellTaxBps) > 0, payBnb: sub === 'buy' && poolPayBnb(ctx, sale.quote, flag), toBnb: flag('to-bnb'),
461
470
  base: { kind: 'fair', pair: sale.pair, ...notes.pool, ...(outWarning && { ...outWarning, confirmedOut: true }) },
471
+ ...(sub === 'sell' && { bond: { testnet, breakBond: flag('break-bond'), settleFirst: flag('settle-first') } }),
462
472
  }, { rawOnly, summarize, revertOf }))
463
473
  }
464
474
 
@@ -473,7 +483,7 @@ export async function runFair(sub, { opt, flag, out, fail, summarize, resolveLog
473
483
  if (r.claimableRaw === 0n) {
474
484
  return fail('nothing to claim yet', {
475
485
  ...(unsold && { creditedUnsoldEstimate: r.creditedUnsoldEstimate, note: r.note }),
476
- hint: `gang fair status --token ${token}${testnet ? ' --testnet' : ''}`,
486
+ hint: `gang status --token ${token}${testnet ? ' --testnet' : ''}`,
477
487
  })
478
488
  }
479
489
  const data = encodeFunctionData({ abi: VAULT_ABI, functionName: 'claim', args: [token] })
package/src/gangpad.js CHANGED
@@ -69,7 +69,7 @@ export const MERGED_ERRORS_ABI = parseAbi([
69
69
  ])
70
70
  export const MERGED_HINTS = {
71
71
  Untaxed: 'this token is untaxed (0% / 0%): it never gets a pool tax, a tax route or dividends; nothing to release or claim on it',
72
- NotReclaimable: 'only a 打新 (fair-mint) token can be sold back to the pad; a V3 token is sold on its curve (gang v3 sell)',
72
+ NotReclaimable: 'only a 打新 (fair-mint) token can be sold back to the pad; a V3 token is sold on its curve (gang sell)',
73
73
  NotLocked: 'the sale already launched (the token is unlocked): sell-back is over, trade the token on PancakeSwap',
74
74
  WrongKind: 'this token is the other kind on the merged pad: V3 tokens use `gang v3 …`, 打新 tokens use `gang fair …`',
75
75
  BadKind: 'the tax handler refused the token kind (a pad / handler wiring problem): report it to gang',
@@ -99,10 +99,10 @@ export async function notThisKind(ctx, token, want, net = '') {
99
99
  const kind = await kindOf(ctx, token)
100
100
  const ask = want === 'v3' ? 'a LaunchpadV3 (tax) token' : 'a 打新 (fair-mint) sale'
101
101
  if (kind === 'fair' && want === 'v3') {
102
- return { error: `this is a 打新 (fair-mint) token, not a V3 token: use gang fair (e.g. gang fair info --token ${token}${net})`, kind }
102
+ return { error: `this is a 打新 (fair-mint) token, not a V3 token: gang info / buy / sell / mint / withdraw --token ${token}${net} pick the right pad code`, kind }
103
103
  }
104
104
  if (kind === 'v3' && want === 'fair') {
105
- return { error: `this is a V3 (bonding-curve) token, not a 打新 sale: use gang v3 (e.g. gang v3 info --token ${token}${net})`, kind }
105
+ return { error: `this is a V3 (bonding-curve) token, not a 打新 sale: gang info / buy / sell --token ${token}${net} pick the right pad code`, kind }
106
106
  }
107
107
  return { error: `not ${ask} on this pad`, kind }
108
108
  }