@logswap/sdk 0.5.39 → 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,12 +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
- /** 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. */
273
- export async function fPoolDeepen(c, a) {
274
- return writeFPool(c, a.poolId, "deepen", [a.amount], a.account);
275
- }
276
280
  // ─── internals ────────────────────────────────────────────────────────────────
277
281
  function defaultDeadline() {
278
282
  return BigInt(Math.floor(Date.now() / 1000) + 1800);
@@ -555,7 +559,7 @@ export async function fPoolInitialize(c, a) {
555
559
  if (sum !== 10n ** 18n)
556
560
  throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
557
561
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
558
- 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 };
559
563
  const wallet = requireWallet(c);
560
564
  const { request } = await c.public.simulateContract({
561
565
  address: c.addresses.fPoolManager,
@@ -569,7 +573,7 @@ export async function fPoolInitialize(c, a) {
569
573
  /** The pool id is the key's hash — pure, so it can be read before or after creation. */
570
574
  export async function fPoolIdOf(c, a) {
571
575
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
572
- 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 };
573
577
  return c.public.readContract({
574
578
  address: c.addresses.fPoolManager,
575
579
  abi: fPoolManagerAbi,
@@ -600,38 +604,99 @@ export async function fPoolSeed(c, a) {
600
604
  return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
601
605
  }
602
606
  /**
603
- * Retire the live generation (decisions 025, 032). Allowed at Q ≤ L/1e9 on every pool — the
604
- * market filled the ask — and on a pool that is not strike-locked at any Q. The live (S, Q, R_j)
605
- * become a frozen bundle every share of this generation redeems ({@link fPoolRedeem}) or rolls
606
- * ({@link fPoolRollIn}), forever; the pool is unseeded until the next {@link fPoolSeed}, which
607
- * 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.
608
613
  */
609
614
  export async function fPoolDissolve(c, a) {
610
615
  return writeFPool(c, a.poolId, "dissolve", [], a.account);
611
616
  }
612
617
  /**
613
- * The sponsor's relaunch as ONE transaction (decisions 025, 032): `dissolve` the live generation,
614
- * `redeem` the sponsor's own shares of it (when `shares` > 0), `seed` the next generation with
615
- * what came back — the manager's `multicall`, the sponsor as sender throughout, the same pool.
616
- * Gates, allowlist, operator, `minBuffer` and names carry over; anything else goes in `extra`,
617
- * pre-encoded against {@link fPoolManagerAbi}. The seed names reserves (decisions 029): `r0`
618
- * 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`.
619
682
  */
620
683
  export async function fPoolRelaunch(c, a) {
621
- const gen = await fPoolGen(c, a.poolId);
622
- 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)));
623
686
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
624
687
  const calls = [
688
+ ...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])),
625
689
  enc("dissolve", [a.poolId]),
626
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 ?? []])] : []),
627
692
  enc("seed", [a.poolId, a.L0, r0, a.Q0]),
628
693
  ...(a.extra ?? []),
629
694
  ];
630
695
  return writeFPool0(c, "multicall", [calls], a.account);
631
696
  }
632
- /** 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. */
633
698
  export const DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
634
- /** 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. */
635
700
  export const F_MIN_SHARES = 1000n;
636
701
  /**
637
702
  * The launch's calls, in order — the batch {@link fPoolLaunch} sends. Pure, so a form can show
@@ -647,15 +712,14 @@ export function fPoolLaunchCalls(poolId, a) {
647
712
  if (given.length !== a.bases.length)
648
713
  throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
649
714
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
650
- 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 };
651
716
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
652
717
  const calls = [];
653
718
  if (!a.initialized)
654
719
  calls.push(enc("initialize", [key]));
655
720
  calls.push(enc("seed", [poolId, a.L0, companions[1], a.Q0]));
656
- // gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`
657
- if (a.lockSeed)
658
- 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).
659
723
  if (a.openBuy) {
660
724
  const j = bases.indexOf(a.bases[a.openBuy.j]);
661
725
  if (j < 0)
@@ -664,10 +728,11 @@ export function fPoolLaunchCalls(poolId, a) {
664
728
  }
665
729
  if (a.name)
666
730
  calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
667
- 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)
668
734
  calls.push(enc("setGates", [poolId, true, false]));
669
735
  calls.push(enc("setAllowed", [poolId, [a.account], true]));
670
- calls.push(enc("appointOperator", [poolId, a.account]));
671
736
  }
672
737
  calls.push(...(a.extra ?? []));
673
738
  return calls;
@@ -753,35 +818,37 @@ export async function fPoolSponsorName(c, who) {
753
818
  }));
754
819
  return bytes32ToText(word);
755
820
  }
756
- /** Appoint (or clear, with the zero address) the operator — the hot key that reshapes and never moves value out. */
757
- export async function fPoolAppointOperator(c, a) {
758
- 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;
759
824
  }
760
825
  /**
761
- * Raise the floor guard: `harvest` may not take Q/L under `minBuffer` (WAD log-distance; 20% below
762
- * 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.
763
829
  */
764
- export async function fPoolRaiseMinBuffer(c, a) {
765
- 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);
766
836
  }
767
837
  /**
768
- * Set leg `j`'s liquidity (operator, sole LP only): grow, shrink, retire (`newLj = 0`), or re-admit
769
- * a retired leg holding `r` of the base (decisions 029 — the price follows, `p = newLj / r`; use
770
- * {@link fPoolReserveAt} from a mark). For a live leg leave `r` undefined — its price is the
771
- * market's, the reserve scales with the liquidity. Base moves single-sidedly through the lock;
772
- * 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.
773
844
  */
774
- export async function fPoolSetLegL(c, a) {
775
- return writeFPool(c, a.poolId, "setLegL", [BigInt(a.j), a.newLj, a.r ?? 0n], a.account);
776
- }
777
- /** Admit a base the pool has never held: `R` of it against liquidity `Lj`, so at `p = Lj / R` (operator, sole LP only). */
778
- export async function fPoolAdmitLeg(c, a) {
779
- 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);
780
847
  }
781
848
  /**
782
849
  * Move shares (ERC-6909 `transfer`) — to another holder, or to `0xdead` to lock them for good.
783
850
  * Burning to the dead address is a sponsor's way to make its liquidity permanent: the shares can
784
- * never be redeemed, and they never count against the sole-LP precondition.
851
+ * never be redeemed.
785
852
  */
786
853
  export async function fPoolTransferShares(c, a) {
787
854
  const wallet = requireWallet(c);
@@ -797,7 +864,7 @@ export async function fPoolTransferShares(c, a) {
797
864
  }
798
865
  /**
799
866
  * Every F pool ever created, from `Initialize` logs alone. Possible only because the event
800
- * 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
801
868
  * never be inverted; contracts PR #17 exists for exactly this call.
802
869
  */
803
870
  export async function discoverFPools(c, opts = {}) {
@@ -830,12 +897,12 @@ export async function discoverFPools(c, opts = {}) {
830
897
  bases: [...a.bases],
831
898
  weights: [...a.weights],
832
899
  phi: a.phi,
833
- lockStrike: a.lockStrike,
900
+ kind: fPoolKindOf(a.kind),
834
901
  authority: a.authority,
835
902
  block: l.blockNumber ?? 0n,
836
903
  seeded: q0 !== undefined,
837
904
  q0: q0 ?? 0n,
838
- 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)
839
906
  };
840
907
  });
841
908
  }
@@ -869,7 +936,7 @@ export async function fPoolShareHolders(c, poolIdHex, opts = {}) {
869
936
  .map(([holder, shares]) => ({ holder: holder, shares }))
870
937
  .sort((a, b) => (b.shares > a.shares ? 1 : -1));
871
938
  }
872
- /** 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, leverTheta). */
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). */
873
940
  export async function getFPoolsRaw(c, poolIds) {
874
941
  const { logswapLensAbi } = await import("./generated.js");
875
942
  return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] }));