@logswap/sdk 0.5.71 → 0.5.72

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
@@ -30,7 +30,7 @@
30
30
  * `addresses.router`.
31
31
  */
32
32
  import { decodeAbiParameters, encodeAbiParameters, encodeFunctionData, erc20Abi, keccak256, toFunctionSelector } from "viem";
33
- import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
33
+ import { fPoolManagerAbi, logswapLensAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
34
34
  /**
35
35
  * One contract, one ABI (F version 12): `harvest`, `claim`, `retire`, `regenerate`,
36
36
  * `setLegs`, the names, the fee parameters are `FPoolManager`'s own code. The roles left it for
@@ -52,20 +52,30 @@ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
52
52
  /** The optional questions a policy declared at birth, as bits of `Pool.asks`. */
53
53
  export const ASK_SWAP = 1;
54
54
  export const ASK_MINT = 2;
55
- /** Read a basket pool whole. Batched — viem multicalls these when the transport allows. */
55
+ /**
56
+ * The lens's raw F pool struct — what the manager's `getPool` returned. Since F version 25 the
57
+ * manager's typed views are behind `extsload` (decisions 062) and the lens reads them; one call.
58
+ */
59
+ async function rawFPool(c, poolId) {
60
+ const [p] = await c.public.readContract({
61
+ address: c.addresses.lens,
62
+ abi: logswapLensAbi,
63
+ functionName: "getFPools",
64
+ args: [[poolId]],
65
+ });
66
+ if (!p || /^0x0{40}$/i.test(p.quote))
67
+ throw new Error(`logswap: no F pool with id ${poolId}`);
68
+ return p;
69
+ }
70
+ /** Read a basket pool whole: one lens call (`fPool`, F version 25) plus the policy's kind and name. */
56
71
  export async function getFPool(c, poolId) {
57
- // viem types `functionName` as a literal union; this reader is deliberately generic, so the
58
- // cast lives here once rather than at each call site below.
59
- const rd = (functionName, args) => c.public.readContract({
60
- address: c.addresses.fPoolManager,
61
- abi: fPoolManagerAbi,
62
- functionName,
63
- args,
72
+ const v = await c.public.readContract({
73
+ address: c.addresses.lens,
74
+ abi: logswapLensAbi,
75
+ functionName: "fPool",
76
+ args: [poolId],
64
77
  });
65
- // `getPool` returns the whole scalar struct in one call — the singleton's shape makes what used
66
- // to be a dozen getters a single read. No role in it since F version 15, no kind and no name
67
- // since 18: those are the policy's.
68
- const p = await rd("getPool", [poolId]);
78
+ const p = v.pool;
69
79
  // A pool that was never created reads as an all-zero struct rather than reverting, so the
70
80
  // absence has to be named here or every caller mistakes "no such pool" for "a pool holding
71
81
  // nothing". `quote` is the manager's own existence test (`_live`), and it is what the create
@@ -73,45 +83,33 @@ export async function getFPool(c, poolId) {
73
83
  // cannot be a check on `seeded`.
74
84
  if (/^0x0{40}$/i.test(p.quote))
75
85
  throw new Error(`logswap: no F pool with id ${poolId}`);
76
- const n = Number(p.n);
77
- const idx = Array.from({ length: n }, (_, i) => BigInt(i));
78
- const [theta, compositeX, legs, kind, name] = await Promise.all([
79
- // before the seed there is no L and no floor: θ is X by convention (older deployments revert
80
- // with DivWadFailed, so do not ask them)
81
- p.L > 0n ? rd("theta", [poolId]) : rd("compositeX", [poolId]),
82
- rd("compositeX", [poolId]),
83
- Promise.all(idx.map((i) => rd("legOf", [poolId, i]))),
84
- fPoolKindOfPolicy(c, p.policy),
85
- fPoolNameOf(c, poolId, p.policy),
86
- ]);
87
- const bases = legs.map((l) => l[0]);
88
- const weights = legs.map((l) => l[1]);
89
- const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j)
90
- const reserves = legs.map((l) => l[3]); // the state
91
- const { quote, Q, L, phi, theta0, bigSigma, policy, restructureBlock, seeded, gen } = p;
92
- const totalSupply = p.shares;
86
+ const [kind, name] = await Promise.all([fPoolKindOfPolicy(c, p.policy), fPoolNameOf(c, poolId, p.policy)]);
93
87
  return {
94
88
  poolId,
95
- quote,
96
- bases,
97
- weights,
98
- x,
99
- Q,
100
- L,
101
- phi,
102
- theta0,
103
- theta,
104
- compositeX,
105
- bigSigma,
106
- policy,
89
+ quote: p.quote,
90
+ bases: v.legs.map((l) => l.base),
91
+ weights: v.legs.map((l) => l.weight),
92
+ x: v.legs.map((l) => l.x), // derived by the manager: x_j = ln(w_j·L / R̃_j)
93
+ Q: p.Q,
94
+ L: p.L,
95
+ swapFee: p.swapFee,
96
+ refLogFloor: p.refLogFloor,
97
+ logFloor: v.logFloor,
98
+ compositeX: v.compositeX,
99
+ unharvested: v.unharvested,
100
+ cap: Number(p.cap),
101
+ bigSigma: p.bigSigma,
102
+ policy: p.policy,
107
103
  asks: Number(p.asks),
108
104
  name,
109
- restructureBlock: Number(restructureBlock),
105
+ restructureBlock: Number(p.restructureBlock),
110
106
  kind,
111
- seeded,
112
- gen: Number(gen),
113
- totalSupply,
114
- reserves,
107
+ seeded: p.seeded,
108
+ gen: Number(p.gen),
109
+ totalSupply: p.shares,
110
+ reserves: v.legs.map((l) => l.reserve), // the state
111
+ virtualReserves: v.legs.map((l) => l.virtualReserve),
112
+ kappa: v.legs.map((l) => l.kappa),
115
113
  };
116
114
  }
117
115
  const F_KIND_NAMES = ["public", "private", "pol", "pad"];
@@ -154,15 +152,7 @@ export function fPoolPolicyFor(c, kind) {
154
152
  }
155
153
  /** The policy a pool named in its key — one read; {@link getFPool} carries it too. */
156
154
  export async function fPoolPolicyOf(c, poolId) {
157
- const p = (await c.public.readContract({
158
- address: c.addresses.fPoolManager,
159
- abi: fPoolManagerAbi,
160
- functionName: "getPool",
161
- args: [poolId],
162
- }));
163
- if (/^0x0{40}$/i.test(p.quote))
164
- throw new Error(`logswap: no F pool with id ${poolId}`);
165
- return p.policy;
155
+ return (await rawFPool(c, poolId)).policy;
166
156
  }
167
157
  /** Read a pool's roles from its policy. `state` saves the `getPool` round trip when already in hand. */
168
158
  export async function getFPoolRoles(c, poolId, state) {
@@ -186,18 +176,11 @@ export async function getFPoolRoles(c, poolId, state) {
186
176
  return { policy: s.policy, creator, authority, pendingAuthority, operator, pendingOperator, gateMint, gateSwap, maxHarvest };
187
177
  }
188
178
  export async function describeFPool(c, s) {
189
- const rd = (functionName, args = [s.poolId]) => c.public.readContract({
190
- address: c.addresses.fPoolManager,
191
- abi: fPoolManagerAbi,
192
- functionName,
193
- args,
194
- });
195
179
  // an unseeded pool has no L: nothing earned, nothing to harvest, no pot. The bid is the
196
180
  // manager's ceiling (`harvestable()` until F version 21, which cut the getter): Q once seeded.
197
181
  const harvestable = s.L > 0n ? s.Q : 0n;
198
- const [unharvested, pot] = s.L > 0n
199
- ? await Promise.all([rd("unharvested"), rd("dividendOf", [s.poolId, BigInt(s.gen), DEAD_ADDRESS])])
200
- : [0n, 0n];
182
+ const unharvested = s.L > 0n ? s.unharvested : 0n;
183
+ const pot = s.L > 0n ? await fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, s.gen) : 0n;
201
184
  return {
202
185
  shape: s.kind === "pad" ? "launch" : "basket", // the kind, not n = 1
203
186
  legs: s.bases.length,
@@ -211,11 +194,24 @@ export async function describeFPool(c, s) {
211
194
  export function fPoolPriceOf(x, scale = 1) {
212
195
  return Math.exp(Number(x) / 1e18) * scale;
213
196
  }
214
- /** Value per share at the pool's own marks. `V = L + Q`, so this is just that, pro-rated. */
197
+ /**
198
+ * Value per share at the pool's own marks, WAD. Uncapped, `V = L + Q`. Capped (decisions 061), leg
199
+ * j's base is worth `L_j·R_j/R̃_j`, less than `L_j` by the phantom the cap removed — the lens's
200
+ * `shareValue`, without the call.
201
+ */
215
202
  export function fPoolShareValue(s) {
216
203
  if (s.totalSupply === 0n)
217
204
  return 0n;
218
- return ((s.L + s.Q) * WAD) / s.totalSupply;
205
+ let v = s.L + s.Q;
206
+ if (s.cap !== 0) {
207
+ v = s.Q;
208
+ for (let j = 0; j < s.reserves.length; j++) {
209
+ const rv = s.virtualReserves[j];
210
+ if (rv !== 0n)
211
+ v += (((s.weights[j] * s.L) / WAD) * s.reserves[j]) / rv;
212
+ }
213
+ }
214
+ return (v * WAD) / s.totalSupply;
219
215
  }
220
216
  /**
221
217
  * All three F swaps go through the ROUTER's `swapExactIn` overload — the same surface as a C
@@ -385,16 +381,63 @@ export async function fPoolClaim(c, a) {
385
381
  const gen = a.gen ?? (await fPoolGen(c, a.poolId));
386
382
  return writeFPool(c, a.poolId, "claim", [BigInt(gen), a.to ?? a.account], a.account);
387
383
  }
388
- /** A holder's dividend as of now on generation `gen` (the live one by default) — banked plus pending on the current balance. */
384
+ /**
385
+ * A holder's claimable dividend as of now on generation `gen` (the live one by default) — banked
386
+ * plus pending on the current balance; under a lien, the free fraction's only: the locked
387
+ * fraction's is the lien's ({@link fPoolLienOf}, decisions 063).
388
+ */
389
389
  export async function fPoolDividendOf(c, poolId, holder, gen) {
390
390
  const g = gen ?? (await fPoolGen(c, poolId));
391
391
  return c.public.readContract({
392
- address: c.addresses.fPoolManager,
393
- abi: fPoolManagerAbi,
394
- functionName: "dividendOf",
392
+ address: c.addresses.lens,
393
+ abi: logswapLensAbi,
394
+ functionName: "fDividendOf",
395
395
  args: [poolId, BigInt(g), holder],
396
396
  });
397
397
  }
398
+ /**
399
+ * The lien on `owner`'s shares of `poolId`'s generation `gen` (the live one by default). `owed` counts
400
+ * what has been banked into it; the locked fraction of the owner's pending since its last bank is
401
+ * not yet in it — {@link fPoolBankLien} banks it.
402
+ */
403
+ export async function fPoolLienOf(c, poolId, owner, gen) {
404
+ const g = gen ?? (await fPoolGen(c, poolId));
405
+ const [locker, amount, owed] = await c.public.readContract({
406
+ address: c.addresses.lens,
407
+ abi: logswapLensAbi,
408
+ functionName: "fLienOf",
409
+ args: [fPoolShareId(poolId, g), owner],
410
+ });
411
+ return { locker, amount, owed };
412
+ }
413
+ /**
414
+ * Lock `amount` of `owner`'s shares for the caller, the locker (`account`). Paid with the owner's
415
+ * per-id allowance to the locker — the owner first sends `approve(locker, shareId, amount)` on the
416
+ * F manager ({@link fPoolApproveShares}) — or an operator grant. One locker per (id, owner) while a
417
+ * lien stands. In practice the lending contract calls `lock` itself; this is the EOA path.
418
+ */
419
+ export async function fPoolLock(c, a) {
420
+ const g = a.gen ?? (await fPoolGen(c, a.poolId));
421
+ return writeFPool0(c, "lock", [fPoolShareId(a.poolId, g), a.owner, a.amount], a.account);
422
+ }
423
+ /**
424
+ * The locker's `unlock`: `amount` of the lien lifted with its share of the lien's dividend, credited
425
+ * to `to` — the owner (a release) or anyone else (a seizure, the shares moving to `to`). It banks
426
+ * first. The locker's alone.
427
+ */
428
+ export async function fPoolUnlock(c, a) {
429
+ const g = a.gen ?? (await fPoolGen(c, a.poolId));
430
+ return writeFPool0(c, "unlock", [fPoolShareId(a.poolId, g), a.owner, a.amount, a.to], a.account);
431
+ }
432
+ /** Bank the owner's pending into the lien now — `unlock(id, owner, 0, owner)`: dividends bank lazily. The locker's. */
433
+ export async function fPoolBankLien(c, a) {
434
+ return fPoolUnlock(c, { ...a, amount: 0n, to: a.owner });
435
+ }
436
+ /** The owner's per-id allowance to a locker on the F manager's ERC-6909 shares — what `lock` spends. */
437
+ export async function fPoolApproveShares(c, a) {
438
+ const g = a.gen ?? (await fPoolGen(c, a.poolId));
439
+ return writeFPool0(c, "approve", [a.spender, fPoolShareId(a.poolId, g), a.amount], a.account);
440
+ }
398
441
  // ─── internals ────────────────────────────────────────────────────────────────
399
442
  function defaultDeadline() {
400
443
  return BigInt(Math.floor(Date.now() / 1000) + 1800);
@@ -529,13 +572,7 @@ export function fPoolGenOfShareId(id) {
529
572
  }
530
573
  /** The pool's live generation, one read. */
531
574
  export async function fPoolGen(c, poolId) {
532
- const p = (await c.public.readContract({
533
- address: c.addresses.fPoolManager,
534
- abi: fPoolManagerAbi,
535
- functionName: "getPool",
536
- args: [poolId],
537
- }));
538
- return Number(p.gen);
575
+ return Number((await rawFPool(c, poolId)).gen);
539
576
  }
540
577
  /** The ERC-6909 id carrying claims on a token. Bit 255 clear, so it can never meet a share id. */
541
578
  export function fPoolClaimId(token) {
@@ -553,12 +590,12 @@ export async function fPoolShareBalance(c, poolId, owner, gen) {
553
590
  }
554
591
  /** A retired generation's bundle: what is left to redeem — supply, quote, base per leg index. */
555
592
  export async function fPoolBundleOf(c, poolId, gen) {
556
- const [supply, quote, base] = (await c.public.readContract({
557
- address: c.addresses.fPoolManager,
558
- abi: fPoolManagerAbi,
559
- functionName: "bundleOf",
593
+ const [supply, quote, base] = await c.public.readContract({
594
+ address: c.addresses.lens,
595
+ abi: logswapLensAbi,
596
+ functionName: "fBundleOf",
560
597
  args: [poolId, BigInt(gen)],
561
- }));
598
+ });
562
599
  return { supply, quote, base: [...base] };
563
600
  }
564
601
  /** A holder's claims on a pool's retired generations — one row per generation still held. */
@@ -678,10 +715,11 @@ const F_POOL_KEY_ABI = [
678
715
  { name: "quote", type: "address" },
679
716
  { name: "bases", type: "address[]" },
680
717
  { name: "weights", type: "uint256[]" },
681
- { name: "phi", type: "uint256" },
718
+ { name: "swapFee", type: "uint256" },
682
719
  { name: "policy", type: "address" },
683
720
  { name: "creator", type: "address" },
684
721
  { name: "salt", type: "bytes32" },
722
+ { name: "cap", type: "uint24" },
685
723
  ],
686
724
  },
687
725
  ];
@@ -700,10 +738,11 @@ export function fPoolKeyOf(c, a) {
700
738
  quote: a.quote,
701
739
  bases,
702
740
  weights: companions[0],
703
- phi: a.phi,
741
+ swapFee: a.swapFee,
704
742
  policy: a.policy ?? fPoolPolicyFor(c, a.kind),
705
743
  creator: a.creator,
706
744
  salt: a.salt ?? ZERO_SALT,
745
+ cap: a.cap ?? 0,
707
746
  };
708
747
  }
709
748
  /** Create the pool (no funds move — `seed` arms it). Returns the tx hash; read the id with `fPoolIdOf`. */
@@ -753,16 +792,17 @@ export async function fPoolKeyOfPool(c, poolId, opts = {}) {
753
792
  quote: a.quote,
754
793
  bases: [...a.bases],
755
794
  weights: [...a.weights],
756
- phi: a.phi,
795
+ swapFee: a.swapFee,
757
796
  policy: a.policy,
758
797
  creator: a.creator,
759
798
  salt: opts.salt ?? ZERO_SALT,
799
+ cap: Number(a.cap),
760
800
  };
761
801
  if (fPoolIdOfKey(key).toLowerCase() !== poolId.toLowerCase() && opts.salt === undefined) {
762
802
  // a pad's salt carries its split (decisions 056): rebuild it from the policy's record with the default nonce
763
803
  try {
764
- const [creatorShare, burnPerRung] = (await c.public.readContract({ address: a.policy, abi: padPolicyAbi, functionName: "splitOf", args: [poolId] }));
765
- const padKey = { ...key, salt: padSalt({ creatorShare, burnPerRung }) };
804
+ const [creatorShare, burnShare] = (await c.public.readContract({ address: a.policy, abi: padPolicyAbi, functionName: "splitOf", args: [poolId] }));
805
+ const padKey = { ...key, salt: padSalt({ creatorShare, burnShare }) };
766
806
  if (fPoolIdOfKey(padKey).toLowerCase() === poolId.toLowerCase())
767
807
  return padKey;
768
808
  }
@@ -782,10 +822,38 @@ export async function fPoolKeyOfPool(c, poolId, opts = {}) {
782
822
  * precision (~1e-16 relative) is what a seed needs: any reserve is a legal seed, and the mark
783
823
  * the pool derives from it is the one the sponsor meant to within that.
784
824
  */
785
- export function fPoolReserveAt(Lj, x) {
786
- const e = Math.exp(-Number(x) / 1e18);
825
+ export function fPoolReserveAt(Lj, x, k = 0) {
826
+ // on a capped pool (decisions 061) the mark reads the virtual reserve R̃ = R + c, and the seed
827
+ // puts c = R·k/(1 − k): so the REAL reserve for a mark is L_j·e^{−x}·(1 − k)
828
+ const e = Math.exp(-Number(x) / 1e18) * (1 - k);
787
829
  return (Lj * BigInt(Math.round(e * 1e18))) / 10n ** 18n;
788
830
  }
831
+ /**
832
+ * The seed's cap constant k = e^{Q0/L0 − d} (decisions 061): every leg's phantom is c_j = r_j·k/(1 − k).
833
+ * 0 on an uncapped pool. Throws when the seed would be priced at or above its cap (Q0/L0 ≥ d), which
834
+ * the manager refuses.
835
+ */
836
+ export function fPoolCapK(cap, Q0, L0) {
837
+ if (cap === 0)
838
+ return 0;
839
+ const e = Number((Q0 * WAD) / L0) / 1e18 - cap / 1e6;
840
+ if (e >= 0)
841
+ throw new Error(`logswap: a seed with Q0/L0 = ${Number((Q0 * WAD) / L0) / 1e18} is at or above its cap d = ${cap / 1e6}`);
842
+ return Math.exp(e);
843
+ }
844
+ /** The cap d (1e-6 log units) for a leverage λ > 1 at the floor: d = ln(λ/(λ − 1)) (decisions 061). λ = 2 puts the cap at twice the floor. */
845
+ export function fPoolCapOfLeverage(lambda) {
846
+ if (!(lambda > 1))
847
+ throw new Error("logswap: leverage must exceed 1 (1 is uncapped: cap 0)");
848
+ const d = Math.round(Math.log(lambda / (lambda - 1)) * 1e6);
849
+ if (d < 1 || d > 0xffffff)
850
+ throw new Error(`logswap: leverage ${lambda} needs a cap outside a uint24 of 1e-6 units`);
851
+ return d;
852
+ }
853
+ /** The leverage at the floor λ = 1/(1 − e^{−d}) for a cap d in 1e-6 log units; Infinity is impossible, 1 is uncapped. */
854
+ export function fPoolLeverageOfCap(cap) {
855
+ return cap === 0 ? 1 : 1 / (1 - Math.exp(-cap / 1e6));
856
+ }
789
857
  /**
790
858
  * Arm the pool: deposit `r0[j]` of each base plus `Q0` of quote against exposure `L0`. Pass either
791
859
  * the reserves outright (`r0`) or the marks the pool should open at (`x0`, converted here with
@@ -794,7 +862,13 @@ export function fPoolReserveAt(Lj, x) {
794
862
  * {@link sortFPoolLegs} on (bases, weights, x0) together when building forms.
795
863
  */
796
864
  export async function fPoolSeed(c, a) {
797
- const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / 10n ** 18n, x));
865
+ let r0;
866
+ if ("r0" in a)
867
+ r0 = a.r0;
868
+ else {
869
+ const k = fPoolCapK(Number((await rawFPool(c, a.poolId)).cap), a.Q0, a.L0);
870
+ r0 = a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / 10n ** 18n, x, k));
871
+ }
798
872
  return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
799
873
  }
800
874
  /**
@@ -855,8 +929,9 @@ export async function fPoolEmptyingHarvests(c, poolId) {
855
929
  * and redeems it with {@link fPoolRedeem} / rolls it with {@link fPoolRollIn} (`gen: g`); their
856
930
  * dividends on `g` stay claimable ({@link fPoolClaim} with `gen: g`). {@link fPoolClaimsOf}
857
931
  * lists them;
858
- * - the pot: `toPayee` to the payee, `protocolCut` to the collector, the rest bought in as dead
859
- * shares of `g + 1`; then the burn.
932
+ * - the pot, all in quote (decisions 057): `toPayee` to the payee, `protocolCut` to the collector,
933
+ * `burn` spent buying base at the curve and burning it (the price up, the floor held), and the
934
+ * rest into the bid (the floor down). No share is minted; L does not move.
860
935
  *
861
936
  * A pad's gate is `threshold(gen)` (`PadPolicy.NotGraduated` below it); POL and private carry the
862
937
  * authority and send it the whole pot; a public pool refuses (`Refused`). A carried holder with no
@@ -878,8 +953,10 @@ export async function fPoolRegenerate(c, a) {
878
953
  * against the next table's `weights`.
879
954
  */
880
955
  export async function fPoolRelaunch(c, a) {
881
- const [gen, harvests] = await Promise.all([fPoolGen(c, a.poolId), fPoolEmptyingHarvests(c, a.poolId)]);
882
- 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)));
956
+ const [raw, harvests] = await Promise.all([rawFPool(c, a.poolId), fPoolEmptyingHarvests(c, a.poolId)]);
957
+ const gen = Number(raw.gen);
958
+ const k = "r0" in a ? 0 : fPoolCapK(Number(raw.cap), a.Q0, a.L0);
959
+ 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, k)));
883
960
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
884
961
  const calls = [
885
962
  ...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])),
@@ -905,13 +982,14 @@ export function fPoolLaunchCalls(poolId, a) {
905
982
  const sum = a.weights.reduce((x, y) => x + y, 0n);
906
983
  if (sum !== WAD)
907
984
  throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
908
- const given = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / WAD, x));
985
+ const k = "r0" in a ? 0 : fPoolCapK(a.cap ?? 0, a.Q0, a.L0);
986
+ const given = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j] * a.L0) / WAD, x, k));
909
987
  if (given.length !== a.bases.length)
910
988
  throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
911
989
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
912
990
  if (!a.policy)
913
991
  throw new Error("logswap: fPoolLaunchCalls needs the key's policy — fPoolLaunch fills it from the deployment");
914
- const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, policy: a.policy, creator: a.creator, salt: a.salt ?? ZERO_SALT };
992
+ const key = { quote: a.quote, bases, weights: companions[0], swapFee: a.swapFee, policy: a.policy, creator: a.creator, salt: a.salt ?? ZERO_SALT, cap: a.cap ?? 0 };
915
993
  const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
916
994
  const calls = [];
917
995
  if (!a.initialized)
@@ -1229,7 +1307,7 @@ export async function fPoolTransferShares(c, a) {
1229
1307
  /**
1230
1308
  * Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
1231
1309
  * policy, cached. Possible only because the event carries the full key preimage (bases,
1232
- * weights, phi, policy) — the poolId is a hash and could never be inverted; contracts PR #17
1310
+ * weights, swapFee, policy, cap) — the poolId is a hash and could never be inverted; contracts PR #17
1233
1311
  * exists for exactly this call. With `indexer`, the logs come from it (the node when it fails):
1234
1312
  * a scan from block 0 per call is what made the first load wait on the node.
1235
1313
  */
@@ -1257,7 +1335,8 @@ export async function discoverFPools(c, opts = {}) {
1257
1335
  quote: a.quote,
1258
1336
  bases: [...a.bases],
1259
1337
  weights: [...a.weights],
1260
- phi: a.phi,
1338
+ swapFee: a.swapFee,
1339
+ cap: Number(a.cap ?? 0),
1261
1340
  kind,
1262
1341
  policy: a.policy,
1263
1342
  creator: a.creator,
@@ -1271,22 +1350,22 @@ export async function discoverFPools(c, opts = {}) {
1271
1350
  const MAX_U64 = (1n << 64n) - 1n;
1272
1351
  const MAX_U128 = (1n << 128n) - 1n;
1273
1352
  /**
1274
- * The salt that carries a pad's split (`PadPolicy.pack`, decisions 056): `creatorShare` in the top 64 bits, `burnPerRung` in
1275
- * the next 64 (WAD fractions), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
1353
+ * The salt that carries a pad's split (`PadPolicy.pack`, decisions 056, 057): `creatorShare` in the top 64 bits, `burnShare` in
1354
+ * the next 64 (WAD fractions of the pot), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
1276
1355
  * identity, so it is chosen at launch (`FPoolLaunchArgs.salt`) and never changes; the policy refuses a split outside its ranges.
1277
1356
  */
1278
1357
  export function padSalt(s) {
1279
1358
  const nonce = s.nonce ?? 0n;
1280
- if (s.creatorShare < 0n || s.creatorShare > MAX_U64 || s.burnPerRung < 0n || s.burnPerRung > MAX_U64)
1359
+ if (s.creatorShare < 0n || s.creatorShare > MAX_U64 || s.burnShare < 0n || s.burnShare > MAX_U64)
1281
1360
  throw new Error("logswap: a pad's split is a WAD fraction that fits 64 bits");
1282
1361
  if (nonce < 0n || nonce > MAX_U128)
1283
1362
  throw new Error("logswap: a pad salt's nonce fits 128 bits");
1284
- return `0x${((s.creatorShare << 192n) | (s.burnPerRung << 128n) | nonce).toString(16).padStart(64, "0")}`;
1363
+ return `0x${((s.creatorShare << 192n) | (s.burnShare << 128n) | nonce).toString(16).padStart(64, "0")}`;
1285
1364
  }
1286
1365
  /** The inverse of {@link padSalt}. */
1287
1366
  export function padSplitOfSalt(salt) {
1288
1367
  const v = BigInt(salt);
1289
- return { creatorShare: v >> 192n, burnPerRung: (v >> 128n) & MAX_U64, nonce: v & MAX_U128 };
1368
+ return { creatorShare: v >> 192n, burnShare: (v >> 128n) & MAX_U64, nonce: v & MAX_U128 };
1290
1369
  }
1291
1370
  /** The policy's terms for one pool, by its kind. */
1292
1371
  export async function getFPoolRules(c, s, roles) {
@@ -1305,9 +1384,9 @@ export async function getFPoolRules(c, s, roles) {
1305
1384
  rd(padPolicyAbi, "burnMin"),
1306
1385
  rd(padPolicyAbi, "burnMax"),
1307
1386
  ]);
1308
- const [creatorShare, burnPerRung, protocolShare] = split;
1387
+ const [creatorShare, burnShare, protocolShare] = split;
1309
1388
  const ranges = { protocolShare: protocolNow, creatorShareMin, creatorShareMax, burnMin, burnMax };
1310
- return { kind: "pad", gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnPerRung, ranges };
1389
+ return { kind: "pad", gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnShare, ranges };
1311
1390
  }
1312
1391
  case "pol": {
1313
1392
  const [keepQuote, minQuoteAtSeed] = await Promise.all([rd(polPolicyAbi, "keepQuote"), rd(polPolicyAbi, "minQuoteAtSeed")]);
@@ -1370,7 +1449,7 @@ export async function fPoolShareHolders(c, poolIdHex, opts = {}) {
1370
1449
  .map(([holder, shares]) => ({ holder: holder, shares }))
1371
1450
  .sort((a, b) => (b.shares > a.shares ? 1 : -1));
1372
1451
  }
1373
- /** 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). */
1452
+ /** Every F pool's raw state in one lens call (the manager's `Pool` struct: quote, swapFee, policy, seeded, gen, n, restructureBlock, asks, cap, Q, bigSigma, L, shares, refLogFloor). */
1374
1453
  export async function getFPoolsRaw(c, poolIds) {
1375
1454
  const { logswapLensAbi } = await import("./generated.js");
1376
1455
  return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] }));