@logswap/sdk 0.5.44 → 0.5.46
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/README.md +3 -3
- package/dist/admin.d.ts +41 -9
- package/dist/admin.js +53 -14
- package/dist/client.d.ts +15 -2
- package/dist/client.js +16 -2
- package/dist/fpool.d.ts +682 -74
- package/dist/fpool.js +251 -113
- package/dist/generated.d.ts +2507 -872
- package/dist/generated.js +1645 -490
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/indexer.js +3 -5
- package/dist/keys.d.ts +9 -8
- package/dist/keys.js +4 -8
- package/dist/liquidity.d.ts +2 -0
- package/dist/liquidity.js +1 -0
- package/dist/pools.d.ts +7 -4
- package/dist/pools.js +8 -6
- package/package.json +1 -1
- package/src/admin.ts +68 -15
- package/src/client.ts +24 -2
- package/src/fpool.ts +365 -162
- package/src/generated.ts +1653 -490
- package/src/index.ts +1 -1
- package/src/indexer.ts +5 -7
- package/src/keys.ts +11 -14
- package/src/liquidity.ts +3 -0
- package/src/pools.ts +9 -8
package/dist/fpool.js
CHANGED
|
@@ -12,10 +12,17 @@
|
|
|
12
12
|
* key is variable-length and only ever partially needed (no swap path is O(n)), so it is stored
|
|
13
13
|
* once at `initialize` and every later call names `(poolId, j)`. Callers hold a `bytes32`.
|
|
14
14
|
*
|
|
15
|
-
* **Two products, one contract.** The basket (n > 1
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* **Two products, one contract.** The basket (n > 1) and the launch (n = 1, the creator alone on
|
|
16
|
+
* the lever) are the same bytecode under two key fields — the KIND (what the manager promises
|
|
17
|
+
* structurally) and the HOOK (the pool's policy: its roles, its gates, the terms of every harvest
|
|
18
|
+
* and release; decisions 041, 044) — and nothing in the maths. `describeFPool` below reports
|
|
19
|
+
* which shape a given pool is, because a UI has to decide what to render.
|
|
20
|
+
*
|
|
21
|
+
* **The roles are the hook's, not the manager's** (F version 15). The manager holds no
|
|
22
|
+
* authority, operator, gate or list: it asks the pool's hook `may(who, action)` and acts on the
|
|
23
|
+
* answer. So `getFPool` carries the hook and what it declared, {@link getFPoolRoles} reads the
|
|
24
|
+
* roles FROM the hook, and every role write — the handover, the operator, the gates, the list —
|
|
25
|
+
* is a write to the hook ({@link fPoolProposeAuthority}, {@link fPoolSetGates}, …).
|
|
19
26
|
*
|
|
20
27
|
* **Zaps live on the ROUTER, not here.** Permit2 is AllowanceTransfer mode, whose standing
|
|
21
28
|
* allowance is keyed by spender, so a second router would cost the user an allowance per token
|
|
@@ -23,17 +30,26 @@
|
|
|
23
30
|
* `addresses.router`.
|
|
24
31
|
*/
|
|
25
32
|
import { encodeFunctionData, toFunctionSelector } from "viem";
|
|
26
|
-
import { fPoolManagerAbi, logswapRouterAbi } from "./generated.js";
|
|
33
|
+
import { fPoolManagerAbi, logswapRouterAbi, padHookAbi, polHookAbi, privateHookAbi, publicHookAbi } from "./generated.js";
|
|
34
|
+
/**
|
|
35
|
+
* One contract, one ABI (decisions 039, F version 12): `harvest`, `claim`, `retire`, `regenerate`,
|
|
36
|
+
* `setLegs`, the names, the fee parameters are `FPoolManager`'s own code. The roles left it for
|
|
37
|
+
* the hooks in F version 15 (decisions 044): {@link fHookBaseAbi} is what every hook shares,
|
|
38
|
+
* {@link privateHookAbi} the desk's, {@link padHookAbi} / {@link polHookAbi} / {@link publicHookAbi}
|
|
39
|
+
* the kinds'.
|
|
40
|
+
*/
|
|
41
|
+
export { fPoolManagerAbi, padHookAbi, polHookAbi, privateHookAbi, publicHookAbi };
|
|
27
42
|
/**
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
*
|
|
31
|
-
* (`FPoolSponsor`) and this ABI was the union of the two compiled halves; the union is gone with
|
|
32
|
-
* the facet, and the generated ABI is the whole surface.
|
|
43
|
+
* What every protocol hook shares (`FHookBase`): the roles and their handover, `harvestCut`,
|
|
44
|
+
* `maxHarvest`. `polHookAbi` is a superset of it and carries nothing that collides, so it stands
|
|
45
|
+
* in for the base — a call through it to any of the four hooks decodes the base's functions.
|
|
33
46
|
*/
|
|
34
|
-
export
|
|
47
|
+
export const fHookBaseAbi = polHookAbi;
|
|
35
48
|
const WAD = 10n ** 18n;
|
|
36
49
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
50
|
+
/** The optional questions a hook declared at birth (decisions 044), as bits of `Pool.asks`. */
|
|
51
|
+
export const ASK_SWAP = 1;
|
|
52
|
+
export const ASK_MINT = 2;
|
|
37
53
|
/** Read a basket pool whole. Batched — viem multicalls these when the transport allows. */
|
|
38
54
|
export async function getFPool(c, poolId) {
|
|
39
55
|
// viem types `functionName` as a literal union; this reader is deliberately generic, so the
|
|
@@ -45,7 +61,7 @@ export async function getFPool(c, poolId) {
|
|
|
45
61
|
args,
|
|
46
62
|
});
|
|
47
63
|
// `getPool` returns the whole scalar struct in one call — the singleton's shape makes what used
|
|
48
|
-
// to be a dozen getters a single read.
|
|
64
|
+
// to be a dozen getters a single read. No role in it since F version 15: those are the hook's.
|
|
49
65
|
const p = await rd("getPool", [poolId]);
|
|
50
66
|
// A pool that was never created reads as an all-zero struct rather than reverting, so the
|
|
51
67
|
// absence has to be named here or every caller mistakes "no such pool" for "a pool holding
|
|
@@ -67,7 +83,7 @@ export async function getFPool(c, poolId) {
|
|
|
67
83
|
const weights = legs.map((l) => l[1]);
|
|
68
84
|
const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j) (decisions 026)
|
|
69
85
|
const reserves = legs.map((l) => l[3]); // the state
|
|
70
|
-
const { quote, Q, L, phi, theta0, bigSigma,
|
|
86
|
+
const { quote, Q, L, phi, theta0, bigSigma, hook, restructureBlock, seeded, gen } = p;
|
|
71
87
|
const totalSupply = p.shares;
|
|
72
88
|
return {
|
|
73
89
|
poolId,
|
|
@@ -82,13 +98,9 @@ export async function getFPool(c, poolId) {
|
|
|
82
98
|
theta,
|
|
83
99
|
compositeX,
|
|
84
100
|
bigSigma,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
operator,
|
|
88
|
-
pendingOperator,
|
|
101
|
+
hook,
|
|
102
|
+
asks: Number(p.asks),
|
|
89
103
|
name: bytes32ToText(p.name),
|
|
90
|
-
gateMint,
|
|
91
|
-
gateSwap,
|
|
92
104
|
restructureBlock: Number(restructureBlock),
|
|
93
105
|
kind: fPoolKindOf(p.kind),
|
|
94
106
|
seeded,
|
|
@@ -106,24 +118,65 @@ export function fPoolKindOf(n) {
|
|
|
106
118
|
throw new Error(`logswap: unknown pool kind ${n}`);
|
|
107
119
|
return k;
|
|
108
120
|
}
|
|
121
|
+
/** The deployment's hook for a kind (decisions 044): what a key of that kind names. */
|
|
122
|
+
export function fPoolHookFor(c, kind) {
|
|
123
|
+
const a = c.addresses;
|
|
124
|
+
const hook = kind === "public" ? a.publicHook : kind === "private" ? a.privateHook : kind === "pol" ? a.polHook : a.padHook;
|
|
125
|
+
if (!hook)
|
|
126
|
+
throw new Error(`logswap: this deployment carries no ${kind} hook`);
|
|
127
|
+
return hook;
|
|
128
|
+
}
|
|
129
|
+
/** The hook a pool named in its key — one read; {@link getFPool} carries it too. */
|
|
130
|
+
export async function fPoolHookOf(c, poolId) {
|
|
131
|
+
const p = (await c.public.readContract({
|
|
132
|
+
address: c.addresses.fPoolManager,
|
|
133
|
+
abi: fPoolManagerAbi,
|
|
134
|
+
functionName: "getPool",
|
|
135
|
+
args: [poolId],
|
|
136
|
+
}));
|
|
137
|
+
if (/^0x0{40}$/i.test(p.quote))
|
|
138
|
+
throw new Error(`logswap: no F pool with id ${poolId}`);
|
|
139
|
+
return p.hook;
|
|
140
|
+
}
|
|
141
|
+
/** Read a pool's roles from its hook. `state` saves the `getPool` round trip when already in hand. */
|
|
142
|
+
export async function getFPoolRoles(c, poolId, state) {
|
|
143
|
+
const s = state ?? (await getFPool(c, poolId));
|
|
144
|
+
const rdHook = (abi, functionName) => c.public.readContract({ address: s.hook, abi, functionName, args: [poolId] });
|
|
145
|
+
const [creator, authority, pendingAuthority, maxHarvest] = await Promise.all([
|
|
146
|
+
rdHook(fHookBaseAbi, "creator"),
|
|
147
|
+
rdHook(fHookBaseAbi, "authority"),
|
|
148
|
+
rdHook(fHookBaseAbi, "pendingAuthority"),
|
|
149
|
+
rdHook(fHookBaseAbi, "maxHarvest"),
|
|
150
|
+
]);
|
|
151
|
+
const desk = s.kind === "private";
|
|
152
|
+
const [operator, pendingOperator, gateMint, gateSwap] = desk
|
|
153
|
+
? await Promise.all([
|
|
154
|
+
rdHook(privateHookAbi, "operator"),
|
|
155
|
+
rdHook(privateHookAbi, "pendingOperator"),
|
|
156
|
+
rdHook(privateHookAbi, "gateMint"),
|
|
157
|
+
rdHook(privateHookAbi, "gateSwap"),
|
|
158
|
+
])
|
|
159
|
+
: [ZERO_ADDRESS, ZERO_ADDRESS, false, false];
|
|
160
|
+
return { hook: s.hook, creator, authority, pendingAuthority, operator, pendingOperator, gateMint, gateSwap, maxHarvest };
|
|
161
|
+
}
|
|
109
162
|
export async function describeFPool(c, s) {
|
|
110
|
-
const rd = (functionName) => c.public.readContract({
|
|
163
|
+
const rd = (functionName, args = [s.poolId]) => c.public.readContract({
|
|
111
164
|
address: c.addresses.fPoolManager,
|
|
112
165
|
abi: fPoolManagerAbi,
|
|
113
166
|
functionName,
|
|
114
|
-
args
|
|
167
|
+
args,
|
|
115
168
|
});
|
|
116
|
-
// an unseeded pool has no L: nothing earned,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
: [0n, 0n];
|
|
169
|
+
// an unseeded pool has no L: nothing earned, nothing to harvest, no pot
|
|
170
|
+
const [unharvested, harvestable, pot] = s.L > 0n
|
|
171
|
+
? await Promise.all([rd("unharvested"), rd("harvestable"), rd("dividendOf", [s.poolId, BigInt(s.gen), DEAD_ADDRESS])])
|
|
172
|
+
: [0n, 0n, 0n];
|
|
121
173
|
return {
|
|
122
174
|
shape: s.kind === "pad" ? "launch" : "basket", // the kind, not n = 1 (decisions 036)
|
|
123
175
|
legs: s.bases.length,
|
|
124
176
|
atFloor: s.Q === 0n,
|
|
125
|
-
|
|
126
|
-
|
|
177
|
+
unharvested,
|
|
178
|
+
harvestable,
|
|
179
|
+
pot,
|
|
127
180
|
};
|
|
128
181
|
}
|
|
129
182
|
/** `p_j = e^{x_j}`, lossy the way any float conversion is. Compare in log space when it matters. */
|
|
@@ -553,13 +606,26 @@ export function sortFPoolLegs(bases, ...companions) {
|
|
|
553
606
|
const idx = bases.map((_, i) => i).sort((a, b) => (bases[a].toLowerCase() < bases[b].toLowerCase() ? -1 : 1));
|
|
554
607
|
return { bases: idx.map((i) => bases[i]), companions: companions.map((arr) => idx.map((i) => arr[i])) };
|
|
555
608
|
}
|
|
609
|
+
/** The key as the manager hashes it: legs sorted, the kind's hook filled in when none is named. */
|
|
610
|
+
export function fPoolKeyOf(c, a) {
|
|
611
|
+
const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
|
|
612
|
+
return {
|
|
613
|
+
quote: a.quote,
|
|
614
|
+
bases,
|
|
615
|
+
weights: companions[0],
|
|
616
|
+
phi: a.phi,
|
|
617
|
+
kind: F_KIND[a.kind],
|
|
618
|
+
hook: a.hook ?? fPoolHookFor(c, a.kind),
|
|
619
|
+
creator: a.creator,
|
|
620
|
+
salt: a.salt ?? ZERO_SALT,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
556
623
|
/** Create the pool (no funds move — `seed` arms it). Returns the tx hash; read the id with `fPoolIdOf`. */
|
|
557
624
|
export async function fPoolInitialize(c, a) {
|
|
558
625
|
const sum = a.weights.reduce((x, y) => x + y, 0n);
|
|
559
626
|
if (sum !== 10n ** 18n)
|
|
560
627
|
throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
|
|
561
|
-
const
|
|
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 };
|
|
628
|
+
const key = fPoolKeyOf(c, a);
|
|
563
629
|
const wallet = requireWallet(c);
|
|
564
630
|
const { request } = await c.public.simulateContract({
|
|
565
631
|
address: c.addresses.fPoolManager,
|
|
@@ -572,8 +638,7 @@ export async function fPoolInitialize(c, a) {
|
|
|
572
638
|
}
|
|
573
639
|
/** The pool id is the key's hash — pure, so it can be read before or after creation. */
|
|
574
640
|
export async function fPoolIdOf(c, a) {
|
|
575
|
-
const
|
|
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 };
|
|
641
|
+
const key = fPoolKeyOf(c, a);
|
|
577
642
|
return c.public.readContract({
|
|
578
643
|
address: c.addresses.fPoolManager,
|
|
579
644
|
abi: fPoolManagerAbi,
|
|
@@ -604,81 +669,71 @@ export async function fPoolSeed(c, a) {
|
|
|
604
669
|
return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
|
|
605
670
|
}
|
|
606
671
|
/**
|
|
607
|
-
* Retire the live generation (decisions 025, 032, 035) — the raw entry
|
|
608
|
-
* bid, Q ≤ L/1e9, on every pool: the market took it, or the
|
|
609
|
-
* ({@link fPoolRetire} does both in one transaction). The live
|
|
610
|
-
* every share of this generation redeems ({@link fPoolRedeem})
|
|
611
|
-
* forever; the pool is unseeded until the next {@link fPoolSeed},
|
|
612
|
-
* `gen + 1` under the same id, gates and lists.
|
|
672
|
+
* Retire the live generation (decisions 025, 032, 035) — the raw entry (`dissolve` before F
|
|
673
|
+
* version 14). Allowed only at an empty bid, Q ≤ L/1e9, on every pool: the market took it, or the
|
|
674
|
+
* operator harvested it out first ({@link fPoolRetire} does both in one transaction). The live
|
|
675
|
+
* (S, Q, R_j) become a frozen bundle every share of this generation redeems ({@link fPoolRedeem})
|
|
676
|
+
* or rolls ({@link fPoolRollIn}), forever; the pool is unseeded until the next {@link fPoolSeed},
|
|
677
|
+
* which opens generation `gen + 1` under the same id, hook, roles, gates and lists.
|
|
613
678
|
*/
|
|
614
|
-
export async function
|
|
615
|
-
return writeFPool(c, a.poolId, "
|
|
679
|
+
export async function fPoolRetireRaw(c, a) {
|
|
680
|
+
return writeFPool(c, a.poolId, "retire", [], a.account);
|
|
616
681
|
}
|
|
617
682
|
/**
|
|
618
|
-
* Retire the live generation as ONE transaction: the
|
|
683
|
+
* Retire the live generation as ONE transaction: the harvest that empties the bid
|
|
619
684
|
* ({@link fPoolEmptyingHarvests} — a dividend to every share; none when it is already empty),
|
|
620
|
-
* then `
|
|
621
|
-
* "
|
|
685
|
+
* then `retire` — the manager's `multicall`, the operator as sender. What a sponsor's
|
|
686
|
+
* "retire" button should call. Throws before sending on a pad that still bids.
|
|
622
687
|
*/
|
|
623
688
|
export async function fPoolRetire(c, a) {
|
|
624
689
|
const harvests = await fPoolEmptyingHarvests(c, a.poolId);
|
|
625
690
|
if (harvests.length === 0)
|
|
626
|
-
return
|
|
691
|
+
return fPoolRetireRaw(c, a);
|
|
627
692
|
const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
|
|
628
|
-
const calls = [...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])), enc("
|
|
693
|
+
const calls = [...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])), enc("retire", [a.poolId])];
|
|
629
694
|
return writeFPool0(c, "multicall", [calls], a.account);
|
|
630
695
|
}
|
|
631
696
|
/**
|
|
632
697
|
* The harvests that empty an unlocked pool's bid, in order — what a relaunch sends before its
|
|
633
|
-
* `
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
* the one shape two harvests cannot empty (no collector, income above Q — harvest by hand).
|
|
698
|
+
* `retire` (decisions 035: a retire needs an empty bid on every pool). Since F version 14 one
|
|
699
|
+
* `harvest(Q)` does it: the protocol's cut leaves the pool with the amount (with no collector
|
|
700
|
+
* there is no cut). `[]` when the bid is already empty. Throws on a pad that still bids (its bid
|
|
701
|
+
* empties only as the market sells it down), and when the pool's hook keeps a floor on the bid
|
|
702
|
+
* (`PolHook.keepQuote`, decisions 044): a `retire` under that hook waits for the market.
|
|
639
703
|
*/
|
|
640
704
|
export async function fPoolEmptyingHarvests(c, poolId) {
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
rd("getPool", [poolId]),
|
|
644
|
-
rd("feePerL", [poolId]),
|
|
645
|
-
rd("HARVEST_PROTOCOL_CUT"),
|
|
646
|
-
rd("harvestCutExempt", [poolId]),
|
|
647
|
-
rd("feeCollector"),
|
|
648
|
-
]);
|
|
649
|
-
if (p.Q === 0n)
|
|
705
|
+
const s = await getFPool(c, poolId);
|
|
706
|
+
if (s.Q === 0n)
|
|
650
707
|
return [];
|
|
651
|
-
if (
|
|
708
|
+
if (s.kind === "pad")
|
|
652
709
|
throw new Error("logswap: a pad is retired only once its bid is empty (decisions 035)");
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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;
|
|
710
|
+
const { maxHarvest } = await getFPoolRoles(c, poolId, s);
|
|
711
|
+
if (maxHarvest < s.Q)
|
|
712
|
+
throw new Error("logswap: the pool's hook keeps a floor on the bid — it cannot be harvested empty (decisions 044)");
|
|
713
|
+
return [s.Q];
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Release the dead shares' pot under the hook's terms (decisions 041, 044) — a pad's graduation.
|
|
717
|
+
* The manager harvests the generation's income first (the pot then holds everything the dead
|
|
718
|
+
* shares earned), asks the hook, and applies its answer: a share to the payee it names (the pad's
|
|
719
|
+
* creator — the pool's authority), a cut to the collector, the rest bought in as more of the
|
|
720
|
+
* float the pool owns, a slice of every reserve burned. The hook refuses under its rung's
|
|
721
|
+
* threshold (`PadHook.NotGraduated`). Whoever runs the pool.
|
|
722
|
+
*/
|
|
723
|
+
export async function fPoolRegenerate(c, a) {
|
|
724
|
+
return writeFPool(c, a.poolId, "regenerate", [a.to ?? a.account], a.account);
|
|
670
725
|
}
|
|
671
726
|
/**
|
|
672
727
|
* The relaunch as ONE transaction (decisions 025, 032, 035, 037): `harvest` the bid out — a
|
|
673
|
-
* dividend to every share, {@link fPoolEmptyingHarvests} — `
|
|
728
|
+
* dividend to every share, {@link fPoolEmptyingHarvests} — `retire` the live generation at the
|
|
674
729
|
* empty bid, `redeem` the caller's own shares of it (when `shares` > 0), optionally `setLegs` the
|
|
675
730
|
* next generation's composition (`legs`: a private pool's reshape — retire, admit, reweight), and
|
|
676
731
|
* `seed` it with what came back — the manager's `multicall`, one sender throughout, the same
|
|
677
732
|
* pool. The operator's when one is appointed and the seed's inventory is its own; by default the
|
|
678
|
-
* authority's.
|
|
679
|
-
* against {@link fPoolManagerAbi}. The seed names reserves (decisions
|
|
680
|
-
* per leg of the NEXT table, 0 for a retired leg — or `x0` marks
|
|
681
|
-
* `weights`.
|
|
733
|
+
* authority's. The hook, its roles, gates, allowlist and the name carry over; anything else goes
|
|
734
|
+
* in `extra`, pre-encoded against {@link fPoolManagerAbi}. The seed names reserves (decisions
|
|
735
|
+
* 029): `r0` outright — one per leg of the NEXT table, 0 for a retired leg — or `x0` marks
|
|
736
|
+
* against the next table's `weights`.
|
|
682
737
|
*/
|
|
683
738
|
export async function fPoolRelaunch(c, a) {
|
|
684
739
|
const [gen, harvests] = await Promise.all([fPoolGen(c, a.poolId), fPoolEmptyingHarvests(c, a.poolId)]);
|
|
@@ -686,7 +741,7 @@ export async function fPoolRelaunch(c, a) {
|
|
|
686
741
|
const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
|
|
687
742
|
const calls = [
|
|
688
743
|
...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])),
|
|
689
|
-
enc("
|
|
744
|
+
enc("retire", [a.poolId]),
|
|
690
745
|
...(a.shares > 0n ? [enc("redeem", [a.poolId, BigInt(gen), a.account, a.shares])] : []),
|
|
691
746
|
...(a.legs ? [enc("setLegs", [a.poolId, a.legs.weights, a.legs.newBases ?? []])] : []),
|
|
692
747
|
enc("seed", [a.poolId, a.L0, r0, a.Q0]),
|
|
@@ -712,7 +767,9 @@ export function fPoolLaunchCalls(poolId, a) {
|
|
|
712
767
|
if (given.length !== a.bases.length)
|
|
713
768
|
throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
|
|
714
769
|
const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
|
|
715
|
-
|
|
770
|
+
if (!a.hook)
|
|
771
|
+
throw new Error("logswap: fPoolLaunchCalls needs the key's hook — fPoolLaunch fills it from the deployment");
|
|
772
|
+
const key = { quote: a.quote, bases, weights: companions[0], phi: a.phi, kind: F_KIND[a.kind], hook: a.hook, creator: a.creator, salt: a.salt ?? ZERO_SALT };
|
|
716
773
|
const enc = (functionName, args) => encodeFunctionData({ abi: fPoolManagerAbi, functionName, args });
|
|
717
774
|
const calls = [];
|
|
718
775
|
if (!a.initialized)
|
|
@@ -728,15 +785,23 @@ export function fPoolLaunchCalls(poolId, a) {
|
|
|
728
785
|
}
|
|
729
786
|
if (a.name)
|
|
730
787
|
calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
// authority is the operator until one is appointed (decisions 037)
|
|
734
|
-
calls.push(enc("setGates", [poolId, true, false]));
|
|
735
|
-
calls.push(enc("setAllowed", [poolId, [a.account], true]));
|
|
736
|
-
}
|
|
788
|
+
// the desk's gates and list are the HOOK's (decisions 044), not the manager's: they cannot ride
|
|
789
|
+
// in this batch — `fPoolDeskOpening` sends them right after, as the same creator
|
|
737
790
|
calls.push(...(a.extra ?? []));
|
|
738
791
|
return calls;
|
|
739
792
|
}
|
|
793
|
+
/**
|
|
794
|
+
* The desk's opening posture, on its hook (decisions 044): gated minting, the creator listed. No
|
|
795
|
+
* appointment: the authority is the operator until one is appointed (decisions 037). Two writes
|
|
796
|
+
* to `PrivateHook`; the launch batch cannot carry them (they are not manager calls).
|
|
797
|
+
*/
|
|
798
|
+
export async function fPoolDeskOpening(c, a) {
|
|
799
|
+
const hook = a.hook ?? fPoolHookFor(c, "private");
|
|
800
|
+
return [
|
|
801
|
+
await fPoolSetGates(c, { poolId: a.poolId, gateMint: true, gateSwap: false, account: a.account, hook }),
|
|
802
|
+
await fPoolSetAllowed(c, { poolId: a.poolId, who: [a.account], allowed: true, account: a.account, hook }),
|
|
803
|
+
];
|
|
804
|
+
}
|
|
740
805
|
/**
|
|
741
806
|
* Create a pool as ONE transaction: `initialize`, `seed`, the seed lock, the opening buy, the
|
|
742
807
|
* name, the private-pool gates — the manager's `multicall`, the creator as sender throughout.
|
|
@@ -756,36 +821,62 @@ export function fPoolLaunchCalls(poolId, a) {
|
|
|
756
821
|
*/
|
|
757
822
|
export async function fPoolLaunch(c, a) {
|
|
758
823
|
const poolId = await fPoolIdOf(c, a);
|
|
759
|
-
const
|
|
824
|
+
const calls = fPoolLaunchCalls(poolId, { ...a, hook: a.hook ?? fPoolHookFor(c, a.kind) });
|
|
825
|
+
const hash = await writeFPool0(c, "multicall", [calls], a.account);
|
|
760
826
|
return { hash, poolId };
|
|
761
827
|
}
|
|
828
|
+
// ─── the roles, on the hook (decisions 044) ─────────────────────────────────────────────
|
|
829
|
+
//
|
|
830
|
+
// Every write here goes to the pool's HOOK, not the manager: the manager holds no role. `hook`
|
|
831
|
+
// may be passed when in hand (`FPoolState.hook`, `FPoolRoles.hook`); otherwise it is read once.
|
|
832
|
+
/** A write keyed by poolId, on the pool's hook. */
|
|
833
|
+
async function writeHook(c, abi, a, functionName, args) {
|
|
834
|
+
const wallet = requireWallet(c);
|
|
835
|
+
const hook = a.hook ?? (await fPoolHookOf(c, a.poolId));
|
|
836
|
+
const { request } = await c.public.simulateContract({
|
|
837
|
+
address: hook,
|
|
838
|
+
abi,
|
|
839
|
+
functionName,
|
|
840
|
+
args: [a.poolId, ...args],
|
|
841
|
+
account: a.account,
|
|
842
|
+
});
|
|
843
|
+
return wallet.writeContract(request);
|
|
844
|
+
}
|
|
762
845
|
/**
|
|
763
|
-
* Propose the next authority — step one of two (
|
|
846
|
+
* Propose the next authority — step one of two (decisions 020), on the pool's hook. Nothing moves
|
|
764
847
|
* until `next` accepts; proposing again (the zero address included) cancels a pending proposal.
|
|
765
|
-
* Indexed on-chain via AuthorityProposed.
|
|
848
|
+
* Indexed on-chain via the hook's AuthorityProposed.
|
|
766
849
|
*/
|
|
767
850
|
export async function fPoolProposeAuthority(c, a) {
|
|
768
|
-
return
|
|
851
|
+
return writeHook(c, fHookBaseAbi, a, "proposeAuthority", [a.next]);
|
|
769
852
|
}
|
|
770
|
-
/**
|
|
853
|
+
/**
|
|
854
|
+
* Accept a pending proposal — step two; only the proposed address can. Indexed via the hook's
|
|
855
|
+
* AuthoritySet. On the desk's hook this clears the operator (pending and accepted) and voids the
|
|
856
|
+
* seller's list (decisions 040, 044).
|
|
857
|
+
*/
|
|
771
858
|
export async function fPoolAcceptAuthority(c, a) {
|
|
772
|
-
return
|
|
859
|
+
return writeHook(c, fHookBaseAbi, a, "acceptAuthority", []);
|
|
773
860
|
}
|
|
774
|
-
// ─── the
|
|
775
|
-
/** Turn the mint / swap gates on or off (authority). `burn` is never gated. */
|
|
861
|
+
// ─── the desk's controls (PrivateHook; decisions 021, 044) ───────────────────────────────
|
|
862
|
+
/** Turn the mint / swap gates on or off (the authority, on the desk's hook). `burn` is never gated. */
|
|
776
863
|
export async function fPoolSetGates(c, a) {
|
|
777
|
-
return
|
|
864
|
+
return writeHook(c, privateHookAbi, a, "setGates", [a.gateMint, a.gateSwap]);
|
|
778
865
|
}
|
|
779
|
-
/**
|
|
866
|
+
/**
|
|
867
|
+
* Add to or remove from the pool's list, in one call (the authority, on the desk's hook). The list
|
|
868
|
+
* is keyed by the authority that granted it: a handover starts the buyer with an empty one.
|
|
869
|
+
*/
|
|
780
870
|
export async function fPoolSetAllowed(c, a) {
|
|
781
|
-
return
|
|
871
|
+
return writeHook(c, privateHookAbi, a, "setAllowed", [a.who, a.allowed]);
|
|
782
872
|
}
|
|
783
|
-
/** Whether `who` is on the pool's list (consulted only while a gate is on). */
|
|
784
|
-
export async function fPoolIsAllowed(c, poolId, who) {
|
|
873
|
+
/** Whether `who` is on the pool's list under its current authority (consulted only while a gate is on). */
|
|
874
|
+
export async function fPoolIsAllowed(c, poolId, who, hook) {
|
|
875
|
+
const address = hook ?? (await fPoolHookOf(c, poolId));
|
|
785
876
|
return (await c.public.readContract({
|
|
786
|
-
address
|
|
787
|
-
abi:
|
|
788
|
-
functionName: "
|
|
877
|
+
address,
|
|
878
|
+
abi: privateHookAbi,
|
|
879
|
+
functionName: "isAllowed",
|
|
789
880
|
args: [poolId, who],
|
|
790
881
|
}));
|
|
791
882
|
}
|
|
@@ -823,20 +914,20 @@ export function fPoolOperatorOf(s) {
|
|
|
823
914
|
return s.operator === ZERO_ADDRESS ? s.authority : s.operator;
|
|
824
915
|
}
|
|
825
916
|
/**
|
|
826
|
-
* Propose the operator (authority
|
|
827
|
-
* `
|
|
917
|
+
* Propose the operator (the authority, on the desk's hook) — the hot key that runs the pool:
|
|
918
|
+
* `harvest`, `retire`, `regenerate`, `setLegs`. Two-step: the proposed address takes the role with
|
|
828
919
|
* {@link fPoolAcceptOperator}. The zero address clears at once — the role returns to the authority.
|
|
829
920
|
*/
|
|
830
921
|
export async function fPoolAppointOperator(c, a) {
|
|
831
|
-
return
|
|
922
|
+
return writeHook(c, privateHookAbi, a, "appointOperator", [a.operator]);
|
|
832
923
|
}
|
|
833
924
|
/** The proposed operator takes the role. */
|
|
834
925
|
export async function fPoolAcceptOperator(c, a) {
|
|
835
|
-
return
|
|
926
|
+
return writeHook(c, privateHookAbi, a, "acceptOperator", []);
|
|
836
927
|
}
|
|
837
928
|
/**
|
|
838
929
|
* The next generation's composition (operator; private pools only; ONLY while the pool is
|
|
839
|
-
* unseeded — between a
|
|
930
|
+
* unseeded — between a retire and the seed that prices it, decisions 037). `weights` has one
|
|
840
931
|
* entry per leg the pool has ever had, in index order (0 retires a leg; indices are never reused),
|
|
841
932
|
* then one per base in `newBases`, appended; they sum to 1e18 exactly. No value moves: the
|
|
842
933
|
* following {@link fPoolSeed} names every reserve. A live reshape is {@link fPoolRelaunch} with
|
|
@@ -898,7 +989,8 @@ export async function discoverFPools(c, opts = {}) {
|
|
|
898
989
|
weights: [...a.weights],
|
|
899
990
|
phi: a.phi,
|
|
900
991
|
kind: fPoolKindOf(a.kind),
|
|
901
|
-
|
|
992
|
+
hook: a.hook,
|
|
993
|
+
creator: a.creator,
|
|
902
994
|
block: l.blockNumber ?? 0n,
|
|
903
995
|
seeded: q0 !== undefined,
|
|
904
996
|
q0: q0 ?? 0n,
|
|
@@ -906,6 +998,52 @@ export async function discoverFPools(c, opts = {}) {
|
|
|
906
998
|
};
|
|
907
999
|
});
|
|
908
1000
|
}
|
|
1001
|
+
/** The hook's terms for one pool, by its kind. */
|
|
1002
|
+
export async function getFPoolPolicy(c, s, roles) {
|
|
1003
|
+
const rd = (abi, functionName, args = []) => c.public.readContract({ address: s.hook, abi, functionName, args });
|
|
1004
|
+
const harvestCut = await rd(fHookBaseAbi, "harvestCut");
|
|
1005
|
+
switch (s.kind) {
|
|
1006
|
+
case "pad": {
|
|
1007
|
+
const [rung, creatorShare, protocolShare, burnPerRung, pot] = await Promise.all([
|
|
1008
|
+
rd(padHookAbi, "rung", [s.poolId]),
|
|
1009
|
+
rd(padHookAbi, "creatorShare"),
|
|
1010
|
+
rd(padHookAbi, "protocolShare"),
|
|
1011
|
+
rd(padHookAbi, "burnPerRung"),
|
|
1012
|
+
fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, s.gen),
|
|
1013
|
+
]);
|
|
1014
|
+
const threshold = await rd(padHookAbi, "threshold", [rung]);
|
|
1015
|
+
return { kind: "pad", harvestCut, rung: Number(rung), threshold, pot, creatorShare, protocolShare, burnPerRung };
|
|
1016
|
+
}
|
|
1017
|
+
case "pol": {
|
|
1018
|
+
const [keepQuote, minQuoteAtSeed] = await Promise.all([rd(polHookAbi, "keepQuote"), rd(polHookAbi, "minQuoteAtSeed")]);
|
|
1019
|
+
return { kind: "pol", harvestCut, keepQuote, minQuoteAtSeed };
|
|
1020
|
+
}
|
|
1021
|
+
case "public": {
|
|
1022
|
+
const curated = await rd(publicHookAbi, "curated");
|
|
1023
|
+
return { kind: "public", harvestCut, curated };
|
|
1024
|
+
}
|
|
1025
|
+
case "private": {
|
|
1026
|
+
const r = roles ?? (await getFPoolRoles(c, s.poolId, s));
|
|
1027
|
+
return { kind: "private", harvestCut, gateMint: r.gateMint, gateSwap: r.gateSwap, operator: fPoolOperatorOf(r) };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Every F pool `who` is the AUTHORITY of, across the four hooks — the policy page's list. Pools
|
|
1033
|
+
* `who` created and handed over are not here (the buyer's page shows them); pools handed TO
|
|
1034
|
+
* `who` are. `pools` may be passed (from the indexer, say) to skip the log scan.
|
|
1035
|
+
*/
|
|
1036
|
+
export async function fPoolsControlledBy(c, who, opts = {}) {
|
|
1037
|
+
const pools = opts.pools ?? (await discoverFPools(c, { fromBlock: opts.fromBlock }));
|
|
1038
|
+
const authorities = await Promise.all(pools.map((p) => c.public.readContract({ address: p.hook, abi: fHookBaseAbi, functionName: "authority", args: [p.poolId] })));
|
|
1039
|
+
const mine = pools.filter((_, i) => authorities[i].toLowerCase() === who.toLowerCase());
|
|
1040
|
+
return Promise.all(mine.map(async (pool) => {
|
|
1041
|
+
const state = await getFPool(c, pool.poolId);
|
|
1042
|
+
const roles = await getFPoolRoles(c, pool.poolId, state);
|
|
1043
|
+
const policy = await getFPoolPolicy(c, state, roles);
|
|
1044
|
+
return { pool, roles, state, policy };
|
|
1045
|
+
}));
|
|
1046
|
+
}
|
|
909
1047
|
/**
|
|
910
1048
|
* Every holder of a pool's shares, from the ERC-6909 Transfer stream — the id is an indexed
|
|
911
1049
|
* topic, so one filtered log query aggregates the whole ledger. The 0xdead entry is the
|