@logswap/sdk 0.5.71 → 0.5.73
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 +21 -2
- package/dist/admin.d.ts +38 -16
- package/dist/admin.js +47 -18
- package/dist/client.d.ts +7 -2
- package/dist/client.js +15 -2
- package/dist/fees.d.ts +1 -1
- package/dist/fees.js +2 -2
- package/dist/fpool.d.ts +203 -26
- package/dist/fpool.js +186 -107
- package/dist/generated.d.ts +812 -446
- package/dist/generated.js +486 -246
- package/dist/indexer.js +2 -2
- package/dist/keys.d.ts +4 -3
- package/dist/keys.js +3 -3
- package/dist/pools.d.ts +5 -4
- package/dist/pools.js +7 -6
- package/dist/positions.d.ts +3 -2
- package/dist/positions.js +3 -2
- package/package.json +1 -1
- package/src/admin.ts +51 -18
- package/src/client.ts +15 -2
- package/src/fees.ts +2 -2
- package/src/fpool.ts +244 -146
- package/src/generated.ts +486 -246
- package/src/indexer.ts +3 -3
- package/src/keys.ts +6 -5
- package/src/pools.ts +7 -6
- package/src/positions.ts +3 -2
package/src/fpool.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { decodeAbiParameters, encodeAbiParameters, encodeFunctionData, erc20Abi, keccak256, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
34
|
-
import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
34
|
+
import { fPoolManagerAbi, logswapLensAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* One contract, one ABI (F version 12): `harvest`, `claim`, `retire`, `regenerate`,
|
|
@@ -72,14 +72,21 @@ export interface FPoolState {
|
|
|
72
72
|
Q: bigint;
|
|
73
73
|
/** Total exposure. Grows on mint, shrinks on burn; never moves on a swap. */
|
|
74
74
|
L: bigint;
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
/** θ = X − Q/L, the
|
|
80
|
-
|
|
75
|
+
/** The swap fee, WAD (`phi` before F version 25, decisions 059). At or above `FEE_MIN` — a zero-fee basket is not constructible. */
|
|
76
|
+
swapFee: bigint;
|
|
77
|
+
/** θ₀, the reference log floor: where the floor stood at the seed (re-based by a capital harvest and at each step); income is counted from it. `theta0` before F version 25. */
|
|
78
|
+
refLogFloor: bigint;
|
|
79
|
+
/** θ = X − Q/L, the log of the pool's current floor price: where the bid runs out. Derived, never stored. `theta` before F version 25. */
|
|
80
|
+
logFloor: bigint;
|
|
81
81
|
/** X = Σ w_j x_j, the composite index. */
|
|
82
82
|
compositeX: bigint;
|
|
83
|
+
/** This generation's income still in the pool, quote: L·(θ₀ − θ). */
|
|
84
|
+
unharvested: bigint;
|
|
85
|
+
/**
|
|
86
|
+
* The common cap (decisions 061): d = ln(cap price / floor price) in 1e-6 log units, from the key;
|
|
87
|
+
* 0 is uncapped. Leverage at the floor is 1/(1 − e^{−d}): {@link fPoolLeverageOfCap}.
|
|
88
|
+
*/
|
|
89
|
+
cap: number;
|
|
83
90
|
/** Σ = ∫ Σ_j w_j (dx_j)², accrued even though the fee is fixed — the kernel stays calculable. */
|
|
84
91
|
bigSigma: bigint;
|
|
85
92
|
/**
|
|
@@ -110,87 +117,69 @@ export interface FPoolState {
|
|
|
110
117
|
totalSupply: bigint;
|
|
111
118
|
/** What the pool holds of each base — the STATE; the mark `x` is derived from it. */
|
|
112
119
|
reserves: bigint[];
|
|
120
|
+
/** The reserve every price reads: `reserves[j] + L_j·κ_j` on a capped pool (061), `reserves[j]` otherwise. */
|
|
121
|
+
virtualReserves: bigint[];
|
|
122
|
+
/** κ_j = e^{−μ_j}, WAD: each leg's phantom reserve per unit of its liquidity. 0 uncapped. */
|
|
123
|
+
kappa: bigint[];
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// since 18: those are the policy's.
|
|
130
|
-
const p = await rd<{
|
|
131
|
-
quote: Address;
|
|
132
|
-
phi: bigint;
|
|
133
|
-
policy: Address;
|
|
134
|
-
seeded: boolean;
|
|
135
|
-
gen: number;
|
|
136
|
-
n: number;
|
|
137
|
-
restructureBlock: number;
|
|
138
|
-
asks: number;
|
|
139
|
-
Q: bigint;
|
|
140
|
-
bigSigma: bigint;
|
|
141
|
-
L: bigint;
|
|
142
|
-
shares: bigint;
|
|
143
|
-
theta0: bigint;
|
|
144
|
-
}>("getPool", [poolId]);
|
|
126
|
+
/**
|
|
127
|
+
* The lens's raw F pool struct — what the manager's `getPool` returned. Since F version 25 the
|
|
128
|
+
* manager's typed views are behind `extsload` (decisions 062) and the lens reads them; one call.
|
|
129
|
+
*/
|
|
130
|
+
async function rawFPool(c: LogswapClient, poolId: Hex) {
|
|
131
|
+
const [p] = await c.public.readContract({
|
|
132
|
+
address: c.addresses.lens,
|
|
133
|
+
abi: logswapLensAbi,
|
|
134
|
+
functionName: "getFPools",
|
|
135
|
+
args: [[poolId]],
|
|
136
|
+
});
|
|
137
|
+
if (!p || /^0x0{40}$/i.test(p.quote)) throw new Error(`logswap: no F pool with id ${poolId}`);
|
|
138
|
+
return p;
|
|
139
|
+
}
|
|
145
140
|
|
|
141
|
+
/** Read a basket pool whole: one lens call (`fPool`, F version 25) plus the policy's kind and name. */
|
|
142
|
+
export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolState> {
|
|
143
|
+
const v = await c.public.readContract({
|
|
144
|
+
address: c.addresses.lens,
|
|
145
|
+
abi: logswapLensAbi,
|
|
146
|
+
functionName: "fPool",
|
|
147
|
+
args: [poolId],
|
|
148
|
+
});
|
|
149
|
+
const p = v.pool;
|
|
146
150
|
// A pool that was never created reads as an all-zero struct rather than reverting, so the
|
|
147
151
|
// absence has to be named here or every caller mistakes "no such pool" for "a pool holding
|
|
148
152
|
// nothing". `quote` is the manager's own existence test (`_live`), and it is what the create
|
|
149
153
|
// flow's resumability turns on: an initialized-but-unseeded pool MUST still read, so this
|
|
150
154
|
// cannot be a check on `seeded`.
|
|
151
155
|
if (/^0x0{40}$/i.test(p.quote)) throw new Error(`logswap: no F pool with id ${poolId}`);
|
|
152
|
-
|
|
153
|
-
const n = Number(p.n);
|
|
154
|
-
const idx = Array.from({ length: n }, (_, i) => BigInt(i));
|
|
155
|
-
const [theta, compositeX, legs, kind, name] = await Promise.all([
|
|
156
|
-
// before the seed there is no L and no floor: θ is X by convention (older deployments revert
|
|
157
|
-
// with DivWadFailed, so do not ask them)
|
|
158
|
-
p.L > 0n ? rd<bigint>("theta", [poolId]) : rd<bigint>("compositeX", [poolId]),
|
|
159
|
-
rd<bigint>("compositeX", [poolId]),
|
|
160
|
-
Promise.all(idx.map((i) => rd<readonly [Address, bigint, bigint, bigint]>("legOf", [poolId, i]))),
|
|
161
|
-
fPoolKindOfPolicy(c, p.policy),
|
|
162
|
-
fPoolNameOf(c, poolId, p.policy),
|
|
163
|
-
]);
|
|
164
|
-
|
|
165
|
-
const bases = legs.map((l) => l[0]);
|
|
166
|
-
const weights = legs.map((l) => l[1]);
|
|
167
|
-
const x = legs.map((l) => l[2]); // derived by the manager: x_j = ln(w_j·L / R_j)
|
|
168
|
-
const reserves = legs.map((l) => l[3]); // the state
|
|
169
|
-
const { quote, Q, L, phi, theta0, bigSigma, policy, restructureBlock, seeded, gen } = p;
|
|
170
|
-
const totalSupply = p.shares;
|
|
171
|
-
|
|
156
|
+
const [kind, name] = await Promise.all([fPoolKindOfPolicy(c, p.policy), fPoolNameOf(c, poolId, p.policy)]);
|
|
172
157
|
return {
|
|
173
158
|
poolId,
|
|
174
|
-
quote,
|
|
175
|
-
bases,
|
|
176
|
-
weights,
|
|
177
|
-
x,
|
|
178
|
-
Q,
|
|
179
|
-
L,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
compositeX,
|
|
184
|
-
|
|
185
|
-
|
|
159
|
+
quote: p.quote,
|
|
160
|
+
bases: v.legs.map((l) => l.base),
|
|
161
|
+
weights: v.legs.map((l) => l.weight),
|
|
162
|
+
x: v.legs.map((l) => l.x), // derived by the manager: x_j = ln(w_j·L / R̃_j)
|
|
163
|
+
Q: p.Q,
|
|
164
|
+
L: p.L,
|
|
165
|
+
swapFee: p.swapFee,
|
|
166
|
+
refLogFloor: p.refLogFloor,
|
|
167
|
+
logFloor: v.logFloor,
|
|
168
|
+
compositeX: v.compositeX,
|
|
169
|
+
unharvested: v.unharvested,
|
|
170
|
+
cap: Number(p.cap),
|
|
171
|
+
bigSigma: p.bigSigma,
|
|
172
|
+
policy: p.policy,
|
|
186
173
|
asks: Number(p.asks),
|
|
187
174
|
name,
|
|
188
|
-
restructureBlock: Number(restructureBlock),
|
|
175
|
+
restructureBlock: Number(p.restructureBlock),
|
|
189
176
|
kind,
|
|
190
|
-
seeded,
|
|
191
|
-
gen: Number(gen),
|
|
192
|
-
totalSupply,
|
|
193
|
-
reserves,
|
|
177
|
+
seeded: p.seeded,
|
|
178
|
+
gen: Number(p.gen),
|
|
179
|
+
totalSupply: p.shares,
|
|
180
|
+
reserves: v.legs.map((l) => l.reserve), // the state
|
|
181
|
+
virtualReserves: v.legs.map((l) => l.virtualReserve),
|
|
182
|
+
kappa: v.legs.map((l) => l.kappa),
|
|
194
183
|
};
|
|
195
184
|
}
|
|
196
185
|
|
|
@@ -248,14 +237,7 @@ export function fPoolPolicyFor(c: LogswapClient, kind: FPoolKind): Address {
|
|
|
248
237
|
|
|
249
238
|
/** The policy a pool named in its key — one read; {@link getFPool} carries it too. */
|
|
250
239
|
export async function fPoolPolicyOf(c: LogswapClient, poolId: Hex): Promise<Address> {
|
|
251
|
-
|
|
252
|
-
address: c.addresses.fPoolManager!,
|
|
253
|
-
abi: fPoolManagerAbi,
|
|
254
|
-
functionName: "getPool",
|
|
255
|
-
args: [poolId],
|
|
256
|
-
} as never)) as { quote: Address; policy: Address };
|
|
257
|
-
if (/^0x0{40}$/i.test(p.quote)) throw new Error(`logswap: no F pool with id ${poolId}`);
|
|
258
|
-
return p.policy;
|
|
240
|
+
return (await rawFPool(c, poolId)).policy;
|
|
259
241
|
}
|
|
260
242
|
|
|
261
243
|
/**
|
|
@@ -329,19 +311,11 @@ export interface FPoolDescription {
|
|
|
329
311
|
}
|
|
330
312
|
|
|
331
313
|
export async function describeFPool(c: LogswapClient, s: FPoolState): Promise<FPoolDescription> {
|
|
332
|
-
const rd = <T>(functionName: string, args: readonly unknown[] = [s.poolId]) =>
|
|
333
|
-
c.public.readContract({
|
|
334
|
-
address: c.addresses.fPoolManager!,
|
|
335
|
-
abi: fPoolManagerAbi,
|
|
336
|
-
functionName,
|
|
337
|
-
args,
|
|
338
|
-
} as never) as Promise<T>;
|
|
339
314
|
// an unseeded pool has no L: nothing earned, nothing to harvest, no pot. The bid is the
|
|
340
315
|
// manager's ceiling (`harvestable()` until F version 21, which cut the getter): Q once seeded.
|
|
341
316
|
const harvestable = s.L > 0n ? s.Q : 0n;
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
: [0n, 0n];
|
|
317
|
+
const unharvested = s.L > 0n ? s.unharvested : 0n;
|
|
318
|
+
const pot = s.L > 0n ? await fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, s.gen) : 0n;
|
|
345
319
|
return {
|
|
346
320
|
shape: s.kind === "pad" ? "launch": "basket", // the kind, not n = 1
|
|
347
321
|
legs: s.bases.length,
|
|
@@ -357,10 +331,22 @@ export function fPoolPriceOf(x: bigint, scale = 1): number {
|
|
|
357
331
|
return Math.exp(Number(x) / 1e18) * scale;
|
|
358
332
|
}
|
|
359
333
|
|
|
360
|
-
/**
|
|
334
|
+
/**
|
|
335
|
+
* Value per share at the pool's own marks, WAD. Uncapped, `V = L + Q`. Capped (decisions 061), leg
|
|
336
|
+
* j's base is worth `L_j·R_j/R̃_j`, less than `L_j` by the phantom the cap removed — the lens's
|
|
337
|
+
* `shareValue`, without the call.
|
|
338
|
+
*/
|
|
361
339
|
export function fPoolShareValue(s: FPoolState): bigint {
|
|
362
340
|
if (s.totalSupply === 0n) return 0n;
|
|
363
|
-
|
|
341
|
+
let v = s.L + s.Q;
|
|
342
|
+
if (s.cap !== 0) {
|
|
343
|
+
v = s.Q;
|
|
344
|
+
for (let j = 0; j < s.reserves.length; j++) {
|
|
345
|
+
const rv = s.virtualReserves[j]!;
|
|
346
|
+
if (rv !== 0n) v += (((s.weights[j]! * s.L) / WAD) * s.reserves[j]!) / rv;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return (v * WAD) / s.totalSupply;
|
|
364
350
|
}
|
|
365
351
|
|
|
366
352
|
// ─── swaps ────────────────────────────────────────────────────────────────────
|
|
@@ -624,15 +610,84 @@ export async function fPoolClaim(c: LogswapClient, a: { poolId: Hex; gen?: bigin
|
|
|
624
610
|
return writeFPool(c, a.poolId, "claim", [BigInt(gen), a.to ?? a.account], a.account);
|
|
625
611
|
}
|
|
626
612
|
|
|
627
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* A holder's claimable dividend as of now on generation `gen` (the live one by default) — banked
|
|
615
|
+
* plus pending on the current balance; under a lien, the free fraction's only: the locked
|
|
616
|
+
* fraction's is the lien's ({@link fPoolLienOf}, decisions 063).
|
|
617
|
+
*/
|
|
628
618
|
export async function fPoolDividendOf(c: LogswapClient, poolId: Hex, holder: Address, gen?: bigint | number): Promise<bigint> {
|
|
629
619
|
const g = gen ?? (await fPoolGen(c, poolId));
|
|
630
620
|
return c.public.readContract({
|
|
631
|
-
address: c.addresses.
|
|
632
|
-
abi:
|
|
633
|
-
functionName: "
|
|
621
|
+
address: c.addresses.lens,
|
|
622
|
+
abi: logswapLensAbi,
|
|
623
|
+
functionName: "fDividendOf",
|
|
634
624
|
args: [poolId, BigInt(g), holder],
|
|
635
|
-
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ─── the lien (decisions 063) ─────────────────────────────────────────────────
|
|
629
|
+
// A lending contract — the LOCKER — locks part of an owner's shares in place: no transfer, burn or
|
|
630
|
+
// redeem past the free balance. The dividend the locked amount earns is HELD BY THE LIEN, claimable
|
|
631
|
+
// by nobody while it stands; lifted, it goes with the lifted part — to the owner on a release (a
|
|
632
|
+
// repaid loan gets it back), to the seizer on a seizure. A borrower frozen out by `regenerate` keeps
|
|
633
|
+
// its lien on the old generation's id.
|
|
634
|
+
|
|
635
|
+
/** A lien on one share id: the locker, the amount locked, and the dividend the lien holds (banked). */
|
|
636
|
+
export interface FPoolLien {
|
|
637
|
+
locker: Address;
|
|
638
|
+
amount: bigint;
|
|
639
|
+
owed: bigint;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The lien on `owner`'s shares of `poolId`'s generation `gen` (the live one by default). `owed` counts
|
|
644
|
+
* what has been banked into it; the locked fraction of the owner's pending since its last bank is
|
|
645
|
+
* not yet in it — {@link fPoolBankLien} banks it.
|
|
646
|
+
*/
|
|
647
|
+
export async function fPoolLienOf(c: LogswapClient, poolId: Hex, owner: Address, gen?: bigint | number): Promise<FPoolLien> {
|
|
648
|
+
const g = gen ?? (await fPoolGen(c, poolId));
|
|
649
|
+
const [locker, amount, owed] = await c.public.readContract({
|
|
650
|
+
address: c.addresses.lens,
|
|
651
|
+
abi: logswapLensAbi,
|
|
652
|
+
functionName: "fLienOf",
|
|
653
|
+
args: [fPoolShareId(poolId, g), owner],
|
|
654
|
+
});
|
|
655
|
+
return { locker, amount, owed };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Lock `amount` of `owner`'s shares for the caller, the locker (`account`). Paid with the owner's
|
|
660
|
+
* per-id allowance to the locker — the owner first sends `approve(locker, shareId, amount)` on the
|
|
661
|
+
* F manager ({@link fPoolApproveShares}) — or an operator grant. One locker per (id, owner) while a
|
|
662
|
+
* lien stands. In practice the lending contract calls `lock` itself; this is the EOA path.
|
|
663
|
+
*/
|
|
664
|
+
export async function fPoolLock(c: LogswapClient, a: { poolId: Hex; owner: Address; amount: bigint; gen?: bigint | number; account: Address }) {
|
|
665
|
+
const g = a.gen ?? (await fPoolGen(c, a.poolId));
|
|
666
|
+
return writeFPool0(c, "lock", [fPoolShareId(a.poolId, g), a.owner, a.amount], a.account);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* The locker's `unlock`: `amount` of the lien lifted with its share of the lien's dividend, credited
|
|
671
|
+
* to `to` — the owner (a release) or anyone else (a seizure, the shares moving to `to`). It banks
|
|
672
|
+
* first. The locker's alone.
|
|
673
|
+
*/
|
|
674
|
+
export async function fPoolUnlock(
|
|
675
|
+
c: LogswapClient,
|
|
676
|
+
a: { poolId: Hex; owner: Address; amount: bigint; to: Address; gen?: bigint | number; account: Address },
|
|
677
|
+
) {
|
|
678
|
+
const g = a.gen ?? (await fPoolGen(c, a.poolId));
|
|
679
|
+
return writeFPool0(c, "unlock", [fPoolShareId(a.poolId, g), a.owner, a.amount, a.to], a.account);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Bank the owner's pending into the lien now — `unlock(id, owner, 0, owner)`: dividends bank lazily. The locker's. */
|
|
683
|
+
export async function fPoolBankLien(c: LogswapClient, a: { poolId: Hex; owner: Address; gen?: bigint | number; account: Address }) {
|
|
684
|
+
return fPoolUnlock(c, { ...a, amount: 0n, to: a.owner });
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** The owner's per-id allowance to a locker on the F manager's ERC-6909 shares — what `lock` spends. */
|
|
688
|
+
export async function fPoolApproveShares(c: LogswapClient, a: { poolId: Hex; spender: Address; amount: bigint; gen?: bigint | number; account: Address }) {
|
|
689
|
+
const g = a.gen ?? (await fPoolGen(c, a.poolId));
|
|
690
|
+
return writeFPool0(c, "approve", [a.spender, fPoolShareId(a.poolId, g), a.amount], a.account);
|
|
636
691
|
}
|
|
637
692
|
|
|
638
693
|
// ─── internals ────────────────────────────────────────────────────────────────
|
|
@@ -788,13 +843,7 @@ export function fPoolGenOfShareId(id: bigint): number {
|
|
|
788
843
|
|
|
789
844
|
/** The pool's live generation, one read. */
|
|
790
845
|
export async function fPoolGen(c: LogswapClient, poolId: Hex): Promise<number> {
|
|
791
|
-
|
|
792
|
-
address: c.addresses.fPoolManager!,
|
|
793
|
-
abi: fPoolManagerAbi,
|
|
794
|
-
functionName: "getPool",
|
|
795
|
-
args: [poolId],
|
|
796
|
-
} as never)) as { gen: number };
|
|
797
|
-
return Number(p.gen);
|
|
846
|
+
return Number((await rawFPool(c, poolId)).gen);
|
|
798
847
|
}
|
|
799
848
|
|
|
800
849
|
/** The ERC-6909 id carrying claims on a token. Bit 255 clear, so it can never meet a share id. */
|
|
@@ -819,12 +868,12 @@ export async function fPoolBundleOf(
|
|
|
819
868
|
poolId: Hex,
|
|
820
869
|
gen: bigint | number,
|
|
821
870
|
): Promise<{ supply: bigint; quote: bigint; base: bigint[] }> {
|
|
822
|
-
const [supply, quote, base] =
|
|
823
|
-
address: c.addresses.
|
|
824
|
-
abi:
|
|
825
|
-
functionName: "
|
|
871
|
+
const [supply, quote, base] = await c.public.readContract({
|
|
872
|
+
address: c.addresses.lens,
|
|
873
|
+
abi: logswapLensAbi,
|
|
874
|
+
functionName: "fBundleOf",
|
|
826
875
|
args: [poolId, BigInt(gen)],
|
|
827
|
-
}
|
|
876
|
+
});
|
|
828
877
|
return { supply, quote, base: [...base] };
|
|
829
878
|
}
|
|
830
879
|
|
|
@@ -945,8 +994,13 @@ export interface FPoolCreateArgs {
|
|
|
945
994
|
bases: Address[];
|
|
946
995
|
/** WAD each; must sum to 1e18 — validated here so the revert is readable. */
|
|
947
996
|
weights: bigint[];
|
|
948
|
-
/**
|
|
949
|
-
|
|
997
|
+
/** The swap fee, WAD (`phi` before F version 25). The base-for-base fee is pinned at 2φ on-chain. A curated policy admits only its listed tiers (decisions 059). */
|
|
998
|
+
swapFee: bigint;
|
|
999
|
+
/**
|
|
1000
|
+
* The common cap (decisions 061): d = ln(cap price / floor price) in 1e-6 log units, a key field;
|
|
1001
|
+
* 0 or absent is uncapped. From a leverage λ: {@link fPoolCapOfLeverage}.
|
|
1002
|
+
*/
|
|
1003
|
+
cap?: number;
|
|
950
1004
|
/**
|
|
951
1005
|
* Which of the protocol's four the pool is — see {@link FPoolKind}. NOT a key field (decisions
|
|
952
1006
|
* 049: the manager knows no kind): it selects the deployment's policy of that kind when `policy`
|
|
@@ -1006,11 +1060,13 @@ export interface FPoolKey {
|
|
|
1006
1060
|
bases: Address[];
|
|
1007
1061
|
/** WAD, aligned with `bases`, summing to 1e18. */
|
|
1008
1062
|
weights: bigint[];
|
|
1009
|
-
|
|
1063
|
+
swapFee: bigint;
|
|
1010
1064
|
/** The policy AT BIRTH — not the live one after a move. */
|
|
1011
1065
|
policy: Address;
|
|
1012
1066
|
creator: Address;
|
|
1013
1067
|
salt: Hex;
|
|
1068
|
+
/** The common cap, d in 1e-6 log units; 0 uncapped (decisions 061). Appended in F version 25: every pool id moved. */
|
|
1069
|
+
cap: number;
|
|
1014
1070
|
}
|
|
1015
1071
|
|
|
1016
1072
|
const F_POOL_KEY_ABI = [
|
|
@@ -1020,10 +1076,11 @@ const F_POOL_KEY_ABI = [
|
|
|
1020
1076
|
{ name: "quote", type: "address" },
|
|
1021
1077
|
{ name: "bases", type: "address[]" },
|
|
1022
1078
|
{ name: "weights", type: "uint256[]" },
|
|
1023
|
-
{ name: "
|
|
1079
|
+
{ name: "swapFee", type: "uint256" },
|
|
1024
1080
|
{ name: "policy", type: "address" },
|
|
1025
1081
|
{ name: "creator", type: "address" },
|
|
1026
1082
|
{ name: "salt", type: "bytes32" },
|
|
1083
|
+
{ name: "cap", type: "uint24" },
|
|
1027
1084
|
],
|
|
1028
1085
|
},
|
|
1029
1086
|
] as const;
|
|
@@ -1044,10 +1101,11 @@ export function fPoolKeyOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">
|
|
|
1044
1101
|
quote: a.quote,
|
|
1045
1102
|
bases,
|
|
1046
1103
|
weights: companions[0]!,
|
|
1047
|
-
|
|
1104
|
+
swapFee: a.swapFee,
|
|
1048
1105
|
policy: a.policy ?? fPoolPolicyFor(c, a.kind),
|
|
1049
1106
|
creator: a.creator,
|
|
1050
1107
|
salt: a.salt ?? ZERO_SALT,
|
|
1108
|
+
cap: a.cap ?? 0,
|
|
1051
1109
|
};
|
|
1052
1110
|
}
|
|
1053
1111
|
|
|
@@ -1094,21 +1152,22 @@ export async function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts: { salt
|
|
|
1094
1152
|
});
|
|
1095
1153
|
const l = logs[0];
|
|
1096
1154
|
if (!l) throw new Error(`logswap: no FInitialize for F pool ${poolId}`);
|
|
1097
|
-
const a = l.args as { quote: Address; creator: Address; bases: readonly Address[]; weights: readonly bigint[];
|
|
1155
|
+
const a = l.args as { quote: Address; creator: Address; bases: readonly Address[]; weights: readonly bigint[]; swapFee: bigint; policy: Address; cap: number };
|
|
1098
1156
|
const key: FPoolKey = {
|
|
1099
1157
|
quote: a.quote,
|
|
1100
1158
|
bases: [...a.bases],
|
|
1101
1159
|
weights: [...a.weights],
|
|
1102
|
-
|
|
1160
|
+
swapFee: a.swapFee,
|
|
1103
1161
|
policy: a.policy,
|
|
1104
1162
|
creator: a.creator,
|
|
1105
1163
|
salt: opts.salt ?? ZERO_SALT,
|
|
1164
|
+
cap: Number(a.cap),
|
|
1106
1165
|
};
|
|
1107
1166
|
if (fPoolIdOfKey(key).toLowerCase() !== poolId.toLowerCase() && opts.salt === undefined) {
|
|
1108
1167
|
// a pad's salt carries its split (decisions 056): rebuild it from the policy's record with the default nonce
|
|
1109
1168
|
try {
|
|
1110
|
-
const [creatorShare,
|
|
1111
|
-
const padKey = { ...key, salt: padSalt({ creatorShare,
|
|
1169
|
+
const [creatorShare, burnShare] = (await c.public.readContract({ address: a.policy, abi: padPolicyAbi, functionName: "splitOf", args: [poolId] } as never)) as readonly [bigint, bigint, bigint];
|
|
1170
|
+
const padKey = { ...key, salt: padSalt({ creatorShare, burnShare }) };
|
|
1112
1171
|
if (fPoolIdOfKey(padKey).toLowerCase() === poolId.toLowerCase()) return padKey;
|
|
1113
1172
|
} catch {
|
|
1114
1173
|
/* not a pad: fall through to the error */
|
|
@@ -1127,11 +1186,38 @@ export async function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts: { salt
|
|
|
1127
1186
|
* precision (~1e-16 relative) is what a seed needs: any reserve is a legal seed, and the mark
|
|
1128
1187
|
* the pool derives from it is the one the sponsor meant to within that.
|
|
1129
1188
|
*/
|
|
1130
|
-
export function fPoolReserveAt(Lj: bigint, x: bigint): bigint {
|
|
1131
|
-
|
|
1189
|
+
export function fPoolReserveAt(Lj: bigint, x: bigint, k = 0): bigint {
|
|
1190
|
+
// on a capped pool (decisions 061) the mark reads the virtual reserve R̃ = R + c, and the seed
|
|
1191
|
+
// puts c = R·k/(1 − k): so the REAL reserve for a mark is L_j·e^{−x}·(1 − k)
|
|
1192
|
+
const e = Math.exp(-Number(x) / 1e18) * (1 - k);
|
|
1132
1193
|
return (Lj * BigInt(Math.round(e * 1e18))) / 10n ** 18n;
|
|
1133
1194
|
}
|
|
1134
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* The seed's cap constant k = e^{Q0/L0 − d} (decisions 061): every leg's phantom is c_j = r_j·k/(1 − k).
|
|
1198
|
+
* 0 on an uncapped pool. Throws when the seed would be priced at or above its cap (Q0/L0 ≥ d), which
|
|
1199
|
+
* the manager refuses.
|
|
1200
|
+
*/
|
|
1201
|
+
export function fPoolCapK(cap: number, Q0: bigint, L0: bigint): number {
|
|
1202
|
+
if (cap === 0) return 0;
|
|
1203
|
+
const e = Number((Q0 * WAD) / L0) / 1e18 - cap / 1e6;
|
|
1204
|
+
if (e >= 0) throw new Error(`logswap: a seed with Q0/L0 = ${Number((Q0 * WAD) / L0) / 1e18} is at or above its cap d = ${cap / 1e6}`);
|
|
1205
|
+
return Math.exp(e);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/** The cap d (1e-6 log units) for a leverage λ > 1 at the floor: d = ln(λ/(λ − 1)) (decisions 061). λ = 2 puts the cap at twice the floor. */
|
|
1209
|
+
export function fPoolCapOfLeverage(lambda: number): number {
|
|
1210
|
+
if (!(lambda > 1)) throw new Error("logswap: leverage must exceed 1 (1 is uncapped: cap 0)");
|
|
1211
|
+
const d = Math.round(Math.log(lambda / (lambda - 1)) * 1e6);
|
|
1212
|
+
if (d < 1 || d > 0xffffff) throw new Error(`logswap: leverage ${lambda} needs a cap outside a uint24 of 1e-6 units`);
|
|
1213
|
+
return d;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/** The leverage at the floor λ = 1/(1 − e^{−d}) for a cap d in 1e-6 log units; Infinity is impossible, 1 is uncapped. */
|
|
1217
|
+
export function fPoolLeverageOfCap(cap: number): number {
|
|
1218
|
+
return cap === 0 ? 1 : 1 / (1 - Math.exp(-cap / 1e6));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1135
1221
|
/**
|
|
1136
1222
|
* Arm the pool: deposit `r0[j]` of each base plus `Q0` of quote against exposure `L0`. Pass either
|
|
1137
1223
|
* the reserves outright (`r0`) or the marks the pool should open at (`x0`, converted here with
|
|
@@ -1143,7 +1229,12 @@ export async function fPoolSeed(
|
|
|
1143
1229
|
c: LogswapClient,
|
|
1144
1230
|
a: { poolId: Hex; L0: bigint; Q0: bigint; account: Address } & ({ r0: bigint[] } | { x0: bigint[]; weights: bigint[] }),
|
|
1145
1231
|
) {
|
|
1146
|
-
|
|
1232
|
+
let r0: bigint[];
|
|
1233
|
+
if ("r0" in a) r0 = a.r0;
|
|
1234
|
+
else {
|
|
1235
|
+
const k = fPoolCapK(Number((await rawFPool(c, a.poolId)).cap), a.Q0, a.L0);
|
|
1236
|
+
r0 = a.x0.map((x, j) => fPoolReserveAt((a.weights[j]! * a.L0) / 10n ** 18n, x, k));
|
|
1237
|
+
}
|
|
1147
1238
|
return writeFPool(c, a.poolId, "seed", [a.L0, r0, a.Q0], a.account);
|
|
1148
1239
|
}
|
|
1149
1240
|
|
|
@@ -1205,8 +1296,9 @@ export async function fPoolEmptyingHarvests(c: LogswapClient, poolId: Hex): Prom
|
|
|
1205
1296
|
* and redeems it with {@link fPoolRedeem} / rolls it with {@link fPoolRollIn} (`gen: g`); their
|
|
1206
1297
|
* dividends on `g` stay claimable ({@link fPoolClaim} with `gen: g`). {@link fPoolClaimsOf}
|
|
1207
1298
|
* lists them;
|
|
1208
|
-
* - the pot: `toPayee` to the payee, `protocolCut` to the collector,
|
|
1209
|
-
*
|
|
1299
|
+
* - the pot, all in quote (decisions 057): `toPayee` to the payee, `protocolCut` to the collector,
|
|
1300
|
+
* `burn` spent buying base at the curve and burning it (the price up, the floor held), and the
|
|
1301
|
+
* rest into the bid (the floor down). No share is minted; L does not move.
|
|
1210
1302
|
*
|
|
1211
1303
|
* A pad's gate is `threshold(gen)` (`PadPolicy.NotGraduated` below it); POL and private carry the
|
|
1212
1304
|
* authority and send it the whole pot; a public pool refuses (`Refused`). A carried holder with no
|
|
@@ -1242,8 +1334,10 @@ export async function fPoolRelaunch(
|
|
|
1242
1334
|
account: Address;
|
|
1243
1335
|
} & ({ r0: bigint[] } | { x0: bigint[]; weights: bigint[] }),
|
|
1244
1336
|
) {
|
|
1245
|
-
const [
|
|
1246
|
-
const
|
|
1337
|
+
const [raw, harvests] = await Promise.all([rawFPool(c, a.poolId), fPoolEmptyingHarvests(c, a.poolId)]);
|
|
1338
|
+
const gen = Number(raw.gen);
|
|
1339
|
+
const k = "r0" in a ? 0 : fPoolCapK(Number(raw.cap), a.Q0, a.L0);
|
|
1340
|
+
const r0 = "r0" in a ? a.r0 : a.x0.map((x, j) => (a.weights[j]! === 0n ? 0n : fPoolReserveAt((a.weights[j]! * a.L0) / WAD, x, k)));
|
|
1247
1341
|
const enc = (functionName: string, args: unknown[]) =>
|
|
1248
1342
|
encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
|
|
1249
1343
|
const calls: Hex[] = [
|
|
@@ -1334,11 +1428,12 @@ export function fPoolLaunchCalls(
|
|
|
1334
1428
|
): Hex[] {
|
|
1335
1429
|
const sum = a.weights.reduce((x, y) => x + y, 0n);
|
|
1336
1430
|
if (sum !== WAD) throw new Error(`logswap: weights sum to ${sum}, expected 1e18`);
|
|
1337
|
-
const
|
|
1431
|
+
const k = "r0" in a ? 0 : fPoolCapK(a.cap ?? 0, a.Q0, a.L0);
|
|
1432
|
+
const given = "r0" in a ? a.r0 : a.x0.map((x, j) => fPoolReserveAt((a.weights[j]! * a.L0) / WAD, x, k));
|
|
1338
1433
|
if (given.length !== a.bases.length) throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
|
|
1339
1434
|
const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
|
|
1340
1435
|
if (!a.policy) throw new Error("logswap: fPoolLaunchCalls needs the key's policy — fPoolLaunch fills it from the deployment");
|
|
1341
|
-
const key = { quote: a.quote, bases, weights: companions[0]!,
|
|
1436
|
+
const key = { quote: a.quote, bases, weights: companions[0]!, swapFee: a.swapFee, policy: a.policy, creator: a.creator, salt: a.salt ?? ZERO_SALT, cap: a.cap ?? 0 };
|
|
1342
1437
|
const enc = (functionName: string, args: unknown[]) =>
|
|
1343
1438
|
encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
|
|
1344
1439
|
const calls: Hex[] = [];
|
|
@@ -1703,7 +1798,9 @@ export interface DiscoveredFPool {
|
|
|
1703
1798
|
quote: Address;
|
|
1704
1799
|
bases: Address[];
|
|
1705
1800
|
weights: bigint[];
|
|
1706
|
-
|
|
1801
|
+
swapFee: bigint;
|
|
1802
|
+
/** The common cap, d in 1e-6 log units; 0 uncapped (decisions 061). */
|
|
1803
|
+
cap: number;
|
|
1707
1804
|
kind: FPoolKind;
|
|
1708
1805
|
/** The pool's policy AT BIRTH — the contract the key names. The LIVE one is {@link getFPool}'s `policy`: the registry's owner may have moved the pool (046, `PolicyMigrated`). */
|
|
1709
1806
|
policy: Address;
|
|
@@ -1727,7 +1824,7 @@ export interface DiscoveredFPool {
|
|
|
1727
1824
|
/**
|
|
1728
1825
|
* Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
|
|
1729
1826
|
* policy, cached. Possible only because the event carries the full key preimage (bases,
|
|
1730
|
-
* weights,
|
|
1827
|
+
* weights, swapFee, policy, cap) — the poolId is a hash and could never be inverted; contracts PR #17
|
|
1731
1828
|
* exists for exactly this call. With `indexer`, the logs come from it (the node when it fails):
|
|
1732
1829
|
* a scan from block 0 per call is what made the first load wait on the node.
|
|
1733
1830
|
*/
|
|
@@ -1754,7 +1851,7 @@ export async function discoverFPools(
|
|
|
1754
1851
|
logs.map(async (l) => {
|
|
1755
1852
|
const a = l.args as {
|
|
1756
1853
|
poolId: Hex; quote: Address; creator: Address; bases: readonly Address[];
|
|
1757
|
-
weights: readonly bigint[];
|
|
1854
|
+
weights: readonly bigint[]; swapFee: bigint; policy: Address; cap: number;
|
|
1758
1855
|
};
|
|
1759
1856
|
const q0 = q0Of.get(a.poolId.toLowerCase());
|
|
1760
1857
|
const kind = await fPoolKindOfPolicy(c, a.policy); // one read per distinct policy, cached
|
|
@@ -1763,7 +1860,8 @@ export async function discoverFPools(
|
|
|
1763
1860
|
quote: a.quote,
|
|
1764
1861
|
bases: [...a.bases],
|
|
1765
1862
|
weights: [...a.weights],
|
|
1766
|
-
|
|
1863
|
+
swapFee: a.swapFee,
|
|
1864
|
+
cap: Number(a.cap ?? 0),
|
|
1767
1865
|
kind,
|
|
1768
1866
|
policy: a.policy,
|
|
1769
1867
|
creator: a.creator,
|
|
@@ -1802,10 +1900,10 @@ export type FPoolRules =
|
|
|
1802
1900
|
/** The pot the next graduation needs: `PadPolicy.threshold(gen)`. */
|
|
1803
1901
|
threshold: bigint;
|
|
1804
1902
|
pot: bigint;
|
|
1805
|
-
/** This pad's split, recorded at its birth from the key's salt and never changed (decisions 056): the creator's cut of the pot, the protocol's, and the burn
|
|
1903
|
+
/** This pad's split, recorded at its birth from the key's salt and never changed (decisions 056, 057): the creator's cut of the pot, the protocol's, and the burn — all fractions of the pot. The rest deepens the bid. */
|
|
1806
1904
|
creatorShare: bigint;
|
|
1807
1905
|
protocolShare: bigint;
|
|
1808
|
-
|
|
1906
|
+
burnShare: bigint;
|
|
1809
1907
|
/** The ranges the collector's policy holds NOW for the pads born after this read: what a new creator may choose. */
|
|
1810
1908
|
ranges: PadRanges;
|
|
1811
1909
|
}
|
|
@@ -1813,7 +1911,7 @@ export type FPoolRules =
|
|
|
1813
1911
|
| { kind: "public"; curated: boolean }
|
|
1814
1912
|
| { kind: "private"; gateMint: boolean; gateSwap: boolean; operator: Address };
|
|
1815
1913
|
|
|
1816
|
-
/** The collector's ranges on a pad's creator choices (`PadPolicy`, decisions 056), WAD fractions
|
|
1914
|
+
/** The collector's ranges on a pad's creator choices (`PadPolicy`, decisions 056, 057), WAD fractions of the pot. */
|
|
1817
1915
|
export interface PadRanges {
|
|
1818
1916
|
/** The protocol's share of the pot, recorded into each pad at birth. */
|
|
1819
1917
|
protocolShare: bigint;
|
|
@@ -1827,21 +1925,21 @@ const MAX_U64 = (1n << 64n) - 1n;
|
|
|
1827
1925
|
const MAX_U128 = (1n << 128n) - 1n;
|
|
1828
1926
|
|
|
1829
1927
|
/**
|
|
1830
|
-
* The salt that carries a pad's split (`PadPolicy.pack`, decisions 056): `creatorShare` in the top 64 bits, `
|
|
1831
|
-
* the next 64 (WAD fractions), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
|
|
1928
|
+
* The salt that carries a pad's split (`PadPolicy.pack`, decisions 056, 057): `creatorShare` in the top 64 bits, `burnShare` in
|
|
1929
|
+
* the next 64 (WAD fractions of the pot), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
|
|
1832
1930
|
* identity, so it is chosen at launch (`FPoolLaunchArgs.salt`) and never changes; the policy refuses a split outside its ranges.
|
|
1833
1931
|
*/
|
|
1834
|
-
export function padSalt(s: { creatorShare: bigint;
|
|
1932
|
+
export function padSalt(s: { creatorShare: bigint; burnShare: bigint; nonce?: bigint }): Hex {
|
|
1835
1933
|
const nonce = s.nonce ?? 0n;
|
|
1836
|
-
if (s.creatorShare < 0n || s.creatorShare > MAX_U64 || s.
|
|
1934
|
+
if (s.creatorShare < 0n || s.creatorShare > MAX_U64 || s.burnShare < 0n || s.burnShare > MAX_U64) throw new Error("logswap: a pad's split is a WAD fraction that fits 64 bits");
|
|
1837
1935
|
if (nonce < 0n || nonce > MAX_U128) throw new Error("logswap: a pad salt's nonce fits 128 bits");
|
|
1838
|
-
return `0x${((s.creatorShare << 192n) | (s.
|
|
1936
|
+
return `0x${((s.creatorShare << 192n) | (s.burnShare << 128n) | nonce).toString(16).padStart(64, "0")}` as Hex;
|
|
1839
1937
|
}
|
|
1840
1938
|
|
|
1841
1939
|
/** The inverse of {@link padSalt}. */
|
|
1842
|
-
export function padSplitOfSalt(salt: Hex): { creatorShare: bigint;
|
|
1940
|
+
export function padSplitOfSalt(salt: Hex): { creatorShare: bigint; burnShare: bigint; nonce: bigint } {
|
|
1843
1941
|
const v = BigInt(salt);
|
|
1844
|
-
return { creatorShare: v >> 192n,
|
|
1942
|
+
return { creatorShare: v >> 192n, burnShare: (v >> 128n) & MAX_U64, nonce: v & MAX_U128 };
|
|
1845
1943
|
}
|
|
1846
1944
|
|
|
1847
1945
|
/** The policy's terms for one pool, by its kind. */
|
|
@@ -1862,9 +1960,9 @@ export async function getFPoolRules(c: LogswapClient, s: Pick<FPoolState, "poolI
|
|
|
1862
1960
|
rd<bigint>(padPolicyAbi, "burnMin"),
|
|
1863
1961
|
rd<bigint>(padPolicyAbi, "burnMax"),
|
|
1864
1962
|
]);
|
|
1865
|
-
const [creatorShare,
|
|
1963
|
+
const [creatorShare, burnShare, protocolShare] = split;
|
|
1866
1964
|
const ranges: PadRanges = { protocolShare: protocolNow, creatorShareMin, creatorShareMax, burnMin, burnMax };
|
|
1867
|
-
return { kind: "pad", gen, rung: gen, threshold, pot, creatorShare, protocolShare,
|
|
1965
|
+
return { kind: "pad", gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnShare, ranges };
|
|
1868
1966
|
}
|
|
1869
1967
|
case "pol": {
|
|
1870
1968
|
const [keepQuote, minQuoteAtSeed] = await Promise.all([rd<bigint>(polPolicyAbi, "keepQuote"), rd<bigint>(polPolicyAbi, "minQuoteAtSeed")]);
|
|
@@ -1952,7 +2050,7 @@ export interface FPolicyTerms {
|
|
|
1952
2050
|
carried: Address;
|
|
1953
2051
|
}
|
|
1954
2052
|
|
|
1955
|
-
/** Every F pool's state in one lens call (the
|
|
2053
|
+
/** Every F pool's raw state in one lens call (the manager's `Pool` struct: quote, swapFee, policy, seeded, gen, n, restructureBlock, asks, cap, Q, bigSigma, L, shares, refLogFloor). */
|
|
1956
2054
|
export async function getFPoolsRaw(c: LogswapClient, poolIds: Hex[]): Promise<readonly unknown[]> {
|
|
1957
2055
|
const { logswapLensAbi } = await import("./generated.js");
|
|
1958
2056
|
return (await c.public.readContract({ address: c.addresses.lens, abi: logswapLensAbi, functionName: "getFPools", args: [poolIds] } as never)) as readonly unknown[];
|