@logswap/sdk 0.5.68 → 0.5.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin.d.ts CHANGED
@@ -7,7 +7,7 @@
7
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
9
  * (049). The harvest's cut is the policy's answer at every harvest, set on the
10
- * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
10
+ * policy (`PadPolicy.setSchedule`: the protocol's pot share and the ranges a creator chooses inside) and capped by the manager (`POT_CUT_MAX`, `BURN_MAX`; a harvest has no cut, decisions 056); the release's
11
11
  * protocol share likewise (`POT_CUT_MAX`).
12
12
  */
13
13
  import type { Address, Hash } from "viem";
@@ -89,17 +89,21 @@ export declare function fPoolMigrate(c: LogswapClient, a: {
89
89
  * answer. "" for a policy the registry never approved (or a deployment with no registry).
90
90
  */
91
91
  export declare function registryKindOf(c: LogswapClient, policy: Address): Promise<FPoolKind | "">;
92
- /** The protocol's rate on the income part of every harvest under `policy`, WAD ≤ `HARVEST_CUT_MAX`. */
93
- export declare function setHarvestCut(c: LogswapClient, policy: Address, rateWad: bigint): Promise<Hash>;
94
- /** `PadPolicy.setSchedule`: `T(0)`, the rung's growth, the creator's and the protocol's shares of the pot (≤ `POT_CUT_MAX`), the burn per rung (≤ `BURN_MAX`). The rung is the pool's generation since F version 21. */
92
+ /**
93
+ * `PadPolicy.setSchedule` (decisions 056): `T(0)`, the rung's growth, the protocol's share of the pot (≤ `POT_CUT_MAX`,
94
+ * recorded into each pad at birth), and the RANGES a creator chooses inside at creation — his cut of the pot and the burn per
95
+ * rung (≤ `BURN_MAX`). A change applies to pads born after it. The rung is the pool's generation since F version 21.
96
+ */
95
97
  export declare function padSetSchedule(c: LogswapClient, policy: Address, s: {
96
98
  thresholdBase: bigint;
97
99
  thresholdGrowth: bigint;
98
- creatorShare: bigint;
99
100
  protocolShare: bigint;
100
- burnPerRung: bigint;
101
+ creatorShareMin: bigint;
102
+ creatorShareMax: bigint;
103
+ burnMin: bigint;
104
+ burnMax: bigint;
101
105
  }): Promise<Hash>;
102
- /** `PolPolicy.setRules`: what every harvest leaves in the bid and what a relaunch's seed must bring, WAD fractions of L. */
106
+ /** `PolPolicy.setRules`: the reserve a harvest must leave in the bid (`may(Harvest)` refuses less) and what a relaunch's seed must bring, WAD fractions of the pool's L. */
103
107
  export declare function polSetRules(c: LogswapClient, policy: Address, s: {
104
108
  keepQuote: bigint;
105
109
  minQuoteAtSeed: bigint;
package/dist/admin.js CHANGED
@@ -7,7 +7,7 @@
7
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
9
  * (049). The harvest's cut is the policy's answer at every harvest, set on the
10
- * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
10
+ * policy (`PadPolicy.setSchedule`: the protocol's pot share and the ranges a creator chooses inside) and capped by the manager (`POT_CUT_MAX`, `BURN_MAX`; a harvest has no cut, decisions 056); the release's
11
11
  * protocol share likewise (`POT_CUT_MAX`).
12
12
  */
13
13
  import { cPoolManagerAbi, fPoolManagerAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, publicPolicyAbi } from "./generated.js";
@@ -140,15 +140,23 @@ export async function registryKindOf(c, policy) {
140
140
  return bytes32ToText(word);
141
141
  }
142
142
  // ─── the policies' numbers (the collector's; each bounded where it reaches the manager) ─────
143
- /** The protocol's rate on the income part of every harvest under `policy`, WAD ≤ `HARVEST_CUT_MAX`. */
144
- export async function setHarvestCut(c, policy, rateWad) {
145
- return write(c, policy, fPolicyBaseAbi, "setHarvestCut", [rateWad]);
146
- }
147
- /** `PadPolicy.setSchedule`: `T(0)`, the rung's growth, the creator's and the protocol's shares of the pot (≤ `POT_CUT_MAX`), the burn per rung (≤ `BURN_MAX`). The rung is the pool's generation since F version 21. */
143
+ /**
144
+ * `PadPolicy.setSchedule` (decisions 056): `T(0)`, the rung's growth, the protocol's share of the pot (≤ `POT_CUT_MAX`,
145
+ * recorded into each pad at birth), and the RANGES a creator chooses inside at creation — his cut of the pot and the burn per
146
+ * rung (≤ `BURN_MAX`). A change applies to pads born after it. The rung is the pool's generation since F version 21.
147
+ */
148
148
  export async function padSetSchedule(c, policy, s) {
149
- return write(c, policy, padPolicyAbi, "setSchedule", [s.thresholdBase, s.thresholdGrowth, s.creatorShare, s.protocolShare, s.burnPerRung]);
149
+ return write(c, policy, padPolicyAbi, "setSchedule", [
150
+ s.thresholdBase,
151
+ s.thresholdGrowth,
152
+ s.protocolShare,
153
+ s.creatorShareMin,
154
+ s.creatorShareMax,
155
+ s.burnMin,
156
+ s.burnMax,
157
+ ]);
150
158
  }
151
- /** `PolPolicy.setRules`: what every harvest leaves in the bid and what a relaunch's seed must bring, WAD fractions of L. */
159
+ /** `PolPolicy.setRules`: the reserve a harvest must leave in the bid (`may(Harvest)` refuses less) and what a relaunch's seed must bring, WAD fractions of the pool's L. */
152
160
  export async function polSetRules(c, policy, s) {
153
161
  return write(c, policy, polPolicyAbi, "setRules", [s.keepQuote, s.minQuoteAtSeed]);
154
162
  }
package/dist/client.d.ts CHANGED
@@ -83,7 +83,7 @@ export declare const EXPECTED_VERSION = 5n;
83
83
  * passes it; when one lands, raise this constant in the same change, which is the discipline
84
84
  * `EXPECTED_VERSION` enforces mechanically and this one asks for.
85
85
  */
86
- export declare const EXPECTED_F_VERSION = 21n;
86
+ export declare const EXPECTED_F_VERSION = 23n;
87
87
  export declare function assertVersion(c: LogswapClient, expected?: bigint): Promise<void>;
88
88
  /**
89
89
  * The versions say the managers are the right code; this says the rest of the addresses belong
package/dist/client.js CHANGED
@@ -133,7 +133,9 @@ export const EXPECTED_VERSION = 5n;
133
133
  // (the bid: `getPool().Q` when L > 0), `idOf(FPoolKey)` (hashed locally, `fPoolIdOfKey`) and
134
134
  // `reserveOf` (`legOf`'s fourth word). `PadPolicy.rung` is gone: the pad's rung is `gen`, the
135
135
  // next threshold `threshold(gen)`. A pad never retires; a public pool refuses `regenerate`.
136
- export const EXPECTED_F_VERSION = 21n;
136
+ // 23: a harvest asks `may(Harvest)` and takes no terms and no cut (decisions 056): `onHarvest`, `HARVEST_CUT_MAX`,
137
+ // `harvestCut`, `setHarvestCut` are gone; a pad's split (creator's cut, token burn) rides in the key's salt.
138
+ export const EXPECTED_F_VERSION = 23n;
137
139
  export async function assertVersion(c, expected = EXPECTED_VERSION) {
138
140
  const { cPoolManagerAbi, fPoolManagerAbi } = await import("./generated.js");
139
141
  // three independent reads: one round trip, not three in a row
package/dist/fpool.d.ts CHANGED
@@ -40,8 +40,8 @@ import { fPoolManagerAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPo
40
40
  */
41
41
  export { fPoolManagerAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPolicyAbi };
42
42
  /**
43
- * What every protocol policy shares (`FPolicyBase`): the roles and their handover, `harvestCut`,
44
- * `maxHarvest`. `polPolicyAbi` is a superset of it and carries nothing that collides, so it stands
43
+ * What every protocol policy shares (`FPolicyBase`): the roles and their handover and the
44
+ * `maxHarvest` readout (a harvest has no cut, decisions 056). `polPolicyAbi` is a superset of it and carries nothing that collides, so it stands
45
45
  * in for the base — a call through it to any of the four policies decodes the base's functions.
46
46
  */
47
47
  export declare const fPolicyBaseAbi: readonly [{
@@ -90,16 +90,6 @@ export declare const fPolicyBaseAbi: readonly [{
90
90
  readonly type: "address";
91
91
  }];
92
92
  readonly stateMutability: "view";
93
- }, {
94
- readonly type: "function";
95
- readonly inputs: readonly [];
96
- readonly name: "harvestCut";
97
- readonly outputs: readonly [{
98
- readonly name: "";
99
- readonly internalType: "uint256";
100
- readonly type: "uint256";
101
- }];
102
- readonly stateMutability: "view";
103
93
  }, {
104
94
  readonly type: "function";
105
95
  readonly inputs: readonly [];
@@ -219,61 +209,6 @@ export declare const fPolicyBaseAbi: readonly [{
219
209
  readonly type: "bytes32";
220
210
  }];
221
211
  readonly stateMutability: "view";
222
- }, {
223
- readonly type: "function";
224
- readonly inputs: readonly [{
225
- readonly name: "a";
226
- readonly internalType: "struct IFPolicy.Ask";
227
- readonly type: "tuple";
228
- readonly components: readonly [{
229
- readonly name: "poolId";
230
- readonly internalType: "bytes32";
231
- readonly type: "bytes32";
232
- }, {
233
- readonly name: "gen";
234
- readonly internalType: "uint256";
235
- readonly type: "uint256";
236
- }, {
237
- readonly name: "action";
238
- readonly internalType: "enum IFPolicy.Action";
239
- readonly type: "uint8";
240
- }, {
241
- readonly name: "who";
242
- readonly internalType: "address";
243
- readonly type: "address";
244
- }, {
245
- readonly name: "amount";
246
- readonly internalType: "uint256";
247
- readonly type: "uint256";
248
- }, {
249
- readonly name: "income";
250
- readonly internalType: "uint256";
251
- readonly type: "uint256";
252
- }, {
253
- readonly name: "Q";
254
- readonly internalType: "uint256";
255
- readonly type: "uint256";
256
- }, {
257
- readonly name: "L";
258
- readonly internalType: "uint256";
259
- readonly type: "uint256";
260
- }, {
261
- readonly name: "n";
262
- readonly internalType: "uint256";
263
- readonly type: "uint256";
264
- }];
265
- }];
266
- readonly name: "onHarvest";
267
- readonly outputs: readonly [{
268
- readonly name: "amount";
269
- readonly internalType: "uint256";
270
- readonly type: "uint256";
271
- }, {
272
- readonly name: "cut";
273
- readonly internalType: "uint256";
274
- readonly type: "uint256";
275
- }];
276
- readonly stateMutability: "view";
277
212
  }, {
278
213
  readonly type: "function";
279
214
  readonly inputs: readonly [{
@@ -475,16 +410,6 @@ export declare const fPolicyBaseAbi: readonly [{
475
410
  readonly type: "bool";
476
411
  }];
477
412
  readonly stateMutability: "view";
478
- }, {
479
- readonly type: "function";
480
- readonly inputs: readonly [{
481
- readonly name: "_harvestCut";
482
- readonly internalType: "uint256";
483
- readonly type: "uint256";
484
- }];
485
- readonly name: "setHarvestCut";
486
- readonly outputs: readonly [];
487
- readonly stateMutability: "nonpayable";
488
413
  }, {
489
414
  readonly type: "function";
490
415
  readonly inputs: readonly [{
@@ -577,16 +502,6 @@ export declare const fPolicyBaseAbi: readonly [{
577
502
  readonly indexed: true;
578
503
  }];
579
504
  readonly name: "AuthoritySet";
580
- }, {
581
- readonly type: "event";
582
- readonly anonymous: false;
583
- readonly inputs: readonly [{
584
- readonly name: "harvestCut";
585
- readonly internalType: "uint256";
586
- readonly type: "uint256";
587
- readonly indexed: false;
588
- }];
589
- readonly name: "HarvestCutSet";
590
505
  }, {
591
506
  readonly type: "event";
592
507
  readonly anonymous: false;
@@ -1150,7 +1065,7 @@ export declare function fPoolIdOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "ac
1150
1065
  /**
1151
1066
  * Rebuild a live pool's KEY from its id: the `FInitialize` log carries every field but the salt,
1152
1067
  * which is taken from `opts.salt` (zero by default — every pool the SDK's create flow makes
1153
- * without naming one). The result is checked against the id, so a wrong salt throws rather than
1068
+ * without naming one; a pad's carries its split, so it is rebuilt from the policy's record with nonce 0 — pass `opts.salt` for another nonce). The result is checked against the id, so a wrong salt throws rather than
1154
1069
  * returning a key that names another pool. What {@link fPoolMigrate} needs when handed an id.
1155
1070
  */
1156
1071
  export declare function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts?: {
@@ -1273,6 +1188,27 @@ export declare function fPoolRelaunch(c: LogswapClient, a: {
1273
1188
  export declare const DEAD_ADDRESS: Address;
1274
1189
  /** The manager's `MIN_SHARES`: the seed mints this much to 0xdead on every pool, the rest to the seeder — or, on a pad, to 0xdead too. */
1275
1190
  export declare const F_MIN_SHARES = 1000n;
1191
+ /**
1192
+ * One genesis transaction: a swap the creator sends right after the seed, inside the launch's
1193
+ * `multicall`. A `buy` spends `amountIn` of quote for leg `j` (an index into `bases` AS GIVEN); a
1194
+ * `sell` spends `amountIn` of that leg for quote. The creator pays and is paid — the manager settles
1195
+ * to its sender — so a `recipient` is paid by an ERC-20 transfer of the row's output
1196
+ * ({@link fPoolGenesisTransfers}); without one the output stays with the creator.
1197
+ */
1198
+ export interface FPoolGenesisRow {
1199
+ side: "buy" | "sell";
1200
+ j: number;
1201
+ amountIn: bigint;
1202
+ /** Defaults to 0. With a `recipient`, {@link fPoolLaunch} pins it to the simulated output, which is what the transfer pays. */
1203
+ minOut?: bigint;
1204
+ recipient?: Address;
1205
+ }
1206
+ /** An ERC-20 transfer owed to a genesis recipient: one per (token, recipient), rows summed. */
1207
+ export interface FPoolGenesisTransfer {
1208
+ token: Address;
1209
+ to: Address;
1210
+ amount: bigint;
1211
+ }
1276
1212
  export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
1277
1213
  L0: bigint;
1278
1214
  /** Quote the seed pulls. A pad seeds at 0 — pure float, a resting ask, nothing raised. */
@@ -1289,6 +1225,18 @@ export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
1289
1225
  amountIn: bigint;
1290
1226
  minOut?: bigint;
1291
1227
  };
1228
+ /**
1229
+ * The genesis transactions: an ordered list of swaps right after the seed, each with an optional
1230
+ * recipient ({@link FPoolGenesisRow}). The same batch as `openBuy`, which stays as the one-buy
1231
+ * case and runs first when both are given. See `specs/f-pools/opening.md`.
1232
+ */
1233
+ openBatch?: FPoolGenesisRow[];
1234
+ /**
1235
+ * How a row's recipient is paid. `auto` (default): in the launch's own transaction when the
1236
+ * wallet batches atomically (EIP-5792), else left to {@link fPoolSendGenesisTransfers};
1237
+ * `followup`: always left to it. Ignored when no row names a recipient.
1238
+ */
1239
+ deliver?: "auto" | "followup";
1292
1240
  /** 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. */
1293
1241
  name?: string;
1294
1242
  /**
@@ -1312,6 +1260,32 @@ export declare function fPoolLaunchCalls(poolId: Hex, a: FPoolLaunchArgs & ({
1312
1260
  } | {
1313
1261
  x0: bigint[];
1314
1262
  })): Hex[];
1263
+ /** The genesis rows in the order they run: `openBuy` first when given, then `openBatch`. Legs as given. */
1264
+ export declare function fPoolGenesisRows(a: Pick<FPoolLaunchArgs, "openBuy" | "openBatch">): FPoolGenesisRow[];
1265
+ /**
1266
+ * What each genesis row returned, read by simulating the launch's `multicall` on the chain — the
1267
+ * contract's own rounding, no re-implementation. Exact at send time: the pool is new, and nothing
1268
+ * can trade between the seed and the rows. Needs the creator's funds and allowances, as the launch
1269
+ * does. One output per row, in {@link fPoolGenesisRows} order: base for a buy, quote for a sell.
1270
+ */
1271
+ export declare function fPoolGenesisOutputs(c: LogswapClient, poolId: Hex, a: FPoolLaunchArgs & ({
1272
+ r0: bigint[];
1273
+ } | {
1274
+ x0: bigint[];
1275
+ })): Promise<bigint[]>;
1276
+ /**
1277
+ * The transfers that pay the recipients: each row's output (`outs`, aligned with
1278
+ * {@link fPoolGenesisRows}) to its `recipient`, summed per (token, recipient), zero amounts dropped.
1279
+ * The token is the leg's base for a buy and the quote for a sell. Pure.
1280
+ */
1281
+ export declare function fPoolGenesisTransfers(a: Pick<FPoolLaunchArgs, "quote" | "bases" | "openBuy" | "openBatch">, outs: bigint[]): FPoolGenesisTransfer[];
1282
+ /** Pay genesis recipients with plain ERC-20 transfers, one transaction each, in order. For a launch that did not deliver itself. */
1283
+ export declare function fPoolSendGenesisTransfers(c: LogswapClient, a: {
1284
+ transfers: FPoolGenesisTransfer[];
1285
+ account: Address;
1286
+ }): Promise<Hash[]>;
1287
+ /** Whether the wallet runs a batch of calls atomically (EIP-5792 `atomic` capability), on the client's chain. A wallet without it, or one that errors, says no. */
1288
+ export declare function fPoolWalletBatchesAtomically(c: LogswapClient, account: Address): Promise<boolean>;
1315
1289
  /**
1316
1290
  * The desk's opening posture, on its policy: gated minting, the creator a
1317
1291
  * minter. No appointment: the authority is the operator until one is appointed.
@@ -1346,6 +1320,8 @@ export declare function fPoolLaunch(c: LogswapClient, a: FPoolLaunchArgs & ({
1346
1320
  })): Promise<{
1347
1321
  hash: Hash;
1348
1322
  poolId: Hex;
1323
+ transfers: FPoolGenesisTransfer[];
1324
+ delivered: boolean;
1349
1325
  }>;
1350
1326
  /**
1351
1327
  * Propose the next authority — step one of two, on the pool's policy. Nothing moves
@@ -1517,7 +1493,6 @@ export interface FPoolControlled {
1517
1493
  /** What a pool's policy says about it, by kind: the terms a buyer reads before entering. */
1518
1494
  export type FPoolRules = {
1519
1495
  kind: "pad";
1520
- harvestCut: bigint;
1521
1496
  /** The pad's graduation count: the pool's generation (F version 21 — `PadPolicy.rung` is gone). */
1522
1497
  gen: number;
1523
1498
  /** @deprecated Equal to {@link gen} since F version 21, kept for callers; read `gen`. */
@@ -1525,25 +1500,50 @@ export type FPoolRules = {
1525
1500
  /** The pot the next graduation needs: `PadPolicy.threshold(gen)`. */
1526
1501
  threshold: bigint;
1527
1502
  pot: bigint;
1503
+ /** This pad's split, recorded at its birth from the key's salt and never changed (decisions 056): the creator's cut of the pot, the protocol's, and the burn per rung. The rest of the pot is bought in. */
1528
1504
  creatorShare: bigint;
1529
1505
  protocolShare: bigint;
1530
1506
  burnPerRung: bigint;
1507
+ /** The ranges the collector's policy holds NOW for the pads born after this read: what a new creator may choose. */
1508
+ ranges: PadRanges;
1531
1509
  } | {
1532
1510
  kind: "pol";
1533
- harvestCut: bigint;
1534
1511
  keepQuote: bigint;
1535
1512
  minQuoteAtSeed: bigint;
1536
1513
  } | {
1537
1514
  kind: "public";
1538
- harvestCut: bigint;
1539
1515
  curated: boolean;
1540
1516
  } | {
1541
1517
  kind: "private";
1542
- harvestCut: bigint;
1543
1518
  gateMint: boolean;
1544
1519
  gateSwap: boolean;
1545
1520
  operator: Address;
1546
1521
  };
1522
+ /** The collector's ranges on a pad's creator choices (`PadPolicy`, decisions 056), WAD fractions: of the pot, and of every reserve per rung. */
1523
+ export interface PadRanges {
1524
+ /** The protocol's share of the pot, recorded into each pad at birth. */
1525
+ protocolShare: bigint;
1526
+ creatorShareMin: bigint;
1527
+ creatorShareMax: bigint;
1528
+ burnMin: bigint;
1529
+ burnMax: bigint;
1530
+ }
1531
+ /**
1532
+ * The salt that carries a pad's split (`PadPolicy.pack`, decisions 056): `creatorShare` in the top 64 bits, `burnPerRung` in
1533
+ * the next 64 (WAD fractions), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
1534
+ * identity, so it is chosen at launch (`FPoolLaunchArgs.salt`) and never changes; the policy refuses a split outside its ranges.
1535
+ */
1536
+ export declare function padSalt(s: {
1537
+ creatorShare: bigint;
1538
+ burnPerRung: bigint;
1539
+ nonce?: bigint;
1540
+ }): Hex;
1541
+ /** The inverse of {@link padSalt}. */
1542
+ export declare function padSplitOfSalt(salt: Hex): {
1543
+ creatorShare: bigint;
1544
+ burnPerRung: bigint;
1545
+ nonce: bigint;
1546
+ };
1547
1547
  /** The policy's terms for one pool, by its kind. */
1548
1548
  export declare function getFPoolRules(c: LogswapClient, s: Pick<FPoolState, "poolId" | "policy" | "kind" | "gen">, roles?: FPoolRoles): Promise<FPoolRules>;
1549
1549
  /**
@@ -1568,8 +1568,7 @@ export declare function fPoolShareHolders(c: LogswapClient, poolIdHex: Hex, opts
1568
1568
  shares: bigint;
1569
1569
  }>>;
1570
1570
  /**
1571
- * What a policy answers `onRegenerate` with (and `onHarvest`'s split, the first four words):
1572
- * `IFPolicy.Terms`. `carried` is F version 21's fifth word — the holder the next generation
1571
+ * What a policy answers `onRegenerate` with: `IFPolicy.Terms`. `carried` is F version 21's fifth word — the holder the next generation
1573
1572
  * carries beside the dead shares; the reply is exactly 160 bytes.
1574
1573
  */
1575
1574
  export interface FPolicyTerms {
package/dist/fpool.js CHANGED
@@ -29,7 +29,7 @@
29
29
  * twice, forever. One router is one spender — see `fPoolZapIn`/`fPoolZapOut` below, which target
30
30
  * `addresses.router`.
31
31
  */
32
- import { encodeAbiParameters, encodeFunctionData, keccak256, toFunctionSelector } from "viem";
32
+ import { decodeAbiParameters, encodeAbiParameters, encodeFunctionData, erc20Abi, keccak256, toFunctionSelector } from "viem";
33
33
  import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
34
34
  /**
35
35
  * One contract, one ABI (F version 12): `harvest`, `claim`, `retire`, `regenerate`,
@@ -40,8 +40,8 @@ import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRe
40
40
  */
41
41
  export { fPoolManagerAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPolicyAbi };
42
42
  /**
43
- * What every protocol policy shares (`FPolicyBase`): the roles and their handover, `harvestCut`,
44
- * `maxHarvest`. `polPolicyAbi` is a superset of it and carries nothing that collides, so it stands
43
+ * What every protocol policy shares (`FPolicyBase`): the roles and their handover and the
44
+ * `maxHarvest` readout (a harvest has no cut, decisions 056). `polPolicyAbi` is a superset of it and carries nothing that collides, so it stands
45
45
  * in for the base — a call through it to any of the four policies decodes the base's functions.
46
46
  */
47
47
  export const fPolicyBaseAbi = polPolicyAbi;
@@ -733,7 +733,7 @@ export async function fPoolIdOf(c, a) {
733
733
  /**
734
734
  * Rebuild a live pool's KEY from its id: the `FInitialize` log carries every field but the salt,
735
735
  * which is taken from `opts.salt` (zero by default — every pool the SDK's create flow makes
736
- * without naming one). The result is checked against the id, so a wrong salt throws rather than
736
+ * without naming one; a pad's carries its split, so it is rebuilt from the policy's record with nonce 0 — pass `opts.salt` for another nonce). The result is checked against the id, so a wrong salt throws rather than
737
737
  * returning a key that names another pool. What {@link fPoolMigrate} needs when handed an id.
738
738
  */
739
739
  export async function fPoolKeyOfPool(c, poolId, opts = {}) {
@@ -758,6 +758,18 @@ export async function fPoolKeyOfPool(c, poolId, opts = {}) {
758
758
  creator: a.creator,
759
759
  salt: opts.salt ?? ZERO_SALT,
760
760
  };
761
+ if (fPoolIdOfKey(key).toLowerCase() !== poolId.toLowerCase() && opts.salt === undefined) {
762
+ // a pad's salt carries its split (decisions 056): rebuild it from the policy's record with the default nonce
763
+ try {
764
+ const [creatorShare, burnPerRung] = (await c.public.readContract({ address: a.policy, abi: padPolicyAbi, functionName: "splitOf", args: [poolId] }));
765
+ const padKey = { ...key, salt: padSalt({ creatorShare, burnPerRung }) };
766
+ if (fPoolIdOfKey(padKey).toLowerCase() === poolId.toLowerCase())
767
+ return padKey;
768
+ }
769
+ catch {
770
+ /* not a pad: fall through to the error */
771
+ }
772
+ }
761
773
  if (fPoolIdOfKey(key).toLowerCase() !== poolId.toLowerCase()) {
762
774
  throw new Error(`logswap: F pool ${poolId} was not born with salt ${key.salt} — pass its salt, or its whole key`);
763
775
  }
@@ -907,11 +919,11 @@ export function fPoolLaunchCalls(poolId, a) {
907
919
  calls.push(enc("seed", [poolId, a.L0, companions[1], a.Q0]));
908
920
  // gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`.
909
921
  // No transfer to 0xdead: on a pad `seed` mints every share there itself.
910
- if (a.openBuy) {
911
- const j = bases.indexOf(a.bases[a.openBuy.j]);
922
+ for (const row of fPoolGenesisRows(a)) {
923
+ const j = bases.indexOf(a.bases[row.j]);
912
924
  if (j < 0)
913
- throw new Error(`logswap: openBuy.j = ${a.openBuy.j} is not a leg`);
914
- calls.push(enc("swapQuoteIn", [poolId, BigInt(j), a.openBuy.amountIn, a.openBuy.minOut ?? 0n, a.account]));
925
+ throw new Error(`logswap: genesis row j = ${row.j} is not a leg`);
926
+ calls.push(enc(row.side === "buy" ? "swapQuoteIn" : "swapBaseIn", [poolId, BigInt(j), row.amountIn, row.minOut ?? 0n, a.account]));
915
927
  }
916
928
  // the name, the desk's gates and its list are the POLICY's, not the
917
929
  // manager's: they cannot ride in this batch — `fPoolLaunch` and `fPoolDeskOpening` send them
@@ -919,6 +931,80 @@ export function fPoolLaunchCalls(poolId, a) {
919
931
  calls.push(...(a.extra ?? []));
920
932
  return calls;
921
933
  }
934
+ /** The genesis rows in the order they run: `openBuy` first when given, then `openBatch`. Legs as given. */
935
+ export function fPoolGenesisRows(a) {
936
+ return [...(a.openBuy ? [{ side: "buy", ...a.openBuy }] : []), ...(a.openBatch ?? [])];
937
+ }
938
+ /**
939
+ * What each genesis row returned, read by simulating the launch's `multicall` on the chain — the
940
+ * contract's own rounding, no re-implementation. Exact at send time: the pool is new, and nothing
941
+ * can trade between the seed and the rows. Needs the creator's funds and allowances, as the launch
942
+ * does. One output per row, in {@link fPoolGenesisRows} order: base for a buy, quote for a sell.
943
+ */
944
+ export async function fPoolGenesisOutputs(c, poolId, a) {
945
+ const rows = fPoolGenesisRows(a);
946
+ if (rows.length === 0)
947
+ return [];
948
+ const calls = fPoolLaunchCalls(poolId, a);
949
+ const { result } = await c.public.simulateContract({
950
+ address: c.addresses.fPoolManager,
951
+ abi: fPoolManagerAbi,
952
+ functionName: "multicall",
953
+ args: [calls],
954
+ account: signerFor(c, a.account),
955
+ });
956
+ const results = result;
957
+ const first = (a.initialized ? 0 : 1) + 1; // [initialize] · seed · rows
958
+ return rows.map((_, i) => decodeAbiParameters([{ type: "uint256" }], results[first + i])[0]);
959
+ }
960
+ /**
961
+ * The transfers that pay the recipients: each row's output (`outs`, aligned with
962
+ * {@link fPoolGenesisRows}) to its `recipient`, summed per (token, recipient), zero amounts dropped.
963
+ * The token is the leg's base for a buy and the quote for a sell. Pure.
964
+ */
965
+ export function fPoolGenesisTransfers(a, outs) {
966
+ const out = new Map();
967
+ fPoolGenesisRows(a).forEach((row, i) => {
968
+ if (!row.recipient || !outs[i])
969
+ return;
970
+ const token = row.side === "buy" ? a.bases[row.j] : a.quote;
971
+ const k = `${token.toLowerCase()}:${row.recipient.toLowerCase()}`;
972
+ const t = out.get(k);
973
+ if (t)
974
+ t.amount += outs[i];
975
+ else
976
+ out.set(k, { token, to: row.recipient, amount: outs[i] });
977
+ });
978
+ return [...out.values()];
979
+ }
980
+ /** Pay genesis recipients with plain ERC-20 transfers, one transaction each, in order. For a launch that did not deliver itself. */
981
+ export async function fPoolSendGenesisTransfers(c, a) {
982
+ const wallet = requireWallet(c);
983
+ const hashes = [];
984
+ for (const t of a.transfers) {
985
+ const { request } = await c.public.simulateContract({
986
+ address: t.token,
987
+ abi: erc20Abi,
988
+ functionName: "transfer",
989
+ args: [t.to, t.amount],
990
+ account: signerFor(c, a.account),
991
+ });
992
+ hashes.push(await wallet.writeContract(request));
993
+ }
994
+ return hashes;
995
+ }
996
+ /** Whether the wallet runs a batch of calls atomically (EIP-5792 `atomic` capability), on the client's chain. A wallet without it, or one that errors, says no. */
997
+ export async function fPoolWalletBatchesAtomically(c, account) {
998
+ try {
999
+ const wallet = requireWallet(c);
1000
+ const chainId = wallet.chain?.id;
1001
+ const caps = (await wallet.getCapabilities({ account, chainId }));
1002
+ return caps?.atomic?.status === "supported" || caps?.atomic?.status === "ready";
1003
+ }
1004
+ catch {
1005
+ return false;
1006
+ }
1007
+ }
922
1008
  /**
923
1009
  * The desk's opening posture, on its policy: gated minting, the creator a
924
1010
  * minter. No appointment: the authority is the operator until one is appointed.
@@ -951,11 +1037,42 @@ export async function fPoolDeskOpening(c, a) {
951
1037
  export async function fPoolLaunch(c, a) {
952
1038
  const poolId = await fPoolIdOf(c, a);
953
1039
  const policy = a.policy ?? fPoolPolicyFor(c, a.kind);
954
- const calls = fPoolLaunchCalls(poolId, { ...a, policy });
955
- const hash = await writeFPool0(c, "multicall", [calls], a.account);
1040
+ let args = { ...a, policy };
1041
+ // With recipients, each row's minOut is pinned to its simulated output and the transfers pay
1042
+ // exactly that: a row that returns less reverts the launch, a wei of surplus stays with the creator.
1043
+ let transfers = [];
1044
+ const rows = fPoolGenesisRows(a);
1045
+ if (rows.some((r) => r.recipient)) {
1046
+ const outs = await fPoolGenesisOutputs(c, poolId, args);
1047
+ args = { ...args, openBuy: undefined, openBatch: rows.map((r, i) => ({ ...r, minOut: outs[i] })) };
1048
+ transfers = fPoolGenesisTransfers(args, outs);
1049
+ }
1050
+ const calls = fPoolLaunchCalls(poolId, args);
1051
+ let hash;
1052
+ let delivered = transfers.length === 0;
1053
+ if (transfers.length > 0 && a.deliver !== "followup" && (await fPoolWalletBatchesAtomically(c, a.account))) {
1054
+ const wallet = requireWallet(c);
1055
+ const { id } = await wallet.sendCalls({
1056
+ account: signerFor(c, a.account),
1057
+ forceAtomic: true,
1058
+ calls: [
1059
+ { to: c.addresses.fPoolManager, data: encodeFunctionData({ abi: fPoolManagerAbi, functionName: "multicall", args: [calls] }) },
1060
+ ...transfers.map((t) => ({ to: t.token, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [t.to, t.amount] }) })),
1061
+ ],
1062
+ });
1063
+ const status = await wallet.waitForCallsStatus({ id });
1064
+ const receipt = status.receipts?.[0];
1065
+ if (status.status !== "success" || !receipt)
1066
+ throw new Error("logswap: the launch batch did not succeed — nothing was created");
1067
+ hash = receipt.transactionHash;
1068
+ delivered = true;
1069
+ }
1070
+ else {
1071
+ hash = await writeFPool0(c, "multicall", [calls], a.account);
1072
+ }
956
1073
  if (a.name)
957
1074
  await fPoolSetName(c, { poolId, name: a.name, account: a.account, policy });
958
- return { hash, poolId };
1075
+ return { hash, poolId, transfers, delivered };
959
1076
  }
960
1077
  // ─── the roles, on the policy ─────────────────────────────────────────────
961
1078
  //
@@ -1151,34 +1268,58 @@ export async function discoverFPools(c, opts = {}) {
1151
1268
  };
1152
1269
  }));
1153
1270
  }
1271
+ const MAX_U64 = (1n << 64n) - 1n;
1272
+ const MAX_U128 = (1n << 128n) - 1n;
1273
+ /**
1274
+ * The salt that carries a pad's split (`PadPolicy.pack`, decisions 056): `creatorShare` in the top 64 bits, `burnPerRung` in
1275
+ * the next 64 (WAD fractions), a nonce in the low 128 that tells two otherwise identical pads apart. It is part of the pool's
1276
+ * identity, so it is chosen at launch (`FPoolLaunchArgs.salt`) and never changes; the policy refuses a split outside its ranges.
1277
+ */
1278
+ export function padSalt(s) {
1279
+ const nonce = s.nonce ?? 0n;
1280
+ if (s.creatorShare < 0n || s.creatorShare > MAX_U64 || s.burnPerRung < 0n || s.burnPerRung > MAX_U64)
1281
+ throw new Error("logswap: a pad's split is a WAD fraction that fits 64 bits");
1282
+ if (nonce < 0n || nonce > MAX_U128)
1283
+ throw new Error("logswap: a pad salt's nonce fits 128 bits");
1284
+ return `0x${((s.creatorShare << 192n) | (s.burnPerRung << 128n) | nonce).toString(16).padStart(64, "0")}`;
1285
+ }
1286
+ /** The inverse of {@link padSalt}. */
1287
+ export function padSplitOfSalt(salt) {
1288
+ const v = BigInt(salt);
1289
+ return { creatorShare: v >> 192n, burnPerRung: (v >> 128n) & MAX_U64, nonce: v & MAX_U128 };
1290
+ }
1154
1291
  /** The policy's terms for one pool, by its kind. */
1155
1292
  export async function getFPoolRules(c, s, roles) {
1156
1293
  const rd = (abi, functionName, args = []) => c.public.readContract({ address: s.policy, abi, functionName, args });
1157
- const harvestCut = await rd(fPolicyBaseAbi, "harvestCut");
1158
1294
  switch (s.kind) {
1159
1295
  case "pad": {
1160
1296
  // the rung is the manager's generation (F version 21): a move carries it, nothing to ask the policy
1161
1297
  const gen = s.gen;
1162
- const [creatorShare, protocolShare, burnPerRung, pot, threshold] = await Promise.all([
1163
- rd(padPolicyAbi, "creatorShare"),
1164
- rd(padPolicyAbi, "protocolShare"),
1165
- rd(padPolicyAbi, "burnPerRung"),
1298
+ const [split, pot, threshold, protocolNow, creatorShareMin, creatorShareMax, burnMin, burnMax] = await Promise.all([
1299
+ rd(padPolicyAbi, "splitOf", [s.poolId]),
1166
1300
  fPoolDividendOf(c, s.poolId, DEAD_ADDRESS, gen),
1167
1301
  rd(padPolicyAbi, "threshold", [BigInt(gen)]),
1302
+ rd(padPolicyAbi, "protocolShare"),
1303
+ rd(padPolicyAbi, "creatorShareMin"),
1304
+ rd(padPolicyAbi, "creatorShareMax"),
1305
+ rd(padPolicyAbi, "burnMin"),
1306
+ rd(padPolicyAbi, "burnMax"),
1168
1307
  ]);
1169
- return { kind: "pad", harvestCut, gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnPerRung };
1308
+ const [creatorShare, burnPerRung, protocolShare] = split;
1309
+ const ranges = { protocolShare: protocolNow, creatorShareMin, creatorShareMax, burnMin, burnMax };
1310
+ return { kind: "pad", gen, rung: gen, threshold, pot, creatorShare, protocolShare, burnPerRung, ranges };
1170
1311
  }
1171
1312
  case "pol": {
1172
1313
  const [keepQuote, minQuoteAtSeed] = await Promise.all([rd(polPolicyAbi, "keepQuote"), rd(polPolicyAbi, "minQuoteAtSeed")]);
1173
- return { kind: "pol", harvestCut, keepQuote, minQuoteAtSeed };
1314
+ return { kind: "pol", keepQuote, minQuoteAtSeed };
1174
1315
  }
1175
1316
  case "public": {
1176
1317
  const curated = await rd(publicPolicyAbi, "curated");
1177
- return { kind: "public", harvestCut, curated };
1318
+ return { kind: "public", curated };
1178
1319
  }
1179
1320
  case "private": {
1180
1321
  const r = roles ?? (await getFPoolRoles(c, s.poolId, s));
1181
- return { kind: "private", harvestCut, gateMint: r.gateMint, gateSwap: r.gateSwap, operator: fPoolOperatorOf(r) };
1322
+ return { kind: "private", gateMint: r.gateMint, gateSwap: r.gateSwap, operator: fPoolOperatorOf(r) };
1182
1323
  }
1183
1324
  }
1184
1325
  }