@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/pool.js
CHANGED
|
@@ -3,7 +3,7 @@ import { decimalsOf, isMissingFunction, poolSellQuote, ROUTER_ABI, routerDeadlin
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* PancakeSwap (V2 pool) buys and sells of a graduated / launched token through GangRouter — V3 and 打新 tokens
|
|
6
|
-
* (
|
|
6
|
+
* (V3 / 打新, also as `gang v3|fair buy|sell`) and V2 / Soft / U / S tokens (`gang buy|sell`), all the same way:
|
|
7
7
|
* poolBuyBNB(token, quote, minTokensOut, deadline) payable path [WBNB, token] or [WBNB, quote, token]
|
|
8
8
|
* poolBuy(token, quote, quoteIn, minTokensOut, deadline) path [quote, token]; approve quote → router first
|
|
9
9
|
* poolSell(token, quote, amountIn, minQuoteOut, deadline) path [token, quote]; approve token → router first
|
package/src/steps.js
CHANGED
|
@@ -263,7 +263,7 @@ export async function swapBackState(ctx, handler, token, reserveToken) {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/** Hint for selling a graduated / launched token on PancakeSwap with the minOut of a pool quote (poolSellQuote) as amountOutMin. */
|
|
266
|
-
export const POOL_SELL_HINT = '`gang
|
|
266
|
+
export const POOL_SELL_HINT = '`gang sell` builds this sell through GangRouter (poolSell / --to-bnb poolSellForBNB, minOut as here); '
|
|
267
267
|
+ 'or sell on PancakeSwap with swapExactTokensForTokensSupportingFeeOnTransferTokens (the token takes its sell tax in the transfer), passing minOut (poolQuote.minOut / poolSellAll.minOut, set by --slippage) as amountOutMin so the sell reverts rather than fills below it; '
|
|
268
268
|
+ 'the quote includes the small chunk of tax the handler sells right before a sell and assumes the tx lands in a new block (previewSellNextBlock). '
|
|
269
269
|
+ 'Let the wallet estimate gas, or give at least 750k: a sell that triggers a chunk uses about 660k (about 330k without), and too little gas reverts with SwapBackOutOfGas'
|
package/src/v3.js
CHANGED
|
@@ -3,7 +3,7 @@ import { decimalsOf, defaultRefPool, dividendOf, VAULT_ABI, fundingStep as fund,
|
|
|
3
3
|
import { API } from './discover.js'
|
|
4
4
|
import { UA } from './version.js'
|
|
5
5
|
import { walletFor } from './fair.js'
|
|
6
|
-
import { MERGED_ERRORS_ABI, MERGED_HINTS, NOT_ON_MAINNET,
|
|
6
|
+
import { MERGED_ERRORS_ABI, MERGED_HINTS, NOT_ON_MAINNET, notThisKind, padNets } from './gangpad.js'
|
|
7
7
|
import { poolPayBnb, poolTrade } from './pool.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -69,7 +69,7 @@ const HINTS = {
|
|
|
69
69
|
BadTaxConfig: 'a tax setting is out of bounds, or the recipient is neither you nor on the whitelist: see reason',
|
|
70
70
|
NotAgent: 'this wallet holds no Agent ID; V3 create and curve trades are Agent-only',
|
|
71
71
|
WalletCapReached: 'this wallet already holds the per-wallet cap (1% of supply)',
|
|
72
|
-
AlreadyGraduated: 'the token graduated: re-run gang
|
|
72
|
+
AlreadyGraduated: 'the token graduated: re-run gang buy / gang sell, which now trade it in the PancakeSwap pool',
|
|
73
73
|
Slippage: 'the price moved past --slippage; re-run to re-quote',
|
|
74
74
|
RefPoolRequired: 'a non-WBNB --quote needs --ref-pool: a Pancake V2 pair or V3 pool that holds the quote token',
|
|
75
75
|
QuoteNotInPool: '--ref-pool does not contain the --quote token',
|
|
@@ -90,6 +90,9 @@ const HINTS = {
|
|
|
90
90
|
VaultFloorTooHigh: 'the platform\'s dividend vault requires a larger holding than V3 dividends allow (10,000 tokens), so a dividend token can\'t be created right now; --dividend 0 still works, or ask gang to fix the vault setting',
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/** What `gang tx` decodes a V3 trade's logs with (the pad, the tax router, the dividend vault). */
|
|
94
|
+
export const V3_EVENT_ABIS = [PAD_ABI, TAX_ROUTER_ABI, VAULT_ABI]
|
|
95
|
+
|
|
93
96
|
export function v3Ctx(testnet) {
|
|
94
97
|
const net = V3_NETS[testnet ? 'testnet' : 'mainnet']
|
|
95
98
|
if (!net.pad) throw new Error(NOT_ON_MAINNET)
|
|
@@ -100,6 +103,7 @@ export function v3Ctx(testnet) {
|
|
|
100
103
|
const symbolOf = async (ctx, token) => { try { return await ctx.read(token, ERC20_ABI, 'symbol') } catch { return null } }
|
|
101
104
|
const pct = (bps) => Number(bps) / 100
|
|
102
105
|
const VIRTUAL_TOKEN = 1_066_666_666_666_666_666_666_666_667n
|
|
106
|
+
const ceilDiv = (a, b) => (a + b - 1n) / b
|
|
103
107
|
|
|
104
108
|
/** Revert name (+ BadTaxConfig reason) of a failed call, or null when it was the RPC. */
|
|
105
109
|
const revertOf = (e) => revertIn(e, [PAD_ABI, TAX_ROUTER_ABI, GANG_ROUTER_ABI, MERGED_ERRORS_ABI], { ...MERGED_HINTS, ...ROUTER_HINTS, ...HINTS })
|
|
@@ -178,10 +182,22 @@ export async function curveOf(ctx, token) {
|
|
|
178
182
|
toGraduation: await units(ctx, quote, remaining),
|
|
179
183
|
progressPct: graduated ? 100 : gradAmt > 0n ? Number((realQuote * 10_000n) / gradAmt) / 100 : 0,
|
|
180
184
|
priceQuote: await units(ctx, quote, price), priceFrom: graduated ? 'pool' : 'curve', priceRaw: price,
|
|
181
|
-
owedToRecipient: await units(ctx, quote, owed), owedRaw: owed, reserveTokenRaw,
|
|
185
|
+
owedToRecipient: await units(ctx, quote, owed), owedRaw: owed, reserveTokenRaw, curveRaw: { realQuote, sold, virtualQuote },
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
/**
|
|
190
|
+
* What a curve buy of `quoteIn` that gets `tokensOut` spends, the fee included (CurveLogic _quoteInFor + _recomputeFee):
|
|
191
|
+
* less than quoteIn when the wallet cap or the graduation cut trimmed it and the rest is refunded.
|
|
192
|
+
*/
|
|
193
|
+
async function spentFor(ctx, c, quoteIn, tokensOut) {
|
|
194
|
+
const { realQuote, sold, virtualQuote } = c.curveRaw
|
|
195
|
+
const net = ceilDiv(virtualQuote * VIRTUAL_TOKEN, VIRTUAL_TOKEN - sold - tokensOut) - (virtualQuote + realQuote)
|
|
196
|
+
const feeBps = BigInt(Math.round((await feesOf(ctx)).curveFeePct * 100))
|
|
197
|
+
const gross = ceilDiv(net * 10_000n, 10_000n - feeBps)
|
|
198
|
+
return gross < quoteIn ? gross : quoteIn
|
|
199
|
+
}
|
|
200
|
+
|
|
185
201
|
/** `token` funding for `need` toward the pad (wrap BNB, then approve): a step object, or null when ready. */
|
|
186
202
|
const fundingStep = (ctx, me, token, need, sym, approveStep, rawOnly, summarize) =>
|
|
187
203
|
fund(ctx, { me, token, need, spender: ctx.pad, sym, approveStep }, rawOnly, summarize)
|
|
@@ -205,14 +221,11 @@ const SUBS = ['list', 'info', 'status', 'create', 'buy', 'sell', 'claim', 'incom
|
|
|
205
221
|
const USAGE = 'usage: gang v3 <list|info|status|create|buy|sell|claim|income|release|tx> [--testnet] — see gang help'
|
|
206
222
|
|
|
207
223
|
/**
|
|
208
|
-
* Entry for `gang v3 <sub>`. io: { opt, flag, out, fail, summarize, resolveLogo, checkMeta, resolveMeta } from
|
|
209
|
-
* (summarize = summarizePreview, so previews look like every other command's).
|
|
224
|
+
* Entry for `gang v3 <sub>`. io: { opt, flag, out, fail, summarize, resolveLogo, checkMeta, resolveMeta, notes } from
|
|
225
|
+
* index.js (summarize = summarizePreview, so previews look like every other command's; notes = padNotes: --note / --follow).
|
|
210
226
|
*/
|
|
211
|
-
export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo, checkMeta, resolveMeta }) {
|
|
227
|
+
export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo, checkMeta, resolveMeta, notes }) {
|
|
212
228
|
if (!SUBS.includes(sub)) return fail(USAGE, { got: sub ?? null })
|
|
213
|
-
// agent notes are matched to the server's V2 / Soft trades; V3 txs are not matched there yet
|
|
214
|
-
const noteError = noteFlagError(flag, 'GangLaunchpad tokens (gang create / gang v3)', 'a GangLaunchpad tx')
|
|
215
|
-
if (noteError) return fail(noteError)
|
|
216
229
|
const testnet = flag('testnet')
|
|
217
230
|
const net = testnet ? ' --testnet' : ''
|
|
218
231
|
const rawOnly = flag('calldata')
|
|
@@ -224,7 +237,7 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
224
237
|
const path = recipient ? `/v3/recipients/${recipient.toLowerCase()}?limit=20` : `/v3/tokens?status=${status}&limit=30`
|
|
225
238
|
const r = await fetch(`${API}${path}`, { headers: { 'user-agent': UA }, signal: AbortSignal.timeout(10_000) })
|
|
226
239
|
.then((x) => (x.ok ? x.json() : { error: x.status })).catch((e) => ({ error: e.message }))
|
|
227
|
-
if (r.error) return out({ success: false, error: `gang.fun V3 data unavailable (${r.error})`, hint: `use gang
|
|
240
|
+
if (r.error) return out({ success: false, error: `gang.fun V3 data unavailable (${r.error})`, hint: `use gang info --token <addr>${net} for one token (reads the chain)` })
|
|
228
241
|
// the quote's own decimals from gang.fun (18 when an older server does not send them)
|
|
229
242
|
const f = (x, d) => (x == null ? null : formatUnits(BigInt(x), d ?? 18))
|
|
230
243
|
if (recipient) {
|
|
@@ -254,7 +267,7 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
254
267
|
const token = addrArg(opt, fail, 'token', 'a V3 token address')
|
|
255
268
|
const c = await curveOf(ctx, token)
|
|
256
269
|
if (!c) return notV3(token)
|
|
257
|
-
const { owedRaw: _raw, priceRaw: _p, reserveTokenRaw: rT, ...pub } = c
|
|
270
|
+
const { owedRaw: _raw, priceRaw: _p, reserveTokenRaw: rT, curveRaw: _cv, ...pub } = c
|
|
258
271
|
// in the pool the tax is held as tokens and sold in small chunks under a per-block budget
|
|
259
272
|
const swapBack = c.graduated && c.pair ? await swapBackState(ctx, c.taxHandler, token, rT) : null
|
|
260
273
|
const fees = await feesOf(ctx)
|
|
@@ -298,7 +311,7 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
298
311
|
if (d.claimableRaw === 0n) {
|
|
299
312
|
return fail('nothing to claim yet', {
|
|
300
313
|
...(d.creditedUnsoldTokens && Number(d.creditedUnsoldTokens) > 0 && { creditedUnsoldEstimate: d.creditedUnsoldEstimate, note: d.note }),
|
|
301
|
-
hint: `gang
|
|
314
|
+
hint: `gang status --token ${token}${testnet ? ' --testnet' : ''}`,
|
|
302
315
|
})
|
|
303
316
|
}
|
|
304
317
|
const sim = await simulate(ctx, me, vault, VAULT_ABI, 'claim', [token])
|
|
@@ -367,7 +380,7 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
367
380
|
}
|
|
368
381
|
const rest = 100 - dividend
|
|
369
382
|
const fees = await feesOf(ctx)
|
|
370
|
-
return out(step(ctx, { from: me, to: ctx.pad, data: createData(logo.logoURI, meta.metaURI) }, {
|
|
383
|
+
return out(notes.step(ctx, { from: me, to: ctx.pad, data: createData(logo.logoURI, meta.metaURI) }, {
|
|
371
384
|
step: 'create', chainId: Number(ctx.chainId), name, symbol, logoURI: logo.logoURI, ...(logo.uploaded && { uploaded: true }),
|
|
372
385
|
metaURI: meta.metaURI, metadata: meta.metadata,
|
|
373
386
|
quote: (await symbolOf(ctx, quote)) || quote, graduationAt: await units(ctx, quote, gradAmt),
|
|
@@ -378,8 +391,8 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
378
391
|
? `of each ${buyTax}% buy / ${sellTax}% sell tax in the PancakeSwap pool (after graduation; the curve charges no tax): ${fees.platformTaxPct}% of volume to the platform, then ${dividend}% of the rest to holders, ${rest}% to the recipient in the quote`
|
|
379
392
|
+ (dividend > 0 ? '; if no holder has 10,000+ tokens when a tax is paid, the holders\' part goes to the recipient too' : '')
|
|
380
393
|
: 'untaxed: no tax in the PancakeSwap pool after graduation, no dividend, no recipient',
|
|
381
|
-
note: `${taxed ? 'tax rates and recipient are' : 'untaxed is'} fixed at create. Curve trades pay only the ${fees.curveFeePct}% fee, no tax${taxed ? ': the tax starts in the pool after graduation' : ''}. tokenIfNext changes if another create lands first: the real address is the Created event from \`gang
|
|
382
|
-
},
|
|
394
|
+
note: `${taxed ? 'tax rates and recipient are' : 'untaxed is'} fixed at create. Curve trades pay only the ${fees.curveFeePct}% fee, no tax${taxed ? ': the tax starts in the pool after graduation' : ''}. tokenIfNext changes if another create lands first: the real address is the Created event from \`gang tx --hash …\`.`,
|
|
395
|
+
}, 'create'))
|
|
383
396
|
}
|
|
384
397
|
|
|
385
398
|
if (sub === 'buy') {
|
|
@@ -389,13 +402,14 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
389
402
|
if (c.graduated) {
|
|
390
403
|
// graduated: buy in the PancakeSwap pool through GangRouter (the buy tax applies; no Agent gate, no wallet cap)
|
|
391
404
|
if (!c.pair) return fail('the pad marks this token graduated but has no PancakeSwap pair on record: nothing to trade it on', { token })
|
|
405
|
+
if (notes.following) fail('--follow only counts on curve buys before graduation; this token trades on PancakeSwap now', { pair: c.pair })
|
|
392
406
|
if (!flag('amount') && !flag('bnb')) fail('--amount <quote (or BNB) to spend> required')
|
|
393
407
|
const raw = parseAmount(fail, flag('amount') ? 'amount' : 'bnb', opt('amount') ?? opt('bnb'))
|
|
394
408
|
return out(await poolTrade(ctx, {
|
|
395
409
|
side: 'buy', token, quote: c.quote, me: walletFor(ctx, opt('from')), amount: raw,
|
|
396
410
|
slippage: parseSlippage(fail, opt('slippage', '1')), buyTaxBps: Math.round(c.buyTaxPct * 100),
|
|
397
411
|
sellTaxBps: Math.round(c.sellTaxPct * 100), handler: c.taxHandler, payBnb: poolPayBnb(ctx, c.quote, flag),
|
|
398
|
-
base: { kind: 'v3', pair: c.pair },
|
|
412
|
+
base: { kind: 'v3', pair: c.pair, ...notes.pool },
|
|
399
413
|
}, { rawOnly, summarize, revertOf }))
|
|
400
414
|
}
|
|
401
415
|
const me = walletFor(ctx, opt('from'))
|
|
@@ -405,6 +419,9 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
405
419
|
const raw = parseAmount(fail, flag('amount') ? 'amount' : 'bnb', opt('amount') ?? opt('bnb'))
|
|
406
420
|
const quoteIn = parseUnits(raw, await decimalsOf(ctx, c.quote))
|
|
407
421
|
const slippage = parseSlippage(fail, opt('slippage', '1'))
|
|
422
|
+
// asked from gang.fun before any wrap / approve round, so a follow that can't count fails first
|
|
423
|
+
const follow = await notes.follow(token, me)
|
|
424
|
+
const priced = async (tokensOut) => follow && notes.priced(follow, token, await spentFor(ctx, c, quoteIn, tokensOut), await decimalsOf(ctx, c.quote))
|
|
408
425
|
const extra = (tokensOut, minOut) => ({
|
|
409
426
|
step: 'buy', chainId: Number(ctx.chainId), token, spend: raw, quote: c.quoteSymbol, expectTokens: formatUnits(tokensOut, 18),
|
|
410
427
|
minTokensOut: formatUnits(minOut, 18), curveTaxPct: 0, ...(c.buyTaxPct > 0 && { poolBuyTaxPct: c.buyTaxPct }),
|
|
@@ -422,20 +439,20 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
422
439
|
if (sim.failed) return out({ success: false, step: 'buy', via: 'router', ...sim.failed })
|
|
423
440
|
const minOut = minOf(sim.result, slippage)
|
|
424
441
|
const data = encodeFunctionData({ abi: GANG_ROUTER_ABI, functionName: 'v3BuyBNB', args: [token, minOut, routerDeadline()] })
|
|
425
|
-
return out(step(ctx, { from: me, to: ctx.router, data, value: quoteIn }, {
|
|
442
|
+
return out(notes.step(ctx, { from: me, to: ctx.router, data, value: quoteIn }, {
|
|
426
443
|
...extra(sim.result, minOut), via: 'router', curveFeePct: (await feesOf(ctx)).curveFeePct,
|
|
427
444
|
note: 'paid in BNB through GangRouter; spend includes the 1% curve fee (no tax on the curve; the buy tax applies only in the pool after graduation); at the 1% wallet cap or the graduation cut the excess comes back to this wallet as WBNB (not BNB)',
|
|
428
|
-
},
|
|
445
|
+
}, 'buy', await priced(sim.result)))
|
|
429
446
|
}
|
|
430
447
|
const pre = await fundingStep(ctx, me, c.quote, quoteIn, c.quoteSymbol, 'approve-quote', rawOnly, summarize)
|
|
431
|
-
if (pre) return out(pre)
|
|
448
|
+
if (pre) return out(pre.success ? { ...pre, ...notes.skipped(follow) } : pre)
|
|
432
449
|
const sim = await simulate(ctx, me, ctx.pad, PAD_ABI, 'buy', [token, quoteIn, 0n])
|
|
433
450
|
if (sim.failed) return out({ success: false, step: 'buy', ...sim.failed })
|
|
434
451
|
const minOut = minOf(sim.result, slippage)
|
|
435
|
-
return out(step(ctx, { from: me, to: ctx.pad, data: encodeFunctionData({ abi: PAD_ABI, functionName: 'buy', args: [token, quoteIn, minOut] }) }, {
|
|
452
|
+
return out(notes.step(ctx, { from: me, to: ctx.pad, data: encodeFunctionData({ abi: PAD_ABI, functionName: 'buy', args: [token, quoteIn, minOut] }) }, {
|
|
436
453
|
...extra(sim.result, minOut), curveFeePct: (await feesOf(ctx)).curveFeePct,
|
|
437
454
|
note: 'spend includes the 1% curve fee (no tax on the curve; the buy tax applies only in the pool after graduation); at the 1% wallet cap the excess is refunded',
|
|
438
|
-
},
|
|
455
|
+
}, 'buy', await priced(sim.result)))
|
|
439
456
|
}
|
|
440
457
|
|
|
441
458
|
if (sub === 'sell') {
|
|
@@ -454,7 +471,7 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
454
471
|
return out(await poolTrade(ctx, {
|
|
455
472
|
side: 'sell', token, quote: c.quote, me, amount: flag('all') ? null : parseAmount(fail, 'amount', opt('amount')), all: flag('all'),
|
|
456
473
|
slippage, buyTaxBps: Math.round(c.buyTaxPct * 100), sellTaxBps: Math.round(c.sellTaxPct * 100), handler: c.taxHandler,
|
|
457
|
-
swapBackGas: c.sellTaxPct > 0, toBnb: flag('to-bnb'), base: { kind: 'v3', pair: c.pair },
|
|
474
|
+
swapBackGas: c.sellTaxPct > 0, toBnb: flag('to-bnb'), base: { kind: 'v3', pair: c.pair, ...notes.pool },
|
|
458
475
|
}, { rawOnly, summarize, revertOf }))
|
|
459
476
|
}
|
|
460
477
|
if (flag('all')) return fail('--all needs a wallet (or --from <addr>) whose balance can be read', { pair: c.pair })
|
|
@@ -477,16 +494,16 @@ export async function runV3(sub, { opt, flag, out, fail, summarize, resolveLogo,
|
|
|
477
494
|
const amount = flag('all') ? bal : parseUnits(parseAmount(fail, 'amount', opt('amount')), 18)
|
|
478
495
|
if (amount > bal) fail('amount exceeds balance', { balance: formatUnits(bal, 18) })
|
|
479
496
|
const pre = await fundingStep(ctx, me, token, amount, c.quoteSymbol, 'approve', rawOnly, summarize)
|
|
480
|
-
if (pre) return out(pre)
|
|
497
|
+
if (pre) return out(pre.success ? { ...pre, ...notes.skipped() } : pre)
|
|
481
498
|
const sim = await simulate(ctx, me, ctx.pad, PAD_ABI, 'sell', [token, amount, 0n])
|
|
482
499
|
if (sim.failed) return out({ success: false, step: 'sell', ...sim.failed })
|
|
483
500
|
const minOut = minOf(sim.result, slippage)
|
|
484
|
-
return out(step(ctx, { from: me, to: ctx.pad, data: encodeFunctionData({ abi: PAD_ABI, functionName: 'sell', args: [token, amount, minOut] }) }, {
|
|
501
|
+
return out(notes.step(ctx, { from: me, to: ctx.pad, data: encodeFunctionData({ abi: PAD_ABI, functionName: 'sell', args: [token, amount, minOut] }) }, {
|
|
485
502
|
step: 'sell', chainId: Number(ctx.chainId), token, amount: formatUnits(amount, 18), quote: c.quoteSymbol,
|
|
486
503
|
expectOut: await units(ctx, c.quote, sim.result), minOut: await units(ctx, c.quote, minOut), curveTaxPct: 0,
|
|
487
504
|
...(c.sellTaxPct > 0 && { poolSellTaxPct: c.sellTaxPct }),
|
|
488
505
|
curveFeePct: (await feesOf(ctx)).curveFeePct, note: 'expectOut is after the 1% curve fee only (no tax on the curve; the sell tax applies only in the pool after graduation)',
|
|
489
|
-
},
|
|
506
|
+
}, 'sell'))
|
|
490
507
|
}
|
|
491
508
|
|
|
492
509
|
if (sub === 'release') {
|