@sm-lab/recipes 0.2.0

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/cm.mjs ADDED
@@ -0,0 +1,305 @@
1
+ import { a as deposit, c as RESUME_ROLE, d as addKeys, h as roleMember, m as actAs, n as topUpActiveKeys, o as DEFAULT_ADMIN_ROLE, p as randomSeed, u as SET_TREE_ROLE, v as resolveGate } from "./topup-B2NzJV7_.mjs";
2
+ import { concat, keccak256, toHex, zeroAddress } from "viem";
3
+ import { curatedGateAbi, metaRegistryAbi } from "@sm-lab/receipts";
4
+ import { buildAddressesTree } from "@sm-lab/merkle";
5
+ //#region src/cm/index.ts
6
+ /** An empty MetaRegistry OperatorGroup — used to reset a group to its zero state. */
7
+ const EMPTY_GROUP = {
8
+ name: "",
9
+ subNodeOperators: [],
10
+ externalOperators: []
11
+ };
12
+ /**
13
+ * Create a node operator through a curated gate by temporarily installing a 2-leaf merkle
14
+ * tree that admits `operator`, creating the operator, then restoring the gate's original
15
+ * tree. (Port of `NodeOperators.createCuratedOperator`; no post-assertions — returns noId.)
16
+ */
17
+ async function createCuratedOperator(ctx, opts) {
18
+ if (ctx.module !== "cm") throw new Error("@sm-lab/recipes/cm: createCuratedOperator requires ctx.module === \"cm\"");
19
+ const gate = {
20
+ address: resolveGate(ctx, opts.selector),
21
+ abi: curatedGateAbi
22
+ };
23
+ const extra = opts.extra ?? deriveExtra(opts.operator);
24
+ const origRoot = await ctx.client.readContract({
25
+ ...gate,
26
+ functionName: "treeRoot"
27
+ });
28
+ const origCid = await ctx.client.readContract({
29
+ ...gate,
30
+ functionName: "treeCid"
31
+ });
32
+ const admin = await roleMember(ctx, gate, DEFAULT_ADMIN_ROLE);
33
+ const tree = buildAddressesTree([opts.operator, extra]);
34
+ const tmpRoot = tree.root;
35
+ const proof = tree.getProof([opts.operator]);
36
+ const tmpCid = `tmp-cid-${opts.operator.toLowerCase()}`;
37
+ await actAs(ctx, admin, async (from) => {
38
+ await ctx.client.writeContract({
39
+ ...gate,
40
+ functionName: "grantRole",
41
+ args: [SET_TREE_ROLE, admin],
42
+ account: from,
43
+ chain: null
44
+ });
45
+ if (await ctx.client.readContract({
46
+ ...gate,
47
+ functionName: "isPaused"
48
+ })) {
49
+ await ctx.client.writeContract({
50
+ ...gate,
51
+ functionName: "grantRole",
52
+ args: [RESUME_ROLE, admin],
53
+ account: from,
54
+ chain: null
55
+ });
56
+ await ctx.client.writeContract({
57
+ ...gate,
58
+ functionName: "resume",
59
+ account: from,
60
+ chain: null
61
+ });
62
+ }
63
+ await ctx.client.writeContract({
64
+ ...gate,
65
+ functionName: "setTreeParams",
66
+ args: [tmpRoot, tmpCid],
67
+ account: from,
68
+ chain: null
69
+ });
70
+ });
71
+ try {
72
+ const { result, request } = await ctx.client.simulateContract({
73
+ ...gate,
74
+ functionName: "createNodeOperator",
75
+ args: [
76
+ opts.name ?? "fork-operator",
77
+ opts.description ?? "fork-test",
78
+ zeroAddress,
79
+ zeroAddress,
80
+ proof
81
+ ],
82
+ account: opts.operator
83
+ });
84
+ const noId = result;
85
+ await actAs(ctx, opts.operator, () => ctx.client.writeContract({
86
+ ...request,
87
+ chain: null
88
+ }));
89
+ return { noId };
90
+ } finally {
91
+ await actAs(ctx, admin, (from) => ctx.client.writeContract({
92
+ ...gate,
93
+ functionName: "setTreeParams",
94
+ args: [origRoot, origCid],
95
+ account: from,
96
+ chain: null
97
+ }));
98
+ }
99
+ }
100
+ /**
101
+ * Create a node-operator group on the MetaRegistry: reset any pre-existing memberships of the
102
+ * named operators, then write a fresh group under `NO_GROUP_ID`. Port of
103
+ * `MetaRegistryHelpers.createOperatorGroup` (the `MANAGE_OPERATOR_GROUPS_ROLE` member acts).
104
+ */
105
+ async function createOperatorGroup(ctx, opts) {
106
+ if (ctx.module !== "cm") throw new Error("@sm-lab/recipes/cm: createOperatorGroup requires ctx.module === \"cm\"");
107
+ if (opts.pairs.length < 1) throw new Error("@sm-lab/recipes/cm: createOperatorGroup needs ≥1 [noId, shareBps] pair");
108
+ const shareSum = opts.pairs.reduce((acc, [, s]) => acc + s, 0n);
109
+ if (shareSum !== 10000n) throw new Error(`@sm-lab/recipes/cm: shares must sum to 10000 bps (got ${shareSum})`);
110
+ const mr = {
111
+ address: ctx.addresses.MetaRegistry,
112
+ abi: metaRegistryAbi
113
+ };
114
+ const manager = await roleMember(ctx, mr, await ctx.client.readContract({
115
+ ...mr,
116
+ functionName: "MANAGE_OPERATOR_GROUPS_ROLE"
117
+ }));
118
+ const noGroupId = await ctx.client.readContract({
119
+ ...mr,
120
+ functionName: "NO_GROUP_ID"
121
+ });
122
+ const gids = await Promise.all(opts.pairs.map(([noId]) => ctx.client.readContract({
123
+ ...mr,
124
+ functionName: "getNodeOperatorGroupId",
125
+ args: [noId]
126
+ })));
127
+ const toReset = /* @__PURE__ */ new Set();
128
+ for (const gid of gids) if (gid !== noGroupId) toReset.add(gid);
129
+ const resetGroupIds = [...toReset];
130
+ const subNodeOperators = opts.pairs.map(([noId, share]) => ({
131
+ nodeOperatorId: noId,
132
+ share: Number(share)
133
+ }));
134
+ const group = {
135
+ name: opts.name ?? "",
136
+ subNodeOperators,
137
+ externalOperators: []
138
+ };
139
+ await actAs(ctx, manager, async (from) => {
140
+ for (const gid of resetGroupIds) await ctx.client.writeContract({
141
+ ...mr,
142
+ functionName: "createOrUpdateOperatorGroup",
143
+ args: [gid, EMPTY_GROUP],
144
+ account: from,
145
+ chain: null
146
+ });
147
+ await ctx.client.writeContract({
148
+ ...mr,
149
+ functionName: "createOrUpdateOperatorGroup",
150
+ args: [noGroupId, group],
151
+ account: from,
152
+ chain: null
153
+ });
154
+ });
155
+ return {
156
+ subNodeOperators,
157
+ resetGroupIds
158
+ };
159
+ }
160
+ /**
161
+ * Reset the operator group that contains `noId` to its empty state. Port of
162
+ * `MetaRegistryHelpers.resetOperatorGroup`; throws if the operator is in no group.
163
+ */
164
+ async function resetOperatorGroup(ctx, opts) {
165
+ if (ctx.module !== "cm") throw new Error("@sm-lab/recipes/cm: resetOperatorGroup requires ctx.module === \"cm\"");
166
+ const mr = {
167
+ address: ctx.addresses.MetaRegistry,
168
+ abi: metaRegistryAbi
169
+ };
170
+ const manager = await roleMember(ctx, mr, await ctx.client.readContract({
171
+ ...mr,
172
+ functionName: "MANAGE_OPERATOR_GROUPS_ROLE"
173
+ }));
174
+ const gid = await ctx.client.readContract({
175
+ ...mr,
176
+ functionName: "getNodeOperatorGroupId",
177
+ args: [opts.noId]
178
+ });
179
+ if (gid === await ctx.client.readContract({
180
+ ...mr,
181
+ functionName: "NO_GROUP_ID"
182
+ })) throw new Error("@sm-lab/recipes/cm: operator not in a group");
183
+ await actAs(ctx, manager, (from) => ctx.client.writeContract({
184
+ ...mr,
185
+ functionName: "createOrUpdateOperatorGroup",
186
+ args: [gid, EMPTY_GROUP],
187
+ account: from,
188
+ chain: null
189
+ }));
190
+ return { groupId: gid };
191
+ }
192
+ /**
193
+ * Set a bond-curve weight on the MetaRegistry. Port of
194
+ * `MetaRegistryHelpers.setBondCurveWeight` (the `SET_BOND_CURVE_WEIGHT_ROLE` member acts).
195
+ */
196
+ async function setBondCurveWeight(ctx, opts) {
197
+ if (ctx.module !== "cm") throw new Error("@sm-lab/recipes/cm: setBondCurveWeight requires ctx.module === \"cm\"");
198
+ const mr = {
199
+ address: ctx.addresses.MetaRegistry,
200
+ abi: metaRegistryAbi
201
+ };
202
+ await actAs(ctx, await roleMember(ctx, mr, await ctx.client.readContract({
203
+ ...mr,
204
+ functionName: "SET_BOND_CURVE_WEIGHT_ROLE"
205
+ })), (from) => ctx.client.writeContract({
206
+ ...mr,
207
+ functionName: "setBondCurveWeight",
208
+ args: [opts.curveId, opts.weight],
209
+ account: from,
210
+ chain: null
211
+ }));
212
+ return {
213
+ curveId: opts.curveId,
214
+ weight: opts.weight
215
+ };
216
+ }
217
+ /** A deterministic address distinct from `operator` (low 20 bytes of its keccak hash). */
218
+ function deriveExtra(operator) {
219
+ return `0x${keccak256(operator).slice(-40)}`;
220
+ }
221
+ /** A deterministic address from a seed + label (low 20 bytes of keccak — mirrors deriveExtra). */
222
+ function deriveOperatorAddress(seed, i) {
223
+ return `0x${keccak256(concat([seed, toHex(`cm-operator-${i}`)])).slice(-40)}`;
224
+ }
225
+ /** A deterministic key seed from a seed + label (so the 7 addKeys calls never collide on pubkeys). */
226
+ function keySeed(seed, label) {
227
+ return keccak256(concat([seed, toHex(label)]));
228
+ }
229
+ /**
230
+ * Seed a realistic cm fork in one call: create 3 gate operators, group them 34/33/33, then key /
231
+ * deposit / top-up across 3 rounds (and add a final key to two of them). Port of `fork.just
232
+ * seed-cm`. Composes already-tested recipes; uses the noIds returned by `createCuratedOperator`
233
+ * (not hardcoded 0/1/2) so it is correct on a non-fresh fork too. Deterministic when `seed` is set.
234
+ */
235
+ async function seedCm(ctx, opts = {}) {
236
+ if (ctx.module !== "cm") throw new Error("@sm-lab/recipes/cm: seedCm requires ctx.module === \"cm\"");
237
+ const selector = opts.selector ?? "po";
238
+ const seed = opts.seed ?? randomSeed();
239
+ const operators = [
240
+ deriveOperatorAddress(seed, 0),
241
+ deriveOperatorAddress(seed, 1),
242
+ deriveOperatorAddress(seed, 2)
243
+ ];
244
+ const { noId: na } = await createCuratedOperator(ctx, {
245
+ selector,
246
+ operator: operators[0]
247
+ });
248
+ const { noId: nb } = await createCuratedOperator(ctx, {
249
+ selector,
250
+ operator: operators[1]
251
+ });
252
+ const { noId: nc } = await createCuratedOperator(ctx, {
253
+ selector,
254
+ operator: operators[2]
255
+ });
256
+ const noIds = [
257
+ na,
258
+ nb,
259
+ nc
260
+ ];
261
+ await createOperatorGroup(ctx, { pairs: [
262
+ [na, 3400n],
263
+ [nb, 3300n],
264
+ [nc, 3300n]
265
+ ] });
266
+ await addKeys(ctx, {
267
+ noId: na,
268
+ count: 4,
269
+ seed: keySeed(seed, "r0")
270
+ });
271
+ await deposit(ctx, { count: 100 });
272
+ await topUpActiveKeys(ctx, { noId: na });
273
+ await addKeys(ctx, {
274
+ noId: nb,
275
+ count: 5,
276
+ seed: keySeed(seed, "r1")
277
+ });
278
+ await deposit(ctx, { count: 100 });
279
+ await topUpActiveKeys(ctx, { noId: nb });
280
+ await addKeys(ctx, {
281
+ noId: na,
282
+ count: 6,
283
+ seed: keySeed(seed, "r2")
284
+ });
285
+ await deposit(ctx, { count: 100 });
286
+ await topUpActiveKeys(ctx, { noId: na });
287
+ await addKeys(ctx, {
288
+ noId: na,
289
+ count: 1,
290
+ seed: keySeed(seed, "r3")
291
+ });
292
+ await addKeys(ctx, {
293
+ noId: nb,
294
+ count: 1,
295
+ seed: keySeed(seed, "r4")
296
+ });
297
+ return {
298
+ noIds,
299
+ operators
300
+ };
301
+ }
302
+ //#endregion
303
+ export { createCuratedOperator, createOperatorGroup, resetOperatorGroup, seedCm, setBondCurveWeight };
304
+
305
+ //# sourceMappingURL=cm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cm.mjs","names":[],"sources":["../src/cm/index.ts"],"sourcesContent":["import { buildAddressesTree } from '@sm-lab/merkle';\nimport { curatedGateAbi, metaRegistryAbi } from '@sm-lab/receipts';\nimport type { CmAddressBook, Hex } from '@sm-lab/receipts';\nimport { concat, keccak256, toHex, zeroAddress } from 'viem';\nimport { actAs, roleMember } from '../act-as';\nimport { resolveGate, type Ctx, type CmGateSelector } from '../context';\nimport { randomSeed } from '../random';\nimport { addKeys } from '../recipes/add-keys';\nimport { deposit } from '../recipes/deposit';\nimport { topUpActiveKeys } from '../recipes/topup';\nimport { DEFAULT_ADMIN_ROLE, RESUME_ROLE, SET_TREE_ROLE } from '../roles';\n\n/** An empty MetaRegistry OperatorGroup — used to reset a group to its zero state. */\nconst EMPTY_GROUP = { name: '', subNodeOperators: [], externalOperators: [] };\n\nexport interface CreateCuratedOperatorOptions {\n /** cm gate selector (po/pto/…) or a raw 0x… address. */\n selector: CmGateSelector | string;\n /** The new operator's address; also the merkle leaf proven into the temp tree. */\n operator: Hex;\n /** Second leaf for the N=2 temp tree; derived deterministically from `operator` if omitted. */\n extra?: Hex;\n name?: string;\n description?: string;\n}\n\nexport interface CreateCuratedOperatorResult {\n noId: bigint;\n}\n\n/**\n * Create a node operator through a curated gate by temporarily installing a 2-leaf merkle\n * tree that admits `operator`, creating the operator, then restoring the gate's original\n * tree. (Port of `NodeOperators.createCuratedOperator`; no post-assertions — returns noId.)\n */\nexport async function createCuratedOperator(\n ctx: Ctx,\n opts: CreateCuratedOperatorOptions,\n): Promise<CreateCuratedOperatorResult> {\n if (ctx.module !== 'cm') {\n throw new Error('@sm-lab/recipes/cm: createCuratedOperator requires ctx.module === \"cm\"');\n }\n const gate = { address: resolveGate(ctx, opts.selector), abi: curatedGateAbi } as const;\n const extra = opts.extra ?? deriveExtra(opts.operator);\n\n // Snapshot the original tree so we can restore it after creating the operator.\n const origRoot = await ctx.client.readContract({ ...gate, functionName: 'treeRoot' });\n const origCid = await ctx.client.readContract({ ...gate, functionName: 'treeCid' });\n const admin = await roleMember(ctx, gate, DEFAULT_ADMIN_ROLE);\n\n // N=2 OZ ['address'] tree; prove by value (OZ sorts leaves, so index is unreliable).\n const tree = buildAddressesTree([opts.operator, extra]);\n const tmpRoot = tree.root as Hex;\n const proof = tree.getProof([opts.operator]) as Hex[];\n const tmpCid = `tmp-cid-${opts.operator.toLowerCase()}`;\n\n // Install the temp tree as the gate admin (grant SET_TREE_ROLE, resume if paused, set).\n await actAs(ctx, admin, async (from) => {\n await ctx.client.writeContract({\n ...gate,\n functionName: 'grantRole',\n args: [SET_TREE_ROLE, admin],\n account: from,\n chain: null,\n });\n const paused = await ctx.client.readContract({ ...gate, functionName: 'isPaused' });\n if (paused) {\n await ctx.client.writeContract({\n ...gate,\n functionName: 'grantRole',\n args: [RESUME_ROLE, admin],\n account: from,\n chain: null,\n });\n await ctx.client.writeContract({\n ...gate,\n functionName: 'resume',\n account: from,\n chain: null,\n });\n }\n await ctx.client.writeContract({\n ...gate,\n functionName: 'setTreeParams',\n args: [tmpRoot, tmpCid],\n account: from,\n chain: null,\n });\n });\n\n // Capture the returned noId via simulate, then send the real tx as the operator.\n // The restore runs in `finally` so a failed create never leaves the gate with the\n // temporary tree installed on the fork (matching actAs's stop-on-any-exit discipline).\n try {\n const { result, request } = await ctx.client.simulateContract({\n ...gate,\n functionName: 'createNodeOperator',\n args: [\n opts.name ?? 'fork-operator',\n opts.description ?? 'fork-test',\n zeroAddress,\n zeroAddress,\n proof,\n ],\n account: opts.operator,\n });\n const noId = result as bigint;\n await actAs(ctx, opts.operator, () => ctx.client.writeContract({ ...request, chain: null }));\n return { noId };\n } finally {\n await actAs(ctx, admin, (from) =>\n ctx.client.writeContract({\n ...gate,\n functionName: 'setTreeParams',\n args: [origRoot, origCid],\n account: from,\n chain: null,\n }),\n );\n }\n}\n\nexport interface CreateOperatorGroupOptions {\n /** [nodeOperatorId, shareBps] pairs; shares in basis points (e.g. 6000 = 60%), must sum to 10000. */\n pairs: [bigint, bigint][];\n /** Optional group name (Solidity leaves it ''); default ''. */\n name?: string;\n}\n\nexport interface CreateOperatorGroupResult {\n /** The sub-node-operators written into the NO_GROUP_ID group, in input order. */\n subNodeOperators: { nodeOperatorId: bigint; share: number }[];\n /** Group ids reset to EMPTY_GROUP first (operators previously in a group), de-duplicated. */\n resetGroupIds: bigint[];\n}\n\n/**\n * Create a node-operator group on the MetaRegistry: reset any pre-existing memberships of the\n * named operators, then write a fresh group under `NO_GROUP_ID`. Port of\n * `MetaRegistryHelpers.createOperatorGroup` (the `MANAGE_OPERATOR_GROUPS_ROLE` member acts).\n */\nexport async function createOperatorGroup(\n ctx: Ctx,\n opts: CreateOperatorGroupOptions,\n): Promise<CreateOperatorGroupResult> {\n if (ctx.module !== 'cm') {\n throw new Error('@sm-lab/recipes/cm: createOperatorGroup requires ctx.module === \"cm\"');\n }\n // Validate input BEFORE any chain call — a precise error beats an opaque on-chain revert.\n if (opts.pairs.length < 1)\n throw new Error('@sm-lab/recipes/cm: createOperatorGroup needs ≥1 [noId, shareBps] pair');\n const shareSum = opts.pairs.reduce((acc, [, s]) => acc + s, 0n);\n if (shareSum !== 10000n)\n throw new Error(`@sm-lab/recipes/cm: shares must sum to 10000 bps (got ${shareSum})`);\n\n const mr = {\n address: (ctx.addresses as CmAddressBook).MetaRegistry as Hex,\n abi: metaRegistryAbi,\n } as const;\n const role = (await ctx.client.readContract({\n ...mr,\n functionName: 'MANAGE_OPERATOR_GROUPS_ROLE',\n })) as Hex;\n const manager = await roleMember(ctx, mr, role);\n\n const noGroupId = (await ctx.client.readContract({\n ...mr,\n functionName: 'NO_GROUP_ID',\n })) as bigint;\n\n // Resolve current memberships up front (parallel reads — order-independent), then de-dup the gids\n // to reset (a harmless divergence from Solidity's per-pair reset, which can double-reset an\n // already-emptied gid). A Set preserves insertion order, so resetGroupIds stays deterministic in\n // first-encounter order.\n const gids = (await Promise.all(\n opts.pairs.map(([noId]) =>\n ctx.client.readContract({ ...mr, functionName: 'getNodeOperatorGroupId', args: [noId] }),\n ),\n )) as bigint[];\n const toReset = new Set<bigint>();\n for (const gid of gids) {\n if (gid !== noGroupId) toReset.add(gid);\n }\n const resetGroupIds = [...toReset];\n\n const subNodeOperators = opts.pairs.map(([noId, share]) => ({\n nodeOperatorId: noId, // bigint (uint64)\n share: Number(share), // number (uint16)\n }));\n const group = { name: opts.name ?? '', subNodeOperators, externalOperators: [] };\n\n await actAs(ctx, manager, async (from) => {\n for (const gid of resetGroupIds) {\n // eslint-disable-next-line no-await-in-loop -- sequential by necessity (impersonation is global state)\n await ctx.client.writeContract({\n ...mr,\n functionName: 'createOrUpdateOperatorGroup',\n args: [gid, EMPTY_GROUP],\n account: from,\n chain: null,\n });\n }\n await ctx.client.writeContract({\n ...mr,\n functionName: 'createOrUpdateOperatorGroup',\n args: [noGroupId, group],\n account: from,\n chain: null,\n });\n });\n\n return { subNodeOperators, resetGroupIds };\n}\n\nexport interface ResetOperatorGroupResult {\n groupId: bigint;\n}\n\n/**\n * Reset the operator group that contains `noId` to its empty state. Port of\n * `MetaRegistryHelpers.resetOperatorGroup`; throws if the operator is in no group.\n */\nexport async function resetOperatorGroup(\n ctx: Ctx,\n opts: { noId: bigint },\n): Promise<ResetOperatorGroupResult> {\n if (ctx.module !== 'cm') {\n throw new Error('@sm-lab/recipes/cm: resetOperatorGroup requires ctx.module === \"cm\"');\n }\n const mr = {\n address: (ctx.addresses as CmAddressBook).MetaRegistry as Hex,\n abi: metaRegistryAbi,\n } as const;\n const role = (await ctx.client.readContract({\n ...mr,\n functionName: 'MANAGE_OPERATOR_GROUPS_ROLE',\n })) as Hex;\n const manager = await roleMember(ctx, mr, role);\n\n const gid = (await ctx.client.readContract({\n ...mr,\n functionName: 'getNodeOperatorGroupId',\n args: [opts.noId],\n })) as bigint;\n const noGroupId = (await ctx.client.readContract({\n ...mr,\n functionName: 'NO_GROUP_ID',\n })) as bigint;\n if (gid === noGroupId) throw new Error('@sm-lab/recipes/cm: operator not in a group');\n\n await actAs(ctx, manager, (from) =>\n ctx.client.writeContract({\n ...mr,\n functionName: 'createOrUpdateOperatorGroup',\n args: [gid, EMPTY_GROUP],\n account: from,\n chain: null,\n }),\n );\n\n return { groupId: gid };\n}\n\n/**\n * Set a bond-curve weight on the MetaRegistry. Port of\n * `MetaRegistryHelpers.setBondCurveWeight` (the `SET_BOND_CURVE_WEIGHT_ROLE` member acts).\n */\nexport async function setBondCurveWeight(\n ctx: Ctx,\n opts: { curveId: bigint; weight: bigint },\n): Promise<{ curveId: bigint; weight: bigint }> {\n if (ctx.module !== 'cm') {\n throw new Error('@sm-lab/recipes/cm: setBondCurveWeight requires ctx.module === \"cm\"');\n }\n const mr = {\n address: (ctx.addresses as CmAddressBook).MetaRegistry as Hex,\n abi: metaRegistryAbi,\n } as const;\n const role = (await ctx.client.readContract({\n ...mr,\n functionName: 'SET_BOND_CURVE_WEIGHT_ROLE',\n })) as Hex;\n const setter = await roleMember(ctx, mr, role);\n\n await actAs(ctx, setter, (from) =>\n ctx.client.writeContract({\n ...mr,\n functionName: 'setBondCurveWeight',\n args: [opts.curveId, opts.weight],\n account: from,\n chain: null,\n }),\n );\n\n return { curveId: opts.curveId, weight: opts.weight };\n}\n\n/** A deterministic address distinct from `operator` (low 20 bytes of its keccak hash). */\nfunction deriveExtra(operator: Hex): Hex {\n const h = keccak256(operator); // 0x + 64 hex chars\n return `0x${h.slice(-40)}` as Hex; // low 20 bytes → a valid, distinct address\n}\n\nexport interface SeedCmOptions {\n /** cm gate selector for the 3 created operators (default 'po' = CuratedGates[0]). */\n selector?: CmGateSelector | string;\n /** Deterministic seed for the operator addresses + key material. Omit → fresh random. */\n seed?: Hex;\n}\n\nexport interface SeedCmResult {\n /** The 3 created operators' noIds, in creation order (a/b/c). */\n noIds: [bigint, bigint, bigint];\n /** The operator addresses generated for the gate, parallel to `noIds`. */\n operators: [Hex, Hex, Hex];\n}\n\n/** A deterministic address from a seed + label (low 20 bytes of keccak — mirrors deriveExtra). */\nfunction deriveOperatorAddress(seed: Hex, i: number): Hex {\n const h = keccak256(concat([seed, toHex(`cm-operator-${i}`)]));\n return `0x${h.slice(-40)}` as Hex;\n}\n\n/** A deterministic key seed from a seed + label (so the 7 addKeys calls never collide on pubkeys). */\nfunction keySeed(seed: Hex, label: string): Hex {\n return keccak256(concat([seed, toHex(label)]));\n}\n\n/**\n * Seed a realistic cm fork in one call: create 3 gate operators, group them 34/33/33, then key /\n * deposit / top-up across 3 rounds (and add a final key to two of them). Port of `fork.just\n * seed-cm`. Composes already-tested recipes; uses the noIds returned by `createCuratedOperator`\n * (not hardcoded 0/1/2) so it is correct on a non-fresh fork too. Deterministic when `seed` is set.\n */\nexport async function seedCm(ctx: Ctx, opts: SeedCmOptions = {}): Promise<SeedCmResult> {\n if (ctx.module !== 'cm') {\n throw new Error('@sm-lab/recipes/cm: seedCm requires ctx.module === \"cm\"');\n }\n\n const selector = opts.selector ?? 'po';\n const seed = opts.seed ?? randomSeed();\n const operators: [Hex, Hex, Hex] = [\n deriveOperatorAddress(seed, 0),\n deriveOperatorAddress(seed, 1),\n deriveOperatorAddress(seed, 2),\n ];\n\n // Sequential by necessity: each createCuratedOperator installs/restores the gate temp tree and\n // every later step reads fork state mutated by the prior ones.\n const { noId: na } = await createCuratedOperator(ctx, { selector, operator: operators[0] });\n const { noId: nb } = await createCuratedOperator(ctx, { selector, operator: operators[1] });\n const { noId: nc } = await createCuratedOperator(ctx, { selector, operator: operators[2] });\n const noIds: [bigint, bigint, bigint] = [na, nb, nc];\n\n await createOperatorGroup(ctx, {\n pairs: [\n [na, 3400n],\n [nb, 3300n],\n [nc, 3300n],\n ],\n });\n\n // 3 add/deposit/topup rounds, mapping the source's operator indices 0/1/0 to na/nb/na. Each\n // addKeys gets a distinct per-call label (r0..r4) so na's three calls draw different key material.\n // Note: keccak256(seed) % 2^20 startIndex ranges CAN collide across seeds (birthday in 1M space),\n // but that is benign here — CSM does not dedup pubkeys (SigningKeys.sol has no uniqueness check).\n await addKeys(ctx, { noId: na, count: 4, seed: keySeed(seed, 'r0') });\n await deposit(ctx, { count: 100 });\n await topUpActiveKeys(ctx, { noId: na });\n\n await addKeys(ctx, { noId: nb, count: 5, seed: keySeed(seed, 'r1') });\n await deposit(ctx, { count: 100 });\n await topUpActiveKeys(ctx, { noId: nb });\n\n await addKeys(ctx, { noId: na, count: 6, seed: keySeed(seed, 'r2') });\n await deposit(ctx, { count: 100 });\n await topUpActiveKeys(ctx, { noId: na });\n\n // Final keys, no deposit/topup.\n await addKeys(ctx, { noId: na, count: 1, seed: keySeed(seed, 'r3') });\n await addKeys(ctx, { noId: nb, count: 1, seed: keySeed(seed, 'r4') });\n\n return { noIds, operators };\n}\n"],"mappings":"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"}