@logswap/sdk 0.5.50 → 0.5.52

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/src/fpool.ts CHANGED
@@ -31,7 +31,7 @@
31
31
  */
32
32
 
33
33
  import { encodeFunctionData, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
34
- import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
34
+ import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
35
35
 
36
36
  /**
37
37
  * One contract, one ABI (decisions 039, F version 12): `harvest`, `claim`, `retire`, `regenerate`,
@@ -88,11 +88,11 @@ export interface FPoolState {
88
88
  policy: Address;
89
89
  /** Whether the policy asked to be consulted on swaps ({@link ASK_SWAP}) and mints ({@link ASK_MINT}) — the desk's policy does, no other. */
90
90
  asks: number;
91
- /** The sponsor's own label for this pool, mutable and never identity. "" when unnamed. */
91
+ /** The sponsor's own label for this pool — the policy's word (decisions 049), mutable and never identity. "" when unnamed. */
92
92
  name: string;
93
93
  /** The block of the last harvest / composition action: no swap runs in it. */
94
94
  restructureBlock: number;
95
- /** The rung — see {@link FPoolKind}. */
95
+ /** What the pool is — its POLICY's self-description (decisions 049); see {@link FPoolKind}. */
96
96
  kind: FPoolKind;
97
97
  /** False before the first seed — and between a retire and the next one (decisions 032). */
98
98
  seeded: boolean;
@@ -120,12 +120,12 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
120
120
  } as never) as Promise<T>;
121
121
 
122
122
  // `getPool` returns the whole scalar struct in one call — the singleton's shape makes what used
123
- // to be a dozen getters a single read. No role in it since F version 15: those are the policy's.
123
+ // to be a dozen getters a single read. No role in it since F version 15, no kind and no name
124
+ // since 18: those are the policy's.
124
125
  const p = await rd<{
125
126
  quote: Address;
126
127
  phi: bigint;
127
128
  policy: Address;
128
- kind: number;
129
129
  seeded: boolean;
130
130
  gen: number;
131
131
  n: number;
@@ -136,7 +136,6 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
136
136
  L: bigint;
137
137
  shares: bigint;
138
138
  theta0: bigint;
139
- name: Hex;
140
139
  }>("getPool", [poolId]);
141
140
 
142
141
  // A pool that was never created reads as an all-zero struct rather than reverting, so the
@@ -148,12 +147,14 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
148
147
 
149
148
  const n = Number(p.n);
150
149
  const idx = Array.from({ length: n }, (_, i) => BigInt(i));
151
- const [theta, compositeX, legs] = await Promise.all([
150
+ const [theta, compositeX, legs, kind, name] = await Promise.all([
152
151
  // before the seed there is no L and no floor: θ is X by convention (the manager says so since
153
152
  // bd3dd87's successor; older deployments revert with DivWadFailed, so do not ask them)
154
153
  p.L > 0n ? rd<bigint>("theta", [poolId]) : rd<bigint>("compositeX", [poolId]),
155
154
  rd<bigint>("compositeX", [poolId]),
156
155
  Promise.all(idx.map((i) => rd<readonly [Address, bigint, bigint, bigint]>("legOf", [poolId, i]))),
156
+ fPoolKindOfPolicy(c, p.policy),
157
+ fPoolNameOf(c, poolId, p.policy),
157
158
  ]);
158
159
 
159
160
  const bases = legs.map((l) => l[0]);
@@ -178,9 +179,9 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
178
179
  bigSigma,
179
180
  policy,
180
181
  asks: Number(p.asks),
181
- name: bytes32ToText(p.name),
182
+ name,
182
183
  restructureBlock: Number(restructureBlock),
183
- kind: fPoolKindOf(p.kind),
184
+ kind,
184
185
  seeded,
185
186
  gen: Number(gen),
186
187
  totalSupply,
@@ -189,26 +190,50 @@ export async function getFPool(c: LogswapClient, poolId: Hex): Promise<FPoolStat
189
190
  }
190
191
 
191
192
  /**
192
- * The kind of pool (decisions 036, 038, 044) — what the manager promises structurally, whatever
193
- * the pool's policy answers. `public`: the protocol's own pool — its policy requires the collector as
194
- * creator; no gate, no operator. `private`, the desk: the one kind whose policy may gate swaps and
195
- * mints, with an operator that reshapes the legs between generations. `pol`, a treasury's pool
196
- * or a basket: composition and access fixed at birth. `pad`, a launch: a POL whose harvest is
197
- * income only whatever its policy says and whose seed the contract minted to 0xdead — one asset,
198
- * open to all. private → pol → pad is the ladder of rigidity; public is a pol held by the
199
- * protocol. Every rate, role and gate is the policy's.
193
+ * The kind of pool — the POLICY's self-description (decisions 049: the manager knows no kind; the
194
+ * policy the key names is the kind, and what it promises is its code). `public`: the protocol's
195
+ * own pool — its policy requires the collector as creator; no gate, no operator. `private`, the
196
+ * desk: the one whose policy may gate swaps and mints, with an operator that reshapes the legs
197
+ * between generations. `pol`, a treasury's pool, one base: composition and access fixed at
198
+ * birth. `pad`, a launch, one base: its policy answers income only at every harvest and declared
199
+ * its seed the pool's at birth (`SEED_TO_DEAD`) — open to all. private → pol → pad is the ladder
200
+ * of rigidity; public is a pol held by the protocol. Every rate, role and gate is the policy's.
200
201
  */
201
202
  export type FPoolKind = "public" | "private" | "pol" | "pad";
202
- /** The key's enum values. */
203
- export const F_KIND = { public: 0, private: 1, pol: 2, pad: 3 } as const;
204
203
  const F_KIND_NAMES: readonly FPoolKind[] = ["public", "private", "pol", "pad"];
205
- export function fPoolKindOf(n: number | bigint): FPoolKind {
206
- const k = F_KIND_NAMES[Number(n)];
207
- if (!k) throw new Error(`logswap: unknown pool kind ${n}`);
208
- return k;
204
+ /** The policy's birth-time declaration that the seed's shares are the pool's (a pad's), in `asks`. */
205
+ export const SEED_TO_DEAD = 4;
206
+
207
+ const kindCache = new Map<string, Promise<FPoolKind>>();
208
+
209
+ /**
210
+ * What a policy IS — its `kind()` tag, read once per policy address and cached for the client's
211
+ * chain: a pool's kind is its policy's word (decisions 049), never the manager's. An unknown tag
212
+ * (a policy that is not one of the protocol's four shapes) throws; the app has no screen for it.
213
+ */
214
+ export function fPoolKindOfPolicy(c: LogswapClient, policy: Address): Promise<FPoolKind> {
215
+ const key = `${c.public.chain?.id ?? 0}:${policy.toLowerCase()}`;
216
+ let hit = kindCache.get(key);
217
+ if (!hit) {
218
+ hit = (c.public.readContract({ address: policy, abi: fPolicyBaseAbi, functionName: "kind" } as never) as Promise<Hex>).then((word) => {
219
+ const tag = bytes32ToText(word) as FPoolKind;
220
+ if (!F_KIND_NAMES.includes(tag)) throw new Error(`logswap: policy ${policy} says it is "${tag}", which is no kind this SDK knows`);
221
+ return tag;
222
+ });
223
+ kindCache.set(key, hit);
224
+ hit.catch(() => kindCache.delete(key)); // a failed read is not a fact
225
+ }
226
+ return hit;
227
+ }
228
+
229
+ /** The pool's label, as its policy holds it (decisions 049). "" when unnamed. */
230
+ export async function fPoolNameOf(c: LogswapClient, poolId: Hex, policy?: Address): Promise<string> {
231
+ const p = policy ?? (await fPoolPolicyOf(c, poolId));
232
+ const word = (await c.public.readContract({ address: p, abi: fPolicyBaseAbi, functionName: "nameOf", args: [poolId] } as never)) as Hex;
233
+ return bytes32ToText(word);
209
234
  }
210
235
 
211
- /** The deployment's policy for a kind (decisions 044): what a key of that kind names. */
236
+ /** The deployment's policy for a kind (decisions 044, 049): the protocol's policy that IS that kind. */
212
237
  export function fPoolPolicyFor(c: LogswapClient, kind: FPoolKind): Address {
213
238
  const a = c.addresses;
214
239
  const policy = kind === "public" ? a.publicPolicy : kind === "private" ? a.privatePolicy : kind === "pol" ? a.polPolicy : a.padPolicy;
@@ -811,8 +836,8 @@ export async function fPoolQuoteSwap(
811
836
  ): Promise<bigint> {
812
837
  try {
813
838
  await c.public.simulateContract({
814
- address: c.addresses.fPoolManager!,
815
- abi: fPoolManagerAbi,
839
+ address: c.addresses.router,
840
+ abi: logswapRouterAbi,
816
841
  functionName: "quoteSwap",
817
842
  // the taker matters only on a gated pool: the connected account, or the zero address
818
843
  args: [a.poolId, a.kind, BigInt(a.j), BigInt(a.k ?? 0), a.amountIn, a.taker ?? c.wallet?.account?.address ?? ZERO_ADDRESS],
@@ -866,12 +891,16 @@ export interface FPoolCreateArgs {
866
891
  weights: bigint[];
867
892
  /** Fixed fee, WAD. The base-for-base fee is pinned at 2φ on-chain. */
868
893
  phi: bigint;
869
- /** The rung (decisions 036) — see {@link FPoolKind}. In the key: the pool's identity, never changed after. */
894
+ /**
895
+ * Which of the protocol's four the pool is — see {@link FPoolKind}. NOT a key field (decisions
896
+ * 049: the manager knows no kind): it selects the deployment's policy of that kind when `policy`
897
+ * is not named ({@link fPoolPolicyFor}), and it is what the policy will say it is.
898
+ */
870
899
  kind: FPoolKind;
871
900
  /**
872
- * The pool's policy (decisions 041, 044): the contract the key names, approved by the protocol
873
- * for the kind. Defaults to the deployment's policy for `kind` ({@link fPoolPolicyFor}) — the one
874
- * every protocol pool names.
901
+ * The pool's policy (decisions 041, 044, 049): the contract the key names, approved by the
902
+ * registry, and the pool's kind. Defaults to the deployment's policy for `kind`
903
+ * ({@link fPoolPolicyFor}) — the one every protocol pool names.
875
904
  */
876
905
  policy?: Address;
877
906
  /** Who is born holding the pool: immutable identity, the pool's first authority (the policy records it). */
@@ -880,7 +909,7 @@ export interface FPoolCreateArgs {
880
909
  * The differentiator, and nothing else. Identity is the key's hash, so one sponsor cannot run
881
910
  * two pools of the same shape without varying it — the second `initialize` would land on the
882
911
  * first pool's id. Defaults to zero, which is the first book of any shape. A pool's NAME is not
883
- * this: names are mutable state (`fPoolSetName`), so a rename forks nothing.
912
+ * this: names are the policy's mutable state (`fPoolSetName`), so a rename forks nothing.
884
913
  */
885
914
  salt?: Hex;
886
915
  account: Address;
@@ -909,7 +938,7 @@ export function sortFPoolLegs<T>(bases: Address[], ...companions: T[][]): { base
909
938
  return { bases: idx.map((i) => bases[i]!), companions: companions.map((arr) => idx.map((i) => arr[i]!)) };
910
939
  }
911
940
 
912
- /** The key as the manager hashes it: legs sorted, the kind's policy filled in when none is named. */
941
+ /** The key as the manager hashes it: legs sorted, the kind's policy filled in when none is named; no kind in it (049). */
913
942
  export function fPoolKeyOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">) {
914
943
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights);
915
944
  return {
@@ -917,7 +946,6 @@ export function fPoolKeyOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "account">
917
946
  bases,
918
947
  weights: companions[0]!,
919
948
  phi: a.phi,
920
- kind: F_KIND[a.kind],
921
949
  policy: a.policy ?? fPoolPolicyFor(c, a.kind),
922
950
  creator: a.creator,
923
951
  salt: a.salt ?? ZERO_SALT,
@@ -1092,7 +1120,7 @@ export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
1092
1120
  * this buy — they are one transaction — so `minOut` is safe at its default of 0.
1093
1121
  */
1094
1122
  openBuy?: { j: number; amountIn: bigint; minOut?: bigint };
1095
- /** The pool's label, set in the batch (≤ 31 bytes of UTF-8). */
1123
+ /** The pool's label (≤ 31 bytes of UTF-8) — the policy's word (049), written right after the batch by {@link fPoolLaunch}; not in the batch. */
1096
1124
  name?: string;
1097
1125
  /**
1098
1126
  * The pool already exists (a previous launch got as far as `initialize` and no further — a seed
@@ -1121,7 +1149,7 @@ export function fPoolLaunchCalls(
1121
1149
  if (given.length !== a.bases.length) throw new Error(`logswap: ${given.length} reserves for ${a.bases.length} legs`);
1122
1150
  const { bases, companions } = sortFPoolLegs(a.bases, a.weights, given);
1123
1151
  if (!a.policy) throw new Error("logswap: fPoolLaunchCalls needs the key's policy — fPoolLaunch fills it from the deployment");
1124
- const key = { quote: a.quote, bases, weights: companions[0]!, phi: a.phi, kind: F_KIND[a.kind], policy: a.policy, creator: a.creator, salt: a.salt ?? ZERO_SALT };
1152
+ const key = { quote: a.quote, bases, weights: companions[0]!, phi: a.phi, policy: a.policy, creator: a.creator, salt: a.salt ?? ZERO_SALT };
1125
1153
  const enc = (functionName: string, args: unknown[]) =>
1126
1154
  encodeFunctionData({ abi: fPoolManagerAbi, functionName, args } as never) as Hex;
1127
1155
  const calls: Hex[] = [];
@@ -1134,9 +1162,9 @@ export function fPoolLaunchCalls(
1134
1162
  if (j < 0) throw new Error(`logswap: openBuy.j = ${a.openBuy.j} is not a leg`);
1135
1163
  calls.push(enc("swapQuoteIn", [poolId, BigInt(j), a.openBuy.amountIn, a.openBuy.minOut ?? 0n, a.account]));
1136
1164
  }
1137
- if (a.name) calls.push(enc("setName", [poolId, textToBytes32(a.name)]));
1138
- // the desk's gates and list are the POLICY's (decisions 044), not the manager's: they cannot ride
1139
- // in this batch — `fPoolDeskOpening` sends them right after, as the same creator
1165
+ // the name, the desk's gates and its list are the POLICY's (decisions 044, 049), not the
1166
+ // manager's: they cannot ride in this batch — `fPoolLaunch` and `fPoolDeskOpening` send them
1167
+ // right after, as the same creator
1140
1168
  calls.push(...(a.extra ?? []));
1141
1169
  return calls;
1142
1170
  }
@@ -1176,8 +1204,10 @@ export async function fPoolLaunch(
1176
1204
  a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
1177
1205
  ): Promise<{ hash: Hash; poolId: Hex }> {
1178
1206
  const poolId = await fPoolIdOf(c, a);
1179
- const calls = fPoolLaunchCalls(poolId, { ...a, policy: a.policy ?? fPoolPolicyFor(c, a.kind) });
1207
+ const policy = a.policy ?? fPoolPolicyFor(c, a.kind);
1208
+ const calls = fPoolLaunchCalls(poolId, { ...a, policy });
1180
1209
  const hash = await writeFPool0(c, "multicall", [calls], a.account);
1210
+ if (a.name) await fPoolSetName(c, { poolId, name: a.name, account: a.account, policy });
1181
1211
  return { hash, poolId };
1182
1212
  }
1183
1213
 
@@ -1269,20 +1299,22 @@ export async function fPoolIsAllowed(c: LogswapClient, poolId: Hex, who: Address
1269
1299
  return readDesk<boolean>(c, poolId, who, "isAllowed", policy);
1270
1300
  }
1271
1301
 
1272
- /** Label the pool — the sponsor's own words, mutable, never identity. At most 31 bytes of UTF-8. */
1273
- export async function fPoolSetName(c: LogswapClient, a: { poolId: Hex; name: string; account: Address }) {
1274
- return writeFPool(c, a.poolId, "setName", [textToBytes32(a.name)], a.account);
1302
+ /** Label the pool — the authority's own words on the pool's POLICY (decisions 049), mutable, never identity. At most 31 bytes of UTF-8. */
1303
+ export async function fPoolSetName(c: LogswapClient, a: { poolId: Hex; name: string; account: Address; policy?: Address }) {
1304
+ return writePolicy(c, fPolicyBaseAbi, a, "setName", [textToBytes32(a.name)]);
1275
1305
  }
1276
1306
 
1277
1307
  /**
1278
- * What the PROTOCOL says an address is — "Keyrock". Only `feeCollector` may write it, which is
1279
- * what makes it an attestation rather than a claim anyone could make about themselves.
1308
+ * What the PROTOCOL says an address is — "Keyrock" — on the REGISTRY (decisions 049). Only its
1309
+ * owner, the LOG authority, may write it, which is what makes it an attestation rather than a
1310
+ * claim anyone could make about themselves.
1280
1311
  */
1281
1312
  export async function fPoolSetSponsorName(c: LogswapClient, a: { who: Address; name: string; account: Address }) {
1282
1313
  const wallet = requireWallet(c);
1314
+ const registry = await registryAddress(c);
1283
1315
  const { request } = await c.public.simulateContract({
1284
- address: c.addresses.fPoolManager!,
1285
- abi: fPoolManagerAbi,
1316
+ address: registry,
1317
+ abi: policyRegistryAbi,
1286
1318
  functionName: "setSponsorName",
1287
1319
  args: [a.who, textToBytes32(a.name)],
1288
1320
  account: a.account,
@@ -1290,17 +1322,26 @@ export async function fPoolSetSponsorName(c: LogswapClient, a: { who: Address; n
1290
1322
  return wallet.writeContract(request as never);
1291
1323
  }
1292
1324
 
1293
- /** The protocol's attestation for an address, or "" when unattested — which most addresses are. */
1325
+ /** The protocol's attestation for an address, or "" when unattested — which most addresses are, and every address on a deployment with no registry. */
1294
1326
  export async function fPoolSponsorName(c: LogswapClient, who: Address): Promise<string> {
1295
- const word = (await c.public.readContract({
1296
- address: c.addresses.fPoolManager!,
1297
- abi: fPoolManagerAbi,
1298
- functionName: "sponsorName",
1299
- args: [who],
1300
- } as never)) as Hex;
1327
+ const registry = c.addresses.policyRegistry ?? (await fPoolRegistryOf(c));
1328
+ if (registry === ZERO_ADDRESS) return "";
1329
+ const word = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "sponsorName", args: [who] } as never)) as Hex;
1301
1330
  return bytes32ToText(word);
1302
1331
  }
1303
1332
 
1333
+ /** The registry the manager asks at every birth — `addresses.policyRegistry`, else the manager's word; zero on a bare deployment. */
1334
+ export async function fPoolRegistryOf(c: LogswapClient): Promise<Address> {
1335
+ if (c.addresses.policyRegistry) return c.addresses.policyRegistry;
1336
+ return (await c.public.readContract({ address: c.addresses.fPoolManager!, abi: fPoolManagerAbi, functionName: "registry" } as never)) as Address;
1337
+ }
1338
+
1339
+ async function registryAddress(c: LogswapClient): Promise<Address> {
1340
+ const registry = await fPoolRegistryOf(c);
1341
+ if (registry === ZERO_ADDRESS) throw new Error("logswap: this deployment carries no policy registry");
1342
+ return registry;
1343
+ }
1344
+
1304
1345
  /** Who runs the pool (decisions 037): the appointed operator, or the authority until one is. */
1305
1346
  export function fPoolOperatorOf(s: Pick<FPoolRoles, "authority" | "operator">): Address {
1306
1347
  return s.operator === ZERO_ADDRESS ? s.authority : s.operator;
@@ -1385,9 +1426,10 @@ export interface DiscoveredFPool {
1385
1426
  }
1386
1427
 
1387
1428
  /**
1388
- * Every F pool ever created, from `Initialize` logs alone. Possible only because the event
1389
- * carries the full key preimage (bases, weights, phi, pad) — the poolId is a hash and could
1390
- * never be inverted; contracts PR #17 exists for exactly this call.
1429
+ * Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
1430
+ * policy, cached. Possible only because the event carries the full key preimage (bases,
1431
+ * weights, phi, policy) — the poolId is a hash and could never be inverted; contracts PR #17
1432
+ * exists for exactly this call.
1391
1433
  */
1392
1434
  export async function discoverFPools(
1393
1435
  c: LogswapClient,
@@ -1414,27 +1456,30 @@ export async function discoverFPools(
1414
1456
  const a = l.args as { poolId: Hex; Q0: bigint };
1415
1457
  q0Of.set(a.poolId.toLowerCase(), a.Q0);
1416
1458
  }
1417
- return logs.map((l) => {
1418
- const a = l.args as {
1419
- poolId: Hex; quote: Address; creator: Address; bases: readonly Address[];
1420
- weights: readonly bigint[]; phi: bigint; kind: number; policy: Address;
1421
- };
1422
- const q0 = q0Of.get(a.poolId.toLowerCase());
1423
- return {
1424
- poolId: a.poolId,
1425
- quote: a.quote,
1426
- bases: [...a.bases],
1427
- weights: [...a.weights],
1428
- phi: a.phi,
1429
- kind: fPoolKindOf(a.kind),
1430
- policy: a.policy,
1431
- creator: a.creator,
1432
- block: l.blockNumber ?? 0n,
1433
- seeded: q0 !== undefined,
1434
- q0: q0 ?? 0n,
1435
- shape: a.kind === F_KIND.pad ? "launch" : "basket", // the kind, not n = 1: a mono POL is a pool (decisions 036)
1436
- };
1437
- });
1459
+ return Promise.all(
1460
+ logs.map(async (l) => {
1461
+ const a = l.args as {
1462
+ poolId: Hex; quote: Address; creator: Address; bases: readonly Address[];
1463
+ weights: readonly bigint[]; phi: bigint; policy: Address;
1464
+ };
1465
+ const q0 = q0Of.get(a.poolId.toLowerCase());
1466
+ const kind = await fPoolKindOfPolicy(c, a.policy); // one read per distinct policy, cached
1467
+ return {
1468
+ poolId: a.poolId,
1469
+ quote: a.quote,
1470
+ bases: [...a.bases],
1471
+ weights: [...a.weights],
1472
+ phi: a.phi,
1473
+ kind,
1474
+ policy: a.policy,
1475
+ creator: a.creator,
1476
+ block: l.blockNumber ?? 0n,
1477
+ seeded: q0 !== undefined,
1478
+ q0: q0 ?? 0n,
1479
+ shape: kind === "pad" ? "launch" : "basket", // the policy's word, not n = 1: a mono POL is a pool (decisions 036, 049)
1480
+ };
1481
+ }),
1482
+ );
1438
1483
  }
1439
1484
 
1440
1485
  // ─── the policy page: what an authority controls (decisions 044) ────────────────────────