@logswap/sdk 0.5.55 → 0.5.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # logswap-sdk
2
2
 
3
- TypeScript SDK for [Logswap](https://github.com/logswap/logswap-contract), the liquidity
3
+ TypeScript SDK for [Logswap](https://logswap.org), the liquidity
4
4
  primitive introducing the **logarithmic invariant** — typed wrappers over the `CPoolManager` /
5
5
  `FPoolManager` / `LogswapRouter` / `LogswapLens` surface.
6
6
 
@@ -38,48 +38,6 @@ Both are computed **offline** — no RPC, no deployment needed to address a mark
38
38
  `fPoolShareBalance`, `fPoolShareValue`; `previewZapIn` reads the **lens** (the router's F flows
39
39
  live in a facet, its previews moved to the lens).
40
40
 
41
- ## The derivation gate
42
-
43
- Both derivations are **tier-1 frozen** (`docs/app.md`): `poolId` is the primary key of the entire
44
- read model, and the position id is `(1 << 255) | (keccak256(poolId ‖ packed) >> 1)`, baked into every
45
- client. If the TypeScript and the Solidity disagree, a client addresses the wrong pool or the wrong
46
- position — silently, with funds behind it.
47
-
48
- So `test/derivation.test.ts` checks against **vectors generated by the contract**, not by hand:
49
-
50
- ```bash
51
- npm run vectors # regenerate from the pinned contracts
52
- npm test # 45 vectors × poolId, positionId, pack/unpack round-trip
53
- ```
54
-
55
- That makes it a genuine cross-implementation check. It is also verified non-vacuous: changing the
56
- id derivation's shift by one bit fails all 45 position-id vectors.
57
-
58
- A diff in `vectors/derivation-vectors.json` during an ordinary change means something is wrong — by
59
- definition, a change there is a tier-1 event.
60
-
61
- ## The pipeline
62
-
63
- ```
64
- logswap (forge) ──► contracts/abi/*.json ──► wagmi codegen ──► src/generated.ts
65
- ▲ │
66
- contracts.ref (pinned SHA) the SDK wrappers
67
- ```
68
-
69
- Nothing is ever hand-copied. `contracts.ref` pins the SHA; `npm run codegen` clones it, builds, and
70
- regenerates; `tsc` is the drift gate, so a contract change that breaks the SDK is a red build rather
71
- than a runtime surprise.
72
-
73
- ```bash
74
- npm run codegen # sync the pinned contracts, forge build, wagmi generate -> src/generated.ts
75
- npm run typecheck # consumes generated.ts; run codegen first on a fresh checkout
76
- npm run build
77
- npm test
78
- ```
79
-
80
- `typecheck` and `build` both *consume* `src/generated.ts` and neither produces it, so codegen
81
- runs once and explicitly rather than being chained into both.
82
-
83
41
  ## Conventions
84
42
 
85
43
  - **The SDK hides the manager/router/lens split.** Which contract hosts which action is currently
@@ -97,38 +55,7 @@ runs once and explicitly rather than being chained into both.
97
55
  npm i @logswap/sdk viem
98
56
  ```
99
57
 
100
- Published as **`@logswap/sdk`** from v0.5.35. Everything up to v0.5.34 was
101
- `@unimodular/logswap-sdk`, which is deprecated and points here — the code is identical, only the
102
- name moved, along with the repos to the `logswap` org.
58
+ Published as **`@logswap/sdk`** from v0.5.35; earlier versions used another name.
103
59
 
104
60
  `viem` is a peer dependency. The package ships `dist/` plus `src/`, including the generated ABIs, so
105
61
  the contract surface it was built against travels with it.
106
-
107
- ## Release
108
-
109
- Push-driven, and there is **no npm token anywhere**: publishing uses OIDC trusted publishing, so npm
110
- verifies a GitHub-issued token per run and there is no long-lived credential to rotate or leak.
111
-
112
- ```
113
- logswap-contract push
114
- → Update SDK contracts pin (GitHub App token, cross-repo)
115
- → commit contracts.ref here
116
- → Release on contracts pin change
117
- → codegen at the pinned SHA → build → test → version bump → npm publish
118
- ```
119
-
120
- `workflow_dispatch` covers SDK-only fixes with no contracts change: same pipeline, manual trigger.
121
- The version bump touches `package.json` only, not `contracts.ref`, so it cannot re-trigger itself.
122
-
123
- Two things that are easy to get wrong:
124
-
125
- - **`codegen` must run before `typecheck`.** `src/generated.ts` is gitignored, so it exists on a
126
- developer machine and never in CI — a `typecheck`-first pipeline fails only in CI, which is where
127
- it is least convenient to debug.
128
- - **No provenance since 0.5.55.** The repository went private (2026-09-24), and npm generates no
129
- provenance from a private source repo; every release after 0.1.0 up to 0.5.54 carries it.
130
- Trusted publishing is unaffected.
131
- Put `--provenance` back in `release.yml` if the repo is made public again.
132
- - **`0.1.0` has no provenance attestation either.** Trusted publishing cannot be
133
- configured for a package that does not exist yet, so the first publish is necessarily manual.
134
- `prepublishOnly` runs the build, so a hand-run publish cannot ship a stale `dist/`.
package/dist/actions.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * actions.ts — the router's transaction: a BATCH (decisions 010).
2
+ * actions.ts — the router's transaction: a BATCH.
3
3
  *
4
4
  * `execute(actions, settle)` runs a list of actions — mint, burn, update, claim, swap, path, in any
5
5
  * order, across any C markets — under ONE manager lock. Each action accrues deltas on the router
package/dist/actions.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * actions.ts — the router's transaction: a BATCH (decisions 010).
2
+ * actions.ts — the router's transaction: a BATCH.
3
3
  *
4
4
  * `execute(actions, settle)` runs a list of actions — mint, burn, update, claim, swap, path, in any
5
5
  * order, across any C markets — under ONE manager lock. Each action accrues deltas on the router
package/dist/admin.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
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
3
+ * constant), the policies a pool may be born with (the registry's), the
4
4
  * policies' own numbers, and the two-step collector handover. Plain writes to the managers,
5
5
  * the registry and the policies; no router.
6
6
  *
7
- * One vocabulary (decisions 040): both managers call the protocol's cut of the mint fee
7
+ * One vocabulary: both managers call the protocol's cut of the mint fee
8
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
9
+ * (049). The harvest's cut is the policy's answer at every harvest, set on the
10
10
  * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
11
11
  * protocol share likewise (`POT_CUT_MAX`).
12
12
  */
@@ -15,7 +15,7 @@ import type { LogswapClient } from "./client.js";
15
15
  import { type FPoolKind } from "./fpool.js";
16
16
  /** The C manager's `setFeeParams(mintFeeProtocolCut)` — one cut. */
17
17
  export declare function setFeeParamsC(c: LogswapClient, mintFeeProtocolCutWad: bigint): Promise<Hash>;
18
- /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18 (decisions 049); there is no setter. */
18
+ /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18; there is no setter. */
19
19
  export declare function fMintFeeProtocolCut(c: LogswapClient): Promise<bigint>;
20
20
  /**
21
21
  * The policy registry the F manager asks at every birth — `addresses.policyRegistry` when the
@@ -28,7 +28,7 @@ export declare function policyApproved(c: LogswapClient, policy: Address): Promi
28
28
  /** One policy on the registry's list, with what it says it is. */
29
29
  export interface RegistryEntry {
30
30
  policy: Address;
31
- /** The policy's own `kind()` tag (decisions 049) — what a pool born under it is. */
31
+ /** The policy's own `kind()` tag — what a pool born under it is. */
32
32
  kind: FPoolKind;
33
33
  name: string;
34
34
  /** The block it closes to new pools from; 0 = open. */
@@ -37,7 +37,7 @@ export interface RegistryEntry {
37
37
  approved: boolean;
38
38
  }
39
39
  /**
40
- * Every policy listed, sunset ones included — one flat list (decisions 049), each with the kind
40
+ * Every policy listed, sunset ones included — one flat list, each with the kind
41
41
  * it says it is; `kind` narrows it to one, which is what a create form's dropdown reads. Empty
42
42
  * with no registry.
43
43
  */
@@ -62,7 +62,7 @@ export declare function registryAcceptOwner(c: LogswapClient): Promise<Hash>;
62
62
  */
63
63
  export declare function setPolicyApproved(c: LogswapClient, policy: Address, approved: boolean): Promise<Hash>;
64
64
  /**
65
- * Move a pool to another approved policy of the same kind (decisions 046, 049): the registry
65
+ * Move a pool to another approved policy of the same kind: the registry
66
66
  * owner's alone, orchestrated on the registry — `migrate(manager, poolId, to)`. The new policy
67
67
  * takes the authority and the name from the old; the operator and the lists void; no swap lands
68
68
  * in the block.
package/dist/admin.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
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
3
+ * constant), the policies a pool may be born with (the registry's), the
4
4
  * policies' own numbers, and the two-step collector handover. Plain writes to the managers,
5
5
  * the registry and the policies; no router.
6
6
  *
7
- * One vocabulary (decisions 040): both managers call the protocol's cut of the mint fee
7
+ * One vocabulary: both managers call the protocol's cut of the mint fee
8
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
9
+ * (049). The harvest's cut is the policy's answer at every harvest, set on the
10
10
  * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
11
11
  * protocol share likewise (`POT_CUT_MAX`).
12
12
  */
@@ -24,11 +24,11 @@ function write(c, address, abi, functionName, args) {
24
24
  export async function setFeeParamsC(c, mintFeeProtocolCutWad) {
25
25
  return write(c, c.addresses.cPoolManager, cPoolManagerAbi, "setFeeParams", [mintFeeProtocolCutWad]);
26
26
  }
27
- /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18 (decisions 049); there is no setter. */
27
+ /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18; there is no setter. */
28
28
  export async function fMintFeeProtocolCut(c) {
29
29
  return c.public.readContract({ address: c.addresses.fPoolManager, abi: fPoolManagerAbi, functionName: "MINT_FEE_PROTOCOL_CUT" });
30
30
  }
31
- // ─── the registry (decisions 046): the LOG authority's, not the collector's ─────────────────
31
+ // ─── the registry: the LOG authority's, not the collector's ─────────────────
32
32
  const ZERO = "0x0000000000000000000000000000000000000000";
33
33
  /**
34
34
  * The policy registry the F manager asks at every birth — `addresses.policyRegistry` when the
@@ -48,7 +48,7 @@ export async function policyApproved(c, policy) {
48
48
  return c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "isApproved", args: [policy] });
49
49
  }
50
50
  /**
51
- * Every policy listed, sunset ones included — one flat list (decisions 049), each with the kind
51
+ * Every policy listed, sunset ones included — one flat list, each with the kind
52
52
  * it says it is; `kind` narrows it to one, which is what a create form's dropdown reads. Empty
53
53
  * with no registry.
54
54
  */
@@ -111,7 +111,7 @@ export async function setPolicyApproved(c, policy, approved) {
111
111
  return approved ? registryApprove(c, policy, await fPoolKindOfPolicy(c, policy)) : registryRevoke(c, policy);
112
112
  }
113
113
  /**
114
- * Move a pool to another approved policy of the same kind (decisions 046, 049): the registry
114
+ * Move a pool to another approved policy of the same kind: the registry
115
115
  * owner's alone, orchestrated on the registry — `migrate(manager, poolId, to)`. The new policy
116
116
  * takes the authority and the name from the old; the operator and the lists void; no swap lands
117
117
  * in the block.
package/dist/claims.js CHANGED
@@ -59,40 +59,21 @@ export async function isRouterApproved(c, owner) {
59
59
  });
60
60
  }
61
61
  export async function checkReadiness(c, key, owner, need = {}) {
62
- const erc20 = [
63
- {
64
- type: "function",
65
- name: "allowance",
66
- stateMutability: "view",
67
- inputs: [
68
- { name: "owner", type: "address" },
69
- { name: "spender", type: "address" },
70
- ],
71
- outputs: [{ type: "uint256" }],
72
- },
73
- ];
74
- const [operator, baseAllow, quoteAllow] = await Promise.all([
62
+ // A token is ready when BOTH legs of its pull path are: the ERC-20 allowance to whoever pulls
63
+ // (Permit2 when wired) and, with Permit2, its unexpired allowance to the router. This used to
64
+ // read the ERC-20 allowance to the ROUTER, which on a Permit2 deployment is never granted — so
65
+ // it said "approve" forever, and approving the router changed nothing.
66
+ const { checkTokenReadiness } = await import("./onboard.js");
67
+ const ready = async (token, amount) => {
68
+ if (amount === undefined)
69
+ return true;
70
+ const r = await checkTokenReadiness(c, token, owner, amount);
71
+ return !r.needsTokenApproval && !r.needsPermit2Approval;
72
+ };
73
+ const [operator, baseOk, quoteOk] = await Promise.all([
75
74
  need.positions ? isRouterApproved(c, owner) : Promise.resolve(true),
76
- need.base
77
- ? c.public.readContract({
78
- address: key.base,
79
- abi: erc20,
80
- functionName: "allowance",
81
- args: [owner, c.addresses.router],
82
- })
83
- : Promise.resolve(0n),
84
- need.quote
85
- ? c.public.readContract({
86
- address: key.quote,
87
- abi: erc20,
88
- functionName: "allowance",
89
- args: [owner, c.addresses.router],
90
- })
91
- : Promise.resolve(0n),
75
+ ready(key.base, need.base),
76
+ ready(key.quote, need.quote),
92
77
  ]);
93
- return {
94
- needsOperator: !operator,
95
- needsBaseAllowance: need.base !== undefined && baseAllow < need.base,
96
- needsQuoteAllowance: need.quote !== undefined && quoteAllow < need.quote,
97
- };
78
+ return { needsOperator: !operator, needsBaseAllowance: !baseOk, needsQuoteAllowance: !quoteOk };
98
79
  }
package/dist/client.d.ts CHANGED
@@ -22,7 +22,7 @@ export interface LogswapAddresses {
22
22
  */
23
23
  fPoolManager?: Address;
24
24
  /**
25
- * The protocol's four F policies, one per kind (decisions 044): every F pool names one in its key,
25
+ * The protocol's four F policies, one per kind: every F pool names one in its key,
26
26
  * and the pool's roles, gates and terms live on it. A deployment predating them carries none.
27
27
  */
28
28
  publicPolicy?: Address;
@@ -30,7 +30,7 @@ export interface LogswapAddresses {
30
30
  polPolicy?: Address;
31
31
  padPolicy?: Address;
32
32
  /**
33
- * The policy registry (decisions 046): what a pool of each kind may be born under, and the
33
+ * The policy registry: what a pool of each kind may be born under, and the
34
34
  * owner that may move a pool between policies. Read from the F manager (`registry()`) when
35
35
  * absent; a deployment with none asks nobody.
36
36
  */
@@ -62,18 +62,18 @@ export declare function createLogswapClient(args: {
62
62
  * manager sailed through `assertVersion` while `FEE_MAX()` still answered on it. Move this in the
63
63
  * same change as `CParams.VERSION`, never after it.
64
64
  *
65
- * **3** since 2026-09-03 (contracts `d8406fb`): `burnById` and `update` take an `owner`, `update`
65
+ * **3** since 2026-09-03: `burnById` and `update` take an `owner`, `update`
66
66
  * mints to `to`, `positions()` returns `aps` as uint128 — and a returned quote flow now carries the
67
- * swept fees (decisions 018), which is why `exit`'s settle-to-one-asset math changed with it.
67
+ * swept fees, which is why `exit`'s settle-to-one-asset math changed with it.
68
68
  *
69
- * **4** since 2026-09-18 (decisions 042 + 040): `PoolKey` is `(base, quote, tickSpacing, phi)` —
69
+ * **4** since 2026-09-18: `PoolKey` is `(base, quote, tickSpacing, phi)` —
70
70
  * the variance kernel and its two key fields are gone, so every pool id changed; `phiEff` is
71
71
  * `phiOf`; `getPool` lost `sigma2Ema`; `Initialize` lost two fields; the cut is
72
72
  * `MINT_FEE_PROTOCOL_CUT` / `setFeeParams`.
73
73
  */
74
74
  export declare const EXPECTED_VERSION = 4n;
75
75
  /**
76
- * The F manager's own tag (contracts 25a7bcf: 5 — the key's salt, pool names, the sponsor registry;
76
+ * The F manager's own tag (5 — the key's salt, pool names, the sponsor registry;
77
77
  * 4 was the income-only harvest cut).
78
78
  *
79
79
  * A FLOOR, not an equality — unlike `EXPECTED_VERSION` above. The F primitive is iterating fast and
@@ -85,3 +85,10 @@ export declare const EXPECTED_VERSION = 4n;
85
85
  */
86
86
  export declare const EXPECTED_F_VERSION = 18n;
87
87
  export declare function assertVersion(c: LogswapClient, expected?: bigint): Promise<void>;
88
+ /**
89
+ * The versions say the managers are the right code; this says the rest of the addresses belong
90
+ * to THEM. A stale router or lens from a previous deployment passes every version check and then
91
+ * fails in the worst way: approvals go to a router that pulls for another manager, or every pull
92
+ * reverts because the router's Permit2 is not the one the user approved.
93
+ */
94
+ export declare function assertWiring(c: LogswapClient): Promise<void>;
package/dist/client.js CHANGED
@@ -8,7 +8,39 @@
8
8
  * the protocol, not configuration that grows.
9
9
  */
10
10
  export function createLogswapClient(args) {
11
- return { public: args.public, wallet: args.wallet, addresses: args.addresses };
11
+ return { public: args.public, wallet: args.wallet && guardChain(args.wallet, args.public), addresses: args.addresses };
12
+ }
13
+ /**
14
+ * The wallet, with every signing call first checking that it is on the chain the reads come from.
15
+ *
16
+ * An injected wallet switched to another network would otherwise estimate and simulate against
17
+ * the public client's chain and then sign and send on its own — with deterministic deploy
18
+ * addresses, possibly into a different contract. Checked at each call, not once: a user can
19
+ * switch networks mid-session.
20
+ */
21
+ function guardChain(wallet, pub) {
22
+ if (typeof wallet.getChainId !== "function")
23
+ return wallet; // a test double
24
+ let readChain = pub.chain?.id;
25
+ const check = async () => {
26
+ readChain ??= await pub.getChainId();
27
+ const walletChain = await wallet.getChainId();
28
+ if (walletChain !== readChain) {
29
+ throw new Error(`logswap: the wallet is on chain ${walletChain} but this app reads chain ${readChain} — switch the wallet's network`);
30
+ }
31
+ };
32
+ const guarded = new Set(["writeContract", "sendTransaction", "signTypedData"]);
33
+ return new Proxy(wallet, {
34
+ get(target, prop, receiver) {
35
+ const v = Reflect.get(target, prop, receiver);
36
+ if (!guarded.has(prop) || typeof v !== "function")
37
+ return v;
38
+ return async (...a) => {
39
+ await check();
40
+ return v.apply(target, a);
41
+ };
42
+ },
43
+ });
12
44
  }
13
45
  /**
14
46
  * Assert the deployment matches the ABI this build was generated against.
@@ -24,18 +56,18 @@ export function createLogswapClient(args) {
24
56
  * manager sailed through `assertVersion` while `FEE_MAX()` still answered on it. Move this in the
25
57
  * same change as `CParams.VERSION`, never after it.
26
58
  *
27
- * **3** since 2026-09-03 (contracts `d8406fb`): `burnById` and `update` take an `owner`, `update`
59
+ * **3** since 2026-09-03: `burnById` and `update` take an `owner`, `update`
28
60
  * mints to `to`, `positions()` returns `aps` as uint128 — and a returned quote flow now carries the
29
- * swept fees (decisions 018), which is why `exit`'s settle-to-one-asset math changed with it.
61
+ * swept fees, which is why `exit`'s settle-to-one-asset math changed with it.
30
62
  *
31
- * **4** since 2026-09-18 (decisions 042 + 040): `PoolKey` is `(base, quote, tickSpacing, phi)` —
63
+ * **4** since 2026-09-18: `PoolKey` is `(base, quote, tickSpacing, phi)` —
32
64
  * the variance kernel and its two key fields are gone, so every pool id changed; `phiEff` is
33
65
  * `phiOf`; `getPool` lost `sigma2Ema`; `Initialize` lost two fields; the cut is
34
66
  * `MINT_FEE_PROTOCOL_CUT` / `setFeeParams`.
35
67
  */
36
68
  export const EXPECTED_VERSION = 4n;
37
69
  /**
38
- * The F manager's own tag (contracts 25a7bcf: 5 — the key's salt, pool names, the sponsor registry;
70
+ * The F manager's own tag (5 — the key's salt, pool names, the sponsor registry;
39
71
  * 4 was the income-only harvest cut).
40
72
  *
41
73
  * A FLOOR, not an equality — unlike `EXPECTED_VERSION` above. The F primitive is iterating fast and
@@ -47,43 +79,43 @@ export const EXPECTED_VERSION = 4n;
47
79
  */
48
80
  // 7 (2026-09-15): the reserves are the state and the marks derived, `lockStrike`, the dividend
49
81
  // harvest, the relaunch (`dissolve(poolId, successor)`, `rollIn`), the reserve-named seed, the
50
- // sponsor facet — decisions 025–031.
51
- // 8 (2026-09-15): the relaunch is in place (decisions 032) — every share id carries the
82
+ // sponsor facet.
83
+ // 8 (2026-09-15): the relaunch is in place — every share id carries the
52
84
  // generation (`fPoolShareId(poolId, gen)`), `dissolve(poolId)`, `redeem`, `claim` / `dividendOf`
53
85
  // / `rollIn` / `previewRollIn` take the generation, `Pool.gen` replaces `dissolved` and
54
86
  // `successor`. A 7 deployment's share ids do not even resolve with this SDK.
55
- // 9 (2026-09-16): `deepen` is gone, `Pool.leverTheta` is `harvestedTheta` (decisions 034).
87
+ // 9 (2026-09-16): `deepen` is gone, `Pool.leverTheta` is `harvestedTheta`.
56
88
  // 10 (2026-09-17): no floor guard — `Pool.minBuffer`, `raiseMinBuffer`, `LEVER_FLOOR` gone;
57
- // `dissolve` needs an empty bid on every pool (decisions 035); the key bit is the CLASS, `pad`
89
+ // `dissolve` needs an empty bid on every pool; the key bit is the CLASS, `pad`
58
90
  // (036): `seed` mints a pad's shares to 0xdead, and the operator, the gates and the list are
59
91
  // refused on it — `fPoolLaunch` no longer transfers the seed itself.
60
- // 11 (2026-09-17): roles and entries (decisions 037) — the operator runs the pool (`harvest`,
92
+ // 11 (2026-09-17): roles and entries — the operator runs the pool (`harvest`,
61
93
  // `dissolve`, `setLegs`) and is the authority until appointed, two-step (`acceptOperator`,
62
94
  // `Pool.pendingOperator`); `setLegL`/`admitLeg` are gone, `setLegs` edits the next generation's
63
95
  // table while unseeded; `fPoolRelaunch` takes `legs`.
64
96
  // `getPool` decodes one field fewer, so a 9 deployment's struct no longer matches this SDK.
65
- // 12 (2026-09-17): one contract (decisions 039) — the sponsor facet is folded back into the
97
+ // 12 (2026-09-17): one contract — the sponsor facet is folded back into the
66
98
  // manager; `sponsor()` and the fallback are gone, every entry, error and event unchanged, so
67
99
  // `fPoolManagerAbi` is the manager's own ABI and no longer a union. Layout unchanged.
68
- // 13 (2026-09-18): one vocabulary (decisions 040) — `HARVEST_FEE` is `HARVEST_PROTOCOL_CUT` (a
100
+ // 13 (2026-09-18): one vocabulary — `HARVEST_FEE` is `HARVEST_PROTOCOL_CUT` (a
69
101
  // cut of harvested income, not a fee on the harvest) and the exemption is `harvestCutExempt` /
70
102
  // `setHarvestCutExempt`; `acceptAuthority` clears the operator, pending and accepted (a mandate
71
103
  // is voided by the handover of the role that granted it); `setLegs` needs a generation to have
72
104
  // lived (`gen > 0`); `initialize` refuses a codeless base and `mint` refuses `to = address(0)`
73
105
  // on both managers. Layout unchanged.
74
- // 14 (2026-09-20): the policy (decisions 041) — `FPoolKey.policy`, every pool id moves; `dissolve` is
106
+ // 14 (2026-09-20): the policy — `FPoolKey.policy`, every pool id moves; `dissolve` is
75
107
  // `retire`, `regenerate` releases the dead shares' pot, `harvestable`/`unharvested` are the
76
108
  // readouts; `feePerL`, `edgePerL`, `HARVEST_PROTOCOL_CUT`, `harvestCutExempt` are gone.
77
- // 15 (2026-09-20): the policy answers every question (decisions 044) — `FPoolKey.authority` is
109
+ // 15 (2026-09-20): the policy answers every question — `FPoolKey.authority` is
78
110
  // `creator` (every pool id moves again); the roles, gates and list are the pool's POLICY's
79
111
  // (`FPolicyBase` / `PrivatePolicy`), not the manager's: `getPool` has `policy` and `asks` and no
80
112
  // role; `proposeAuthority`, `appointOperator`, `setGates`, `setAllowed` (`setMinters` / `setTakers` since 17) are policy writes;
81
113
  // `policyApproved(kind, policy)` / `setPolicyApproved(kind, policy, ok)`; `Regenerated` names the payee;
82
114
  // `HARVEST_CUT_MAX` / `POT_CUT_MAX` cap what a policy may route to the protocol.
83
- // 16 (2026-09-21): the word is POLICY (decisions 045) — a rename, no pool id moves: `FPoolKey.policy`,
115
+ // 16 (2026-09-21): the word is POLICY — a rename, no pool id moves: `FPoolKey.policy`,
84
116
  // `IFPolicy`, `FPolicyBase`, `PublicPolicy` … `PadPolicy`, `policyApproved` / `setPolicyApproved`,
85
117
  // `PolicyApproved`; `PolPolicy.setPolicy` / `PolicySet` are `setRules` / `RulesSet`.
86
- // 17 (2026-09-21): the registry, the move, the stamp, two lists, one base (decisions 046–048) — no
118
+ // 17 (2026-09-21): the registry, the move, the stamp, two lists, one base — no
87
119
  // pool id moves: the F manager takes a `PolicyRegistry` at construction and asks it at a birth
88
120
  // (`policyApproved` / `setPolicyApproved` left the manager: `registryApprove` …); the key's policy
89
121
  // is the policy at birth, `migrate` moves a pool for the registry's owner; `stamp` / `setAsks` are
@@ -101,7 +133,7 @@ export async function assertVersion(c, expected = EXPECTED_VERSION) {
101
133
  throw new Error(`logswap: manager at ${c.addresses.cPoolManager} reports version ${got}, but this SDK was built ` +
102
134
  `against version ${expected}. Upgrade the SDK or point at the matching deployment.`);
103
135
  }
104
- // the F manager carries its own tag since contracts c782811; assert it the same way when deployed
136
+ // the F manager carries its own tag; assert it the same way when deployed
105
137
  if (c.addresses.fPoolManager) {
106
138
  const { fPoolManagerAbi } = await import("./generated.js");
107
139
  const gotF = await c.public.readContract({
@@ -114,4 +146,29 @@ export async function assertVersion(c, expected = EXPECTED_VERSION) {
114
146
  `at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`);
115
147
  }
116
148
  }
149
+ await assertWiring(c);
150
+ }
151
+ /**
152
+ * The versions say the managers are the right code; this says the rest of the addresses belong
153
+ * to THEM. A stale router or lens from a previous deployment passes every version check and then
154
+ * fails in the worst way: approvals go to a router that pulls for another manager, or every pull
155
+ * reverts because the router's Permit2 is not the one the user approved.
156
+ */
157
+ export async function assertWiring(c) {
158
+ const { logswapRouterAbi, logswapLensAbi } = await import("./generated.js");
159
+ const rd = (address, abi, functionName) => c.public.readContract({ address, abi, functionName });
160
+ const ZERO = "0x0000000000000000000000000000000000000000";
161
+ const want = [
162
+ ["router.mgr()", rd(c.addresses.router, logswapRouterAbi, "mgr"), c.addresses.cPoolManager],
163
+ ["router.permit2()", rd(c.addresses.router, logswapRouterAbi, "permit2"), c.addresses.permit2 ?? ZERO],
164
+ ["lens.mgr()", rd(c.addresses.lens, logswapLensAbi, "mgr"), c.addresses.cPoolManager],
165
+ ];
166
+ if (c.addresses.fPoolManager) {
167
+ want.push(["router.fmgr()", rd(c.addresses.router, logswapRouterAbi, "fmgr"), c.addresses.fPoolManager]);
168
+ want.push(["lens.fmgr()", rd(c.addresses.lens, logswapLensAbi, "fmgr"), c.addresses.fPoolManager]);
169
+ }
170
+ const got = await Promise.all(want.map((w) => w[1]));
171
+ const bad = want.flatMap(([what, , expected], i) => got[i].toLowerCase() === expected.toLowerCase() ? [] : [`${what} is ${got[i]}, expected ${expected}`]);
172
+ if (bad.length)
173
+ throw new Error(`logswap: the deployment's addresses do not belong together — ${bad.join("; ")}`);
117
174
  }