@logswap/sdk 0.5.65 → 0.5.67
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 +26 -8
- package/dist/admin.js +27 -7
- package/dist/client.d.ts +1 -1
- package/dist/client.js +13 -1
- package/dist/fpool.d.ts +92 -23
- package/dist/fpool.js +97 -28
- package/dist/generated.d.ts +16 -129
- package/dist/generated.js +4 -80
- package/dist/keys.d.ts +10 -1
- package/dist/keys.js +9 -0
- package/dist/pools.d.ts +26 -0
- package/dist/pools.js +31 -0
- package/dist/write.d.ts +24 -0
- package/dist/write.js +56 -2
- package/package.json +1 -1
- package/src/admin.ts +31 -8
- package/src/client.ts +13 -1
- package/src/fpool.ts +160 -35
- package/src/generated.ts +4 -80
- package/src/keys.ts +11 -1
- package/src/pools.ts +40 -0
- package/src/write.ts +61 -2
package/src/client.ts
CHANGED
|
@@ -165,7 +165,19 @@ export const EXPECTED_VERSION = 5n;
|
|
|
165
165
|
// is the policy at birth, `migrate` moves a pool for the registry's owner; `stamp` / `setAsks` are
|
|
166
166
|
// the policy's; the desk's `setAllowed` / `isAllowed` are `setMinters` / `setTakers` / `isMinter` /
|
|
167
167
|
// `isTaker`; a pad and a POL hold one base (`OneBase`).
|
|
168
|
-
|
|
168
|
+
// 18–19: no role, no kind and no name on the manager (049); the events carry what a reader needs (050).
|
|
169
|
+
// 20 (2026-09-25, logswap-contract #53): policy isolation — `PolicyRegistry.migrate(manager, key, to)`
|
|
170
|
+
// takes the pool's KEY (was its id), `kindOf(policy)` is the tag recorded at approval, the
|
|
171
|
+
// registry emits `Migrated`; `IFPolicy.onMigrate` is brought the creator, authority and name;
|
|
172
|
+
// the desk's lists are keyed by `listEpoch`. The manager's ABI did not move.
|
|
173
|
+
// 21 (2026-09-26, #54): `regenerate` is the generation step — the policy's `Terms` name a
|
|
174
|
+
// `carried` holder (a fifth word, 160 B), every other holder is frozen out pro rata into
|
|
175
|
+
// generation g's bundle and redeems it (`fPoolRedeem` / `fPoolRollIn` with gen = g), and the
|
|
176
|
+
// pool goes on as g + 1. The manager lost `FEE_MIN` / `FEE_MAX` (see keys.ts), `harvestable`
|
|
177
|
+
// (the bid: `getPool().Q` when L > 0), `idOf(FPoolKey)` (hashed locally, `fPoolIdOfKey`) and
|
|
178
|
+
// `reserveOf` (`legOf`'s fourth word). `PadPolicy.rung` is gone: the pad's rung is `gen`, the
|
|
179
|
+
// next threshold `threshold(gen)`. A pad never retires; a public pool refuses `regenerate`.
|
|
180
|
+
export const EXPECTED_F_VERSION = 21n;
|
|
169
181
|
|
|
170
182
|
export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSION): Promise<void> {
|
|
171
183
|
const { cPoolManagerAbi, fPoolManagerAbi } = await import("./generated.js");
|
package/src/fpool.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* `addresses.router`.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import { encodeFunctionData, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
33
|
+
import { encodeAbiParameters, encodeFunctionData, keccak256, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
34
34
|
import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -99,9 +99,12 @@ export interface FPoolState {
|
|
|
99
99
|
/** False before the first seed — and between a retire and the next one. */
|
|
100
100
|
seeded: boolean;
|
|
101
101
|
/**
|
|
102
|
-
* The live GENERATION. Every retire freezes one into a bundle its shares
|
|
103
|
-
*
|
|
104
|
-
* ({@link
|
|
102
|
+
* The live GENERATION. Every retire freezes one into a bundle its shares redeem
|
|
103
|
+
* ({@link fPoolRedeem}) and opens the next; since F version 21 so does every `regenerate`
|
|
104
|
+
* (the generation step, {@link fPoolRegenerate}): the carried holder and the dead shares go on
|
|
105
|
+
* as `gen + 1`, every other holder of `gen` is frozen out into its bundle. The share id carries
|
|
106
|
+
* it ({@link fPoolShareId}), so generations `0 … gen-1` are claims ({@link fPoolClaimsOf}) and
|
|
107
|
+
* `gen` is the pool. On a pad, `gen` is the graduation count — the rung.
|
|
105
108
|
*/
|
|
106
109
|
gen: number;
|
|
107
110
|
totalSupply: bigint;
|
|
@@ -272,7 +275,7 @@ export interface FPoolRoles {
|
|
|
272
275
|
pendingOperator: Address;
|
|
273
276
|
gateMint: boolean;
|
|
274
277
|
gateSwap: boolean;
|
|
275
|
-
/** The policy's own ceiling on a harvest now — the
|
|
278
|
+
/** The policy's own ceiling on a harvest now — the bid ({@link FPoolDescription.harvestable}) narrowed by the policy's rule. */
|
|
276
279
|
maxHarvest: bigint;
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -314,7 +317,12 @@ export interface FPoolDescription {
|
|
|
314
317
|
* from the first wei after it.
|
|
315
318
|
*/
|
|
316
319
|
unharvested: bigint;
|
|
317
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* The most a harvest may take now by the MANAGER's rule: the bid, `Q` once seeded (0 before).
|
|
322
|
+
* The manager's `harvestable()` getter was cut in F version 21; this is the same number, read
|
|
323
|
+
* off `getPool`. The policy may allow less (a pad: the income; a POL: above `keepQuote`) —
|
|
324
|
+
* {@link FPoolRoles.maxHarvest}.
|
|
325
|
+
*/
|
|
318
326
|
harvestable: bigint;
|
|
319
327
|
/** The dead shares' banked dividend — a pad's pot, the creator's income until a rung is climbed. */
|
|
320
328
|
pot: bigint;
|
|
@@ -328,10 +336,12 @@ export async function describeFPool(c: LogswapClient, s: FPoolState): Promise<FP
|
|
|
328
336
|
functionName,
|
|
329
337
|
args,
|
|
330
338
|
} as never) as Promise<T>;
|
|
331
|
-
// an unseeded pool has no L: nothing earned, nothing to harvest, no pot
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
339
|
+
// an unseeded pool has no L: nothing earned, nothing to harvest, no pot. The bid is the
|
|
340
|
+
// manager's ceiling (`harvestable()` until F version 21, which cut the getter): Q once seeded.
|
|
341
|
+
const harvestable = s.L > 0n ? s.Q : 0n;
|
|
342
|
+
const [unharvested, pot] = s.L > 0n
|
|
343
|
+
? await Promise.all([rd<bigint>("unharvested"), rd<bigint>("dividendOf", [s.poolId, BigInt(s.gen), DEAD_ADDRESS])])
|
|
344
|
+
: [0n, 0n];
|
|
335
345
|
return {
|
|
336
346
|
shape: s.kind === "pad" ? "launch": "basket", // the kind, not n = 1
|
|
337
347
|
legs: s.bases.length,
|
|
@@ -985,7 +995,50 @@ export function sortFPoolLegs<T>(bases: Address[], ...companions: T[][]): { base
|
|
|
985
995
|
}
|
|
986
996
|
|
|
987
997
|
/** The key as the manager hashes it: legs sorted, the kind's policy filled in when none is named; no kind in it (049). */
|
|
988
|
-
|
|
998
|
+
/**
|
|
999
|
+
* The tuple an F pool's id hashes — `src/types/FPoolKey.sol`, field order load-bearing (it is
|
|
1000
|
+
* the ABI encoding order). Stored by the manager at `initialize` and never in calldata again,
|
|
1001
|
+
* except `PolicyRegistry.migrate` (F version 20), which is handed it to check against the id.
|
|
1002
|
+
*/
|
|
1003
|
+
export interface FPoolKey {
|
|
1004
|
+
quote: Address;
|
|
1005
|
+
/** Sorted ascending, unique. */
|
|
1006
|
+
bases: Address[];
|
|
1007
|
+
/** WAD, aligned with `bases`, summing to 1e18. */
|
|
1008
|
+
weights: bigint[];
|
|
1009
|
+
phi: bigint;
|
|
1010
|
+
/** The policy AT BIRTH — not the live one after a move. */
|
|
1011
|
+
policy: Address;
|
|
1012
|
+
creator: Address;
|
|
1013
|
+
salt: Hex;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const F_POOL_KEY_ABI = [
|
|
1017
|
+
{
|
|
1018
|
+
type: "tuple",
|
|
1019
|
+
components: [
|
|
1020
|
+
{ name: "quote", type: "address" },
|
|
1021
|
+
{ name: "bases", type: "address[]" },
|
|
1022
|
+
{ name: "weights", type: "uint256[]" },
|
|
1023
|
+
{ name: "phi", type: "uint256" },
|
|
1024
|
+
{ name: "policy", type: "address" },
|
|
1025
|
+
{ name: "creator", type: "address" },
|
|
1026
|
+
{ name: "salt", type: "bytes32" },
|
|
1027
|
+
],
|
|
1028
|
+
},
|
|
1029
|
+
] as const;
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* An F pool's id: `keccak256(abi.encode(key))`, offline — what `initialize` computes inline. The
|
|
1033
|
+
* manager's `idOf(FPoolKey)` getter was cut in F version 21; this is the same hash (checked
|
|
1034
|
+
* against Foundry's encoder and the local deployment's pools in the tests). The key must be the
|
|
1035
|
+
* one the pool was initialized with: legs sorted ({@link fPoolKeyOf} sorts them), the birth policy.
|
|
1036
|
+
*/
|
|
1037
|
+
export function fPoolIdOfKey(key: FPoolKey): Hex {
|
|
1038
|
+
return keccak256(encodeAbiParameters(F_POOL_KEY_ABI, [key]));
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export function fPoolKeyOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">): FPoolKey {
|
|
989
1042
|
const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
|
|
990
1043
|
return {
|
|
991
1044
|
quote: a.quote,
|
|
@@ -1014,15 +1067,47 @@ export async function fPoolInitialize(c: LogswapClient, a: FPoolCreateArgs) {
|
|
|
1014
1067
|
return c.wallet!.writeContract(request as never);
|
|
1015
1068
|
}
|
|
1016
1069
|
|
|
1017
|
-
/**
|
|
1070
|
+
/**
|
|
1071
|
+
* The pool id is the key's hash — pure, so it can be read before or after creation. Computed
|
|
1072
|
+
* locally since F version 21 ({@link fPoolIdOfKey}; the manager's `idOf` is gone): no RPC. Still
|
|
1073
|
+
* async, so callers written against the read keep working.
|
|
1074
|
+
*/
|
|
1018
1075
|
export async function fPoolIdOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">): Promise<Hex> {
|
|
1019
|
-
|
|
1020
|
-
|
|
1076
|
+
return fPoolIdOfKey(fPoolKeyOf(c, a));
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Rebuild a live pool's KEY from its id: the `FInitialize` log carries every field but the salt,
|
|
1081
|
+
* which is taken from `opts.salt` (zero by default — every pool the SDK's create flow makes
|
|
1082
|
+
* without naming one). The result is checked against the id, so a wrong salt throws rather than
|
|
1083
|
+
* returning a key that names another pool. What {@link fPoolMigrate} needs when handed an id.
|
|
1084
|
+
*/
|
|
1085
|
+
export async function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts: { salt?: Hex; fromBlock?: bigint } = {}): Promise<FPoolKey> {
|
|
1086
|
+
type Ev = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "FInitialize" }>;
|
|
1087
|
+
const ev = fPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "FInitialize")!;
|
|
1088
|
+
const logs = await c.public.getLogs({
|
|
1021
1089
|
address: c.addresses.fPoolManager!,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1090
|
+
event: ev,
|
|
1091
|
+
args: { poolId },
|
|
1092
|
+
fromBlock: opts.fromBlock ?? 0n,
|
|
1093
|
+
toBlock: "latest",
|
|
1094
|
+
});
|
|
1095
|
+
const l = logs[0];
|
|
1096
|
+
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[]; phi: bigint; policy: Address };
|
|
1098
|
+
const key: FPoolKey = {
|
|
1099
|
+
quote: a.quote,
|
|
1100
|
+
bases: [...a.bases],
|
|
1101
|
+
weights: [...a.weights],
|
|
1102
|
+
phi: a.phi,
|
|
1103
|
+
policy: a.policy,
|
|
1104
|
+
creator: a.creator,
|
|
1105
|
+
salt: opts.salt ?? ZERO_SALT,
|
|
1106
|
+
};
|
|
1107
|
+
if (fPoolIdOfKey(key).toLowerCase() !== poolId.toLowerCase()) {
|
|
1108
|
+
throw new Error(`logswap: F pool ${poolId} was not born with salt ${key.salt} — pass its salt, or its whole key`);
|
|
1109
|
+
}
|
|
1110
|
+
return key;
|
|
1026
1111
|
}
|
|
1027
1112
|
|
|
1028
1113
|
/**
|
|
@@ -1068,7 +1153,7 @@ export async function fPoolRetireRaw(c: LogswapClient, a: { poolId: Hex; account
|
|
|
1068
1153
|
* Retire the live generation as ONE transaction: the harvest that empties the bid
|
|
1069
1154
|
* ({@link fPoolEmptyingHarvests} — a dividend to every share; none when it is already empty),
|
|
1070
1155
|
* then `retire` — the manager's `multicall`, the operator as sender. What a sponsor's
|
|
1071
|
-
* "retire" button should call. Throws before sending on a pad
|
|
1156
|
+
* "retire" button should call. Throws before sending on a pad: a pad never retires (F version 21).
|
|
1072
1157
|
*/
|
|
1073
1158
|
export async function fPoolRetire(c: LogswapClient, a: { poolId: Hex; account: Address }) {
|
|
1074
1159
|
const harvests = await fPoolEmptyingHarvests(c, a.poolId);
|
|
@@ -1083,26 +1168,39 @@ export async function fPoolRetire(c: LogswapClient, a: { poolId: Hex; account: A
|
|
|
1083
1168
|
* The harvests that empty an unlocked pool's bid, in order — what a relaunch sends before its
|
|
1084
1169
|
* `retire` (a retire needs an empty bid on every pool). Since F version 14 one
|
|
1085
1170
|
* `harvest(Q)` does it: the protocol's cut leaves the pool with the amount (with no collector
|
|
1086
|
-
* there is no cut). `[]` when the bid is already empty. Throws on a pad
|
|
1087
|
-
*
|
|
1171
|
+
* there is no cut). `[]` when the bid is already empty. Throws on a pad (F version 21: a pad
|
|
1172
|
+
* never retires, whatever its bid), and when the pool's policy keeps a floor on the bid
|
|
1088
1173
|
* (`PolPolicy.keepQuote`): a `retire` under that policy waits for the market.
|
|
1089
1174
|
*/
|
|
1090
1175
|
export async function fPoolEmptyingHarvests(c: LogswapClient, poolId: Hex): Promise<bigint[]> {
|
|
1091
1176
|
const s = await getFPool(c, poolId);
|
|
1177
|
+
// F version 21: `PadPolicy` refuses `retire` at any bid — a pad graduates (regenerate), it
|
|
1178
|
+
// never closes; a fresh start is a new pad with a new id
|
|
1179
|
+
if (s.kind === "pad") throw new Error("logswap: a pad never retires (F version 21) — it graduates by regenerate; a fresh start is a new pad");
|
|
1092
1180
|
if (s.Q === 0n) return [];
|
|
1093
|
-
if (s.kind === "pad") throw new Error("logswap: a pad is retired only once its bid is empty");
|
|
1094
1181
|
const { maxHarvest } = await getFPoolRoles(c, poolId, s);
|
|
1095
1182
|
if (maxHarvest < s.Q) throw new Error("logswap: the pool's policy keeps a floor on the bid — it cannot be harvested empty");
|
|
1096
1183
|
return [s.Q];
|
|
1097
1184
|
}
|
|
1098
1185
|
|
|
1099
1186
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1187
|
+
* The GENERATION STEP (F version 21) — on a pad, its graduation. The manager harvests the
|
|
1188
|
+
* generation's income first, releases the dead shares' pot, and asks the policy
|
|
1189
|
+
* (`onRegenerate`), whose {@link FPolicyTerms} name a `carried` holder. Then, for live
|
|
1190
|
+
* generation `g`:
|
|
1191
|
+
*
|
|
1192
|
+
* - the carried holder's and the dead shares' `g` shares are re-minted 1:1 as `g + 1`: they go on
|
|
1193
|
+
* as the pool;
|
|
1194
|
+
* - every OTHER holder of `g` is frozen out pro rata into generation `g`'s bundle (`Retired(g, …)`)
|
|
1195
|
+
* and redeems it with {@link fPoolRedeem} / rolls it with {@link fPoolRollIn} (`gen: g`); their
|
|
1196
|
+
* dividends on `g` stay claimable ({@link fPoolClaim} with `gen: g`). {@link fPoolClaimsOf}
|
|
1197
|
+
* lists them;
|
|
1198
|
+
* - the pot: `toPayee` to the payee, `protocolCut` to the collector, the rest bought in as dead
|
|
1199
|
+
* shares of `g + 1`; then the burn.
|
|
1200
|
+
*
|
|
1201
|
+
* A pad's gate is `threshold(gen)` (`PadPolicy.NotGraduated` below it); POL and private carry the
|
|
1202
|
+
* authority and send it the whole pot; a public pool refuses (`Refused`). A carried holder with no
|
|
1203
|
+
* share is `BadTerms`. Whoever runs the pool.
|
|
1106
1204
|
*/
|
|
1107
1205
|
export async function fPoolRegenerate(c: LogswapClient, a: { poolId: Hex; to?: Address; account: Address }) {
|
|
1108
1206
|
return writeFPool(c, a.poolId, "regenerate", [a.to ?? a.account], a.account);
|
|
@@ -1534,13 +1632,26 @@ export interface FPoolControlled {
|
|
|
1534
1632
|
pool: DiscoveredFPool;
|
|
1535
1633
|
roles: FPoolRoles;
|
|
1536
1634
|
state: FPoolState;
|
|
1537
|
-
/** The policy's numbers for this pool's kind — a pad's rung and threshold, a POL's floors, the desk's list flags. */
|
|
1635
|
+
/** The policy's numbers for this pool's kind — a pad's gen (its rung) and threshold, a POL's floors, the desk's list flags. */
|
|
1538
1636
|
rules: FPoolRules;
|
|
1539
1637
|
}
|
|
1540
1638
|
|
|
1541
1639
|
/** What a pool's policy says about it, by kind: the terms a buyer reads before entering. */
|
|
1542
1640
|
export type FPoolRules =
|
|
1543
|
-
| {
|
|
1641
|
+
| {
|
|
1642
|
+
kind: "pad";
|
|
1643
|
+
harvestCut: bigint;
|
|
1644
|
+
/** The pad's graduation count: the pool's generation (F version 21 — `PadPolicy.rung` is gone). */
|
|
1645
|
+
gen: number;
|
|
1646
|
+
/** @deprecated Equal to {@link gen} since F version 21, kept for callers; read `gen`. */
|
|
1647
|
+
rung: number;
|
|
1648
|
+
/** The pot the next graduation needs: `PadPolicy.threshold(gen)`. */
|
|
1649
|
+
threshold: bigint;
|
|
1650
|
+
pot: bigint;
|
|
1651
|
+
creatorShare: bigint;
|
|
1652
|
+
protocolShare: bigint;
|
|
1653
|
+
burnPerRung: bigint;
|
|
1654
|
+
}
|
|
1544
1655
|
| { kind: "pol"; harvestCut: bigint; keepQuote: bigint; minQuoteAtSeed: bigint }
|
|
1545
1656
|
| { kind: "public"; harvestCut: bigint; curated: boolean }
|
|
1546
1657
|
| { kind: "private"; harvestCut: bigint; gateMint: boolean; gateSwap: boolean; operator: Address };
|
|
@@ -1552,15 +1663,16 @@ export async function getFPoolRules(c: LogswapClient, s: Pick<FPoolState, "poolI
|
|
|
1552
1663
|
const harvestCut = await rd<bigint>(fPolicyBaseAbi, "harvestCut");
|
|
1553
1664
|
switch (s.kind) {
|
|
1554
1665
|
case "pad": {
|
|
1555
|
-
|
|
1556
|
-
|
|
1666
|
+
// the rung is the manager's generation (F version 21): a move carries it, nothing to ask the policy
|
|
1667
|
+
const gen = s.gen;
|
|
1668
|
+
const [creatorShare, protocolShare, burnPerRung, pot, threshold] = await Promise.all([
|
|
1557
1669
|
rd<bigint>(padPolicyAbi, "creatorShare"),
|
|
1558
1670
|
rd<bigint>(padPolicyAbi, "protocolShare"),
|
|
1559
1671
|
rd<bigint>(padPolicyAbi, "burnPerRung"),
|
|
1560
|
-
fPoolDividendOf(c, s.poolId, DEAD_ADDRESS,
|
|
1672
|
+
fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, gen),
|
|
1673
|
+
rd<bigint>(padPolicyAbi, "threshold", [BigInt(gen)]),
|
|
1561
1674
|
]);
|
|
1562
|
-
|
|
1563
|
-
return { kind: "pad", harvestCut, rung: Number(rung), threshold, pot, creatorShare, protocolShare, burnPerRung };
|
|
1675
|
+
return { kind: "pad", harvestCut, gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnPerRung };
|
|
1564
1676
|
}
|
|
1565
1677
|
case "pol": {
|
|
1566
1678
|
const [keepQuote, minQuoteAtSeed] = await Promise.all([rd<bigint>(polPolicyAbi, "keepQuote"), rd<bigint>(polPolicyAbi, "minQuoteAtSeed")]);
|
|
@@ -1640,6 +1752,19 @@ export async function fPoolShareHolders(
|
|
|
1640
1752
|
.sort((a, b) => (b.shares > a.shares ? 1 : -1));
|
|
1641
1753
|
}
|
|
1642
1754
|
|
|
1755
|
+
/**
|
|
1756
|
+
* What a policy answers `onRegenerate` with (and `onHarvest`'s split, the first four words):
|
|
1757
|
+
* `IFPolicy.Terms`. `carried` is F version 21's fifth word — the holder the next generation
|
|
1758
|
+
* carries beside the dead shares; the reply is exactly 160 bytes.
|
|
1759
|
+
*/
|
|
1760
|
+
export interface FPolicyTerms {
|
|
1761
|
+
payee: Address;
|
|
1762
|
+
toPayee: bigint;
|
|
1763
|
+
protocolCut: bigint;
|
|
1764
|
+
burn: bigint;
|
|
1765
|
+
carried: Address;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1643
1768
|
/** Every F pool's state in one lens call (the raw manager struct: quote, phi, authority, kind, seeded, gen, n, Q, L, shares, theta0, bigSigma, harvestedTheta). */
|
|
1644
1769
|
export async function getFPoolsRaw(c: LogswapClient, poolIds: Hex[]): Promise<readonly unknown[]> {
|
|
1645
1770
|
const { logswapLensAbi } = await import("./generated.js");
|
package/src/generated.ts
CHANGED
|
@@ -1100,20 +1100,6 @@ export const fPoolManagerAbi = [
|
|
|
1100
1100
|
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
1101
1101
|
stateMutability: 'view',
|
|
1102
1102
|
},
|
|
1103
|
-
{
|
|
1104
|
-
type: 'function',
|
|
1105
|
-
inputs: [],
|
|
1106
|
-
name: 'FEE_MAX',
|
|
1107
|
-
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
1108
|
-
stateMutability: 'view',
|
|
1109
|
-
},
|
|
1110
|
-
{
|
|
1111
|
-
type: 'function',
|
|
1112
|
-
inputs: [],
|
|
1113
|
-
name: 'FEE_MIN',
|
|
1114
|
-
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
1115
|
-
stateMutability: 'view',
|
|
1116
|
-
},
|
|
1117
1103
|
{
|
|
1118
1104
|
type: 'function',
|
|
1119
1105
|
inputs: [],
|
|
@@ -1330,35 +1316,6 @@ export const fPoolManagerAbi = [
|
|
|
1330
1316
|
outputs: [],
|
|
1331
1317
|
stateMutability: 'nonpayable',
|
|
1332
1318
|
},
|
|
1333
|
-
{
|
|
1334
|
-
type: 'function',
|
|
1335
|
-
inputs: [{ name: 'poolId', internalType: 'bytes32', type: 'bytes32' }],
|
|
1336
|
-
name: 'harvestable',
|
|
1337
|
-
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
1338
|
-
stateMutability: 'view',
|
|
1339
|
-
},
|
|
1340
|
-
{
|
|
1341
|
-
type: 'function',
|
|
1342
|
-
inputs: [
|
|
1343
|
-
{
|
|
1344
|
-
name: 'key',
|
|
1345
|
-
internalType: 'struct FPoolKey',
|
|
1346
|
-
type: 'tuple',
|
|
1347
|
-
components: [
|
|
1348
|
-
{ name: 'quote', internalType: 'address', type: 'address' },
|
|
1349
|
-
{ name: 'bases', internalType: 'address[]', type: 'address[]' },
|
|
1350
|
-
{ name: 'weights', internalType: 'uint256[]', type: 'uint256[]' },
|
|
1351
|
-
{ name: 'phi', internalType: 'uint256', type: 'uint256' },
|
|
1352
|
-
{ name: 'policy', internalType: 'address', type: 'address' },
|
|
1353
|
-
{ name: 'creator', internalType: 'address', type: 'address' },
|
|
1354
|
-
{ name: 'salt', internalType: 'bytes32', type: 'bytes32' },
|
|
1355
|
-
],
|
|
1356
|
-
},
|
|
1357
|
-
],
|
|
1358
|
-
name: 'idOf',
|
|
1359
|
-
outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }],
|
|
1360
|
-
stateMutability: 'pure',
|
|
1361
|
-
},
|
|
1362
1319
|
{
|
|
1363
1320
|
type: 'function',
|
|
1364
1321
|
inputs: [
|
|
@@ -1516,16 +1473,6 @@ export const fPoolManagerAbi = [
|
|
|
1516
1473
|
],
|
|
1517
1474
|
stateMutability: 'view',
|
|
1518
1475
|
},
|
|
1519
|
-
{
|
|
1520
|
-
type: 'function',
|
|
1521
|
-
inputs: [
|
|
1522
|
-
{ name: 'poolId', internalType: 'bytes32', type: 'bytes32' },
|
|
1523
|
-
{ name: 'j', internalType: 'uint256', type: 'uint256' },
|
|
1524
|
-
],
|
|
1525
|
-
name: 'reserveOf',
|
|
1526
|
-
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
1527
|
-
stateMutability: 'view',
|
|
1528
|
-
},
|
|
1529
1476
|
{
|
|
1530
1477
|
type: 'function',
|
|
1531
1478
|
inputs: [{ name: 'poolId', internalType: 'bytes32', type: 'bytes32' }],
|
|
@@ -3951,6 +3898,7 @@ export const padPolicyAbi = [
|
|
|
3951
3898
|
{ name: 'toPayee', internalType: 'uint256', type: 'uint256' },
|
|
3952
3899
|
{ name: 'protocolCut', internalType: 'uint256', type: 'uint256' },
|
|
3953
3900
|
{ name: 'burn', internalType: 'uint256', type: 'uint256' },
|
|
3901
|
+
{ name: 'carried', internalType: 'address', type: 'address' },
|
|
3954
3902
|
],
|
|
3955
3903
|
},
|
|
3956
3904
|
],
|
|
@@ -3994,13 +3942,6 @@ export const padPolicyAbi = [
|
|
|
3994
3942
|
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
|
|
3995
3943
|
stateMutability: 'view',
|
|
3996
3944
|
},
|
|
3997
|
-
{
|
|
3998
|
-
type: 'function',
|
|
3999
|
-
inputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }],
|
|
4000
|
-
name: 'rung',
|
|
4001
|
-
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
|
|
4002
|
-
stateMutability: 'view',
|
|
4003
|
-
},
|
|
4004
3945
|
{
|
|
4005
3946
|
type: 'function',
|
|
4006
3947
|
inputs: [{ name: '_harvestCut', internalType: 'uint256', type: 'uint256' }],
|
|
@@ -4178,26 +4119,6 @@ export const padPolicyAbi = [
|
|
|
4178
4119
|
],
|
|
4179
4120
|
name: 'QuoteListed',
|
|
4180
4121
|
},
|
|
4181
|
-
{
|
|
4182
|
-
type: 'event',
|
|
4183
|
-
anonymous: false,
|
|
4184
|
-
inputs: [
|
|
4185
|
-
{
|
|
4186
|
-
name: 'poolId',
|
|
4187
|
-
internalType: 'bytes32',
|
|
4188
|
-
type: 'bytes32',
|
|
4189
|
-
indexed: true,
|
|
4190
|
-
},
|
|
4191
|
-
{ name: 'from', internalType: 'address', type: 'address', indexed: true },
|
|
4192
|
-
{
|
|
4193
|
-
name: 'rung',
|
|
4194
|
-
internalType: 'uint256',
|
|
4195
|
-
type: 'uint256',
|
|
4196
|
-
indexed: false,
|
|
4197
|
-
},
|
|
4198
|
-
],
|
|
4199
|
-
name: 'RungCarried',
|
|
4200
|
-
},
|
|
4201
4122
|
{
|
|
4202
4123
|
type: 'event',
|
|
4203
4124
|
anonymous: false,
|
|
@@ -4470,6 +4391,7 @@ export const polPolicyAbi = [
|
|
|
4470
4391
|
{ name: 'toPayee', internalType: 'uint256', type: 'uint256' },
|
|
4471
4392
|
{ name: 'protocolCut', internalType: 'uint256', type: 'uint256' },
|
|
4472
4393
|
{ name: 'burn', internalType: 'uint256', type: 'uint256' },
|
|
4394
|
+
{ name: 'carried', internalType: 'address', type: 'address' },
|
|
4473
4395
|
],
|
|
4474
4396
|
},
|
|
4475
4397
|
],
|
|
@@ -5240,6 +5162,7 @@ export const privatePolicyAbi = [
|
|
|
5240
5162
|
{ name: 'toPayee', internalType: 'uint256', type: 'uint256' },
|
|
5241
5163
|
{ name: 'protocolCut', internalType: 'uint256', type: 'uint256' },
|
|
5242
5164
|
{ name: 'burn', internalType: 'uint256', type: 'uint256' },
|
|
5165
|
+
{ name: 'carried', internalType: 'address', type: 'address' },
|
|
5243
5166
|
],
|
|
5244
5167
|
},
|
|
5245
5168
|
],
|
|
@@ -5770,6 +5693,7 @@ export const publicPolicyAbi = [
|
|
|
5770
5693
|
{ name: 'toPayee', internalType: 'uint256', type: 'uint256' },
|
|
5771
5694
|
{ name: 'protocolCut', internalType: 'uint256', type: 'uint256' },
|
|
5772
5695
|
{ name: 'burn', internalType: 'uint256', type: 'uint256' },
|
|
5696
|
+
{ name: 'carried', internalType: 'address', type: 'address' },
|
|
5773
5697
|
],
|
|
5774
5698
|
},
|
|
5775
5699
|
],
|
package/src/keys.ts
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
import { encodeAbiParameters, keccak256, type Address, type Hex } from "viem";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The fee domain of the family, `FeeParams.FEE_MIN` / `FEE_MAX` (decisions 043): one bound for a
|
|
18
|
+
* C market's φ and an F pool's alike, WAD. 1 bp and 1%. The F manager no longer answers
|
|
19
|
+
* `FEE_MIN()` / `FEE_MAX()` (F version 21 cut the getters for bytecode); every F policy still
|
|
20
|
+
* checks φ against these at `onInitialize`, and the C side re-exposes them through the lens's
|
|
21
|
+
* `cParams()`. Constants here, so a create form bounds φ with no read.
|
|
22
|
+
*/
|
|
23
|
+
export const FEE_MIN = 10n ** 14n;
|
|
24
|
+
export const FEE_MAX = 10n ** 16n;
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
27
|
* The economic tuple that IS the market. Field order is load-bearing: it is the ABI encoding order,
|
|
18
28
|
* so it must match `src/types/PoolKey.sol` exactly.
|
|
@@ -23,7 +33,7 @@ export interface PoolKey {
|
|
|
23
33
|
/** Log-tick spacing, WAD. The LP-boundary ladder — position floors and caps. */
|
|
24
34
|
tickSpacing: bigint;
|
|
25
35
|
/**
|
|
26
|
-
* The swap fee, WAD, fixed for the life of the market:
|
|
36
|
+
* The swap fee, WAD, fixed for the life of the market: {@link FEE_MIN} (1 bp) ≤ φ ≤ {@link FEE_MAX} (1%),
|
|
27
37
|
* or `0n`, which selects `DEFAULT_FEE` (2 bp) at read time. Four fields since C version 4
|
|
28
38
|
*: `kappa` and `alpha` left with the variance kernel, and `phiMin` — a floor with
|
|
29
39
|
* nothing above it — became `phi`. Every pool id changed with the shape.
|
package/src/pools.ts
CHANGED
|
@@ -82,6 +82,46 @@ export function priceScale(baseDecimals: number, quoteDecimals: number): number
|
|
|
82
82
|
return 10 ** (baseDecimals - quoteDecimals);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** The C manager's log-price wall: |x| ≤ 46 on the WEI ratio (`MAX_ABS_FLOOR`). */
|
|
86
|
+
export const C_MAX_ABS_X = 46;
|
|
87
|
+
|
|
88
|
+
/** The least derived base reserve a C book keeps, in base wei (`MIN_RESERVE`, decisions 023). */
|
|
89
|
+
export const C_MIN_RESERVE = 10n ** 6n;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The prices a C market can hold, in human units — quote per whole base token (decisions 054).
|
|
93
|
+
*
|
|
94
|
+
* - `listMin`, `listMax`: the wall, `10^(base − quote) · e^{∓46}`. A market cannot be initialized,
|
|
95
|
+
* and no floor or cap placed, outside it. An 18-dec token against 6-dec USDC cannot list under
|
|
96
|
+
* 1.05e-8; against an 18-dec quote the same token lists down to 1.05e-20.
|
|
97
|
+
* - `ceiling`: the highest price the book can trade to with `lActive` of active exposure (quote
|
|
98
|
+
* wei): the derived reserve `lActive / p` must stay at or above `C_MIN_RESERVE` base wei, so
|
|
99
|
+
* `ceiling = lActive · 10^(base − quote) / 1e6`, capped by `listMax`. In whole units that is
|
|
100
|
+
* `L · 10^(base − 6)`: the quote's decimals cancel and the base's set it — harmless on an
|
|
101
|
+
* 18-dec base, but a WBTC book of 1,000 USDC of depth stops at 100,000. Omit `lActive` for the
|
|
102
|
+
* wall alone.
|
|
103
|
+
*
|
|
104
|
+
* Floats, for display and warnings; the chain is the authority.
|
|
105
|
+
*/
|
|
106
|
+
export function cPriceRange(
|
|
107
|
+
baseDecimals: number,
|
|
108
|
+
quoteDecimals: number,
|
|
109
|
+
lActive?: bigint,
|
|
110
|
+
): { listMin: number; listMax: number; ceiling: number } {
|
|
111
|
+
const scale = priceScale(baseDecimals, quoteDecimals);
|
|
112
|
+
const listMin = Math.exp(-C_MAX_ABS_X) * scale;
|
|
113
|
+
const listMax = Math.exp(C_MAX_ABS_X) * scale;
|
|
114
|
+
const ceiling =
|
|
115
|
+
lActive === undefined || lActive === 0n ? listMax : Math.min(listMax, (Number(lActive) / Number(C_MIN_RESERVE)) * scale);
|
|
116
|
+
return { listMin, listMax, ceiling };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether a C market can be initialized at `price` (human units) for this pair's decimals. */
|
|
120
|
+
export function cListable(price: number, baseDecimals: number, quoteDecimals: number): boolean {
|
|
121
|
+
const { listMin, listMax } = cPriceRange(baseDecimals, quoteDecimals);
|
|
122
|
+
return price >= listMin && price <= listMax;
|
|
123
|
+
}
|
|
124
|
+
|
|
85
125
|
/**
|
|
86
126
|
* The model-free LP edge, $F - \tfrac12\Sigma$ — the protocol's headline metric, in quote per unit
|
|
87
127
|
* of exposure.
|
package/src/write.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import type { Address, Hash, Hex } from "viem";
|
|
17
17
|
import type { LogswapClient } from "./client.js";
|
|
18
|
+
import { cPoolManagerAbi, fPoolManagerAbi } from "./generated.js";
|
|
18
19
|
|
|
19
20
|
/** Disables the deadline check. Deliberate opt-out, never a default. */
|
|
20
21
|
export const NEVER = (1n << 256n) - 1n;
|
|
@@ -87,6 +88,64 @@ export function withSlippage(quoted: bigint, bps: number, side: "output" | "inpu
|
|
|
87
88
|
return side === "output" ? (quoted * (10_000n - b)) / 10_000n : (quoted * (10_000n + b) + 9_999n) / 10_000n;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
type AbiError = { type: "error"; name: string; inputs: readonly { type: string }[] };
|
|
92
|
+
|
|
93
|
+
/** Permit2's pull errors — Uniswap's and the devnet mock's — which a router write can revert with. */
|
|
94
|
+
const permit2Errors: readonly AbiError[] = [
|
|
95
|
+
{ type: "error", name: "AllowanceExpired", inputs: [{ type: "uint256" }] },
|
|
96
|
+
{ type: "error", name: "InsufficientAllowance", inputs: [{ type: "uint256" }] },
|
|
97
|
+
{ type: "error", name: "AllowanceExpired", inputs: [] },
|
|
98
|
+
{ type: "error", name: "NotAllowed", inputs: [] },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const errorKey = (e: AbiError) => `${e.name}(${e.inputs.map((i) => i.type).join(",")})`;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Every custom error a router call can revert with that the router's own ABI does not declare: the
|
|
105
|
+
* C and F managers' (a mint, a swap or a settlement bubbles them up unchanged) and Permit2's.
|
|
106
|
+
* Without them viem can name only the router's errors, and anything raised in the manager comes
|
|
107
|
+
* back as a bare selector.
|
|
108
|
+
*/
|
|
109
|
+
export const routerRevertErrors: readonly AbiError[] = (() => {
|
|
110
|
+
const seen = new Set<string>();
|
|
111
|
+
const out: AbiError[] = [];
|
|
112
|
+
for (const x of [...cPoolManagerAbi, ...fPoolManagerAbi, ...permit2Errors] as readonly { type: string }[]) {
|
|
113
|
+
if (x.type !== "error") continue;
|
|
114
|
+
const e = x as AbiError;
|
|
115
|
+
if (!seen.has(errorKey(e))) {
|
|
116
|
+
seen.add(errorKey(e));
|
|
117
|
+
out.push(e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
/** `abi` plus every error in {@link routerRevertErrors} it does not already declare. */
|
|
124
|
+
export function withRevertErrors(abi: readonly unknown[]): readonly unknown[] {
|
|
125
|
+
const have = new Set(
|
|
126
|
+
(abi as readonly { type: string }[]).filter((x) => x.type === "error").map((x) => errorKey(x as AbiError)),
|
|
127
|
+
);
|
|
128
|
+
return [...abi, ...routerRevertErrors.filter((e) => !have.has(errorKey(e)))];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The decoded revert of a contract call, e.g. `SlippageIn()` or `AllowanceExpired(1700000000)`,
|
|
133
|
+
* found however deep viem nested it; the raw selector when the error is not in the ABI; undefined
|
|
134
|
+
* when the failure was not a revert. viem's `message` puts the name on its second line, so a UI
|
|
135
|
+
* that shows only the first line ("The contract function … reverted.") loses it.
|
|
136
|
+
*/
|
|
137
|
+
export function revertName(e: unknown): string | undefined {
|
|
138
|
+
const seen = new Set<unknown>();
|
|
139
|
+
for (let x: unknown = e; x && typeof x === "object" && !seen.has(x); x = (x as { cause?: unknown }).cause) {
|
|
140
|
+
seen.add(x);
|
|
141
|
+
const o = x as { name?: string; data?: { errorName?: string; args?: readonly unknown[] }; signature?: string };
|
|
142
|
+
if (o.name !== "ContractFunctionRevertedError") continue;
|
|
143
|
+
if (o.data?.errorName) return `${o.data.errorName}(${(o.data.args ?? []).map(String).join(", ")})`;
|
|
144
|
+
if (o.signature) return o.signature;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
90
149
|
/**
|
|
91
150
|
* Send a router write with a gas buffer over an explicit estimate.
|
|
92
151
|
*
|
|
@@ -102,7 +161,7 @@ export async function sendRouterWrite(
|
|
|
102
161
|
const { account } = requireWallet(c);
|
|
103
162
|
const params = {
|
|
104
163
|
address: c.addresses.router,
|
|
105
|
-
abi: args.abi,
|
|
164
|
+
abi: withRevertErrors(args.abi),
|
|
106
165
|
functionName: args.functionName,
|
|
107
166
|
args: args.args,
|
|
108
167
|
account,
|
|
@@ -147,7 +206,7 @@ export async function simulateRouterWrite<T>(
|
|
|
147
206
|
const account = args.account ?? c.wallet?.account?.address;
|
|
148
207
|
const { result } = await c.public.simulateContract({
|
|
149
208
|
address: c.addresses.router,
|
|
150
|
-
abi: args.abi,
|
|
209
|
+
abi: withRevertErrors(args.abi),
|
|
151
210
|
functionName: args.functionName,
|
|
152
211
|
args: args.args,
|
|
153
212
|
account,
|