@logswap/sdk 0.5.50 → 0.5.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logswap/sdk",
3
- "version": "0.5.50",
3
+ "version": "0.5.51",
4
4
  "description": "TypeScript SDK for Logswap, the liquidity primitive introducing the logarithmic invariant — typed wrappers over the CPoolManager / FPoolManager / Router / Lens surface.",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "type": "module",
package/src/admin.ts CHANGED
@@ -1,19 +1,20 @@
1
1
  /**
2
- * admin — the protocol's (feeCollector's) writes: the protocol's cut of the mint fee on both
3
- * managers, the policies a pool of each kind may be born with, the policies' own numbers, and the
4
- * two-step collector handover. Plain writes to the managers and the policies; no router.
2
+ * admin — the protocol's writes: the C manager's cut of the mint fee (the F manager's is a
3
+ * constant, decisions 049), the policies a pool may be born with (the registry's), the
4
+ * policies' own numbers, and the two-step collector handover. Plain writes to the managers,
5
+ * the registry and the policies; no router.
5
6
  *
6
7
  * One vocabulary (decisions 040): both managers call the protocol's cut of the mint fee
7
- * `MINT_FEE_PROTOCOL_CUT` and set it through `setFeeParams(uint256)` — one cut each since C
8
- * version 4 / F version 14. The harvest's cut is the policy's answer at every harvest (decisions
9
- * 044), set on the policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the
10
- * release's protocol share likewise (`POT_CUT_MAX`).
8
+ * `MINT_FEE_PROTOCOL_CUT`; C sets it through `setFeeParams(uint256)`, F holds it as a constant
9
+ * (049). The harvest's cut is the policy's answer at every harvest (decisions 044), set on the
10
+ * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
11
+ * protocol share likewise (`POT_CUT_MAX`).
11
12
  */
12
13
 
13
14
  import type { Address, Hash } from "viem";
14
15
  import type { LogswapClient } from "./client.js";
15
16
  import { cPoolManagerAbi, fPoolManagerAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, publicPolicyAbi } from "./generated.js";
16
- import { F_KIND, bytes32ToText, fPolicyBaseAbi, textToBytes32, type FPoolKind } from "./fpool.js";
17
+ import { bytes32ToText, fPolicyBaseAbi, fPoolKindOfPolicy, textToBytes32, type FPoolKind } from "./fpool.js";
17
18
 
18
19
  function wallet(c: LogswapClient) {
19
20
  if (!c.wallet) throw new Error("a wallet client is required");
@@ -28,9 +29,9 @@ function write(c: LogswapClient, address: Address, abi: readonly unknown[], func
28
29
  export async function setFeeParamsC(c: LogswapClient, mintFeeProtocolCutWad: bigint): Promise<Hash> {
29
30
  return write(c, c.addresses.cPoolManager, cPoolManagerAbi, "setFeeParams", [mintFeeProtocolCutWad]);
30
31
  }
31
- /** The F manager's `setFeeParams(mintFeeProtocolCut)` — one cut, the C manager's shape (F version 14). */
32
- export async function setFeeParams(c: LogswapClient, mintFeeProtocolCutWad: bigint): Promise<Hash> {
33
- return write(c, c.addresses.fPoolManager as Address, fPoolManagerAbi, "setFeeParams", [mintFeeProtocolCutWad]);
32
+ /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18 (decisions 049); there is no setter. */
33
+ export async function fMintFeeProtocolCut(c: LogswapClient): Promise<bigint> {
34
+ return c.public.readContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "MINT_FEE_PROTOCOL_CUT" } as never) as Promise<bigint>;
34
35
  }
35
36
 
36
37
  // ─── the registry (decisions 046): the LOG authority's, not the collector's ─────────────────
@@ -47,16 +48,18 @@ export async function policyRegistryOf(c: LogswapClient): Promise<Address> {
47
48
  return (await c.public.readContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "registry" } as never)) as Address;
48
49
  }
49
50
 
50
- /** Whether `policy` is approved for pools of `kind` now — listed and not yet sunset. True everywhere on a deployment with no registry. */
51
- export async function policyApproved(c: LogswapClient, kind: FPoolKind, policy: Address): Promise<boolean> {
51
+ /** Whether `policy` is approved now — listed and not yet sunset. True everywhere on a deployment with no registry. */
52
+ export async function policyApproved(c: LogswapClient, policy: Address): Promise<boolean> {
52
53
  const registry = await policyRegistryOf(c);
53
54
  if (registry === ZERO) return true;
54
- return c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "isApproved", args: [F_KIND[kind], policy] } as never) as Promise<boolean>;
55
+ return c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "isApproved", args: [policy] } as never) as Promise<boolean>;
55
56
  }
56
57
 
57
- /** One policy on a kind's list, as the registry holds it. */
58
+ /** One policy on the registry's list, with what it says it is. */
58
59
  export interface RegistryEntry {
59
60
  policy: Address;
61
+ /** The policy's own `kind()` tag (decisions 049) — what a pool born under it is. */
62
+ kind: FPoolKind;
60
63
  name: string;
61
64
  /** The block it closes to new pools from; 0 = open. */
62
65
  until: bigint;
@@ -64,18 +67,26 @@ export interface RegistryEntry {
64
67
  approved: boolean;
65
68
  }
66
69
 
67
- /** Every policy listed for `kind`, sunset ones included — what a create form's dropdown reads. Empty with no registry. */
68
- export async function registryPolicies(c: LogswapClient, kind: FPoolKind): Promise<RegistryEntry[]> {
70
+ /**
71
+ * Every policy listed, sunset ones included — one flat list (decisions 049), each with the kind
72
+ * it says it is; `kind` narrows it to one, which is what a create form's dropdown reads. Empty
73
+ * with no registry.
74
+ */
75
+ export async function registryPolicies(c: LogswapClient, kind?: FPoolKind): Promise<RegistryEntry[]> {
69
76
  const registry = await policyRegistryOf(c);
70
77
  if (registry === ZERO) return [];
71
- const list = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "policiesOf", args: [F_KIND[kind]] } as never)) as readonly Address[];
78
+ const list = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "policies" } as never)) as readonly Address[];
72
79
  const block = await c.public.getBlockNumber();
73
- return Promise.all(
80
+ const all = await Promise.all(
74
81
  list.map(async (policy) => {
75
- const [listed, until, , name] = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "entries", args: [F_KIND[kind], policy] } as never)) as [boolean, bigint, number, `0x${string}`];
76
- return { policy, name: bytes32ToText(name), until, approved: listed && (until === 0n || block < until) };
82
+ const [[listed, until, , name], tag] = await Promise.all([
83
+ c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "entries", args: [policy] } as never) as Promise<[boolean, bigint, number, `0x${string}`]>,
84
+ fPoolKindOfPolicy(c, policy),
85
+ ]);
86
+ return { policy, kind: tag, name: bytes32ToText(name), until, approved: listed && (until === 0n || block < until) };
77
87
  }),
78
88
  );
89
+ return kind ? all.filter((e) => e.kind === kind) : all;
79
90
  }
80
91
 
81
92
  /** The registry's owner — the LOG authority — and the proposed next one. */
@@ -95,19 +106,19 @@ async function registryWrite(c: LogswapClient, functionName: string, args: reado
95
106
  return write(c, registry, policyRegistryAbi, functionName, args);
96
107
  }
97
108
 
98
- /** `PolicyRegistry.approve`: list `policy` for pools of `kind`, under `name` (at most 31 bytes). Again renames it and clears its sunset. The owner's. */
99
- export async function registryApprove(c: LogswapClient, kind: FPoolKind, policy: Address, name: string): Promise<Hash> {
100
- return registryWrite(c, "approve", [F_KIND[kind], policy, textToBytes32(name)]);
109
+ /** `PolicyRegistry.approve`: list `policy` under `name` (at most 31 bytes). Again renames it and clears its sunset. The owner's. */
110
+ export async function registryApprove(c: LogswapClient, policy: Address, name: string): Promise<Hash> {
111
+ return registryWrite(c, "approve", [policy, textToBytes32(name)]);
101
112
  }
102
113
 
103
- /** `PolicyRegistry.sunset`: close `policy` to new pools of `kind` from `untilBlock` on; 0 reopens. The owner's. */
104
- export async function registrySunset(c: LogswapClient, kind: FPoolKind, policy: Address, untilBlock: bigint): Promise<Hash> {
105
- return registryWrite(c, "sunset", [F_KIND[kind], policy, untilBlock]);
114
+ /** `PolicyRegistry.sunset`: close `policy` to new pools from `untilBlock` on; 0 reopens. The owner's. */
115
+ export async function registrySunset(c: LogswapClient, policy: Address, untilBlock: bigint): Promise<Hash> {
116
+ return registryWrite(c, "sunset", [policy, untilBlock]);
106
117
  }
107
118
 
108
- /** `PolicyRegistry.revoke`: remove `policy` from `kind`'s list at once. Live pools are untouched. The owner's. */
109
- export async function registryRevoke(c: LogswapClient, kind: FPoolKind, policy: Address): Promise<Hash> {
110
- return registryWrite(c, "revoke", [F_KIND[kind], policy]);
119
+ /** `PolicyRegistry.revoke`: remove `policy` from the list at once. Live pools are untouched. The owner's. */
120
+ export async function registryRevoke(c: LogswapClient, policy: Address): Promise<Hash> {
121
+ return registryWrite(c, "revoke", [policy]);
111
122
  }
112
123
 
113
124
  /** The registry owner's two-step handover. */
@@ -120,19 +131,20 @@ export async function registryAcceptOwner(c: LogswapClient): Promise<Hash> {
120
131
 
121
132
  /**
122
133
  * Approve or revoke, the F version 15–16 shape kept for callers: `approved` true is
123
- * {@link registryApprove} under the kind's name, false is {@link registryRevoke}.
134
+ * {@link registryApprove} under the policy's own tag as its name, false is {@link registryRevoke}.
124
135
  */
125
- export async function setPolicyApproved(c: LogswapClient, kind: FPoolKind, policy: Address, approved: boolean): Promise<Hash> {
126
- return approved ? registryApprove(c, kind, policy, kind) : registryRevoke(c, kind, policy);
136
+ export async function setPolicyApproved(c: LogswapClient, policy: Address, approved: boolean): Promise<Hash> {
137
+ return approved ? registryApprove(c, policy, await fPoolKindOfPolicy(c, policy)) : registryRevoke(c, policy);
127
138
  }
128
139
 
129
140
  /**
130
- * Move a pool to another policy the registry approves for its kind (decisions 046): the registry
131
- * owner's alone. The new policy takes the authority from the old; the operator and the lists
132
- * void; no swap lands in the block.
141
+ * Move a pool to another approved policy of the same kind (decisions 046, 049): the registry
142
+ * owner's alone, orchestrated on the registry — `migrate(manager, poolId, to)`. The new policy
143
+ * takes the authority and the name from the old; the operator and the lists void; no swap lands
144
+ * in the block.
133
145
  */
134
146
  export async function fPoolMigrate(c: LogswapClient, a: { poolId: `0x${string}`; to: Address }): Promise<Hash> {
135
- return write(c, c.addresses.fPoolManager as Address, fPoolManagerAbi, "migrate", [a.poolId, a.to]);
147
+ return registryWrite(c, "migrate", [c.addresses.fPoolManager as Address, a.poolId, a.to]);
136
148
  }
137
149
 
138
150
  // ─── the policies' numbers (the collector's; each bounded where it reaches the manager) ─────
@@ -156,6 +168,25 @@ export async function polSetRules(c: LogswapClient, policy: Address, s: { keepQu
156
168
  return write(c, policy, polPolicyAbi, "setRules", [s.keepQuote, s.minQuoteAtSeed]);
157
169
  }
158
170
 
171
+ /** `setQuoteListed` on any protocol policy: a quote a pool of that kind may be born against while it curates quotes. The collector's. */
172
+ export async function policySetQuoteListed(c: LogswapClient, policy: Address, quote: Address, listed: boolean): Promise<Hash> {
173
+ return write(c, policy, fPolicyBaseAbi, "setQuoteListed", [quote, listed]);
174
+ }
175
+
176
+ /** `setQuoteCurated` on any protocol policy: on, a birth against an unlisted quote is refused. The collector's. */
177
+ export async function policySetQuoteCurated(c: LogswapClient, policy: Address, curated: boolean): Promise<Hash> {
178
+ return write(c, policy, fPolicyBaseAbi, "setQuoteCurated", [curated]);
179
+ }
180
+
181
+ /** Whether `quote` passes `policy`'s birth check now: listed, or the policy does not curate quotes. */
182
+ export async function policyQuoteAllowed(c: LogswapClient, policy: Address, quote: Address): Promise<{ curated: boolean; listed: boolean; allowed: boolean }> {
183
+ const [curated, listed] = await Promise.all([
184
+ c.public.readContract({ address: policy, abi: fPolicyBaseAbi, functionName: "quoteCurated" } as never) as Promise<boolean>,
185
+ c.public.readContract({ address: policy, abi: fPolicyBaseAbi, functionName: "quoteListed", args: [quote] } as never) as Promise<boolean>,
186
+ ]);
187
+ return { curated, listed, allowed: !curated || listed };
188
+ }
189
+
159
190
  /** `PublicPolicy.setListed`: an asset a curated seed may hold. */
160
191
  export async function publicSetListed(c: LogswapClient, policy: Address, token: Address, listed: boolean): Promise<Hash> {
161
192
  return write(c, policy, publicPolicyAbi, "setListed", [token, listed]);
package/src/client.ts CHANGED
@@ -134,7 +134,7 @@ export const EXPECTED_VERSION = 4n;
134
134
  // is the policy at birth, `migrate` moves a pool for the registry's owner; `stamp` / `setAsks` are
135
135
  // the policy's; the desk's `setAllowed` / `isAllowed` are `setMinters` / `setTakers` / `isMinter` /
136
136
  // `isTaker`; a pad and a POL hold one base (`OneBase`).
137
- export const EXPECTED_F_VERSION = 17n;
137
+ export const EXPECTED_F_VERSION = 18n;
138
138
 
139
139
  export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSION): Promise<void> {
140
140
  const { cPoolManagerAbi } = await import("./generated.js");
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) ────────────────────────