@logswap/sdk 0.5.37 → 0.5.39

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/src/fpool.ts CHANGED
@@ -85,10 +85,14 @@ export interface FPoolState {
85
85
  * The pad's promise. Pairs with a locked seed (shares at 0xdead) — the class is the two bits.
86
86
  */
87
87
  lockStrike: boolean;
88
+ /** False before the first seed — and between a dissolve and the next one (decisions 032). */
88
89
  seeded: boolean;
89
- dissolved: boolean;
90
- /** The pool this one dissolved into (decisions 025) — the relaunch's lineage; zero when none. */
91
- successor: Hex;
90
+ /**
91
+ * The live GENERATION (decisions 032). Every dissolve retires one into a frozen bundle its
92
+ * shares redeem ({@link fPoolRedeem}) and opens the next; the share id carries it
93
+ * ({@link fPoolShareId}), so generations `0 … gen-1` are retired claims and `gen` is the pool.
94
+ */
95
+ gen: number;
92
96
  totalSupply: bigint;
93
97
  /** What the pool holds of each base — the STATE since decisions 026; the mark `x` is derived from it. */
94
98
  reserves: bigint[];
@@ -114,7 +118,7 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
114
118
  authority: Address;
115
119
  lockStrike: boolean;
116
120
  seeded: boolean;
117
- dissolved: boolean;
121
+ gen: number;
118
122
  n: number;
119
123
  restructureBlock: number;
120
124
  gateMint: boolean;
@@ -130,7 +134,6 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
130
134
  minBuffer: bigint;
131
135
  incomeTaken: bigint;
132
136
  name: Hex;
133
- successor: Hex;
134
137
  }>("getPool", [poolId]);
135
138
 
136
139
  // A pool that was never created reads as an all-zero struct rather than reverting, so the
@@ -154,7 +157,7 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
154
157
  const weights = legs.map((l) => l[1]);
155
158
  const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j) (decisions 026)
156
159
  const reserves = legs.map((l) => l[3]); // the state
157
- const { quote, Q, L, phi, theta0, bigSigma, authority, pendingAuthority, operator, minBuffer, gateMint, gateSwap, restructureBlock, lockStrike, seeded, dissolved, successor } = p;
160
+ const { quote, Q, L, phi, theta0, bigSigma, authority, pendingAuthority, operator, minBuffer, gateMint, gateSwap, restructureBlock, lockStrike, seeded, gen } = p;
158
161
  const totalSupply = p.shares;
159
162
 
160
163
  return {
@@ -180,8 +183,7 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
180
183
  restructureBlock: Number(restructureBlock),
181
184
  lockStrike,
182
185
  seeded,
183
- dissolved,
184
- successor,
186
+ gen: Number(gen),
185
187
  totalSupply,
186
188
  reserves,
187
189
  };
@@ -371,18 +373,20 @@ export async function fPoolZapOut(
371
373
  }
372
374
 
373
375
  /**
374
- * Roll a dissolved pool's bundle into its successor (decisions 025), on the router under one
375
- * lock: burn `shares` of `fromPool` in place — the legs, the quote and any unpulled dividend come
376
- * back — buy through `toPool`'s own curve only what its ratio lacks, mint `dL` of it to `to`, and
377
- * refund the rest in kind. Quote the bundle does not cover is pulled from the payer, at most
378
- * `maxQuoteIn`; surplus base is refunded, never sold. Size `dL` with {@link fPoolPreviewRollIn}.
379
- * The router must be the caller's ERC-6909 operator ({@link approveRouterForFPool}).
376
+ * Roll a retired generation's bundle into the pool's live one (decisions 025, 032), on the
377
+ * router under one lock: redeem `shares` of generation `gen` in place — the legs, the quote and
378
+ * any unpulled dividend come back — buy through the pool's own curve only what the live
379
+ * generation's ratio lacks, mint `dL` of it to `to`, and refund the rest in kind. Quote the
380
+ * bundle does not cover is pulled from the payer, at most `maxQuoteIn`; surplus base is
381
+ * refunded, never sold. Size `dL` with {@link fPoolPreviewRollIn}. The router must be the
382
+ * caller's ERC-6909 operator ({@link approveRouterForFPool}).
380
383
  */
381
384
  export async function fPoolRollIn(
382
385
  c: LogswapClient,
383
386
  a: {
384
- fromPool: Hex;
385
- toPool: Hex;
387
+ poolId: Hex;
388
+ /** the retired generation whose shares roll */
389
+ gen: bigint | number;
386
390
  shares: bigint;
387
391
  dL: bigint;
388
392
  maxQuoteIn?: bigint;
@@ -396,22 +400,23 @@ export async function fPoolRollIn(
396
400
  return writeRouter(
397
401
  c,
398
402
  "rollIn",
399
- [a.fromPool, a.toPool, a.shares, a.dL, a.maxQuoteIn ?? 0n, a.minShares ?? 0n, to, a.deadline ?? defaultDeadline()],
403
+ [a.poolId, BigInt(a.gen), a.shares, a.dL, a.maxQuoteIn ?? 0n, a.minShares ?? 0n, to, a.deadline ?? defaultDeadline()],
400
404
  a.account,
401
405
  );
402
406
  }
403
407
 
404
408
  /**
405
- * The roll `holder`'s `shares` of `fromPool` fund into `toPool` WITHOUT a trade: the largest `dL`
406
- * the bundle's base covers on every successor leg, and the quote the bundle is short of (pass it
407
- * as `maxQuoteIn`) or over (refunded — the up direction's distribution) at that `dL`. A
408
- * successor leg the bundle does not hold makes `dL` zero: such a roll must buy, so size it below
409
- * with `previewZapIn` in mind.
409
+ * The roll `holder`'s `shares` of retired generation `gen` fund into the live one WITHOUT a
410
+ * trade: the largest `dL` the bundle's base covers on every leg, and the quote the bundle is
411
+ * short of (pass it as `maxQuoteIn`) or over (refunded — the up direction's distribution) at
412
+ * that `dL`. A leg the live generation admitted since makes `dL` zero: such a roll must buy, so
413
+ * size it below with `previewZapIn` in mind. Zero for the live generation, or before the next
414
+ * seed.
410
415
  */
411
416
  export async function fPoolPreviewRollIn(
412
417
  c: LogswapClient,
413
- fromPool: Hex,
414
- toPool: Hex,
418
+ poolId: Hex,
419
+ gen: bigint | number,
415
420
  holder: Address,
416
421
  shares: bigint,
417
422
  ): Promise<{ dL: bigint; quoteShort: bigint; quoteExcess: bigint }> {
@@ -420,7 +425,7 @@ export async function fPoolPreviewRollIn(
420
425
  address: c.addresses.lens,
421
426
  abi: logswapLensAbi,
422
427
  functionName: "previewRollIn",
423
- args: [fromPool, toPool, holder, shares],
428
+ args: [poolId, BigInt(gen), holder, shares],
424
429
  } as never)) as readonly [bigint, bigint, bigint];
425
430
  return { dL, quoteShort, quoteExcess };
426
431
  }
@@ -438,18 +443,24 @@ export async function fPoolHarvest(c: LogswapClient, a: { poolId: Hex; amount: b
438
443
  return writeFPool(c, a.poolId, "harvest", [a.amount, a.to ?? a.account], a.account);
439
444
  }
440
445
 
441
- /** Pull one's settled dividend (decisions 028) without moving shares; anyone with shares. */
442
- export async function fPoolClaim(c: LogswapClient, a: { poolId: Hex; to?: Address; account: Address }) {
443
- return writeFPool(c, a.poolId, "claim", [a.to ?? a.account], a.account);
446
+ /**
447
+ * Pull one's settled dividend (decisions 028) on a generation's shares without moving them;
448
+ * anyone with shares. `gen` defaults to the live generation; a retired generation's harvests
449
+ * stay claimable under its own id (decisions 032).
450
+ */
451
+ export async function fPoolClaim(c: LogswapClient, a: { poolId: Hex; gen?: bigint | number; to?: Address; account: Address }) {
452
+ const gen = a.gen ?? (await fPoolGen(c, a.poolId));
453
+ return writeFPool(c, a.poolId, "claim", [BigInt(gen), a.to ?? a.account], a.account);
444
454
  }
445
455
 
446
- /** A holder's dividend as of now — banked plus pending on the current balance (decisions 028). */
447
- export async function fPoolDividendOf(c: LogswapClient, poolId: Hex, holder: Address): Promise<bigint> {
456
+ /** A holder's dividend as of now on generation `gen` (the live one by default) — banked plus pending on the current balance (decisions 028). */
457
+ export async function fPoolDividendOf(c: LogswapClient, poolId: Hex, holder: Address, gen?: bigint | number): Promise<bigint> {
458
+ const g = gen ?? (await fPoolGen(c, poolId));
448
459
  return c.public.readContract({
449
460
  address: c.addresses.fPoolManager!,
450
461
  abi: fPoolManagerAbi,
451
462
  functionName: "dividendOf",
452
- args: [poolId, holder],
463
+ args: [poolId, BigInt(g), holder],
453
464
  } as never) as Promise<bigint>;
454
465
  }
455
466
 
@@ -598,9 +609,29 @@ export async function approveRouterForFPool(c: LogswapClient, account: Address)
598
609
  // than read from the chain: it is pure, and a client that has to call to learn an id cannot build
599
610
  // a multicall that uses it.
600
611
 
601
- /** The ERC-6909 id carrying a pool's shares. Bit 255 set. */
602
- export function fPoolShareId(poolId: Hex): bigint {
603
- return (1n << 255n) | (BigInt(poolId) >> 1n);
612
+ /**
613
+ * The ERC-6909 id carrying generation `gen` of a pool's shares (decisions 032): bit 255 set (the
614
+ * share flag), the pool id's top 239 bits, the generation in the low 16. Each generation's shares
615
+ * are their own asset — a retired one is a claim on its bundle, the live one on the pool.
616
+ */
617
+ export function fPoolShareId(poolId: Hex, gen: bigint | number = 0n): bigint {
618
+ return (1n << 255n) | ((BigInt(poolId) >> 17n) << 16n) | BigInt(gen);
619
+ }
620
+
621
+ /** The generation a share id carries — its low 16 bits. */
622
+ export function fPoolGenOfShareId(id: bigint): number {
623
+ return Number(id & 0xffffn);
624
+ }
625
+
626
+ /** The pool's live generation, one read. */
627
+ export async function fPoolGen(c: LogswapClient, poolId: Hex): Promise<number> {
628
+ const p = (await c.public.readContract({
629
+ address: c.addresses.fPoolManager!,
630
+ abi: fPoolManagerAbi,
631
+ functionName: "getPool",
632
+ args: [poolId],
633
+ } as never)) as { gen: number };
634
+ return Number(p.gen);
604
635
  }
605
636
 
606
637
  /** The ERC-6909 id carrying claims on a token. Bit 255 clear, so it can never meet a share id. */
@@ -608,15 +639,65 @@ export function fPoolClaimId(token: Address): bigint {
608
639
  return BigInt(token);
609
640
  }
610
641
 
611
- export async function fPoolShareBalance(c: LogswapClient, poolId: Hex, owner: Address): Promise<bigint> {
642
+ /** A holder's shares of generation `gen` — the live one by default. */
643
+ export async function fPoolShareBalance(c: LogswapClient, poolId: Hex, owner: Address, gen?: bigint | number): Promise<bigint> {
644
+ const g = gen ?? (await fPoolGen(c, poolId));
612
645
  return c.public.readContract({
613
646
  address: c.addresses.fPoolManager!,
614
647
  abi: fPoolManagerAbi,
615
648
  functionName: "balanceOf",
616
- args: [owner, fPoolShareId(poolId)],
649
+ args: [owner, fPoolShareId(poolId, g)],
617
650
  } as never) as Promise<bigint>;
618
651
  }
619
652
 
653
+ /** A retired generation's bundle (decisions 032): what is left to redeem — supply, quote, base per leg index. */
654
+ export async function fPoolBundleOf(
655
+ c: LogswapClient,
656
+ poolId: Hex,
657
+ gen: bigint | number,
658
+ ): Promise<{ supply: bigint; quote: bigint; base: bigint[] }> {
659
+ const [supply, quote, base] = (await c.public.readContract({
660
+ address: c.addresses.fPoolManager!,
661
+ abi: fPoolManagerAbi,
662
+ functionName: "bundleOf",
663
+ args: [poolId, BigInt(gen)],
664
+ } as never)) as readonly [bigint, bigint, readonly bigint[]];
665
+ return { supply, quote, base: [...base] };
666
+ }
667
+
668
+ /** A holder's claim on one retired generation, as the lens reports it: what `redeem` pays now. */
669
+ export interface FPoolClaimRow {
670
+ gen: number;
671
+ shares: bigint;
672
+ base: bigint[];
673
+ quote: bigint;
674
+ dividend: bigint;
675
+ }
676
+
677
+ /** A holder's claims on a pool's retired generations — one row per generation still held (decisions 032). */
678
+ export async function fPoolClaimsOf(c: LogswapClient, poolId: Hex, owner: Address): Promise<FPoolClaimRow[]> {
679
+ const { logswapLensAbi } = await import("./generated.js");
680
+ const rows = (await c.public.readContract({
681
+ address: c.addresses.lens,
682
+ abi: logswapLensAbi,
683
+ functionName: "fClaimsOf",
684
+ args: [poolId, owner],
685
+ } as never)) as readonly { gen: bigint; shares: bigint; base: readonly bigint[]; quote: bigint; dividend: bigint }[];
686
+ return rows.map((r) => ({ gen: Number(r.gen), shares: r.shares, base: [...r.base], quote: r.quote, dividend: r.dividend }));
687
+ }
688
+
689
+ /**
690
+ * Redeem `shares` of a RETIRED generation (decisions 032): its bundle, pro rata — the base per
691
+ * leg and the quote — plus the owner's settled dividend. Not `burn`: nothing can move a bundle's
692
+ * ratio, so there is no slippage to bound. The owner, or an operator of theirs.
693
+ */
694
+ export async function fPoolRedeem(
695
+ c: LogswapClient,
696
+ a: { poolId: Hex; gen: bigint | number; shares: bigint; owner?: Address; account: Address },
697
+ ) {
698
+ return writeFPool(c, a.poolId, "redeem", [BigInt(a.gen), a.owner ?? a.account, a.shares], a.account);
699
+ }
700
+
620
701
  export async function fPoolClaimBalance(c: LogswapClient, token: Address, owner: Address): Promise<bigint> {
621
702
  return c.public.readContract({
622
703
  address: c.addresses.fPoolManager!,
@@ -796,56 +877,151 @@ export async function fPoolSeed(
796
877
  }
797
878
 
798
879
  /**
799
- * End of life (decisions 025). Allowed at Q ≤ L/1e9 on every pool — the market filled the ask —
800
- * and on a pool that is not strike-locked at any Q, provided `successor` names an initialized
801
- * pool of the same authority and quote: the relaunch. After it every share is a frozen in-kind
802
- * claim redeemed by `burn` (or rolled with {@link fPoolRollIn}), forever; the pool's `successor`
803
- * carries the lineage. Never a second dissolve on one pool: the successor is a NEW pool.
880
+ * Retire the live generation (decisions 025, 032). Allowed at Q ≤ L/1e9 on every pool — the
881
+ * market filled the ask — and on a pool that is not strike-locked at any Q. The live (S, Q, R_j)
882
+ * become a frozen bundle every share of this generation redeems ({@link fPoolRedeem}) or rolls
883
+ * ({@link fPoolRollIn}), forever; the pool is unseeded until the next {@link fPoolSeed}, which
884
+ * opens generation `gen + 1` under the same id, gates, lists and promises.
804
885
  */
805
- export async function fPoolDissolve(c: LogswapClient, a: { poolId: Hex; successor?: Hex; account: Address }) {
806
- return writeFPool(c, a.poolId, "dissolve", [a.successor ?? ZERO_SALT], a.account);
886
+ export async function fPoolDissolve(c: LogswapClient, a: { poolId: Hex; account: Address }) {
887
+ return writeFPool(c, a.poolId, "dissolve", [], a.account);
807
888
  }
808
889
 
809
890
  /**
810
- * The sponsor's relaunch as ONE transaction (decisions 025): `initialize` the successor key,
811
- * `dissolve` the old pool into it, `burn` the sponsor's own shares, `seed` the successor with what
812
- * came back — the manager's `multicall`, the sponsor as sender throughout. Gates, allowlist,
813
- * operator, floor guard and name are re-applied afterwards, or appended to `extra` pre-encoded
814
- * against {@link fPoolManagerAbi}. Returns the tx hash; the successor's id is {@link fPoolIdOf}
815
- * of `nextKey`.
891
+ * The sponsor's relaunch as ONE transaction (decisions 025, 032): `dissolve` the live generation,
892
+ * `redeem` the sponsor's own shares of it (when `shares` > 0), `seed` the next generation with
893
+ * what came back — the manager's `multicall`, the sponsor as sender throughout, the same pool.
894
+ * Gates, allowlist, operator, `minBuffer` and names carry over; anything else goes in `extra`,
895
+ * pre-encoded against {@link fPoolManagerAbi}. The seed names reserves (decisions 029): `r0`
896
+ * outright, or `x0` marks against the key's `weights`.
816
897
  */
817
898
  export async function fPoolRelaunch(
818
899
  c: LogswapClient,
819
900
  a: {
820
901
  poolId: Hex;
821
- nextKey: Omit<FPoolCreateArgs, "account">;
822
- /** the sponsor's old shares to burn — its whole balance, usually */
902
+ /** the sponsor's shares of the retiring generation to redeem inside the batch — its whole balance, usually; 0 to keep the claim */
823
903
  shares: bigint;
824
904
  L0: bigint;
825
905
  Q0: bigint;
826
- r0: bigint[];
827
906
  extra?: Hex[];
828
907
  account: Address;
829
- },
908
+ } & ({ r0: bigint[] } | { x0: bigint[]; weights: bigint[] }),
830
909
  ) {
831
- const { bases, companions } = sortFPoolLegs(a.nextKey.bases, a.nextKey.weights);
832
- const key = {
833
- quote: a.nextKey.quote, bases, weights: companions[0]!, phi: a.nextKey.phi, lockStrike: a.nextKey.lockStrike,
834
- authority: a.nextKey.authority, salt: a.nextKey.salt ?? ZERO_SALT,
835
- };
836
- const next = await fPoolIdOf(c, a.nextKey);
910
+ const gen = await fPoolGen(c, a.poolId);
911
+ const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j]! * a.L0) / WAD, x));
837
912
  const enc = (functionName: string, args: unknown[]) =>
838
913
  encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
839
914
  const calls: Hex[] = [
840
- enc("initialize", [key]),
841
- enc("dissolve", [a.poolId, next]),
842
- enc("burn", [a.poolId, a.account, a.shares, 0n]),
843
- enc("seed", [next, a.L0, a.r0, a.Q0]),
915
+ enc("dissolve", [a.poolId]),
916
+ ...(a.shares > 0n ? [enc("redeem", [a.poolId, BigInt(gen), a.account, a.shares])] : []),
917
+ enc("seed", [a.poolId, a.L0, r0, a.Q0]),
844
918
  ...(a.extra ?? []),
845
919
  ];
846
920
  return writeFPool0(c, "multicall", [calls], a.account);
847
921
  }
848
922
 
923
+ /** ERC-6909 shares sent here are locked for good — the seed's custody (custody.md §4(b)). */
924
+ export const DEAD_ADDRESS: Address = "0x000000000000000000000000000000000000dEaD";
925
+ /** The manager's `MIN_SHARES`: the seed mints this much to 0xdead itself, the rest to the seeder. */
926
+ export const F_MIN_SHARES = 1000n;
927
+
928
+ export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
929
+ L0: bigint;
930
+ /** Quote the seed pulls. A pad seeds at 0 — pure float, a resting ask, nothing raised. */
931
+ Q0: bigint;
932
+ /**
933
+ * Send every share the seed mints to the creator (`L0 − MIN_SHARES`) to 0xdead in the same
934
+ * batch: the float and the raise can never be burned out from under the buyers. The lever
935
+ * (harvest) is the authority's and survives it; the exit does not. A pad's default posture.
936
+ */
937
+ lockSeed?: boolean;
938
+ /**
939
+ * An opening buy of leg `j` (an index into `bases` AS GIVEN) for `amountIn` of quote, ON THE
940
+ * MANAGER — the same sender, a plain allowance to the manager, the router nowhere near it. A
941
+ * pad seeds at Q = 0, so until someone trades it the mark sits at p₀; buying inside the launch
942
+ * gives it a price that has moved and a buffer under it. Nothing can trade between the seed and
943
+ * this buy — they are one transaction — so `minOut` is safe at its default of 0.
944
+ */
945
+ openBuy?: { j: number; amountIn: bigint; minOut?: bigint };
946
+ /** The pool's label, set in the batch (≤ 31 bytes of UTF-8). */
947
+ name?: string;
948
+ /** Gate minting, list the creator, appoint them operator (decisions 021) — three calls, in the batch. */
949
+ privatePool?: boolean;
950
+ /**
951
+ * The pool already exists (a previous launch got as far as `initialize` and no further — a seed
952
+ * that reverted, a closed tab, before the launch was one transaction), so `initialize` is left
953
+ * out: the key is a pure function of the arguments and a second `initialize` can only revert.
954
+ */
955
+ initialized?: boolean;
956
+ /** Anything else, pre-encoded against {@link fPoolManagerAbi}, appended after the above. */
957
+ extra?: Hex[];
958
+ account: Address;
959
+ }
960
+
961
+ /**
962
+ * The launch's calls, in order — the batch {@link fPoolLaunch} sends. Pure, so a form can show
963
+ * what it is about to send, and a test can decode it. Legs are sorted here with their weights and
964
+ * reserves (the key's canonical order; `openBuy.j` is re-indexed to match), and the weights are
965
+ * checked here, where the message is readable.
966
+ */
967
+ export function fPoolLaunchCalls(
968
+ poolId: Hex,
969
+ a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
970
+ ): Hex[] {
971
+ const sum = a.weights.reduce((x, y) => x + y, 0n);
972
+ if (sum !== WAD) throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
973
+ const given = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j]! * a.L0) / WAD, x));
974
+ if (given.length !== a.bases.length) throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
975
+ const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
976
+ const key = { quote: a.quote, bases, weights: companions[0]!, phi: a.phi, lockStrike: a.lockStrike, authority: a.authority, salt: a.salt ?? ZERO_SALT };
977
+ const enc = (functionName: string, args: unknown[]) =>
978
+ encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
979
+ const calls: Hex[] = [];
980
+ if (!a.initialized) calls.push(enc("initialize", [key]));
981
+ calls.push(enc("seed", [poolId, a.L0, companions[1]!, a.Q0]));
982
+ // gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`
983
+ if (a.lockSeed) calls.push(enc("transfer", [DEAD_ADDRESS, fPoolShareId(poolId, 0), a.L0 - F_MIN_SHARES]));
984
+ if (a.openBuy) {
985
+ const j = bases.indexOf(a.bases[a.openBuy.j]!);
986
+ if (j < 0) throw new Error(`logswap: openBuy.j = ${a.openBuy.j} is not a leg`);
987
+ calls.push(enc("swapQuoteIn", [poolId, BigInt(j), a.openBuy.amountIn, a.openBuy.minOut ?? 0n, a.account]));
988
+ }
989
+ if (a.name) calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
990
+ if (a.privatePool) {
991
+ calls.push(enc("setGates", [poolId, true, false]));
992
+ calls.push(enc("setAllowed", [poolId, [a.account], true]));
993
+ calls.push(enc("appointOperator", [poolId, a.account]));
994
+ }
995
+ calls.push(...(a.extra ?? []));
996
+ return calls;
997
+ }
998
+
999
+ /**
1000
+ * Create a pool as ONE transaction: `initialize`, `seed`, the seed lock, the opening buy, the
1001
+ * name, the private-pool gates — the manager's `multicall`, the creator as sender throughout.
1002
+ * Either every call lands or none does; there is no half-built pool to resume.
1003
+ *
1004
+ * Why the manager and not the router: every one of these entries authenticates by `msg.sender`
1005
+ * (`seed` is authority-only, so is everything after it). Routed, the router would be the sender
1006
+ * — the authority, the holder of the seed shares — and the handover back is two-step, so it
1007
+ * could not even close in the same transaction. `multicall` is delegatecall-to-self, so the
1008
+ * sender survives, which is what the manager's own doc says a router cannot do.
1009
+ *
1010
+ * What the batch PULLS, by plain allowance to the manager (never Permit2): `r0[j]` of each base,
1011
+ * `Q0` of quote, plus `openBuy.amountIn` of quote. Grant those first — one `approve` per token,
1012
+ * once ever ({@link approveTokenForFPoolManager}, {@link fPoolManagerAllowanceOk}).
1013
+ *
1014
+ * Returns the hash and the pool's id (the key's hash, {@link fPoolIdOf}).
1015
+ */
1016
+ export async function fPoolLaunch(
1017
+ c: LogswapClient,
1018
+ a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
1019
+ ): Promise<{ hash: Hash; poolId: Hex }> {
1020
+ const poolId = await fPoolIdOf(c, a);
1021
+ const hash = await writeFPool0(c, "multicall", [fPoolLaunchCalls(poolId, a)], a.account);
1022
+ return { hash, poolId };
1023
+ }
1024
+
849
1025
  /**
850
1026
  * Propose the next authority — step one of two (contracts `2aa6b7b`, decisions 020). Nothing moves
851
1027
  * until `next` accepts; proposing again (the zero address included) cancels a pending proposal.
@@ -962,14 +1138,15 @@ export async function fPoolAdmitLeg(
962
1138
  */
963
1139
  export async function fPoolTransferShares(
964
1140
  c: LogswapClient,
965
- a: { poolId: Hex; to: Address; shares: bigint; account: Address },
1141
+ a: { poolId: Hex; to: Address; shares: bigint; account: Address; gen?: bigint | number },
966
1142
  ) {
967
1143
  const wallet = requireWallet(c);
1144
+ const gen = a.gen ?? (await fPoolGen(c, a.poolId));
968
1145
  const { request } = await c.public.simulateContract({
969
1146
  address: c.addresses.fPoolManager!,
970
1147
  abi: fPoolManagerAbi,
971
1148
  functionName: "transfer",
972
- args: [a.to, fPoolShareId(a.poolId), a.shares],
1149
+ args: [a.to, fPoolShareId(a.poolId, gen), a.shares],
973
1150
  account: a.account,
974
1151
  } as never);
975
1152
  return wallet.writeContract(request as never);
@@ -1061,12 +1238,12 @@ export async function discoverFPools(
1061
1238
  export async function fPoolShareHolders(
1062
1239
  c: LogswapClient,
1063
1240
  poolIdHex: Hex,
1064
- opts: { fromBlock?: bigint } = {},
1241
+ opts: { fromBlock?: bigint; gen?: bigint | number } = {},
1065
1242
  ): Promise<Array<{ holder: Address; shares: bigint }>> {
1066
1243
  type Ev = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "Transfer" }>;
1067
1244
  const ev = fPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "Transfer");
1068
1245
  if (!ev) throw new Error("logswap: 6909 Transfer event missing from the generated ABI");
1069
- const id = fPoolShareId(poolIdHex);
1246
+ const id = fPoolShareId(poolIdHex, opts.gen ?? (await fPoolGen(c, poolIdHex)));
1070
1247
  const logs = await c.public.getLogs({
1071
1248
  address: c.addresses.fPoolManager!,
1072
1249
  event: ev,
@@ -1086,7 +1263,7 @@ export async function fPoolShareHolders(
1086
1263
  .sort((a, b) => (b.shares > a.shares ? 1 : -1));
1087
1264
  }
1088
1265
 
1089
- /** 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). */
1266
+ /** 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). */
1090
1267
  export async function getFPoolsRaw(c: LogswapClient, poolIds: Hex[]): Promise<readonly unknown[]> {
1091
1268
  const { logswapLensAbi } = await import("./generated.js");
1092
1269
  return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] } as never)) as readonly unknown[];