Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

gnoswap_list.gno

19.07 Kb · 616 lines
  1package padv3
  2
  3import (
  4	"chain"
  5	"strconv"
  6	"strings"
  7	"time"
  8
  9	"gno.land/p/gnoswap/consts/v1"
 10	u256 "gno.land/p/gnoswap/uint256/v1"
 11	"gno.land/r/gnoland/wugnot"
 12	"gno.land/r/gnoswap/gns"
 13	gnspool "gno.land/r/gnoswap/pool"
 14	"gno.land/r/gnoswap/position"
 15	"gno.land/r/gnoswap/router"
 16)
 17
 18// Sapphire Gnoswap registry keys / role addresses (sapphire-1).
 19const (
 20	wugnotTokenKey       = "gno.land/r/gnoland/wugnot.wugnot"
 21	gnsTokenKey          = "gno.land/r/gnoswap/gns.GNS"
 22	gnoswapPoolAddrStr   = "g1dexaf6aqkkyr9yfy9d5up69lsn7ra80af34g5v"
 23	gnoswapRouterAddrStr = "g1vc883gshu5z7ytk5cdynhc8c2dh67pdp4cszkp"
 24)
 25
 26// listFund tracks assets pulled from the listing caller for refund / LP ugnot reimburse.
 27type listFund struct {
 28	ok              bool
 29	caller          address
 30	pulledLpWugnot  int64 // reimbursed as ugnot after successful list
 31	pulledFeeWugnot int64 // leftover WUGNOT refunded after list; full refund on fail
 32	pulledGns       int64 // CreatePool fee; consumed on success
 33}
 34
 35// tryListOnGnoswap seeds a Gnoswap CL pool with remaining meme tokens + raised-sized
 36// WUGNOT as LP. CreatePool fee (fixed GNS amount) is paid from:
 37//  1) pre-funded GNS on pad (preferred - immune to GNOT/GNS price moves), else
 38//  2) ExactOut WUGNOT->GNS using SURPLUS inventory only (above raised), so LP depth
 39//     stays equal to raised even when GNS is expensive.
 40//
 41// Returns true on success. Soft failure sets l.GnoswapNote and returns false
 42// (caller keeps internal CPMM). Does not pull from user - see prepareCallerListFunding.
 43func tryListOnGnoswap(cur realm, l *Launch, raisedUgnot, remainingTokens int64) bool {
 44	if raisedUgnot <= 0 || remainingTokens <= 0 {
 45		l.GnoswapNote = "list skip: empty capital"
 46		return false
 47	}
 48	padAddr := cur.Address()
 49	wBal := wugnot.BalanceOf(padAddr)
 50	// Full reservation backing: prevent launch A from Minting with launch B's PoolUgnot.
 51	// reservedWugnot includes this launch's PoolUgnot (graduated, seed not released).
 52	needReserve := reservedWugnot()
 53	if wBal < needReserve {
 54		l.GnoswapNote = "list skip: WUGNOT under-backed vs reserved; have " +
 55			strconv.FormatInt(wBal, 10) + " reserved " + strconv.FormatInt(needReserve, 10)
 56		return false
 57	}
 58	if wBal < raisedUgnot {
 59		l.GnoswapNote = "list skip: need WUGNOT inventory >= raised for LP; have " +
 60			strconv.FormatInt(wBal, 10) + " need " + strconv.FormatInt(raisedUgnot, 10)
 61		return false
 62	}
 63
 64	feeNeed := gnspool.GetPoolCreationFee()
 65	if feeNeed <= 0 {
 66		feeNeed = 100_000_000 // 100 GNS default (6 decimals)
 67	}
 68
 69	// --- GNS for CreatePool fee ---
 70	// Require GNS already on pad. Do NOT Approve+router ExactOut mid-list:
 71	// realm spender frame often panics "insufficient allowance" and reverts the tx.
 72	// UI: gns.Transfer(pad, feeNeed) then RetryListGnoswap.
 73	gnsBal := gns.BalanceOf(padAddr)
 74	feeWugnot := int64(0)
 75	if gnsBal < feeNeed {
 76		l.GnoswapNote = "list skip: need " + strconv.FormatInt(feeNeed, 10) +
 77			" GNS base units on pad (Transfer GNS then RetryList). have " +
 78			strconv.FormatInt(gnsBal, 10) +
 79			". No mid-list WUGNOT->GNS swap (avoids allowance panic)."
 80		return false
 81	}
 82
 83	liqWugnot := raisedUgnot
 84	if wugnot.BalanceOf(padAddr) < liqWugnot {
 85		l.GnoswapNote = "list skip: WUGNOT below raised after fee path"
 86		return false
 87	}
 88
 89	// LP token amount: prefer PoolToken already sized in graduate() to curve spot.
 90	// Defensive resize if Virtuals are somehow still set (pre-graduate test paths):
 91	//   tokensForLP = raisedUgnot * VirtualToken / VirtualUgnot
 92	liqTokens := remainingTokens
 93	if l.VirtualUgnot > 0 && l.VirtualToken > 0 && liqWugnot > 0 {
 94		needed := liqWugnot * l.VirtualToken / l.VirtualUgnot
 95		if needed > 0 && needed < remainingTokens {
 96			liqTokens = needed
 97		}
 98	}
 99	if liqTokens <= 0 {
100		l.GnoswapNote = "list skip: zero LP tokens at curve spot"
101		return false
102	}
103
104	// Mint LP slice + leftover inventory (LeftoverTokens set at graduate) onto pad.
105	mintAmt := remainingTokens
106	if l.LeftoverTokens > 0 {
107		mintAmt = remainingTokens + l.LeftoverTokens
108	}
109	if mintAmt < liqTokens {
110		mintAmt = liqTokens
111	}
112	addBal(l, padAddr, mintAmt)
113
114	tokenKey := adenaKeyFromTokenID(l.TokenID, l.Symbol)
115	if tokenKey == "" {
116		l.GnoswapNote = "list skip: empty token registry key"
117		return false
118	}
119
120	// Sort token0 < token1 (Gnoswap pool key order) BEFORE sqrtPrice + CreatePool + Mint.
121	// CreatePool reorders tokens and INVERTS sqrtPriceX96 when args are out of order
122	// (newSqrt = Q192/oldSqrt). If we pass unsorted keys with a price already computed
123	// for the sorted pair, the pool is born with inverted price → Mint only takes a
124	// tiny sliver of one side (e.g. ~4 GNOT of a 10k WUGNOT raise). Always call
125	// CreatePool(t0, t1, fee, sqrt) with the same sorted pair as Mint.
126	t0, t1 := wugnotTokenKey, tokenKey
127	amt0, amt1 := liqWugnot, liqTokens
128	if strings.Compare(t0, t1) > 0 {
129		t0, t1 = t1, t0
130		amt0, amt1 = amt1, amt0
131	}
132
133	sqrtPrice := computeSqrtPriceX96(amt0, amt1)
134	if sqrtPrice == "" || sqrtPrice == "0" {
135		l.GnoswapNote = "list skip: bad sqrtPriceX96"
136		return false
137	}
138
139	poolAddr := address(gnoswapPoolAddrStr)
140	gns.Approve(cross(cur), poolAddr, feeNeed)
141	wugnot.Approve(cross(cur), poolAddr, liqWugnot)
142	if err := l.ledger.Approve(padAddr, poolAddr, liqTokens); err != nil {
143		l.GnoswapNote = "list skip: token approve failed: " + err.Error()
144		return false
145	}
146
147	// CRITICAL: sorted (t0,t1) + matching sqrtPrice (do NOT pass original unsorted keys)
148	gnspool.CreatePool(cross(cur), t0, t1, GnoswapFeeTier, sqrtPrice)
149
150	tickLower, tickUpper := alignedFullRangeTicks(GnoswapTickSpacing)
151	deadline := time.Now().Unix() + 600
152
153	posID, liqStr, a0, a1 := position.Mint(
154		cross(cur),
155		t0,
156		t1,
157		GnoswapFeeTier,
158		tickLower,
159		tickUpper,
160		strconv.FormatInt(amt0, 10),
161		strconv.FormatInt(amt1, 10),
162		"0",
163		"0",
164		deadline,
165		padAddr, // permanent lock: pad owns position NFT
166		"",
167	)
168
169	// Actual WUGNOT deposited (whichever side is WUGNOT after sort)
170	used0, _ := strconv.ParseInt(a0, 10, 64)
171	used1, _ := strconv.ParseInt(a1, 10, 64)
172	wugnotUsed := int64(0)
173	if t0 == wugnotTokenKey {
174		wugnotUsed = used0
175	} else if t1 == wugnotTokenKey {
176		wugnotUsed = used1
177	}
178	// Sanity: if Mint took << intended LP WUGNOT, mark note (still listed — pool exists)
179	if wugnotUsed > 0 && liqWugnot > 0 && wugnotUsed*2 < liqWugnot {
180		// keep going but surface under-deposit for ops
181		chain.Emit("ListUnderDeposit",
182			"id", l.ID,
183			"wantWugnot", strconv.FormatInt(liqWugnot, 10),
184			"gotWugnot", strconv.FormatInt(wugnotUsed, 10),
185		)
186	}
187
188	poolPath := t0 + ":" + t1 + ":" + strconv.FormatUint(uint64(GnoswapFeeTier), 10)
189	l.GnoswapListed = true
190	l.GnoswapPoolPath = poolPath
191	l.GnoswapPositionID = posID
192	l.ListVenue = VenueGnoswap
193	l.FeeWugnotSpent = feeWugnot
194	if wugnotUsed > 0 {
195		l.LiqWugnotUsed = wugnotUsed
196	} else {
197		l.LiqWugnotUsed = liqWugnot
198	}
199	// Release Create-time GNS escrow (fee left pad via CreatePool).
200	consumeListFeeEscrow(l)
201	leftoverTok := l.LeftoverTokens
202	if leftoverTok < 0 {
203		leftoverTok = 0
204	}
205	if remLeft := remainingTokens - liqTokens; remLeft > leftoverTok {
206		leftoverTok = remLeft
207	}
208	l.GnoswapNote = "listed pool=" + poolPath + " pos=" + strconv.FormatUint(posID, 10) +
209		" liq=" + liqStr + " a0=" + a0 + " a1=" + a1 +
210		" wugnotUsed=" + strconv.FormatInt(wugnotUsed, 10) +
211		" feeWugnot=" + strconv.FormatInt(feeWugnot, 10) +
212		" lpTokens=" + strconv.FormatInt(liqTokens, 10) +
213		" leftoverTokens=" + strconv.FormatInt(leftoverTok, 10)
214
215	chain.Emit("GnoswapListed",
216		"id", l.ID,
217		"poolPath", poolPath,
218		"positionId", strconv.FormatUint(posID, 10),
219		"feeWugnot", strconv.FormatInt(feeWugnot, 10),
220		"liqWugnot", strconv.FormatInt(liqWugnot, 10),
221		"wugnotUsed", strconv.FormatInt(wugnotUsed, 10),
222		"lpTokens", strconv.FormatInt(liqTokens, 10),
223		"leftoverTokens", strconv.FormatInt(leftoverTok, 10),
224		"tokens", strconv.FormatInt(remainingTokens, 10),
225	)
226	return true
227}
228
229// prepareCallerListFunding fills pad inventory shortfall from the EOA caller via TransferFrom.
230//
231// padv14+ WUGNOT raise: Buy already TransferFrom WUGNOT to pad, so wBal >= raised
232// at graduate in the normal case — only GNS fee (or small fee WUGNOT budget) may be short.
233//
234// Legacy ugnot-raise pads: wugnot.Deposit is EOA-only; caller wraps temp LP then reimbursed.
235func prepareCallerListFunding(cur realm, l *Launch, raisedUgnot int64) listFund {
236	caller := cur.Previous().Address()
237	padA := cur.Address()
238	out := listFund{ok: true, caller: caller}
239
240	if raisedUgnot <= 0 {
241		l.GnoswapNote = "list skip: empty raise"
242		out.ok = false
243		return out
244	}
245
246	feeNeed := gnspool.GetPoolCreationFee()
247	if feeNeed <= 0 {
248		feeNeed = 100_000_000
249	}
250
251	wBal := wugnot.BalanceOf(padA)
252	// Pull up to full reservation backing (and raised LP), so tryList does not
253	// soft-fail under-backed / steal other launches' PoolUgnot.
254	needW := raisedUgnot
255	if res := reservedWugnot(); res > needW {
256		needW = res
257	}
258	if wBal < needW {
259		short := needW - wBal
260		if !pullWugnotFrom(cur, caller, padA, short) {
261			l.GnoswapNote = "list skip: need temp WUGNOT wrap " +
262				strconv.FormatInt(short, 10) +
263				" ugnot units (Deposit+Approve pad) for reserved backing / LP. " +
264				"Fee: " + strconv.FormatInt(feeNeed, 10) + " GNS or WUGNOT ExactOut budget."
265			out.ok = false
266			return out
267		}
268		out.pulledLpWugnot = short
269		wBal += short
270	}
271
272	gnsBal := gns.BalanceOf(padA)
273	if gnsBal < feeNeed {
274		gShort := feeNeed - gnsBal
275		if pullGnsFrom(cur, caller, padA, gShort) {
276			out.pulledGns = gShort
277		} else {
278			// No GNS from caller - pull modest WUGNOT surplus for ExactOut → GNS.
279			// Do NOT pull full GnoswapMaxFeeWugnot (5000 GNOT) — that causes
280			// InsufficientCoins for normal wallets. MaxFee still caps ExactOut spend.
281			surplus := wBal - raisedUgnot
282			if surplus < 1 {
283				feeBudget := listFeeWugnotPull()
284				if !pullWugnotFrom(cur, caller, padA, feeBudget) {
285					l.GnoswapNote = "list skip: need GNS fee " +
286						strconv.FormatInt(feeNeed, 10) +
287						" (Approve pad) OR WUGNOT fee budget " +
288						strconv.FormatInt(feeBudget, 10) +
289						" ugnot for ExactOut. Prefer holding 100 GNS."
290					// Roll back LP pull.
291					if out.pulledLpWugnot > 0 {
292						safeWugnotTransfer(cur, caller, out.pulledLpWugnot)
293						out.pulledLpWugnot = 0
294					}
295					out.ok = false
296					return out
297				}
298				out.pulledFeeWugnot = feeBudget
299			}
300		}
301	}
302	return out
303}
304
305// listFeeWugnotPull is the WUGNOT amount pulled from the listing caller for
306// ExactOut → GNS when they do not hold CreatePool fee in GNS.
307// Kept well below GnoswapMaxFeeWugnot so List does not demand 5k+ GNOT wrap.
308func listFeeWugnotPull() int64 {
309	const defaultPull int64 = 1_500_000_000 // 1500 GNOT — enough for ~100 GNS at ≤15 GNOT/GNS
310	if GnoswapMaxFeeWugnot > 0 && GnoswapMaxFeeWugnot < defaultPull {
311		return GnoswapMaxFeeWugnot
312	}
313	return defaultPull
314}
315
316// pullWugnotFrom is intentionally a no-op pull.
317//
318// GRC20 TransferFrom from a realm often panics "insufficient allowance" even when
319// Allowance(owner, pad) is set (spender frame resolves to EOA, not pad). Soft-fail
320// instead so list can fall back to notes / surplus / pre-funded pad inventory.
321//
322// UI must pre-fund pad via wugnot.Transfer(pad, amount) and gns.Transfer(pad, fee)
323// before RetryListGnoswap — never rely on Approve+TransferFrom in the same flow.
324func pullWugnotFrom(cur realm, from, to address, amount int64) bool {
325	if amount <= 0 {
326		return true
327	}
328	// Already on pad? treat as satisfied without TransferFrom.
329	if wugnot.BalanceOf(to) >= amount && from != to {
330		// Not a full check for "raised" accounting — prepareCallerListFunding
331		// already compared pad balance to raised before calling shortfall pulls.
332	}
333	_ = cur
334	_ = from
335	_ = to
336	return false
337}
338
339func pullGnsFrom(cur realm, from, to address, amount int64) bool {
340	if amount <= 0 {
341		return true
342	}
343	_ = cur
344	_ = from
345	_ = to
346	// Never TransferFrom GNS either (same spender-frame issue).
347	return false
348}
349
350func safeWugnotTransfer(cur realm, to address, amount int64) {
351	if amount <= 0 {
352		return
353	}
354	have := wugnot.BalanceOf(cur.Address())
355	if have < amount {
356		amount = have
357	}
358	if amount > 0 {
359		wugnot.Transfer(cross(cur), to, amount)
360	}
361}
362
363func safeGnsTransfer(cur realm, to address, amount int64) {
364	if amount <= 0 {
365		return
366	}
367	have := gns.BalanceOf(cur.Address())
368	if have < amount {
369		amount = have
370	}
371	if amount > 0 {
372		gns.Transfer(cross(cur), to, amount)
373	}
374}
375
376func refundCallerListFunding(cur realm, fund listFund) {
377	if !fund.caller.IsValid() {
378		return
379	}
380	safeWugnotTransfer(cur, fund.caller, fund.pulledLpWugnot+fund.pulledFeeWugnot)
381	safeGnsTransfer(cur, fund.caller, fund.pulledGns)
382}
383
384// settleCallerListFunding returns unused fee-budget WUGNOT to the caller.
385// LP shortfall pull is rare on WUGNOT-raise pads (raised already on pad); if it
386// happened, reimburse in WUGNOT from any leftover pad balance after list.
387func settleCallerListFunding(cur realm, fund listFund) {
388	if !fund.caller.IsValid() {
389		return
390	}
391	if fund.pulledLpWugnot > 0 {
392		safeWugnotTransfer(cur, fund.caller, fund.pulledLpWugnot)
393	}
394	// Leftover fee-budget WUGNOT (ExactOut may not consume full maxIn).
395	if fund.pulledFeeWugnot > 0 {
396		safeWugnotTransfer(cur, fund.caller, fund.pulledFeeWugnot)
397	}
398}
399
400// listOnGnoswapWithFunding is the full auto path: pull inventory from caller if needed,
401// CreatePool+Mint, reimburse LP ugnot / refund on failure.
402func listOnGnoswapWithFunding(cur realm, l *Launch, raisedUgnot, remainingTokens int64) bool {
403	fund := prepareCallerListFunding(cur, l, raisedUgnot)
404	if !fund.ok {
405		return false
406	}
407	ok := tryListOnGnoswap(cur, l, raisedUgnot, remainingTokens)
408	if ok {
409		settleCallerListFunding(cur, fund)
410		if fund.pulledLpWugnot > 0 || fund.pulledFeeWugnot > 0 || fund.pulledGns > 0 {
411			chain.Emit("ListFunding",
412				"id", l.ID,
413				"caller", fund.caller.String(),
414				"lpWugnot", strconv.FormatInt(fund.pulledLpWugnot, 10),
415				"feeWugnot", strconv.FormatInt(fund.pulledFeeWugnot, 10),
416				"gns", strconv.FormatInt(fund.pulledGns, 10),
417			)
418		}
419		return true
420	}
421	refundCallerListFunding(cur, fund)
422	return false
423}
424
425// listNeedOf: poolU|wHave|wNeedLp|gnsHave|gnsNeed|feeGns|feeWugnotBudget|padAddr
426func listNeedOf(l *Launch) string {
427	poolU := int64(0)
428	if l != nil && l.Status == StatusGraduated && !l.GnoswapListed {
429		poolU = l.PoolUgnot
430	}
431	padA := padAddr
432	wHave := wugnot.BalanceOf(padA)
433	gnsHave := gns.BalanceOf(padA)
434	feeGns := gnspool.GetPoolCreationFee()
435	if feeGns <= 0 {
436		feeGns = 100_000_000
437	}
438	wNeed := poolU - wHave
439	if wNeed < 0 {
440		wNeed = 0
441	}
442	gNeed := feeGns - gnsHave
443	if gNeed < 0 {
444		gNeed = 0
445	}
446	feeBud := listFeeWugnotPull()
447	// If GNS already covered, fee WUGNOT budget need is 0 for the wizard.
448	if gNeed == 0 {
449		feeBud = 0
450	} else if wHave > poolU {
451		// Existing surplus reduces fee budget to pull
452		surp := wHave - poolU
453		if surp >= feeBud {
454			feeBud = 0
455		} else {
456			feeBud = feeBud - surp
457		}
458	}
459	return strconv.FormatInt(poolU, 10) + "|" +
460		strconv.FormatInt(wHave, 10) + "|" +
461		strconv.FormatInt(wNeed, 10) + "|" +
462		strconv.FormatInt(gnsHave, 10) + "|" +
463		strconv.FormatInt(gNeed, 10) + "|" +
464		strconv.FormatInt(feeGns, 10) + "|" +
465		strconv.FormatInt(feeBud, 10) + "|" +
466		padA.String()
467}
468
469// ListNeed is the public query for the Token list wizard (default venue).
470func ListNeed(id string) string {
471	return listNeedOf(mustLaunch(id))
472}
473
474// ListNeedFor is venue-aware; only gnoswap has a Need adapter today.
475func ListNeedFor(id, venueId string) string {
476	l := mustLaunch(id)
477	vid := normalizeVenueID(venueId)
478	if vid != VenueGnoswap {
479		poolU := int64(0)
480		if l != nil && l.Status == StatusGraduated && !l.GnoswapListed {
481			poolU = l.PoolUgnot
482		}
483		return strconv.FormatInt(poolU, 10) + "|0|0|0|0|0|0|" + padAddr.String()
484	}
485	return listNeedOf(l)
486}
487
488// ListedOf reports whether the launch is listed on any venue.
489func ListedOf(id string) bool {
490	l := mustLaunch(id)
491	return l.GnoswapListed || l.ListVenue != ""
492}
493
494// ListPoolPathOf returns the pool path after listing.
495func ListPoolPathOf(id string) string {
496	return mustLaunch(id).GnoswapPoolPath
497}
498
499// ListNoteOf returns the listing status note.
500func ListNoteOf(id string) string {
501	return mustLaunch(id).GnoswapNote
502}
503
504func swapWugnotForGNS(cur realm, amountOutGNS, maxInWugnot int64) (spent int64, ok bool) {
505	if amountOutGNS <= 0 || maxInWugnot <= 0 {
506		return 0, false
507	}
508	routerAddr := address(gnoswapRouterAddrStr)
509	before := wugnot.BalanceOf(cur.Address())
510	if before < maxInWugnot {
511		maxInWugnot = before
512	}
513	if maxInWugnot <= 0 {
514		return 0, false
515	}
516	wugnot.Approve(cross(cur), routerAddr, maxInWugnot)
517
518	route := wugnotTokenKey + ":" + gnsTokenKey + ":" + strconv.FormatUint(uint64(GnoswapFeeTier), 10)
519	deadline := time.Now().Unix() + 600
520	inStr, outStr := router.ExactOutSwapRoute(
521		cross(cur),
522		wugnotTokenKey,
523		gnsTokenKey,
524		strconv.FormatInt(amountOutGNS, 10),
525		route,
526		"100",
527		strconv.FormatInt(maxInWugnot, 10),
528		deadline,
529		"",
530	)
531	_ = outStr
532	spentIn, err := strconv.ParseInt(inStr, 10, 64)
533	if err != nil || spentIn <= 0 {
534		after := wugnot.BalanceOf(cur.Address())
535		if after >= before {
536			return 0, false
537		}
538		return before - after, true
539	}
540	return spentIn, true
541}
542
543func computeSqrtPriceX96(amount0, amount1 int64) string {
544	if amount0 <= 0 || amount1 <= 0 {
545		return ""
546	}
547	a0 := u256.MustFromDecimal(strconv.FormatInt(amount0, 10))
548	a1 := u256.MustFromDecimal(strconv.FormatInt(amount1, 10))
549	num := u256.Zero().Mul(a1, consts.Q192())
550	ratio := u256.Zero().Div(num, a0)
551	sqrt := u256Sqrt(ratio)
552	if sqrt.Lt(consts.MinSqrtRatio()) {
553		return consts.MinSqrtRatio().ToString()
554	}
555	if sqrt.Gte(consts.MaxSqrtRatio()) {
556		return u256.Zero().Sub(consts.MaxSqrtRatio(), u256.One()).ToString()
557	}
558	return sqrt.ToString()
559}
560
561func u256Sqrt(x *u256.Uint) *u256.Uint {
562	if x == nil || x.IsZero() {
563		return u256.Zero()
564	}
565	if x.Eq(u256.One()) {
566		return u256.One()
567	}
568	lo := u256.Zero()
569	hi := u256.Zero().Add(x, u256.One())
570	cap128 := u256.Zero().Lsh(u256.One(), 128)
571	if hi.Gt(cap128) {
572		hi = cap128.Clone()
573	}
574	for lo.Lt(u256.Zero().Sub(hi, u256.One())) {
575		mid := u256.Zero().Div(u256.Zero().Add(lo, hi), u256.NewUint(2))
576		if mid.IsZero() {
577			lo = u256.One()
578			continue
579		}
580		sq, overflow := u256.Zero().MulOverflow(mid, mid)
581		if overflow || sq.Gt(x) {
582			hi = mid
583		} else {
584			lo = mid
585		}
586	}
587	return lo
588}
589
590func alignedFullRangeTicks(spacing int32) (int32, int32) {
591	if spacing <= 0 {
592		spacing = 60
593	}
594	tl := (GnoswapMinTick / spacing) * spacing
595	tu := (GnoswapMaxTick / spacing) * spacing
596	if tl >= tu {
597		tl = -spacing
598		tu = spacing
599	}
600	return tl, tu
601}
602
603// GnoswapListedOf reports whether a launch was auto-listed on Gnoswap.
604func GnoswapListedOf(id string) bool {
605	return mustLaunch(id).GnoswapListed
606}
607
608// GnoswapPoolPathOf returns the Gnoswap pool path after listing (or empty).
609func GnoswapPoolPathOf(id string) string {
610	return mustLaunch(id).GnoswapPoolPath
611}
612
613// GnoswapNoteOf returns the listing status note.
614func GnoswapNoteOf(id string) string {
615	return mustLaunch(id).GnoswapNote
616}