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