@logswap/sdk 0.5.35 → 0.5.37

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/dist/fpool.js CHANGED
@@ -22,8 +22,16 @@
22
22
  * twice, forever. One router is one spender — see `fPoolZapIn`/`fPoolZapOut` below, which target
23
23
  * `addresses.router`.
24
24
  */
25
- import { toFunctionSelector } from "viem";
26
- import { fPoolManagerAbi, logswapRouterAbi } from "./generated.js";
25
+ import { encodeFunctionData, toFunctionSelector } from "viem";
26
+ import { fPoolManagerAbi as fPoolPrimitiveAbi, fPoolSponsorAbi, logswapRouterAbi } from "./generated.js";
27
+ /**
28
+ * The F manager's ABI is the UNION of its two compiled halves (decisions 031): `FPoolManager`
29
+ * holds the primitive and forwards every other selector from its fallback to `FPoolSponsor` —
30
+ * the lever, `claim`, `dissolve`, the handover, the gates, the desk, the names — by delegatecall.
31
+ * One address from outside, so one ABI here; both halves carry the base's events and errors,
32
+ * and viem tolerates the duplicates.
33
+ */
34
+ export const fPoolManagerAbi = [...fPoolPrimitiveAbi, ...fPoolSponsorAbi];
27
35
  const WAD = 10n ** 18n;
28
36
  const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
29
37
  /** Read a basket pool whole. Batched — viem multicalls these when the transport allows. */
@@ -57,11 +65,9 @@ export async function getFPool(c, poolId) {
57
65
  ]);
58
66
  const bases = legs.map((l) => l[0]);
59
67
  const weights = legs.map((l) => l[1]);
60
- const x = legs.map((l) => l[2]);
61
- // the ISOLATION SHADOW, not the derived reserve: what this pool actually holds of the token.
62
- // In a singleton the contract's balance backs many pools, so this is the only per-pool figure.
63
- const reserves = legs.map((l) => l[3]);
64
- const { quote, Q, L, phi, theta0, bigSigma, authority, pendingAuthority, operator, minBuffer, gateMint, gateSwap, restructureBlock, feesOnly, seeded, dissolved } = p;
68
+ const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j) (decisions 026)
69
+ const reserves = legs.map((l) => l[3]); // the state
70
+ const { quote, Q, L, phi, theta0, bigSigma, authority, pendingAuthority, operator, minBuffer, gateMint, gateSwap, restructureBlock, lockStrike, seeded, dissolved, successor } = p;
65
71
  const totalSupply = p.shares;
66
72
  return {
67
73
  poolId,
@@ -84,9 +90,10 @@ export async function getFPool(c, poolId) {
84
90
  gateMint,
85
91
  gateSwap,
86
92
  restructureBlock: Number(restructureBlock),
87
- feesOnly,
93
+ lockStrike,
88
94
  seeded,
89
95
  dissolved,
96
+ successor,
90
97
  totalSupply,
91
98
  reserves,
92
99
  };
@@ -200,14 +207,61 @@ export async function fPoolZapOut(c, a) {
200
207
  const to = a.to ?? a.account;
201
208
  return writeRouter(c, "zapOut", [a.poolId, a.shares, a.tokenOut, a.minOut ?? 0n, to, a.deadline ?? defaultDeadline()], a.account);
202
209
  }
210
+ /**
211
+ * Roll a dissolved pool's bundle into its successor (decisions 025), on the router under one
212
+ * lock: burn `shares` of `fromPool` in place — the legs, the quote and any unpulled dividend come
213
+ * back — buy through `toPool`'s own curve only what its ratio lacks, mint `dL` of it to `to`, and
214
+ * refund the rest in kind. Quote the bundle does not cover is pulled from the payer, at most
215
+ * `maxQuoteIn`; surplus base is refunded, never sold. Size `dL` with {@link fPoolPreviewRollIn}.
216
+ * The router must be the caller's ERC-6909 operator ({@link approveRouterForFPool}).
217
+ */
218
+ export async function fPoolRollIn(c, a) {
219
+ const to = a.to ?? a.account;
220
+ return writeRouter(c, "rollIn", [a.fromPool, a.toPool, a.shares, a.dL, a.maxQuoteIn ?? 0n, a.minShares ?? 0n, to, a.deadline ?? defaultDeadline()], a.account);
221
+ }
222
+ /**
223
+ * The roll `holder`'s `shares` of `fromPool` fund into `toPool` WITHOUT a trade: the largest `dL`
224
+ * the bundle's base covers on every successor leg, and the quote the bundle is short of (pass it
225
+ * as `maxQuoteIn`) or over (refunded — the up direction's distribution) at that `dL`. A
226
+ * successor leg the bundle does not hold makes `dL` zero: such a roll must buy, so size it below
227
+ * with `previewZapIn` in mind.
228
+ */
229
+ export async function fPoolPreviewRollIn(c, fromPool, toPool, holder, shares) {
230
+ const { logswapLensAbi } = await import("./generated.js");
231
+ const [dL, quoteShort, quoteExcess] = (await c.public.readContract({
232
+ address: c.addresses.lens,
233
+ abi: logswapLensAbi,
234
+ functionName: "previewRollIn",
235
+ args: [fromPool, toPool, holder, shares],
236
+ }));
237
+ return { dL, quoteShort, quoteExcess };
238
+ }
203
239
  // ─── the floor lever (authority only) ─────────────────────────────────────────
204
240
  /**
205
- * Remove quote, lifting θ — price-neutral, pro-rata, no base moves. Under `feesOnly` the lift
206
- * stops at θ₀, so the authority extracts income and never the proceeds backing the floor.
241
+ * Remove quote, lifting θ — price-neutral, no base moves, and since decisions 028 a DIVIDEND to
242
+ * every share: the amount goes into the pool's pot and each holder's slice is settled lazily on
243
+ * their next transfer, mint or burn, or pulled with {@link fPoolClaim}. The sponsor's own slice —
244
+ * its shares' and the locked seed's at 0xdead — is pushed to `to` at once. Under `lockStrike`
245
+ * the lift stops at θ₀ (income only); on every live pool it stops at `LEVER_FLOOR` (Q/L ≥ ln 1.25).
207
246
  */
208
247
  export async function fPoolHarvest(c, a) {
209
248
  return writeFPool(c, a.poolId, "harvest", [a.amount, a.to ?? a.account], a.account);
210
249
  }
250
+ /** Pull one's settled dividend (decisions 028) without moving shares; anyone with shares. */
251
+ export async function fPoolClaim(c, a) {
252
+ return writeFPool(c, a.poolId, "claim", [a.to ?? a.account], a.account);
253
+ }
254
+ /** A holder's dividend as of now — banked plus pending on the current balance (decisions 028). */
255
+ export async function fPoolDividendOf(c, poolId, holder) {
256
+ return c.public.readContract({
257
+ address: c.addresses.fPoolManager,
258
+ abi: fPoolManagerAbi,
259
+ functionName: "dividendOf",
260
+ args: [poolId, holder],
261
+ });
262
+ }
263
+ /** The lever's floor: `harvest` may not take Q/L under ln 1.25 — the floor stays ≥ 20% below the mark (decisions 025). */
264
+ export const LEVER_FLOOR = 223143551314209755n;
211
265
  /** Commit quote, deepening θ — the other sign of `harvest`, the same word as the C floor edit (decisions 011). The mark does not move: governance may write θ, never x. */
212
266
  export async function fPoolDeepen(c, a) {
213
267
  return writeFPool(c, a.poolId, "deepen", [a.amount], a.account);
@@ -235,6 +289,18 @@ async function writeFPool(c, poolId, functionName, args, account) {
235
289
  });
236
290
  return wallet.writeContract(request);
237
291
  }
292
+ /** A manager write that is NOT keyed by a poolId — `multicall`. */
293
+ async function writeFPool0(c, functionName, args, account) {
294
+ const wallet = requireWallet(c);
295
+ const { request } = await c.public.simulateContract({
296
+ address: c.addresses.fPoolManager,
297
+ abi: fPoolManagerAbi,
298
+ functionName,
299
+ args,
300
+ account,
301
+ });
302
+ return wallet.writeContract(request);
303
+ }
238
304
  async function writeRouter(c, functionName, args, account,
239
305
  /** Tokens the call pulls: missing Permit2 allowances are SIGNED and ride in the same tx. */
240
306
  pullTokens = []) {
@@ -433,7 +499,7 @@ export async function fPoolInitialize(c, a) {
433
499
  if (sum !== 10n ** 18n)
434
500
  throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
435
501
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
436
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, feesOnly: a.feesOnly, authority: a.authority, salt: a.salt ?? ZERO_SALT };
502
+ const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
437
503
  const wallet = requireWallet(c);
438
504
  const { request } = await c.public.simulateContract({
439
505
  address: c.addresses.fPoolManager,
@@ -447,7 +513,7 @@ export async function fPoolInitialize(c, a) {
447
513
  /** The pool id is the key's hash — pure, so it can be read before or after creation. */
448
514
  export async function fPoolIdOf(c, a) {
449
515
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
450
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, feesOnly: a.feesOnly, authority: a.authority, salt: a.salt ?? ZERO_SALT };
516
+ const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
451
517
  return c.public.readContract({
452
518
  address: c.addresses.fPoolManager,
453
519
  abi: fPoolManagerAbi,
@@ -456,16 +522,61 @@ export async function fPoolIdOf(c, a) {
456
522
  });
457
523
  }
458
524
  /**
459
- * Arm the pool: deposit the full basket at marks `x0` plus `Q0` of quote. Authority-only on-chain
460
- * (an open seed lets anyone front-run the creator for the strike). `x0` must align with the
461
- * SORTED bases — use {@link sortFPoolLegs} on (bases, weights, x0) together when building forms.
525
+ * The reserve a leg holds at a mark: `R = Lj·e^{−x}`, WAD math. The seed names RESERVES since
526
+ * decisions 029 — the sponsor states holdings and the price follows, `p = Lj / R` — so this is
527
+ * the one conversion from a price to what the pool will hold, done here, on the way in. Float
528
+ * precision (~1e-16 relative) is what a seed needs: any reserve is a legal seed, and the mark
529
+ * the pool derives from it is the one the sponsor meant to within that.
530
+ */
531
+ export function fPoolReserveAt(Lj, x) {
532
+ const e = Math.exp(-Number(x) / 1e18);
533
+ return (Lj * BigInt(Math.round(e * 1e18))) / 10n ** 18n;
534
+ }
535
+ /**
536
+ * Arm the pool: deposit `r0[j]` of each base plus `Q0` of quote against exposure `L0`. Pass either
537
+ * the reserves outright (`r0`) or the marks the pool should open at (`x0`, converted here with
538
+ * {@link fPoolReserveAt} against the key's weights). Authority-only on-chain (an open seed lets
539
+ * anyone front-run the creator for the strike). Arrays align with the SORTED bases — use
540
+ * {@link sortFPoolLegs} on (bases, weights, x0) together when building forms.
462
541
  */
463
542
  export async function fPoolSeed(c, a) {
464
- return writeFPool(c, a.poolId, "seed", [a.L0, a.x0, a.Q0], a.account);
543
+ const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / 10n ** 18n, x));
544
+ return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
465
545
  }
466
- /** End of life. Gated on-chain to the authority and to Q at dust (the raise is not abortable). */
546
+ /**
547
+ * End of life (decisions 025). Allowed at Q ≤ L/1e9 on every pool — the market filled the ask —
548
+ * and on a pool that is not strike-locked at any Q, provided `successor` names an initialized
549
+ * pool of the same authority and quote: the relaunch. After it every share is a frozen in-kind
550
+ * claim redeemed by `burn` (or rolled with {@link fPoolRollIn}), forever; the pool's `successor`
551
+ * carries the lineage. Never a second dissolve on one pool: the successor is a NEW pool.
552
+ */
467
553
  export async function fPoolDissolve(c, a) {
468
- return writeFPool(c, a.poolId, "dissolve", [], a.account);
554
+ return writeFPool(c, a.poolId, "dissolve", [a.successor ?? ZERO_SALT], a.account);
555
+ }
556
+ /**
557
+ * The sponsor's relaunch as ONE transaction (decisions 025): `initialize` the successor key,
558
+ * `dissolve` the old pool into it, `burn` the sponsor's own shares, `seed` the successor with what
559
+ * came back — the manager's `multicall`, the sponsor as sender throughout. Gates, allowlist,
560
+ * operator, floor guard and name are re-applied afterwards, or appended to `extra` pre-encoded
561
+ * against {@link fPoolManagerAbi}. Returns the tx hash; the successor's id is {@link fPoolIdOf}
562
+ * of `nextKey`.
563
+ */
564
+ export async function fPoolRelaunch(c, a) {
565
+ const { bases, companions } = sortFPoolLegs(a.nextKey.bases, a.nextKey.weights);
566
+ const key = {
567
+ quote: a.nextKey.quote, bases, weights: companions[0], phi: a.nextKey.phi, lockStrike: a.nextKey.lockStrike,
568
+ authority: a.nextKey.authority, salt: a.nextKey.salt ?? ZERO_SALT,
569
+ };
570
+ const next = await fPoolIdOf(c, a.nextKey);
571
+ const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
572
+ const calls = [
573
+ enc("initialize", [key]),
574
+ enc("dissolve", [a.poolId, next]),
575
+ enc("burn", [a.poolId, a.account, a.shares, 0n]),
576
+ enc("seed", [next, a.L0, a.r0, a.Q0]),
577
+ ...(a.extra ?? []),
578
+ ];
579
+ return writeFPool0(c, "multicall", [calls], a.account);
469
580
  }
470
581
  /**
471
582
  * Propose the next authority — step one of two (contracts `2aa6b7b`, decisions 020). Nothing moves
@@ -480,8 +591,6 @@ export async function fPoolAcceptAuthority(c, a) {
480
591
  return writeFPool(c, a.poolId, "acceptAuthority", [], a.account);
481
592
  }
482
593
  // ─── the sponsor's controls and the desk (contracts bd3dd87, decisions 021) ───────────────
483
- /** `setLegL`'s "keep the stored mark" sentinel — the only legal `x` for a live leg. */
484
- export const NO_X = -(2n ** 255n);
485
594
  /** Turn the mint / swap gates on or off (authority). `burn` is never gated. */
486
595
  export async function fPoolSetGates(c, a) {
487
596
  return writeFPool(c, a.poolId, "setGates", [a.gateMint, a.gateSwap], a.account);
@@ -541,15 +650,17 @@ export async function fPoolRaiseMinBuffer(c, a) {
541
650
  }
542
651
  /**
543
652
  * Set leg `j`'s liquidity (operator, sole LP only): grow, shrink, retire (`newLj = 0`), or re-admit
544
- * a retired leg at a named mark `x`. For a live leg leave `x` undefined — its mark is the market's.
545
- * The base moves single-sidedly through the lock; shares adjust to hold the share value.
653
+ * a retired leg holding `r` of the base (decisions 029 — the price follows, `p = newLj / r`; use
654
+ * {@link fPoolReserveAt} from a mark). For a live leg leave `r` undefined — its price is the
655
+ * market's, the reserve scales with the liquidity. Base moves single-sidedly through the lock;
656
+ * shares adjust to hold the share value.
546
657
  */
547
658
  export async function fPoolSetLegL(c, a) {
548
- return writeFPool(c, a.poolId, "setLegL", [BigInt(a.j), a.newLj, a.x ?? NO_X], a.account);
659
+ return writeFPool(c, a.poolId, "setLegL", [BigInt(a.j), a.newLj, a.r ?? 0n], a.account);
549
660
  }
550
- /** Admit a base the pool has never held, at a named mark, with liquidity `Lj` (operator, sole LP only). */
661
+ /** Admit a base the pool has never held: `R` of it against liquidity `Lj`, so at `p = Lj / R` (operator, sole LP only). */
551
662
  export async function fPoolAdmitLeg(c, a) {
552
- return writeFPool(c, a.poolId, "admitLeg", [a.base, a.Lj, a.x], a.account);
663
+ return writeFPool(c, a.poolId, "admitLeg", [a.base, a.Lj, a.R], a.account);
553
664
  }
554
665
  /**
555
666
  * Move shares (ERC-6909 `transfer`) — to another holder, or to `0xdead` to lock them for good.
@@ -569,7 +680,7 @@ export async function fPoolTransferShares(c, a) {
569
680
  }
570
681
  /**
571
682
  * Every F pool ever created, from `Initialize` logs alone. Possible only because the event
572
- * carries the full key preimage (bases, weights, phi, feesOnly) — the poolId is a hash and could
683
+ * carries the full key preimage (bases, weights, phi, lockStrike) — the poolId is a hash and could
573
684
  * never be inverted; contracts PR #17 exists for exactly this call.
574
685
  */
575
686
  export async function discoverFPools(c, opts = {}) {
@@ -602,7 +713,7 @@ export async function discoverFPools(c, opts = {}) {
602
713
  bases: [...a.bases],
603
714
  weights: [...a.weights],
604
715
  phi: a.phi,
605
- feesOnly: a.feesOnly,
716
+ lockStrike: a.lockStrike,
606
717
  authority: a.authority,
607
718
  block: l.blockNumber ?? 0n,
608
719
  seeded: q0 !== undefined,
@@ -641,7 +752,7 @@ export async function fPoolShareHolders(c, poolIdHex, opts = {}) {
641
752
  .map(([holder, shares]) => ({ holder: holder, shares }))
642
753
  .sort((a, b) => (b.shares > a.shares ? 1 : -1));
643
754
  }
644
- /** Every F pool's state in one lens call (the raw manager struct: quote, phi, authority, feesOnly, seeded, dissolved, n, Q, L, shares, theta0, bigSigma, leverTheta). */
755
+ /** Every F pool's state in one lens call (the raw manager struct: quote, phi, authority, lockStrike, seeded, dissolved, successor, n, Q, L, shares, theta0, bigSigma, leverTheta). */
645
756
  export async function getFPoolsRaw(c, poolIds) {
646
757
  const { logswapLensAbi } = await import("./generated.js");
647
758
  return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] }));