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