@logswap/sdk 0.5.45 → 0.5.47
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/admin.d.ts +38 -9
- package/dist/admin.js +50 -14
- package/dist/client.d.ts +9 -1
- package/dist/client.js +10 -1
- package/dist/fpool.d.ts +682 -74
- package/dist/fpool.js +251 -113
- package/dist/generated.d.ts +2454 -425
- package/dist/generated.js +1554 -293
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/liquidity.d.ts +2 -0
- package/dist/liquidity.js +1 -0
- package/package.json +1 -1
- package/src/admin.ts +65 -15
- package/src/client.ts +18 -1
- package/src/fpool.ts +365 -162
- package/src/generated.ts +1562 -293
- package/src/index.ts +1 -1
- package/src/liquidity.ts +3 -0
package/src/fpool.ts
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
|
|
@@ -24,21 +31,31 @@
|
|
|
24
31
|
*/
|
|
25
32
|
|
|
26
33
|
import { encodeFunctionData, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
27
|
-
import { fPoolManagerAbi, logswapRouterAbi } from "./generated.js";
|
|
34
|
+
import { fPoolManagerAbi, logswapRouterAbi, padHookAbi, polHookAbi, privateHookAbi, publicHookAbi } from "./generated.js";
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
|
-
* One contract, one ABI (decisions 039, F version 12):
|
|
31
|
-
* `
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* the
|
|
37
|
+
* One contract, one ABI (decisions 039, F version 12): `harvest`, `claim`, `retire`, `regenerate`,
|
|
38
|
+
* `setLegs`, the names, the fee parameters are `FPoolManager`'s own code. The roles left it for
|
|
39
|
+
* the hooks in F version 15 (decisions 044): {@link fHookBaseAbi} is what every hook shares,
|
|
40
|
+
* {@link privateHookAbi} the desk's, {@link padHookAbi} / {@link polHookAbi} / {@link publicHookAbi}
|
|
41
|
+
* the kinds'.
|
|
42
|
+
*/
|
|
43
|
+
export { fPoolManagerAbi, padHookAbi, polHookAbi, privateHookAbi, publicHookAbi };
|
|
44
|
+
/**
|
|
45
|
+
* What every protocol hook shares (`FHookBase`): the roles and their handover, `harvestCut`,
|
|
46
|
+
* `maxHarvest`. `polHookAbi` is a superset of it and carries nothing that collides, so it stands
|
|
47
|
+
* in for the base — a call through it to any of the four hooks decodes the base's functions.
|
|
35
48
|
*/
|
|
36
|
-
export
|
|
49
|
+
export const fHookBaseAbi = polHookAbi;
|
|
37
50
|
import type { LogswapClient } from "./client.js";
|
|
38
51
|
|
|
39
52
|
const WAD = 10n ** 18n;
|
|
40
53
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" as const;
|
|
41
54
|
|
|
55
|
+
/** The optional questions a hook declared at birth (decisions 044), as bits of `Pool.asks`. */
|
|
56
|
+
export const ASK_SWAP = 1;
|
|
57
|
+
export const ASK_MINT = 2;
|
|
58
|
+
|
|
42
59
|
/** Everything scalar about a basket pool, in one round trip's worth of reads. */
|
|
43
60
|
export interface FPoolState {
|
|
44
61
|
/** The pool's id — the hash of its key. There is no per-pool address. */
|
|
@@ -63,32 +80,25 @@ export interface FPoolState {
|
|
|
63
80
|
compositeX: bigint;
|
|
64
81
|
/** Σ = ∫ Σ_j w_j (dx_j)², accrued even though the fee is fixed — the kernel stays calculable. */
|
|
65
82
|
bigSigma: bigint;
|
|
66
|
-
/** The cold key (decisions 037): seeds the pool, hands it over, names it, gates it (private). A creator, a treasury, a governance contract. */
|
|
67
|
-
authority: Address;
|
|
68
|
-
/** The proposed next authority, until it accepts — zero when nothing is pending (decisions 020). */
|
|
69
|
-
pendingAuthority: Address;
|
|
70
83
|
/**
|
|
71
|
-
* The
|
|
72
|
-
*
|
|
84
|
+
* The pool's policy (decisions 041, 044): the contract the key names, holding the pool's roles
|
|
85
|
+
* and answering every question the manager does not decide itself. Read its roles with
|
|
86
|
+
* {@link getFPoolRoles}; one of the deployment's four ({@link fPoolHookFor}) on every protocol pool.
|
|
73
87
|
*/
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
88
|
+
hook: Address;
|
|
89
|
+
/** Whether the hook asked to be consulted on swaps ({@link ASK_SWAP}) and mints ({@link ASK_MINT}) — the desk's hook does, no other. */
|
|
90
|
+
asks: number;
|
|
77
91
|
/** The sponsor's own label for this pool, mutable and never identity. "" when unnamed. */
|
|
78
92
|
name: string;
|
|
79
|
-
/** Only `allowed` accounts may receive minted shares. */
|
|
80
|
-
gateMint: boolean;
|
|
81
|
-
/** Only `allowed` takers may swap. */
|
|
82
|
-
gateSwap: boolean;
|
|
83
93
|
/** The block of the last harvest / composition action: no swap runs in it. */
|
|
84
94
|
restructureBlock: number;
|
|
85
95
|
/** The rung — see {@link FPoolKind}. */
|
|
86
96
|
kind: FPoolKind;
|
|
87
|
-
/** False before the first seed — and between a
|
|
97
|
+
/** False before the first seed — and between a retire and the next one (decisions 032). */
|
|
88
98
|
seeded: boolean;
|
|
89
99
|
/**
|
|
90
|
-
* The live GENERATION (decisions 032). Every
|
|
91
|
-
*
|
|
100
|
+
* The live GENERATION (decisions 032). Every retire freezes one into a bundle its shares
|
|
101
|
+
* redeem ({@link fPoolRedeem}) and opens the next; the share id carries it
|
|
92
102
|
* ({@link fPoolShareId}), so generations `0 … gen-1` are retired claims and `gen` is the pool.
|
|
93
103
|
*/
|
|
94
104
|
gen: number;
|
|
@@ -110,28 +120,22 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
|
|
|
110
120
|
} as never) as Promise<T>;
|
|
111
121
|
|
|
112
122
|
// `getPool` returns the whole scalar struct in one call — the singleton's shape makes what used
|
|
113
|
-
// to be a dozen getters a single read.
|
|
123
|
+
// to be a dozen getters a single read. No role in it since F version 15: those are the hook's.
|
|
114
124
|
const p = await rd<{
|
|
115
125
|
quote: Address;
|
|
116
126
|
phi: bigint;
|
|
117
|
-
|
|
127
|
+
hook: Address;
|
|
118
128
|
kind: number;
|
|
119
129
|
seeded: boolean;
|
|
120
130
|
gen: number;
|
|
121
131
|
n: number;
|
|
122
132
|
restructureBlock: number;
|
|
123
|
-
|
|
124
|
-
gateSwap: boolean;
|
|
133
|
+
asks: number;
|
|
125
134
|
Q: bigint;
|
|
135
|
+
bigSigma: bigint;
|
|
126
136
|
L: bigint;
|
|
127
137
|
shares: bigint;
|
|
128
138
|
theta0: bigint;
|
|
129
|
-
bigSigma: bigint;
|
|
130
|
-
harvestedTheta: bigint;
|
|
131
|
-
pendingAuthority: Address;
|
|
132
|
-
operator: Address;
|
|
133
|
-
pendingOperator: Address;
|
|
134
|
-
incomeTaken: bigint;
|
|
135
139
|
name: Hex;
|
|
136
140
|
}>("getPool", [poolId]);
|
|
137
141
|
|
|
@@ -156,7 +160,7 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
|
|
|
156
160
|
const weights = legs.map((l) => l[1]);
|
|
157
161
|
const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j) (decisions 026)
|
|
158
162
|
const reserves = legs.map((l) => l[3]); // the state
|
|
159
|
-
const { quote, Q, L, phi, theta0, bigSigma,
|
|
163
|
+
const { quote, Q, L, phi, theta0, bigSigma, hook, restructureBlock, seeded, gen } = p;
|
|
160
164
|
const totalSupply = p.shares;
|
|
161
165
|
|
|
162
166
|
return {
|
|
@@ -172,13 +176,9 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
|
|
|
172
176
|
theta,
|
|
173
177
|
compositeX,
|
|
174
178
|
bigSigma,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
operator,
|
|
178
|
-
pendingOperator,
|
|
179
|
+
hook,
|
|
180
|
+
asks: Number(p.asks),
|
|
179
181
|
name: bytes32ToText(p.name),
|
|
180
|
-
gateMint,
|
|
181
|
-
gateSwap,
|
|
182
182
|
restructureBlock: Number(restructureBlock),
|
|
183
183
|
kind: fPoolKindOf(p.kind),
|
|
184
184
|
seeded,
|
|
@@ -189,14 +189,14 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
* The kind of pool (decisions 036, 038)
|
|
193
|
-
*
|
|
194
|
-
* `private`, the desk:
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
192
|
+
* The kind of pool (decisions 036, 038, 044) — what the manager promises structurally, whatever
|
|
193
|
+
* the pool's hook answers. `public`: the protocol's own pool — its hook requires the collector as
|
|
194
|
+
* creator; no gate, no operator. `private`, the desk: the one kind whose hook may gate swaps and
|
|
195
|
+
* mints, with an operator that reshapes the legs between generations. `pol`, a treasury's pool
|
|
196
|
+
* or a basket: composition and access fixed at birth. `pad`, a launch: a POL whose harvest is
|
|
197
|
+
* income only whatever its hook says and whose seed the contract minted to 0xdead — one asset,
|
|
198
|
+
* open to all. private → pol → pad is the ladder of rigidity; public is a pol held by the
|
|
199
|
+
* protocol. Every rate, role and gate is the hook's.
|
|
200
200
|
*/
|
|
201
201
|
export type FPoolKind = "public" | "private" | "pol" | "pad";
|
|
202
202
|
/** The key's enum values. */
|
|
@@ -208,40 +208,110 @@ export function fPoolKindOf(n: number | bigint): FPoolKind {
|
|
|
208
208
|
return k;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/** The deployment's hook for a kind (decisions 044): what a key of that kind names. */
|
|
212
|
+
export function fPoolHookFor(c: LogswapClient, kind: FPoolKind): Address {
|
|
213
|
+
const a = c.addresses;
|
|
214
|
+
const hook = kind === "public" ? a.publicHook : kind === "private" ? a.privateHook : kind === "pol" ? a.polHook : a.padHook;
|
|
215
|
+
if (!hook) throw new Error(`logswap: this deployment carries no ${kind} hook`);
|
|
216
|
+
return hook;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The hook a pool named in its key — one read; {@link getFPool} carries it too. */
|
|
220
|
+
export async function fPoolHookOf(c: LogswapClient, poolId: Hex): Promise<Address> {
|
|
221
|
+
const p = (await c.public.readContract({
|
|
222
|
+
address: c.addresses.fPoolManager!,
|
|
223
|
+
abi: fPoolManagerAbi,
|
|
224
|
+
functionName: "getPool",
|
|
225
|
+
args: [poolId],
|
|
226
|
+
} as never)) as { quote: Address; hook: Address };
|
|
227
|
+
if (/^0x0{40}$/i.test(p.quote)) throw new Error(`logswap: no F pool with id ${poolId}`);
|
|
228
|
+
return p.hook;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A pool's roles, as its HOOK holds them (decisions 044). `creator` is immutable identity —
|
|
233
|
+
* who was born holding the pool; `authority` is the cold key that owns it now, the creator until
|
|
234
|
+
* a two-step handover; on the desk's kind the `operator` runs it (zero = the authority itself,
|
|
235
|
+
* {@link fPoolOperatorOf}) and the two gates and the list are its. Every other kind has no
|
|
236
|
+
* operator and no gate: those fields read zero / false.
|
|
237
|
+
*/
|
|
238
|
+
export interface FPoolRoles {
|
|
239
|
+
hook: Address;
|
|
240
|
+
creator: Address;
|
|
241
|
+
authority: Address;
|
|
242
|
+
/** The proposed next authority, until it accepts — zero when nothing is pending (decisions 020). */
|
|
243
|
+
pendingAuthority: Address;
|
|
244
|
+
operator: Address;
|
|
245
|
+
pendingOperator: Address;
|
|
246
|
+
gateMint: boolean;
|
|
247
|
+
gateSwap: boolean;
|
|
248
|
+
/** The hook's own ceiling on a harvest now — the manager's `harvestable` narrowed by the hook's rule. */
|
|
249
|
+
maxHarvest: bigint;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Read a pool's roles from its hook. `state` saves the `getPool` round trip when already in hand. */
|
|
253
|
+
export async function getFPoolRoles(c: LogswapClient, poolId: Hex, state?: Pick<FPoolState, "hook" | "kind">): Promise<FPoolRoles> {
|
|
254
|
+
const s = state ?? (await getFPool(c, poolId));
|
|
255
|
+
const rdHook = <T>(abi: readonly unknown[], functionName: string) =>
|
|
256
|
+
c.public.readContract({ address: s.hook, abi, functionName, args: [poolId] } as never) as Promise<T>;
|
|
257
|
+
const [creator, authority, pendingAuthority, maxHarvest] = await Promise.all([
|
|
258
|
+
rdHook<Address>(fHookBaseAbi, "creator"),
|
|
259
|
+
rdHook<Address>(fHookBaseAbi, "authority"),
|
|
260
|
+
rdHook<Address>(fHookBaseAbi, "pendingAuthority"),
|
|
261
|
+
rdHook<bigint>(fHookBaseAbi, "maxHarvest"),
|
|
262
|
+
]);
|
|
263
|
+
const desk = s.kind === "private";
|
|
264
|
+
const [operator, pendingOperator, gateMint, gateSwap] = desk
|
|
265
|
+
? await Promise.all([
|
|
266
|
+
rdHook<Address>(privateHookAbi, "operator"),
|
|
267
|
+
rdHook<Address>(privateHookAbi, "pendingOperator"),
|
|
268
|
+
rdHook<boolean>(privateHookAbi, "gateMint"),
|
|
269
|
+
rdHook<boolean>(privateHookAbi, "gateSwap"),
|
|
270
|
+
])
|
|
271
|
+
: [ZERO_ADDRESS, ZERO_ADDRESS, false, false];
|
|
272
|
+
return { hook: s.hook, creator, authority, pendingAuthority, operator, pendingOperator, gateMint, gateSwap, maxHarvest };
|
|
273
|
+
}
|
|
274
|
+
|
|
211
275
|
/** Which of the two products a pool is configured as — a pad is a launch, anything else a basket/pool. A UI has to choose what to render. */
|
|
212
276
|
export type FPoolShape = "launch" | "basket";
|
|
213
277
|
|
|
214
278
|
export interface FPoolDescription {
|
|
215
279
|
shape: FPoolShape;
|
|
216
|
-
/**
|
|
280
|
+
/** The pad is the launch shape, by the kind (decisions 036). */
|
|
217
281
|
legs: number;
|
|
218
282
|
/** True when the pool holds no quote: an all-base resting ask. base→quote is the one blocked path. */
|
|
219
283
|
atFloor: boolean;
|
|
220
|
-
/**
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
284
|
+
/**
|
|
285
|
+
* This generation's fee income still in the pool, quote: `L·(θ₀ − θ)` (F version 14 —
|
|
286
|
+
* `feePerL` before, per unit L). A harvest of capital moves the reference floor, so this counts
|
|
287
|
+
* from the first wei after it.
|
|
288
|
+
*/
|
|
289
|
+
unharvested: bigint;
|
|
290
|
+
/** The most a harvest may take now by the KIND's promise: the bid, and on a pad the income. The hook may allow less — {@link FPoolRoles.maxHarvest}. */
|
|
291
|
+
harvestable: bigint;
|
|
292
|
+
/** The dead shares' banked dividend — a pad's pot, the creator's income until a rung is climbed (decisions 041). */
|
|
293
|
+
pot: bigint;
|
|
224
294
|
}
|
|
225
295
|
|
|
226
296
|
export async function describeFPool(c: LogswapClient, s: FPoolState): Promise<FPoolDescription> {
|
|
227
|
-
const rd = <T>(functionName: string) =>
|
|
297
|
+
const rd = <T>(functionName: string, args: readonly unknown[] = [s.poolId]) =>
|
|
228
298
|
c.public.readContract({
|
|
229
299
|
address: c.addresses.fPoolManager!,
|
|
230
300
|
abi: fPoolManagerAbi,
|
|
231
301
|
functionName,
|
|
232
|
-
args
|
|
302
|
+
args,
|
|
233
303
|
} as never) as Promise<T>;
|
|
234
|
-
// an unseeded pool has no L: nothing earned,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
: [0n, 0n];
|
|
304
|
+
// an unseeded pool has no L: nothing earned, nothing to harvest, no pot
|
|
305
|
+
const [unharvested, harvestable, pot] = s.L > 0n
|
|
306
|
+
? await Promise.all([rd<bigint>("unharvested"), rd<bigint>("harvestable"), rd<bigint>("dividendOf", [s.poolId, BigInt(s.gen), DEAD_ADDRESS])])
|
|
307
|
+
: [0n, 0n, 0n];
|
|
239
308
|
return {
|
|
240
309
|
shape: s.kind === "pad" ? "launch" : "basket", // the kind, not n = 1 (decisions 036)
|
|
241
310
|
legs: s.bases.length,
|
|
242
311
|
atFloor: s.Q === 0n,
|
|
243
|
-
|
|
244
|
-
|
|
312
|
+
unharvested,
|
|
313
|
+
harvestable,
|
|
314
|
+
pot,
|
|
245
315
|
};
|
|
246
316
|
}
|
|
247
317
|
|
|
@@ -798,7 +868,14 @@ export interface FPoolCreateArgs {
|
|
|
798
868
|
phi: bigint;
|
|
799
869
|
/** The rung (decisions 036) — see {@link FPoolKind}. In the key: the pool's identity, never changed after. */
|
|
800
870
|
kind: FPoolKind;
|
|
801
|
-
|
|
871
|
+
/**
|
|
872
|
+
* The pool's policy (decisions 041, 044): the contract the key names, approved by the protocol
|
|
873
|
+
* for the kind. Defaults to the deployment's hook for `kind` ({@link fPoolHookFor}) — the one
|
|
874
|
+
* every protocol pool names.
|
|
875
|
+
*/
|
|
876
|
+
hook?: Address;
|
|
877
|
+
/** Who is born holding the pool: immutable identity, the pool's first authority (the hook records it). */
|
|
878
|
+
creator: Address;
|
|
802
879
|
/**
|
|
803
880
|
* The differentiator, and nothing else. Identity is the key's hash, so one sponsor cannot run
|
|
804
881
|
* two pools of the same shape without varying it — the second `initialize` would land on the
|
|
@@ -832,12 +909,26 @@ export function sortFPoolLegs<T>(bases: Address[], ...companions: T[][]): { base
|
|
|
832
909
|
return { bases: idx.map((i) => bases[i]!), companions: companions.map((arr) => idx.map((i) => arr[i]!)) };
|
|
833
910
|
}
|
|
834
911
|
|
|
912
|
+
/** The key as the manager hashes it: legs sorted, the kind's hook filled in when none is named. */
|
|
913
|
+
export function fPoolKeyOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">) {
|
|
914
|
+
const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
|
|
915
|
+
return {
|
|
916
|
+
quote: a.quote,
|
|
917
|
+
bases,
|
|
918
|
+
weights: companions[0]!,
|
|
919
|
+
phi: a.phi,
|
|
920
|
+
kind: F_KIND[a.kind],
|
|
921
|
+
hook: a.hook ?? fPoolHookFor(c, a.kind),
|
|
922
|
+
creator: a.creator,
|
|
923
|
+
salt: a.salt ?? ZERO_SALT,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
835
927
|
/** Create the pool (no funds move — `seed` arms it). Returns the tx hash; read the id with `fPoolIdOf`. */
|
|
836
928
|
export async function fPoolInitialize(c: LogswapClient, a: FPoolCreateArgs) {
|
|
837
929
|
const sum = a.weights.reduce((x, y) => x + y, 0n);
|
|
838
930
|
if (sum !== 10n ** 18n) throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
|
|
839
|
-
const
|
|
840
|
-
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 };
|
|
931
|
+
const key = fPoolKeyOf(c, a);
|
|
841
932
|
const wallet = requireWallet(c);
|
|
842
933
|
const { request } = await c.public.simulateContract({
|
|
843
934
|
address: c.addresses.fPoolManager!,
|
|
@@ -851,8 +942,7 @@ export async function fPoolInitialize(c: LogswapClient, a: FPoolCreateArgs) {
|
|
|
851
942
|
|
|
852
943
|
/** The pool id is the key's hash — pure, so it can be read before or after creation. */
|
|
853
944
|
export async function fPoolIdOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">): Promise<Hex> {
|
|
854
|
-
const
|
|
855
|
-
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 };
|
|
945
|
+
const key = fPoolKeyOf(c, a);
|
|
856
946
|
return c.public.readContract({
|
|
857
947
|
address: c.addresses.fPoolManager!,
|
|
858
948
|
abi: fPoolManagerAbi,
|
|
@@ -889,82 +979,72 @@ export async function fPoolSeed(
|
|
|
889
979
|
}
|
|
890
980
|
|
|
891
981
|
/**
|
|
892
|
-
* Retire the live generation (decisions 025, 032, 035) — the raw entry
|
|
893
|
-
* bid, Q ≤ L/1e9, on every pool: the market took it, or the
|
|
894
|
-
* ({@link fPoolRetire} does both in one transaction). The live
|
|
895
|
-
* every share of this generation redeems ({@link fPoolRedeem})
|
|
896
|
-
* forever; the pool is unseeded until the next {@link fPoolSeed},
|
|
897
|
-
* `gen + 1` under the same id, gates and lists.
|
|
982
|
+
* Retire the live generation (decisions 025, 032, 035) — the raw entry (`dissolve` before F
|
|
983
|
+
* version 14). Allowed only at an empty bid, Q ≤ L/1e9, on every pool: the market took it, or the
|
|
984
|
+
* operator harvested it out first ({@link fPoolRetire} does both in one transaction). The live
|
|
985
|
+
* (S, Q, R_j) become a frozen bundle every share of this generation redeems ({@link fPoolRedeem})
|
|
986
|
+
* or rolls ({@link fPoolRollIn}), forever; the pool is unseeded until the next {@link fPoolSeed},
|
|
987
|
+
* which opens generation `gen + 1` under the same id, hook, roles, gates and lists.
|
|
898
988
|
*/
|
|
899
|
-
export async function
|
|
900
|
-
return writeFPool(c, a.poolId, "
|
|
989
|
+
export async function fPoolRetireRaw(c: LogswapClient, a: { poolId: Hex; account: Address }) {
|
|
990
|
+
return writeFPool(c, a.poolId, "retire", [], a.account);
|
|
901
991
|
}
|
|
902
992
|
|
|
903
993
|
/**
|
|
904
|
-
* Retire the live generation as ONE transaction: the
|
|
994
|
+
* Retire the live generation as ONE transaction: the harvest that empties the bid
|
|
905
995
|
* ({@link fPoolEmptyingHarvests} — a dividend to every share; none when it is already empty),
|
|
906
|
-
* then `
|
|
907
|
-
* "
|
|
996
|
+
* then `retire` — the manager's `multicall`, the operator as sender. What a sponsor's
|
|
997
|
+
* "retire" button should call. Throws before sending on a pad that still bids.
|
|
908
998
|
*/
|
|
909
999
|
export async function fPoolRetire(c: LogswapClient, a: { poolId: Hex; account: Address }) {
|
|
910
1000
|
const harvests = await fPoolEmptyingHarvests(c, a.poolId);
|
|
911
|
-
if (harvests.length === 0) return
|
|
1001
|
+
if (harvests.length === 0) return fPoolRetireRaw(c, a);
|
|
912
1002
|
const enc = (functionName: string, args: unknown[]) =>
|
|
913
1003
|
encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
|
|
914
|
-
const calls: Hex[] = [...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])), enc("
|
|
1004
|
+
const calls: Hex[] = [...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])), enc("retire", [a.poolId])];
|
|
915
1005
|
return writeFPool0(c, "multicall", [calls], a.account);
|
|
916
1006
|
}
|
|
917
1007
|
|
|
918
1008
|
/**
|
|
919
1009
|
* The harvests that empty an unlocked pool's bid, in order — what a relaunch sends before its
|
|
920
|
-
* `
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
* the one shape two harvests cannot empty (no collector, income above Q — harvest by hand).
|
|
1010
|
+
* `retire` (decisions 035: a retire needs an empty bid on every pool). Since F version 14 one
|
|
1011
|
+
* `harvest(Q)` does it: the protocol's cut leaves the pool with the amount (with no collector
|
|
1012
|
+
* there is no cut). `[]` when the bid is already empty. Throws on a pad that still bids (its bid
|
|
1013
|
+
* empties only as the market sells it down), and when the pool's hook keeps a floor on the bid
|
|
1014
|
+
* (`PolHook.keepQuote`, decisions 044): a `retire` under that hook waits for the market.
|
|
926
1015
|
*/
|
|
927
1016
|
export async function fPoolEmptyingHarvests(c: LogswapClient, poolId: Hex): Promise<bigint[]> {
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const taxable = amount < left ? amount : left;
|
|
947
|
-
taken += taxable;
|
|
948
|
-
const pFee = exempt ? 0n : (taxable * fee) / WAD;
|
|
949
|
-
const leaving = pFee !== 0n && collector !== ZERO_ADDRESS ? amount : amount - pFee;
|
|
950
|
-
q -= leaving;
|
|
951
|
-
amounts.push(amount);
|
|
952
|
-
}
|
|
953
|
-
if (q > 0n) throw new Error("logswap: the bid does not empty in two harvests (no collector, income above Q) — harvest by hand first");
|
|
954
|
-
return amounts;
|
|
1017
|
+
const s = await getFPool(c, poolId);
|
|
1018
|
+
if (s.Q === 0n) return [];
|
|
1019
|
+
if (s.kind === "pad") throw new Error("logswap: a pad is retired only once its bid is empty (decisions 035)");
|
|
1020
|
+
const { maxHarvest } = await getFPoolRoles(c, poolId, s);
|
|
1021
|
+
if (maxHarvest < s.Q) throw new Error("logswap: the pool's hook keeps a floor on the bid — it cannot be harvested empty (decisions 044)");
|
|
1022
|
+
return [s.Q];
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Release the dead shares' pot under the hook's terms (decisions 041, 044) — a pad's graduation.
|
|
1027
|
+
* The manager harvests the generation's income first (the pot then holds everything the dead
|
|
1028
|
+
* shares earned), asks the hook, and applies its answer: a share to the payee it names (the pad's
|
|
1029
|
+
* creator — the pool's authority), a cut to the collector, the rest bought in as more of the
|
|
1030
|
+
* float the pool owns, a slice of every reserve burned. The hook refuses under its rung's
|
|
1031
|
+
* threshold (`PadHook.NotGraduated`). Whoever runs the pool.
|
|
1032
|
+
*/
|
|
1033
|
+
export async function fPoolRegenerate(c: LogswapClient, a: { poolId: Hex; to?: Address; account: Address }) {
|
|
1034
|
+
return writeFPool(c, a.poolId, "regenerate", [a.to ?? a.account], a.account);
|
|
955
1035
|
}
|
|
956
1036
|
|
|
957
1037
|
/**
|
|
958
1038
|
* The relaunch as ONE transaction (decisions 025, 032, 035, 037): `harvest` the bid out — a
|
|
959
|
-
* dividend to every share, {@link fPoolEmptyingHarvests} — `
|
|
1039
|
+
* dividend to every share, {@link fPoolEmptyingHarvests} — `retire` the live generation at the
|
|
960
1040
|
* empty bid, `redeem` the caller's own shares of it (when `shares` > 0), optionally `setLegs` the
|
|
961
1041
|
* next generation's composition (`legs`: a private pool's reshape — retire, admit, reweight), and
|
|
962
1042
|
* `seed` it with what came back — the manager's `multicall`, one sender throughout, the same
|
|
963
1043
|
* pool. The operator's when one is appointed and the seed's inventory is its own; by default the
|
|
964
|
-
* authority's.
|
|
965
|
-
* against {@link fPoolManagerAbi}. The seed names reserves (decisions
|
|
966
|
-
* per leg of the NEXT table, 0 for a retired leg — or `x0` marks
|
|
967
|
-
* `weights`.
|
|
1044
|
+
* authority's. The hook, its roles, gates, allowlist and the name carry over; anything else goes
|
|
1045
|
+
* in `extra`, pre-encoded against {@link fPoolManagerAbi}. The seed names reserves (decisions
|
|
1046
|
+
* 029): `r0` outright — one per leg of the NEXT table, 0 for a retired leg — or `x0` marks
|
|
1047
|
+
* against the next table's `weights`.
|
|
968
1048
|
*/
|
|
969
1049
|
export async function fPoolRelaunch(
|
|
970
1050
|
c: LogswapClient,
|
|
@@ -986,7 +1066,7 @@ export async function fPoolRelaunch(
|
|
|
986
1066
|
encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
|
|
987
1067
|
const calls: Hex[] = [
|
|
988
1068
|
...harvests.map((amount) => enc("harvest", [a.poolId, amount, a.account])),
|
|
989
|
-
enc("
|
|
1069
|
+
enc("retire", [a.poolId]),
|
|
990
1070
|
...(a.shares > 0n ? [enc("redeem", [a.poolId, BigInt(gen), a.account, a.shares])] : []),
|
|
991
1071
|
...(a.legs ? [enc("setLegs", [a.poolId, a.legs.weights, a.legs.newBases ?? []])] : []),
|
|
992
1072
|
enc("seed", [a.poolId, a.L0, r0, a.Q0]),
|
|
@@ -1040,7 +1120,8 @@ export function fPoolLaunchCalls(
|
|
|
1040
1120
|
const given = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j]! * a.L0) / WAD, x));
|
|
1041
1121
|
if (given.length !== a.bases.length) throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
|
|
1042
1122
|
const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
|
|
1043
|
-
|
|
1123
|
+
if (!a.hook) throw new Error("logswap: fPoolLaunchCalls needs the key's hook — fPoolLaunch fills it from the deployment");
|
|
1124
|
+
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 };
|
|
1044
1125
|
const enc = (functionName: string, args: unknown[]) =>
|
|
1045
1126
|
encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
|
|
1046
1127
|
const calls: Hex[] = [];
|
|
@@ -1054,16 +1135,25 @@ export function fPoolLaunchCalls(
|
|
|
1054
1135
|
calls.push(enc("swapQuoteIn", [poolId, BigInt(j), a.openBuy.amountIn, a.openBuy.minOut ?? 0n, a.account]));
|
|
1055
1136
|
}
|
|
1056
1137
|
if (a.name) calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
// authority is the operator until one is appointed (decisions 037)
|
|
1060
|
-
calls.push(enc("setGates", [poolId, true, false]));
|
|
1061
|
-
calls.push(enc("setAllowed", [poolId, [a.account], true]));
|
|
1062
|
-
}
|
|
1138
|
+
// the desk's gates and list are the HOOK's (decisions 044), not the manager's: they cannot ride
|
|
1139
|
+
// in this batch — `fPoolDeskOpening` sends them right after, as the same creator
|
|
1063
1140
|
calls.push(...(a.extra ?? []));
|
|
1064
1141
|
return calls;
|
|
1065
1142
|
}
|
|
1066
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* The desk's opening posture, on its hook (decisions 044): gated minting, the creator listed. No
|
|
1146
|
+
* appointment: the authority is the operator until one is appointed (decisions 037). Two writes
|
|
1147
|
+
* to `PrivateHook`; the launch batch cannot carry them (they are not manager calls).
|
|
1148
|
+
*/
|
|
1149
|
+
export async function fPoolDeskOpening(c: LogswapClient, a: { poolId: Hex; account: Address; hook?: Address }): Promise<Hash[]> {
|
|
1150
|
+
const hook = a.hook ?? fPoolHookFor(c, "private");
|
|
1151
|
+
return [
|
|
1152
|
+
await fPoolSetGates(c, { poolId: a.poolId, gateMint: true, gateSwap: false, account: a.account, hook }),
|
|
1153
|
+
await fPoolSetAllowed(c, { poolId: a.poolId, who: [a.account], allowed: true, account: a.account, hook }),
|
|
1154
|
+
];
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1067
1157
|
/**
|
|
1068
1158
|
* Create a pool as ONE transaction: `initialize`, `seed`, the seed lock, the opening buy, the
|
|
1069
1159
|
* name, the private-pool gates — the manager's `multicall`, the creator as sender throughout.
|
|
@@ -1086,48 +1176,76 @@ export async function fPoolLaunch(
|
|
|
1086
1176
|
a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
|
|
1087
1177
|
): Promise<{ hash: Hash; poolId: Hex }> {
|
|
1088
1178
|
const poolId = await fPoolIdOf(c, a);
|
|
1089
|
-
const
|
|
1179
|
+
const calls = fPoolLaunchCalls(poolId, { ...a, hook: a.hook ?? fPoolHookFor(c, a.kind) });
|
|
1180
|
+
const hash = await writeFPool0(c, "multicall", [calls], a.account);
|
|
1090
1181
|
return { hash, poolId };
|
|
1091
1182
|
}
|
|
1092
1183
|
|
|
1184
|
+
// ─── the roles, on the hook (decisions 044) ─────────────────────────────────────────────
|
|
1185
|
+
//
|
|
1186
|
+
// Every write here goes to the pool's HOOK, not the manager: the manager holds no role. `hook`
|
|
1187
|
+
// may be passed when in hand (`FPoolState.hook`, `FPoolRoles.hook`); otherwise it is read once.
|
|
1188
|
+
|
|
1189
|
+
/** A write keyed by poolId, on the pool's hook. */
|
|
1190
|
+
async function writeHook(c: LogswapClient, abi: readonly unknown[], a: { poolId: Hex; hook?: Address; account: Address }, functionName: string, args: unknown[]) {
|
|
1191
|
+
const wallet = requireWallet(c);
|
|
1192
|
+
const hook = a.hook ?? (await fPoolHookOf(c, a.poolId));
|
|
1193
|
+
const { request } = await c.public.simulateContract({
|
|
1194
|
+
address: hook,
|
|
1195
|
+
abi,
|
|
1196
|
+
functionName,
|
|
1197
|
+
args: [a.poolId, ...args],
|
|
1198
|
+
account: a.account,
|
|
1199
|
+
} as never);
|
|
1200
|
+
return wallet.writeContract(request as never);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1093
1203
|
/**
|
|
1094
|
-
* Propose the next authority — step one of two (
|
|
1204
|
+
* Propose the next authority — step one of two (decisions 020), on the pool's hook. Nothing moves
|
|
1095
1205
|
* until `next` accepts; proposing again (the zero address included) cancels a pending proposal.
|
|
1096
|
-
* Indexed on-chain via AuthorityProposed.
|
|
1206
|
+
* Indexed on-chain via the hook's AuthorityProposed.
|
|
1097
1207
|
*/
|
|
1098
|
-
export async function fPoolProposeAuthority(c: LogswapClient, a: { poolId: Hex; next: Address; account: Address }) {
|
|
1099
|
-
return
|
|
1208
|
+
export async function fPoolProposeAuthority(c: LogswapClient, a: { poolId: Hex; next: Address; account: Address; hook?: Address }) {
|
|
1209
|
+
return writeHook(c, fHookBaseAbi, a, "proposeAuthority", [a.next]);
|
|
1100
1210
|
}
|
|
1101
1211
|
|
|
1102
|
-
/**
|
|
1103
|
-
|
|
1104
|
-
|
|
1212
|
+
/**
|
|
1213
|
+
* Accept a pending proposal — step two; only the proposed address can. Indexed via the hook's
|
|
1214
|
+
* AuthoritySet. On the desk's hook this clears the operator (pending and accepted) and voids the
|
|
1215
|
+
* seller's list (decisions 040, 044).
|
|
1216
|
+
*/
|
|
1217
|
+
export async function fPoolAcceptAuthority(c: LogswapClient, a: { poolId: Hex; account: Address; hook?: Address }) {
|
|
1218
|
+
return writeHook(c, fHookBaseAbi, a, "acceptAuthority", []);
|
|
1105
1219
|
}
|
|
1106
1220
|
|
|
1107
|
-
// ─── the
|
|
1221
|
+
// ─── the desk's controls (PrivateHook; decisions 021, 044) ───────────────────────────────
|
|
1108
1222
|
|
|
1109
|
-
/** Turn the mint / swap gates on or off (authority). `burn` is never gated. */
|
|
1223
|
+
/** Turn the mint / swap gates on or off (the authority, on the desk's hook). `burn` is never gated. */
|
|
1110
1224
|
export async function fPoolSetGates(
|
|
1111
1225
|
c: LogswapClient,
|
|
1112
|
-
a: { poolId: Hex; gateMint: boolean; gateSwap: boolean; account: Address },
|
|
1226
|
+
a: { poolId: Hex; gateMint: boolean; gateSwap: boolean; account: Address; hook?: Address },
|
|
1113
1227
|
) {
|
|
1114
|
-
return
|
|
1228
|
+
return writeHook(c, privateHookAbi, a, "setGates", [a.gateMint, a.gateSwap]);
|
|
1115
1229
|
}
|
|
1116
1230
|
|
|
1117
|
-
/**
|
|
1231
|
+
/**
|
|
1232
|
+
* Add to or remove from the pool's list, in one call (the authority, on the desk's hook). The list
|
|
1233
|
+
* is keyed by the authority that granted it: a handover starts the buyer with an empty one.
|
|
1234
|
+
*/
|
|
1118
1235
|
export async function fPoolSetAllowed(
|
|
1119
1236
|
c: LogswapClient,
|
|
1120
|
-
a: { poolId: Hex; who: readonly Address[]; allowed: boolean; account: Address },
|
|
1237
|
+
a: { poolId: Hex; who: readonly Address[]; allowed: boolean; account: Address; hook?: Address },
|
|
1121
1238
|
) {
|
|
1122
|
-
return
|
|
1239
|
+
return writeHook(c, privateHookAbi, a, "setAllowed", [a.who, a.allowed]);
|
|
1123
1240
|
}
|
|
1124
1241
|
|
|
1125
|
-
/** Whether `who` is on the pool's list (consulted only while a gate is on). */
|
|
1126
|
-
export async function fPoolIsAllowed(c: LogswapClient, poolId: Hex, who: Address): Promise<boolean> {
|
|
1242
|
+
/** Whether `who` is on the pool's list under its current authority (consulted only while a gate is on). */
|
|
1243
|
+
export async function fPoolIsAllowed(c: LogswapClient, poolId: Hex, who: Address, hook?: Address): Promise<boolean> {
|
|
1244
|
+
const address = hook ?? (await fPoolHookOf(c, poolId));
|
|
1127
1245
|
return (await c.public.readContract({
|
|
1128
|
-
address
|
|
1129
|
-
abi:
|
|
1130
|
-
functionName: "
|
|
1246
|
+
address,
|
|
1247
|
+
abi: privateHookAbi,
|
|
1248
|
+
functionName: "isAllowed",
|
|
1131
1249
|
args: [poolId, who],
|
|
1132
1250
|
} as never)) as boolean;
|
|
1133
1251
|
}
|
|
@@ -1165,27 +1283,27 @@ export async function fPoolSponsorName(c: LogswapClient, who: Address): Promise<
|
|
|
1165
1283
|
}
|
|
1166
1284
|
|
|
1167
1285
|
/** Who runs the pool (decisions 037): the appointed operator, or the authority until one is. */
|
|
1168
|
-
export function fPoolOperatorOf(s: Pick<
|
|
1286
|
+
export function fPoolOperatorOf(s: Pick<FPoolRoles, "authority" | "operator">): Address {
|
|
1169
1287
|
return s.operator === ZERO_ADDRESS ? s.authority : s.operator;
|
|
1170
1288
|
}
|
|
1171
1289
|
|
|
1172
1290
|
/**
|
|
1173
|
-
* Propose the operator (authority
|
|
1174
|
-
* `
|
|
1291
|
+
* Propose the operator (the authority, on the desk's hook) — the hot key that runs the pool:
|
|
1292
|
+
* `harvest`, `retire`, `regenerate`, `setLegs`. Two-step: the proposed address takes the role with
|
|
1175
1293
|
* {@link fPoolAcceptOperator}. The zero address clears at once — the role returns to the authority.
|
|
1176
1294
|
*/
|
|
1177
|
-
export async function fPoolAppointOperator(c: LogswapClient, a: { poolId: Hex; operator: Address; account: Address }) {
|
|
1178
|
-
return
|
|
1295
|
+
export async function fPoolAppointOperator(c: LogswapClient, a: { poolId: Hex; operator: Address; account: Address; hook?: Address }) {
|
|
1296
|
+
return writeHook(c, privateHookAbi, a, "appointOperator", [a.operator]);
|
|
1179
1297
|
}
|
|
1180
1298
|
|
|
1181
1299
|
/** The proposed operator takes the role. */
|
|
1182
|
-
export async function fPoolAcceptOperator(c: LogswapClient, a: { poolId: Hex; account: Address }) {
|
|
1183
|
-
return
|
|
1300
|
+
export async function fPoolAcceptOperator(c: LogswapClient, a: { poolId: Hex; account: Address; hook?: Address }) {
|
|
1301
|
+
return writeHook(c, privateHookAbi, a, "acceptOperator", []);
|
|
1184
1302
|
}
|
|
1185
1303
|
|
|
1186
1304
|
/**
|
|
1187
1305
|
* The next generation's composition (operator; private pools only; ONLY while the pool is
|
|
1188
|
-
* unseeded — between a
|
|
1306
|
+
* unseeded — between a retire and the seed that prices it, decisions 037). `weights` has one
|
|
1189
1307
|
* entry per leg the pool has ever had, in index order (0 retires a leg; indices are never reused),
|
|
1190
1308
|
* then one per base in `newBases`, appended; they sum to 1e18 exactly. No value moves: the
|
|
1191
1309
|
* following {@link fPoolSeed} names every reserve. A live reshape is {@link fPoolRelaunch} with
|
|
@@ -1228,7 +1346,10 @@ export interface DiscoveredFPool {
|
|
|
1228
1346
|
weights: bigint[];
|
|
1229
1347
|
phi: bigint;
|
|
1230
1348
|
kind: FPoolKind;
|
|
1231
|
-
|
|
1349
|
+
/** The pool's policy — the hook the key names (decisions 041, 044). */
|
|
1350
|
+
hook: Address;
|
|
1351
|
+
/** Who was born holding the pool (immutable). The LIVE authority is the hook's — {@link getFPoolRoles}. */
|
|
1352
|
+
creator: Address;
|
|
1232
1353
|
/** The block the pool was initialized in — its age, for annualising realized income. */
|
|
1233
1354
|
block: bigint;
|
|
1234
1355
|
/** Whether `seed` has run; an unseeded pool has no floor and no float, so nothing to show. */
|
|
@@ -1276,8 +1397,8 @@ export async function discoverFPools(
|
|
|
1276
1397
|
}
|
|
1277
1398
|
return logs.map((l) => {
|
|
1278
1399
|
const a = l.args as {
|
|
1279
|
-
poolId: Hex; quote: Address;
|
|
1280
|
-
weights: readonly bigint[]; phi: bigint; kind: number;
|
|
1400
|
+
poolId: Hex; quote: Address; creator: Address; bases: readonly Address[];
|
|
1401
|
+
weights: readonly bigint[]; phi: bigint; kind: number; hook: Address;
|
|
1281
1402
|
};
|
|
1282
1403
|
const q0 = q0Of.get(a.poolId.toLowerCase());
|
|
1283
1404
|
return {
|
|
@@ -1287,7 +1408,8 @@ export async function discoverFPools(
|
|
|
1287
1408
|
weights: [...a.weights],
|
|
1288
1409
|
phi: a.phi,
|
|
1289
1410
|
kind: fPoolKindOf(a.kind),
|
|
1290
|
-
|
|
1411
|
+
hook: a.hook,
|
|
1412
|
+
creator: a.creator,
|
|
1291
1413
|
block: l.blockNumber ?? 0n,
|
|
1292
1414
|
seeded: q0 !== undefined,
|
|
1293
1415
|
q0: q0 ?? 0n,
|
|
@@ -1296,6 +1418,87 @@ export async function discoverFPools(
|
|
|
1296
1418
|
});
|
|
1297
1419
|
}
|
|
1298
1420
|
|
|
1421
|
+
// ─── the policy page: what an authority controls (decisions 044) ────────────────────────
|
|
1422
|
+
//
|
|
1423
|
+
// One hook serves every pool of its kind, so "the pools I control" is not a hook's list: it is
|
|
1424
|
+
// every pool whose hook answers `authority(poolId) == me`. Discovery from the log stream (or the
|
|
1425
|
+
// indexer), then one `authority` read per pool on its hook — viem multicalls them.
|
|
1426
|
+
|
|
1427
|
+
/** A pool an authority controls, with what the policy page shows of it. */
|
|
1428
|
+
export interface FPoolControlled {
|
|
1429
|
+
pool: DiscoveredFPool;
|
|
1430
|
+
roles: FPoolRoles;
|
|
1431
|
+
state: FPoolState;
|
|
1432
|
+
/** The hook's numbers for this pool's kind — a pad's rung and threshold, a POL's floors, the desk's list flags. */
|
|
1433
|
+
policy: FPoolPolicy;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/** What a pool's hook says about it, by kind: the terms a buyer reads before entering. */
|
|
1437
|
+
export type FPoolPolicy =
|
|
1438
|
+
| { kind: "pad"; harvestCut: bigint; rung: number; threshold: bigint; pot: bigint; creatorShare: bigint; protocolShare: bigint; burnPerRung: bigint }
|
|
1439
|
+
| { kind: "pol"; harvestCut: bigint; keepQuote: bigint; minQuoteAtSeed: bigint }
|
|
1440
|
+
| { kind: "public"; harvestCut: bigint; curated: boolean }
|
|
1441
|
+
| { kind: "private"; harvestCut: bigint; gateMint: boolean; gateSwap: boolean; operator: Address };
|
|
1442
|
+
|
|
1443
|
+
/** The hook's terms for one pool, by its kind. */
|
|
1444
|
+
export async function getFPoolPolicy(c: LogswapClient, s: Pick<FPoolState, "poolId" | "hook" | "kind" | "gen">, roles?: FPoolRoles): Promise<FPoolPolicy> {
|
|
1445
|
+
const rd = <T>(abi: readonly unknown[], functionName: string, args: readonly unknown[] = []) =>
|
|
1446
|
+
c.public.readContract({ address: s.hook, abi, functionName, args } as never) as Promise<T>;
|
|
1447
|
+
const harvestCut = await rd<bigint>(fHookBaseAbi, "harvestCut");
|
|
1448
|
+
switch (s.kind) {
|
|
1449
|
+
case "pad": {
|
|
1450
|
+
const [rung, creatorShare, protocolShare, burnPerRung, pot] = await Promise.all([
|
|
1451
|
+
rd<bigint>(padHookAbi, "rung", [s.poolId]),
|
|
1452
|
+
rd<bigint>(padHookAbi, "creatorShare"),
|
|
1453
|
+
rd<bigint>(padHookAbi, "protocolShare"),
|
|
1454
|
+
rd<bigint>(padHookAbi, "burnPerRung"),
|
|
1455
|
+
fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, s.gen),
|
|
1456
|
+
]);
|
|
1457
|
+
const threshold = await rd<bigint>(padHookAbi, "threshold", [rung]);
|
|
1458
|
+
return { kind: "pad", harvestCut, rung: Number(rung), threshold, pot, creatorShare, protocolShare, burnPerRung };
|
|
1459
|
+
}
|
|
1460
|
+
case "pol": {
|
|
1461
|
+
const [keepQuote, minQuoteAtSeed] = await Promise.all([rd<bigint>(polHookAbi, "keepQuote"), rd<bigint>(polHookAbi, "minQuoteAtSeed")]);
|
|
1462
|
+
return { kind: "pol", harvestCut, keepQuote, minQuoteAtSeed };
|
|
1463
|
+
}
|
|
1464
|
+
case "public": {
|
|
1465
|
+
const curated = await rd<boolean>(publicHookAbi, "curated");
|
|
1466
|
+
return { kind: "public", harvestCut, curated };
|
|
1467
|
+
}
|
|
1468
|
+
case "private": {
|
|
1469
|
+
const r = roles ?? (await getFPoolRoles(c, s.poolId, s));
|
|
1470
|
+
return { kind: "private", harvestCut, gateMint: r.gateMint, gateSwap: r.gateSwap, operator: fPoolOperatorOf(r) };
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Every F pool `who` is the AUTHORITY of, across the four hooks — the policy page's list. Pools
|
|
1477
|
+
* `who` created and handed over are not here (the buyer's page shows them); pools handed TO
|
|
1478
|
+
* `who` are. `pools` may be passed (from the indexer, say) to skip the log scan.
|
|
1479
|
+
*/
|
|
1480
|
+
export async function fPoolsControlledBy(
|
|
1481
|
+
c: LogswapClient,
|
|
1482
|
+
who: Address,
|
|
1483
|
+
opts: { pools?: DiscoveredFPool[]; fromBlock?: bigint } = {},
|
|
1484
|
+
): Promise<FPoolControlled[]> {
|
|
1485
|
+
const pools = opts.pools ?? (await discoverFPools(c, { fromBlock: opts.fromBlock }));
|
|
1486
|
+
const authorities = await Promise.all(
|
|
1487
|
+
pools.map((p) =>
|
|
1488
|
+
c.public.readContract({ address: p.hook, abi: fHookBaseAbi, functionName: "authority", args: [p.poolId] } as never) as Promise<Address>,
|
|
1489
|
+
),
|
|
1490
|
+
);
|
|
1491
|
+
const mine = pools.filter((_, i) => authorities[i]!.toLowerCase() === who.toLowerCase());
|
|
1492
|
+
return Promise.all(
|
|
1493
|
+
mine.map(async (pool) => {
|
|
1494
|
+
const state = await getFPool(c, pool.poolId);
|
|
1495
|
+
const roles = await getFPoolRoles(c, pool.poolId, state);
|
|
1496
|
+
const policy = await getFPoolPolicy(c, state, roles);
|
|
1497
|
+
return { pool, roles, state, policy };
|
|
1498
|
+
}),
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1299
1502
|
|
|
1300
1503
|
/**
|
|
1301
1504
|
* Every holder of a pool's shares, from the ERC-6909 Transfer stream — the id is an indexed
|