@logswap/sdk 0.5.40 → 0.5.41

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
@@ -67,7 +67,7 @@ export async function getFPool(c, poolId) {
67
67
  const weights = legs.map((l) => l[1]);
68
68
  const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j) (decisions 026)
69
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, gen } = p;
70
+ const { quote, Q, L, phi, theta0, bigSigma, authority, pendingAuthority, operator, pendingOperator, gateMint, gateSwap, restructureBlock, seeded, gen } = p;
71
71
  const totalSupply = p.shares;
72
72
  return {
73
73
  poolId,
@@ -85,18 +85,27 @@ export async function getFPool(c, poolId) {
85
85
  authority,
86
86
  pendingAuthority,
87
87
  operator,
88
+ pendingOperator,
88
89
  name: bytes32ToText(p.name),
89
- minBuffer,
90
90
  gateMint,
91
91
  gateSwap,
92
92
  restructureBlock: Number(restructureBlock),
93
- lockStrike,
93
+ kind: fPoolKindOf(p.kind),
94
94
  seeded,
95
95
  gen: Number(gen),
96
96
  totalSupply,
97
97
  reserves,
98
98
  };
99
99
  }
100
+ /** The key's enum values. */
101
+ export const F_KIND = { public: 0, private: 1, pol: 2, pad: 3 };
102
+ const F_KIND_NAMES = ["public", "private", "pol", "pad"];
103
+ export function fPoolKindOf(n) {
104
+ const k = F_KIND_NAMES[Number(n)];
105
+ if (!k)
106
+ throw new Error(`logswap: unknown pool kind ${n}`);
107
+ return k;
108
+ }
100
109
  export async function describeFPool(c, s) {
101
110
  const rd = (functionName) => c.public.readContract({
102
111
  address: c.addresses.fPoolManager,
@@ -110,7 +119,7 @@ export async function describeFPool(c, s) {
110
119
  ? await Promise.all([rd("feePerL"), rd("edgePerL")])
111
120
  : [0n, 0n];
112
121
  return {
113
- shape: s.bases.length === 1 ? "launch" : "basket",
122
+ shape: s.kind === "pad" ? "launch" : "basket", // the kind, not n = 1 (decisions 036)
114
123
  legs: s.bases.length,
115
124
  atFloor: s.Q === 0n,
116
125
  feePerL,
@@ -242,8 +251,9 @@ export async function fPoolPreviewRollIn(c, poolId, gen, holder, shares) {
242
251
  * Remove quote, lifting θ — price-neutral, no base moves, and since decisions 028 a DIVIDEND to
243
252
  * every share: the amount goes into the pool's pot and each holder's slice is settled lazily on
244
253
  * their next transfer, mint or burn, or pulled with {@link fPoolClaim}. The sponsor's own slice —
245
- * its shares' and the locked seed's at 0xdead — is pushed to `to` at once. Under `lockStrike`
246
- * the lift stops at θ₀ (income only); on every live pool it stops at `LEVER_FLOOR` (Q/L ≥ ln 1.25).
254
+ * its shares' and the locked seed's at 0xdead — is pushed to `to` at once. On a pad
255
+ * the lift stops at the seed floor θ₀ (income only); nothing else bounds it (decisions 035) — an
256
+ * unlocked pool's authority may take Q to zero.
247
257
  */
248
258
  export async function fPoolHarvest(c, a) {
249
259
  return writeFPool(c, a.poolId, "harvest", [a.amount, a.to ?? a.account], a.account);
@@ -267,8 +277,6 @@ export async function fPoolDividendOf(c, poolId, holder, gen) {
267
277
  args: [poolId, BigInt(g), holder],
268
278
  });
269
279
  }
270
- /** The lever's floor: `harvest` may not take Q/L under ln 1.25 — the floor stays ≥ 20% below the mark (decisions 025). */
271
- export const LEVER_FLOOR = 223143551314209755n;
272
280
  // ─── internals ────────────────────────────────────────────────────────────────
273
281
  function defaultDeadline() {
274
282
  return BigInt(Math.floor(Date.now() / 1000) + 1800);
@@ -551,7 +559,7 @@ export async function fPoolInitialize(c, a) {
551
559
  if (sum !== 10n ** 18n)
552
560
  throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
553
561
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
554
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
562
+ const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, kind: F_KIND[a.kind], authority: a.authority, salt: a.salt ?? ZERO_SALT };
555
563
  const wallet = requireWallet(c);
556
564
  const { request } = await c.public.simulateContract({
557
565
  address: c.addresses.fPoolManager,
@@ -565,7 +573,7 @@ export async function fPoolInitialize(c, a) {
565
573
  /** The pool id is the key's hash — pure, so it can be read before or after creation. */
566
574
  export async function fPoolIdOf(c, a) {
567
575
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
568
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
576
+ const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, kind: F_KIND[a.kind], authority: a.authority, salt: a.salt ?? ZERO_SALT };
569
577
  return c.public.readContract({
570
578
  address: c.addresses.fPoolManager,
571
579
  abi: fPoolManagerAbi,
@@ -596,38 +604,99 @@ export async function fPoolSeed(c, a) {
596
604
  return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
597
605
  }
598
606
  /**
599
- * Retire the live generation (decisions 025, 032). Allowed at Q ≤ L/1e9 on every pool — the
600
- * market filled the ask — and on a pool that is not strike-locked at any Q. The live (S, Q, R_j)
601
- * become a frozen bundle every share of this generation redeems ({@link fPoolRedeem}) or rolls
602
- * ({@link fPoolRollIn}), forever; the pool is unseeded until the next {@link fPoolSeed}, which
603
- * opens generation `gen + 1` under the same id, gates, lists and promises.
607
+ * Retire the live generation (decisions 025, 032, 035) — the raw entry. Allowed only at an empty
608
+ * bid, Q ≤ L/1e9, on every pool: the market took it, or the authority harvested it out first
609
+ * ({@link fPoolRetire} does both in one transaction). The live (S, Q, R_j) become a frozen bundle
610
+ * every share of this generation redeems ({@link fPoolRedeem}) or rolls ({@link fPoolRollIn}),
611
+ * forever; the pool is unseeded until the next {@link fPoolSeed}, which opens generation
612
+ * `gen + 1` under the same id, gates and lists.
604
613
  */
605
614
  export async function fPoolDissolve(c, a) {
606
615
  return writeFPool(c, a.poolId, "dissolve", [], a.account);
607
616
  }
608
617
  /**
609
- * The sponsor's relaunch as ONE transaction (decisions 025, 032): `dissolve` the live generation,
610
- * `redeem` the sponsor's own shares of it (when `shares` > 0), `seed` the next generation with
611
- * what came back — the manager's `multicall`, the sponsor as sender throughout, the same pool.
612
- * Gates, allowlist, operator, `minBuffer` and names carry over; anything else goes in `extra`,
613
- * pre-encoded against {@link fPoolManagerAbi}. The seed names reserves (decisions 029): `r0`
614
- * outright, or `x0` marks against the key's `weights`.
618
+ * Retire the live generation as ONE transaction: the harvests that empty the bid
619
+ * ({@link fPoolEmptyingHarvests} — a dividend to every share; none when it is already empty),
620
+ * then `dissolve` — the manager's `multicall`, the authority as sender. What a sponsor's
621
+ * "dissolve" button should call. Throws before sending on a pad that still bids.
622
+ */
623
+ export async function fPoolRetire(c, a) {
624
+ const harvests = await fPoolEmptyingHarvests(c, a.poolId);
625
+ if (harvests.length === 0)
626
+ return fPoolDissolve(c, a);
627
+ const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
628
+ const calls = [...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])), enc("dissolve", [a.poolId])];
629
+ return writeFPool0(c, "multicall", [calls], a.account);
630
+ }
631
+ /**
632
+ * The harvests that empty an unlocked pool's bid, in order — what a relaunch sends before its
633
+ * `dissolve` (decisions 035: a dissolve needs an empty bid on every pool). One `harvest(Q)` when a
634
+ * collector is set. With none, the cut on income STAYS in the pool as LP quote (decisions 002)
635
+ * and reads as taken income, so a second harvest — capital, no cut — takes the rest; the
636
+ * contract's own arithmetic, replayed. `[]` when the bid is already empty. Throws on a
637
+ * pad that still bids (its bid empties only as the market sells it down), and in
638
+ * the one shape two harvests cannot empty (no collector, income above Q — harvest by hand).
639
+ */
640
+ export async function fPoolEmptyingHarvests(c, poolId) {
641
+ const rd = (functionName, args = []) => c.public.readContract({ address: c.addresses.fPoolManager, abi: fPoolManagerAbi, functionName, args });
642
+ const [p, feePerL, fee, exempt, collector] = await Promise.all([
643
+ rd("getPool", [poolId]),
644
+ rd("feePerL", [poolId]),
645
+ rd("HARVEST_FEE"),
646
+ rd("harvestFeeExempt", [poolId]),
647
+ rd("feeCollector"),
648
+ ]);
649
+ if (p.Q === 0n)
650
+ return [];
651
+ if (p.kind === F_KIND.pad)
652
+ throw new Error("logswap: a pad is retired only once its bid is empty (decisions 035)");
653
+ const earned = feePerL > 0n ? (feePerL * p.L) / WAD : 0n;
654
+ let taken = p.incomeTaken;
655
+ let q = p.Q;
656
+ const amounts = [];
657
+ for (let i = 0; i < 2 && q > 0n; i++) {
658
+ const amount = q;
659
+ const left = earned > taken ? earned - taken : 0n;
660
+ const taxable = amount < left ? amount : left;
661
+ taken += taxable;
662
+ const pFee = exempt ? 0n : (taxable * fee) / WAD;
663
+ const leaving = pFee !== 0n && collector !== ZERO_ADDRESS ? amount : amount - pFee;
664
+ q -= leaving;
665
+ amounts.push(amount);
666
+ }
667
+ if (q > 0n)
668
+ throw new Error("logswap: the bid does not empty in two harvests (no collector, income above Q) — harvest by hand first");
669
+ return amounts;
670
+ }
671
+ /**
672
+ * The relaunch as ONE transaction (decisions 025, 032, 035, 037): `harvest` the bid out — a
673
+ * dividend to every share, {@link fPoolEmptyingHarvests} — `dissolve` the live generation at the
674
+ * empty bid, `redeem` the caller's own shares of it (when `shares` > 0), optionally `setLegs` the
675
+ * next generation's composition (`legs`: a private pool's reshape — retire, admit, reweight), and
676
+ * `seed` it with what came back — the manager's `multicall`, one sender throughout, the same
677
+ * pool. The operator's when one is appointed and the seed's inventory is its own; by default the
678
+ * authority's. Gates, allowlist and names carry over; anything else goes in `extra`, pre-encoded
679
+ * against {@link fPoolManagerAbi}. The seed names reserves (decisions 029): `r0` outright — one
680
+ * per leg of the NEXT table, 0 for a retired leg — or `x0` marks against the next table's
681
+ * `weights`.
615
682
  */
616
683
  export async function fPoolRelaunch(c, a) {
617
- const gen = await fPoolGen(c, a.poolId);
618
- const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / WAD, x));
684
+ const [gen, harvests] = await Promise.all([fPoolGen(c, a.poolId), fPoolEmptyingHarvests(c, a.poolId)]);
685
+ const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => (a.weights[j] === 0n ? 0n : fPoolReserveAt((a.weights[j] * a.L0) / WAD, x)));
619
686
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
620
687
  const calls = [
688
+ ...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])),
621
689
  enc("dissolve", [a.poolId]),
622
690
  ...(a.shares > 0n ? [enc("redeem", [a.poolId, BigInt(gen), a.account, a.shares])] : []),
691
+ ...(a.legs ? [enc("setLegs", [a.poolId, a.legs.weights, a.legs.newBases ?? []])] : []),
623
692
  enc("seed", [a.poolId, a.L0, r0, a.Q0]),
624
693
  ...(a.extra ?? []),
625
694
  ];
626
695
  return writeFPool0(c, "multicall", [calls], a.account);
627
696
  }
628
- /** ERC-6909 shares sent here are locked for good — the seed's custody (custody.md §4(b)). */
697
+ /** Where a pad's seed lives (decisions 036): `seed` mints every share of a pad here itself. */
629
698
  export const DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
630
- /** The manager's `MIN_SHARES`: the seed mints this much to 0xdead itself, the rest to the seeder. */
699
+ /** The manager's `MIN_SHARES`: the seed mints this much to 0xdead on every pool, the rest to the seeder — or, on a pad, to 0xdead too. */
631
700
  export const F_MIN_SHARES = 1000n;
632
701
  /**
633
702
  * The launch's calls, in order — the batch {@link fPoolLaunch} sends. Pure, so a form can show
@@ -643,15 +712,14 @@ export function fPoolLaunchCalls(poolId, a) {
643
712
  if (given.length !== a.bases.length)
644
713
  throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
645
714
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
646
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
715
+ const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, kind: F_KIND[a.kind], authority: a.authority, salt: a.salt ?? ZERO_SALT };
647
716
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
648
717
  const calls = [];
649
718
  if (!a.initialized)
650
719
  calls.push(enc("initialize", [key]));
651
720
  calls.push(enc("seed", [poolId, a.L0, companions[1], a.Q0]));
652
- // gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`
653
- if (a.lockSeed)
654
- calls.push(enc("transfer", [DEAD_ADDRESS, fPoolShareId(poolId, 0), a.L0 - F_MIN_SHARES]));
721
+ // gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`.
722
+ // No transfer to 0xdead: on a pad `seed` mints every share there itself (decisions 036).
655
723
  if (a.openBuy) {
656
724
  const j = bases.indexOf(a.bases[a.openBuy.j]);
657
725
  if (j < 0)
@@ -660,10 +728,11 @@ export function fPoolLaunchCalls(poolId, a) {
660
728
  }
661
729
  if (a.name)
662
730
  calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
663
- if (a.privatePool) {
731
+ if (a.kind === "private") {
732
+ // the desk's opening posture: gated minting, the creator listed. No appointment: the
733
+ // authority is the operator until one is appointed (decisions 037)
664
734
  calls.push(enc("setGates", [poolId, true, false]));
665
735
  calls.push(enc("setAllowed", [poolId, [a.account], true]));
666
- calls.push(enc("appointOperator", [poolId, a.account]));
667
736
  }
668
737
  calls.push(...(a.extra ?? []));
669
738
  return calls;
@@ -749,35 +818,37 @@ export async function fPoolSponsorName(c, who) {
749
818
  }));
750
819
  return bytes32ToText(word);
751
820
  }
752
- /** Appoint (or clear, with the zero address) the operator — the hot key that reshapes and never moves value out. */
753
- export async function fPoolAppointOperator(c, a) {
754
- return writeFPool(c, a.poolId, "appointOperator", [a.operator], a.account);
821
+ /** Who runs the pool (decisions 037): the appointed operator, or the authority until one is. */
822
+ export function fPoolOperatorOf(s) {
823
+ return s.operator === ZERO_ADDRESS ? s.authority : s.operator;
755
824
  }
756
825
  /**
757
- * Raise the floor guard: `harvest` may not take Q/L under `minBuffer` (WAD log-distance; 20% below
758
- * spot is ln 1.25 ≈ 0.223e18). Raise-only — a commitment the authority cannot walk back.
826
+ * Propose the operator (authority; private pools only) — the hot key that runs the pool: `harvest`,
827
+ * `dissolve`, `setLegs`. Two-step: the proposed address takes the role with
828
+ * {@link fPoolAcceptOperator}. The zero address clears at once — the role returns to the authority.
759
829
  */
760
- export async function fPoolRaiseMinBuffer(c, a) {
761
- return writeFPool(c, a.poolId, "raiseMinBuffer", [a.minBuffer], a.account);
830
+ export async function fPoolAppointOperator(c, a) {
831
+ return writeFPool(c, a.poolId, "appointOperator", [a.operator], a.account);
832
+ }
833
+ /** The proposed operator takes the role. */
834
+ export async function fPoolAcceptOperator(c, a) {
835
+ return writeFPool(c, a.poolId, "acceptOperator", [], a.account);
762
836
  }
763
837
  /**
764
- * Set leg `j`'s liquidity (operator, sole LP only): grow, shrink, retire (`newLj = 0`), or re-admit
765
- * a retired leg holding `r` of the base (decisions 029 — the price follows, `p = newLj / r`; use
766
- * {@link fPoolReserveAt} from a mark). For a live leg leave `r` undefined — its price is the
767
- * market's, the reserve scales with the liquidity. Base moves single-sidedly through the lock;
768
- * shares adjust to hold the share value.
838
+ * The next generation's composition (operator; private pools only; ONLY while the pool is
839
+ * unseeded — between a dissolve and the seed that prices it, decisions 037). `weights` has one
840
+ * entry per leg the pool has ever had, in index order (0 retires a leg; indices are never reused),
841
+ * then one per base in `newBases`, appended; they sum to 1e18 exactly. No value moves: the
842
+ * following {@link fPoolSeed} names every reserve. A live reshape is {@link fPoolRelaunch} with
843
+ * `legs` set.
769
844
  */
770
- export async function fPoolSetLegL(c, a) {
771
- return writeFPool(c, a.poolId, "setLegL", [BigInt(a.j), a.newLj, a.r ?? 0n], a.account);
772
- }
773
- /** Admit a base the pool has never held: `R` of it against liquidity `Lj`, so at `p = Lj / R` (operator, sole LP only). */
774
- export async function fPoolAdmitLeg(c, a) {
775
- return writeFPool(c, a.poolId, "admitLeg", [a.base, a.Lj, a.R], a.account);
845
+ export async function fPoolSetLegs(c, a) {
846
+ return writeFPool(c, a.poolId, "setLegs", [a.weights, a.newBases ?? []], a.account);
776
847
  }
777
848
  /**
778
849
  * Move shares (ERC-6909 `transfer`) — to another holder, or to `0xdead` to lock them for good.
779
850
  * Burning to the dead address is a sponsor's way to make its liquidity permanent: the shares can
780
- * never be redeemed, and they never count against the sole-LP precondition.
851
+ * never be redeemed.
781
852
  */
782
853
  export async function fPoolTransferShares(c, a) {
783
854
  const wallet = requireWallet(c);
@@ -793,7 +864,7 @@ export async function fPoolTransferShares(c, a) {
793
864
  }
794
865
  /**
795
866
  * Every F pool ever created, from `Initialize` logs alone. Possible only because the event
796
- * carries the full key preimage (bases, weights, phi, lockStrike) — the poolId is a hash and could
867
+ * carries the full key preimage (bases, weights, phi, pad) — the poolId is a hash and could
797
868
  * never be inverted; contracts PR #17 exists for exactly this call.
798
869
  */
799
870
  export async function discoverFPools(c, opts = {}) {
@@ -826,12 +897,12 @@ export async function discoverFPools(c, opts = {}) {
826
897
  bases: [...a.bases],
827
898
  weights: [...a.weights],
828
899
  phi: a.phi,
829
- lockStrike: a.lockStrike,
900
+ kind: fPoolKindOf(a.kind),
830
901
  authority: a.authority,
831
902
  block: l.blockNumber ?? 0n,
832
903
  seeded: q0 !== undefined,
833
904
  q0: q0 ?? 0n,
834
- shape: a.bases.length === 1 && (q0 ?? 0n) === 0n ? "launch" : "basket",
905
+ shape: a.kind === F_KIND.pad ? "launch" : "basket", // the kind, not n = 1: a mono POL is a pool (decisions 036)
835
906
  };
836
907
  });
837
908
  }
@@ -865,7 +936,7 @@ export async function fPoolShareHolders(c, poolIdHex, opts = {}) {
865
936
  .map(([holder, shares]) => ({ holder: holder, shares }))
866
937
  .sort((a, b) => (b.shares > a.shares ? 1 : -1));
867
938
  }
868
- /** Every F pool's state in one lens call (the raw manager struct: quote, phi, authority, lockStrike, seeded, gen, n, Q, L, shares, theta0, bigSigma, harvestedTheta). */
939
+ /** Every F pool's state in one lens call (the raw manager struct: quote, phi, authority, kind, seeded, gen, n, Q, L, shares, theta0, bigSigma, harvestedTheta). */
869
940
  export async function getFPoolsRaw(c, poolIds) {
870
941
  const { logswapLensAbi } = await import("./generated.js");
871
942
  return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] }));