@logswap/sdk 0.5.68 → 0.5.69
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/fpool.d.ts +61 -0
- package/dist/fpool.js +113 -8
- package/package.json +1 -1
- package/src/fpool.ts +154 -9
package/dist/fpool.d.ts
CHANGED
|
@@ -1273,6 +1273,27 @@ export declare function fPoolRelaunch(c: LogswapClient, a: {
|
|
|
1273
1273
|
export declare const DEAD_ADDRESS: Address;
|
|
1274
1274
|
/** 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
1275
|
export declare const F_MIN_SHARES = 1000n;
|
|
1276
|
+
/**
|
|
1277
|
+
* One genesis transaction: a swap the creator sends right after the seed, inside the launch's
|
|
1278
|
+
* `multicall`. A `buy` spends `amountIn` of quote for leg `j` (an index into `bases` AS GIVEN); a
|
|
1279
|
+
* `sell` spends `amountIn` of that leg for quote. The creator pays and is paid — the manager settles
|
|
1280
|
+
* to its sender — so a `recipient` is paid by an ERC-20 transfer of the row's output
|
|
1281
|
+
* ({@link fPoolGenesisTransfers}); without one the output stays with the creator.
|
|
1282
|
+
*/
|
|
1283
|
+
export interface FPoolGenesisRow {
|
|
1284
|
+
side: "buy" | "sell";
|
|
1285
|
+
j: number;
|
|
1286
|
+
amountIn: bigint;
|
|
1287
|
+
/** Defaults to 0. With a `recipient`, {@link fPoolLaunch} pins it to the simulated output, which is what the transfer pays. */
|
|
1288
|
+
minOut?: bigint;
|
|
1289
|
+
recipient?: Address;
|
|
1290
|
+
}
|
|
1291
|
+
/** An ERC-20 transfer owed to a genesis recipient: one per (token, recipient), rows summed. */
|
|
1292
|
+
export interface FPoolGenesisTransfer {
|
|
1293
|
+
token: Address;
|
|
1294
|
+
to: Address;
|
|
1295
|
+
amount: bigint;
|
|
1296
|
+
}
|
|
1276
1297
|
export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
|
|
1277
1298
|
L0: bigint;
|
|
1278
1299
|
/** Quote the seed pulls. A pad seeds at 0 — pure float, a resting ask, nothing raised. */
|
|
@@ -1289,6 +1310,18 @@ export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
|
|
|
1289
1310
|
amountIn: bigint;
|
|
1290
1311
|
minOut?: bigint;
|
|
1291
1312
|
};
|
|
1313
|
+
/**
|
|
1314
|
+
* The genesis transactions: an ordered list of swaps right after the seed, each with an optional
|
|
1315
|
+
* recipient ({@link FPoolGenesisRow}). The same batch as `openBuy`, which stays as the one-buy
|
|
1316
|
+
* case and runs first when both are given. See `specs/f-pools/opening.md`.
|
|
1317
|
+
*/
|
|
1318
|
+
openBatch?: FPoolGenesisRow[];
|
|
1319
|
+
/**
|
|
1320
|
+
* How a row's recipient is paid. `auto` (default): in the launch's own transaction when the
|
|
1321
|
+
* wallet batches atomically (EIP-5792), else left to {@link fPoolSendGenesisTransfers};
|
|
1322
|
+
* `followup`: always left to it. Ignored when no row names a recipient.
|
|
1323
|
+
*/
|
|
1324
|
+
deliver?: "auto" | "followup";
|
|
1292
1325
|
/** 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
1326
|
name?: string;
|
|
1294
1327
|
/**
|
|
@@ -1312,6 +1345,32 @@ export declare function fPoolLaunchCalls(poolId: Hex, a: FPoolLaunchArgs & ({
|
|
|
1312
1345
|
} | {
|
|
1313
1346
|
x0: bigint[];
|
|
1314
1347
|
})): Hex[];
|
|
1348
|
+
/** The genesis rows in the order they run: `openBuy` first when given, then `openBatch`. Legs as given. */
|
|
1349
|
+
export declare function fPoolGenesisRows(a: Pick<FPoolLaunchArgs, "openBuy" | "openBatch">): FPoolGenesisRow[];
|
|
1350
|
+
/**
|
|
1351
|
+
* What each genesis row returned, read by simulating the launch's `multicall` on the chain — the
|
|
1352
|
+
* contract's own rounding, no re-implementation. Exact at send time: the pool is new, and nothing
|
|
1353
|
+
* can trade between the seed and the rows. Needs the creator's funds and allowances, as the launch
|
|
1354
|
+
* does. One output per row, in {@link fPoolGenesisRows} order: base for a buy, quote for a sell.
|
|
1355
|
+
*/
|
|
1356
|
+
export declare function fPoolGenesisOutputs(c: LogswapClient, poolId: Hex, a: FPoolLaunchArgs & ({
|
|
1357
|
+
r0: bigint[];
|
|
1358
|
+
} | {
|
|
1359
|
+
x0: bigint[];
|
|
1360
|
+
})): Promise<bigint[]>;
|
|
1361
|
+
/**
|
|
1362
|
+
* The transfers that pay the recipients: each row's output (`outs`, aligned with
|
|
1363
|
+
* {@link fPoolGenesisRows}) to its `recipient`, summed per (token, recipient), zero amounts dropped.
|
|
1364
|
+
* The token is the leg's base for a buy and the quote for a sell. Pure.
|
|
1365
|
+
*/
|
|
1366
|
+
export declare function fPoolGenesisTransfers(a: Pick<FPoolLaunchArgs, "quote" | "bases" | "openBuy" | "openBatch">, outs: bigint[]): FPoolGenesisTransfer[];
|
|
1367
|
+
/** Pay genesis recipients with plain ERC-20 transfers, one transaction each, in order. For a launch that did not deliver itself. */
|
|
1368
|
+
export declare function fPoolSendGenesisTransfers(c: LogswapClient, a: {
|
|
1369
|
+
transfers: FPoolGenesisTransfer[];
|
|
1370
|
+
account: Address;
|
|
1371
|
+
}): Promise<Hash[]>;
|
|
1372
|
+
/** 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. */
|
|
1373
|
+
export declare function fPoolWalletBatchesAtomically(c: LogswapClient, account: Address): Promise<boolean>;
|
|
1315
1374
|
/**
|
|
1316
1375
|
* The desk's opening posture, on its policy: gated minting, the creator a
|
|
1317
1376
|
* minter. No appointment: the authority is the operator until one is appointed.
|
|
@@ -1346,6 +1405,8 @@ export declare function fPoolLaunch(c: LogswapClient, a: FPoolLaunchArgs & ({
|
|
|
1346
1405
|
})): Promise<{
|
|
1347
1406
|
hash: Hash;
|
|
1348
1407
|
poolId: Hex;
|
|
1408
|
+
transfers: FPoolGenesisTransfer[];
|
|
1409
|
+
delivered: boolean;
|
|
1349
1410
|
}>;
|
|
1350
1411
|
/**
|
|
1351
1412
|
* Propose the next authority — step one of two, on the pool's policy. Nothing moves
|
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`,
|
|
@@ -907,11 +907,11 @@ export function fPoolLaunchCalls(poolId, a) {
|
|
|
907
907
|
calls.push(enc("seed", [poolId, a.L0, companions[1], a.Q0]));
|
|
908
908
|
// gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`.
|
|
909
909
|
// No transfer to 0xdead: on a pad `seed` mints every share there itself.
|
|
910
|
-
|
|
911
|
-
const j = bases.indexOf(a.bases[
|
|
910
|
+
for (const row of fPoolGenesisRows(a)) {
|
|
911
|
+
const j = bases.indexOf(a.bases[row.j]);
|
|
912
912
|
if (j < 0)
|
|
913
|
-
throw new Error(`logswap:
|
|
914
|
-
calls.push(enc("swapQuoteIn", [poolId, BigInt(j),
|
|
913
|
+
throw new Error(`logswap: genesis row j = ${row.j} is not a leg`);
|
|
914
|
+
calls.push(enc(row.side === "buy" ? "swapQuoteIn" : "swapBaseIn", [poolId, BigInt(j), row.amountIn, row.minOut ?? 0n, a.account]));
|
|
915
915
|
}
|
|
916
916
|
// the name, the desk's gates and its list are the POLICY's, not the
|
|
917
917
|
// manager's: they cannot ride in this batch — `fPoolLaunch` and `fPoolDeskOpening` send them
|
|
@@ -919,6 +919,80 @@ export function fPoolLaunchCalls(poolId, a) {
|
|
|
919
919
|
calls.push(...(a.extra ?? []));
|
|
920
920
|
return calls;
|
|
921
921
|
}
|
|
922
|
+
/** The genesis rows in the order they run: `openBuy` first when given, then `openBatch`. Legs as given. */
|
|
923
|
+
export function fPoolGenesisRows(a) {
|
|
924
|
+
return [...(a.openBuy ? [{ side: "buy", ...a.openBuy }] : []), ...(a.openBatch ?? [])];
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* What each genesis row returned, read by simulating the launch's `multicall` on the chain — the
|
|
928
|
+
* contract's own rounding, no re-implementation. Exact at send time: the pool is new, and nothing
|
|
929
|
+
* can trade between the seed and the rows. Needs the creator's funds and allowances, as the launch
|
|
930
|
+
* does. One output per row, in {@link fPoolGenesisRows} order: base for a buy, quote for a sell.
|
|
931
|
+
*/
|
|
932
|
+
export async function fPoolGenesisOutputs(c, poolId, a) {
|
|
933
|
+
const rows = fPoolGenesisRows(a);
|
|
934
|
+
if (rows.length === 0)
|
|
935
|
+
return [];
|
|
936
|
+
const calls = fPoolLaunchCalls(poolId, a);
|
|
937
|
+
const { result } = await c.public.simulateContract({
|
|
938
|
+
address: c.addresses.fPoolManager,
|
|
939
|
+
abi: fPoolManagerAbi,
|
|
940
|
+
functionName: "multicall",
|
|
941
|
+
args: [calls],
|
|
942
|
+
account: signerFor(c, a.account),
|
|
943
|
+
});
|
|
944
|
+
const results = result;
|
|
945
|
+
const first = (a.initialized ? 0 : 1) + 1; // [initialize] · seed · rows
|
|
946
|
+
return rows.map((_, i) => decodeAbiParameters([{ type: "uint256" }], results[first + i])[0]);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* The transfers that pay the recipients: each row's output (`outs`, aligned with
|
|
950
|
+
* {@link fPoolGenesisRows}) to its `recipient`, summed per (token, recipient), zero amounts dropped.
|
|
951
|
+
* The token is the leg's base for a buy and the quote for a sell. Pure.
|
|
952
|
+
*/
|
|
953
|
+
export function fPoolGenesisTransfers(a, outs) {
|
|
954
|
+
const out = new Map();
|
|
955
|
+
fPoolGenesisRows(a).forEach((row, i) => {
|
|
956
|
+
if (!row.recipient || !outs[i])
|
|
957
|
+
return;
|
|
958
|
+
const token = row.side === "buy" ? a.bases[row.j] : a.quote;
|
|
959
|
+
const k = `${token.toLowerCase()}:${row.recipient.toLowerCase()}`;
|
|
960
|
+
const t = out.get(k);
|
|
961
|
+
if (t)
|
|
962
|
+
t.amount += outs[i];
|
|
963
|
+
else
|
|
964
|
+
out.set(k, { token, to: row.recipient, amount: outs[i] });
|
|
965
|
+
});
|
|
966
|
+
return [...out.values()];
|
|
967
|
+
}
|
|
968
|
+
/** Pay genesis recipients with plain ERC-20 transfers, one transaction each, in order. For a launch that did not deliver itself. */
|
|
969
|
+
export async function fPoolSendGenesisTransfers(c, a) {
|
|
970
|
+
const wallet = requireWallet(c);
|
|
971
|
+
const hashes = [];
|
|
972
|
+
for (const t of a.transfers) {
|
|
973
|
+
const { request } = await c.public.simulateContract({
|
|
974
|
+
address: t.token,
|
|
975
|
+
abi: erc20Abi,
|
|
976
|
+
functionName: "transfer",
|
|
977
|
+
args: [t.to, t.amount],
|
|
978
|
+
account: signerFor(c, a.account),
|
|
979
|
+
});
|
|
980
|
+
hashes.push(await wallet.writeContract(request));
|
|
981
|
+
}
|
|
982
|
+
return hashes;
|
|
983
|
+
}
|
|
984
|
+
/** 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. */
|
|
985
|
+
export async function fPoolWalletBatchesAtomically(c, account) {
|
|
986
|
+
try {
|
|
987
|
+
const wallet = requireWallet(c);
|
|
988
|
+
const chainId = wallet.chain?.id;
|
|
989
|
+
const caps = (await wallet.getCapabilities({ account, chainId }));
|
|
990
|
+
return caps?.atomic?.status === "supported" || caps?.atomic?.status === "ready";
|
|
991
|
+
}
|
|
992
|
+
catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
922
996
|
/**
|
|
923
997
|
* The desk's opening posture, on its policy: gated minting, the creator a
|
|
924
998
|
* minter. No appointment: the authority is the operator until one is appointed.
|
|
@@ -951,11 +1025,42 @@ export async function fPoolDeskOpening(c, a) {
|
|
|
951
1025
|
export async function fPoolLaunch(c, a) {
|
|
952
1026
|
const poolId = await fPoolIdOf(c, a);
|
|
953
1027
|
const policy = a.policy ?? fPoolPolicyFor(c, a.kind);
|
|
954
|
-
|
|
955
|
-
|
|
1028
|
+
let args = { ...a, policy };
|
|
1029
|
+
// With recipients, each row's minOut is pinned to its simulated output and the transfers pay
|
|
1030
|
+
// exactly that: a row that returns less reverts the launch, a wei of surplus stays with the creator.
|
|
1031
|
+
let transfers = [];
|
|
1032
|
+
const rows = fPoolGenesisRows(a);
|
|
1033
|
+
if (rows.some((r) => r.recipient)) {
|
|
1034
|
+
const outs = await fPoolGenesisOutputs(c, poolId, args);
|
|
1035
|
+
args = { ...args, openBuy: undefined, openBatch: rows.map((r, i) => ({ ...r, minOut: outs[i] })) };
|
|
1036
|
+
transfers = fPoolGenesisTransfers(args, outs);
|
|
1037
|
+
}
|
|
1038
|
+
const calls = fPoolLaunchCalls(poolId, args);
|
|
1039
|
+
let hash;
|
|
1040
|
+
let delivered = transfers.length === 0;
|
|
1041
|
+
if (transfers.length > 0 && a.deliver !== "followup" && (await fPoolWalletBatchesAtomically(c, a.account))) {
|
|
1042
|
+
const wallet = requireWallet(c);
|
|
1043
|
+
const { id } = await wallet.sendCalls({
|
|
1044
|
+
account: signerFor(c, a.account),
|
|
1045
|
+
forceAtomic: true,
|
|
1046
|
+
calls: [
|
|
1047
|
+
{ to: c.addresses.fPoolManager, data: encodeFunctionData({ abi: fPoolManagerAbi, functionName: "multicall", args: [calls] }) },
|
|
1048
|
+
...transfers.map((t) => ({ to: t.token, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [t.to, t.amount] }) })),
|
|
1049
|
+
],
|
|
1050
|
+
});
|
|
1051
|
+
const status = await wallet.waitForCallsStatus({ id });
|
|
1052
|
+
const receipt = status.receipts?.[0];
|
|
1053
|
+
if (status.status !== "success" || !receipt)
|
|
1054
|
+
throw new Error("logswap: the launch batch did not succeed — nothing was created");
|
|
1055
|
+
hash = receipt.transactionHash;
|
|
1056
|
+
delivered = true;
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
hash = await writeFPool0(c, "multicall", [calls], a.account);
|
|
1060
|
+
}
|
|
956
1061
|
if (a.name)
|
|
957
1062
|
await fPoolSetName(c, { poolId, name: a.name, account: a.account, policy });
|
|
958
|
-
return { hash, poolId };
|
|
1063
|
+
return { hash, poolId, transfers, delivered };
|
|
959
1064
|
}
|
|
960
1065
|
// ─── the roles, on the policy ─────────────────────────────────────────────
|
|
961
1066
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logswap/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.69",
|
|
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/fpool.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* `addresses.router`.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import { encodeAbiParameters, encodeFunctionData, keccak256, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
33
|
+
import { decodeAbiParameters, encodeAbiParameters, encodeFunctionData, erc20Abi, keccak256, toFunctionSelector, type Address, type Hash, type Hex } from "viem";
|
|
34
34
|
import { fPoolManagerAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -1252,6 +1252,29 @@ export const DEAD_ADDRESS: Address = "0x000000000000000000000000000000000000dEaD
|
|
|
1252
1252
|
/** 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. */
|
|
1253
1253
|
export const F_MIN_SHARES = 1000n;
|
|
1254
1254
|
|
|
1255
|
+
/**
|
|
1256
|
+
* One genesis transaction: a swap the creator sends right after the seed, inside the launch's
|
|
1257
|
+
* `multicall`. A `buy` spends `amountIn` of quote for leg `j` (an index into `bases` AS GIVEN); a
|
|
1258
|
+
* `sell` spends `amountIn` of that leg for quote. The creator pays and is paid — the manager settles
|
|
1259
|
+
* to its sender — so a `recipient` is paid by an ERC-20 transfer of the row's output
|
|
1260
|
+
* ({@link fPoolGenesisTransfers}); without one the output stays with the creator.
|
|
1261
|
+
*/
|
|
1262
|
+
export interface FPoolGenesisRow {
|
|
1263
|
+
side: "buy" | "sell";
|
|
1264
|
+
j: number;
|
|
1265
|
+
amountIn: bigint;
|
|
1266
|
+
/** Defaults to 0. With a `recipient`, {@link fPoolLaunch} pins it to the simulated output, which is what the transfer pays. */
|
|
1267
|
+
minOut?: bigint;
|
|
1268
|
+
recipient?: Address;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/** An ERC-20 transfer owed to a genesis recipient: one per (token, recipient), rows summed. */
|
|
1272
|
+
export interface FPoolGenesisTransfer {
|
|
1273
|
+
token: Address;
|
|
1274
|
+
to: Address;
|
|
1275
|
+
amount: bigint;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1255
1278
|
export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
|
|
1256
1279
|
L0: bigint;
|
|
1257
1280
|
/** Quote the seed pulls. A pad seeds at 0 — pure float, a resting ask, nothing raised. */
|
|
@@ -1264,6 +1287,18 @@ export interface FPoolLaunchArgs extends Omit<FPoolCreateArgs, "account"> {
|
|
|
1264
1287
|
* this buy — they are one transaction — so `minOut` is safe at its default of 0.
|
|
1265
1288
|
*/
|
|
1266
1289
|
openBuy?: { j: number; amountIn: bigint; minOut?: bigint };
|
|
1290
|
+
/**
|
|
1291
|
+
* The genesis transactions: an ordered list of swaps right after the seed, each with an optional
|
|
1292
|
+
* recipient ({@link FPoolGenesisRow}). The same batch as `openBuy`, which stays as the one-buy
|
|
1293
|
+
* case and runs first when both are given. See `specs/f-pools/opening.md`.
|
|
1294
|
+
*/
|
|
1295
|
+
openBatch?: FPoolGenesisRow[];
|
|
1296
|
+
/**
|
|
1297
|
+
* How a row's recipient is paid. `auto` (default): in the launch's own transaction when the
|
|
1298
|
+
* wallet batches atomically (EIP-5792), else left to {@link fPoolSendGenesisTransfers};
|
|
1299
|
+
* `followup`: always left to it. Ignored when no row names a recipient.
|
|
1300
|
+
*/
|
|
1301
|
+
deliver?: "auto" | "followup";
|
|
1267
1302
|
/** 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. */
|
|
1268
1303
|
name?: string;
|
|
1269
1304
|
/**
|
|
@@ -1301,10 +1336,10 @@ export function fPoolLaunchCalls(
|
|
|
1301
1336
|
calls.push(enc("seed", [poolId, a.L0, companions[1]!, a.Q0]));
|
|
1302
1337
|
// gen 0 by construction: a launch is the pool's FIRST seed — a relaunch is `fPoolRelaunch`.
|
|
1303
1338
|
// No transfer to 0xdead: on a pad `seed` mints every share there itself.
|
|
1304
|
-
|
|
1305
|
-
const j = bases.indexOf(a.bases[
|
|
1306
|
-
if (j < 0) throw new Error(`logswap:
|
|
1307
|
-
calls.push(enc("swapQuoteIn", [poolId, BigInt(j),
|
|
1339
|
+
for (const row of fPoolGenesisRows(a)) {
|
|
1340
|
+
const j = bases.indexOf(a.bases[row.j]!);
|
|
1341
|
+
if (j < 0) throw new Error(`logswap: genesis row j = ${row.j} is not a leg`);
|
|
1342
|
+
calls.push(enc(row.side === "buy" ? "swapQuoteIn" : "swapBaseIn", [poolId, BigInt(j), row.amountIn, row.minOut ?? 0n, a.account]));
|
|
1308
1343
|
}
|
|
1309
1344
|
// the name, the desk's gates and its list are the POLICY's, not the
|
|
1310
1345
|
// manager's: they cannot ride in this batch — `fPoolLaunch` and `fPoolDeskOpening` send them
|
|
@@ -1313,6 +1348,87 @@ export function fPoolLaunchCalls(
|
|
|
1313
1348
|
return calls;
|
|
1314
1349
|
}
|
|
1315
1350
|
|
|
1351
|
+
/** The genesis rows in the order they run: `openBuy` first when given, then `openBatch`. Legs as given. */
|
|
1352
|
+
export function fPoolGenesisRows(a: Pick<FPoolLaunchArgs, "openBuy" | "openBatch">): FPoolGenesisRow[] {
|
|
1353
|
+
return [...(a.openBuy ? [{ side: "buy" as const, ...a.openBuy }] : []), ...(a.openBatch ?? [])];
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* What each genesis row returned, read by simulating the launch's `multicall` on the chain — the
|
|
1358
|
+
* contract's own rounding, no re-implementation. Exact at send time: the pool is new, and nothing
|
|
1359
|
+
* can trade between the seed and the rows. Needs the creator's funds and allowances, as the launch
|
|
1360
|
+
* does. One output per row, in {@link fPoolGenesisRows} order: base for a buy, quote for a sell.
|
|
1361
|
+
*/
|
|
1362
|
+
export async function fPoolGenesisOutputs(
|
|
1363
|
+
c: LogswapClient,
|
|
1364
|
+
poolId: Hex,
|
|
1365
|
+
a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
|
|
1366
|
+
): Promise<bigint[]> {
|
|
1367
|
+
const rows = fPoolGenesisRows(a);
|
|
1368
|
+
if (rows.length === 0) return [];
|
|
1369
|
+
const calls = fPoolLaunchCalls(poolId, a);
|
|
1370
|
+
const { result } = await c.public.simulateContract({
|
|
1371
|
+
address: c.addresses.fPoolManager!,
|
|
1372
|
+
abi: fPoolManagerAbi,
|
|
1373
|
+
functionName: "multicall",
|
|
1374
|
+
args: [calls],
|
|
1375
|
+
account: signerFor(c, a.account),
|
|
1376
|
+
} as never);
|
|
1377
|
+
const results = result as readonly Hex[];
|
|
1378
|
+
const first = (a.initialized ? 0 : 1) + 1; // [initialize] · seed · rows
|
|
1379
|
+
return rows.map((_, i) => decodeAbiParameters([{ type: "uint256" }], results[first + i]!)[0] as bigint);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* The transfers that pay the recipients: each row's output (`outs`, aligned with
|
|
1384
|
+
* {@link fPoolGenesisRows}) to its `recipient`, summed per (token, recipient), zero amounts dropped.
|
|
1385
|
+
* The token is the leg's base for a buy and the quote for a sell. Pure.
|
|
1386
|
+
*/
|
|
1387
|
+
export function fPoolGenesisTransfers(
|
|
1388
|
+
a: Pick<FPoolLaunchArgs, "quote" | "bases" | "openBuy" | "openBatch">,
|
|
1389
|
+
outs: bigint[],
|
|
1390
|
+
): FPoolGenesisTransfer[] {
|
|
1391
|
+
const out = new Map<string, FPoolGenesisTransfer>();
|
|
1392
|
+
fPoolGenesisRows(a).forEach((row, i) => {
|
|
1393
|
+
if (!row.recipient || !outs[i]) return;
|
|
1394
|
+
const token = row.side === "buy" ? a.bases[row.j]! : a.quote;
|
|
1395
|
+
const k = `${token.toLowerCase()}:${row.recipient.toLowerCase()}`;
|
|
1396
|
+
const t = out.get(k);
|
|
1397
|
+
if (t) t.amount += outs[i]!;
|
|
1398
|
+
else out.set(k, { token, to: row.recipient, amount: outs[i]! });
|
|
1399
|
+
});
|
|
1400
|
+
return [...out.values()];
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/** Pay genesis recipients with plain ERC-20 transfers, one transaction each, in order. For a launch that did not deliver itself. */
|
|
1404
|
+
export async function fPoolSendGenesisTransfers(c: LogswapClient, a: { transfers: FPoolGenesisTransfer[]; account: Address }): Promise<Hash[]> {
|
|
1405
|
+
const wallet = requireWallet(c);
|
|
1406
|
+
const hashes: Hash[] = [];
|
|
1407
|
+
for (const t of a.transfers) {
|
|
1408
|
+
const { request } = await c.public.simulateContract({
|
|
1409
|
+
address: t.token,
|
|
1410
|
+
abi: erc20Abi,
|
|
1411
|
+
functionName: "transfer",
|
|
1412
|
+
args: [t.to, t.amount],
|
|
1413
|
+
account: signerFor(c, a.account),
|
|
1414
|
+
} as never);
|
|
1415
|
+
hashes.push(await wallet.writeContract(request as never));
|
|
1416
|
+
}
|
|
1417
|
+
return hashes;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/** 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. */
|
|
1421
|
+
export async function fPoolWalletBatchesAtomically(c: LogswapClient, account: Address): Promise<boolean> {
|
|
1422
|
+
try {
|
|
1423
|
+
const wallet = requireWallet(c);
|
|
1424
|
+
const chainId = wallet.chain?.id;
|
|
1425
|
+
const caps = (await wallet.getCapabilities({ account, chainId })) as { atomic?: { status?: string } };
|
|
1426
|
+
return caps?.atomic?.status === "supported" || caps?.atomic?.status === "ready";
|
|
1427
|
+
} catch {
|
|
1428
|
+
return false;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1316
1432
|
/**
|
|
1317
1433
|
* The desk's opening posture, on its policy: gated minting, the creator a
|
|
1318
1434
|
* minter. No appointment: the authority is the operator until one is appointed.
|
|
@@ -1346,13 +1462,42 @@ export async function fPoolDeskOpening(c: LogswapClient, a: { poolId: Hex; accou
|
|
|
1346
1462
|
export async function fPoolLaunch(
|
|
1347
1463
|
c: LogswapClient,
|
|
1348
1464
|
a: FPoolLaunchArgs & ({ r0: bigint[] } | { x0: bigint[] }),
|
|
1349
|
-
): Promise<{ hash: Hash; poolId: Hex }> {
|
|
1465
|
+
): Promise<{ hash: Hash; poolId: Hex; transfers: FPoolGenesisTransfer[]; delivered: boolean }> {
|
|
1350
1466
|
const poolId = await fPoolIdOf(c, a);
|
|
1351
1467
|
const policy = a.policy ?? fPoolPolicyFor(c, a.kind);
|
|
1352
|
-
|
|
1353
|
-
|
|
1468
|
+
let args = { ...a, policy };
|
|
1469
|
+
// With recipients, each row's minOut is pinned to its simulated output and the transfers pay
|
|
1470
|
+
// exactly that: a row that returns less reverts the launch, a wei of surplus stays with the creator.
|
|
1471
|
+
let transfers: FPoolGenesisTransfer[] = [];
|
|
1472
|
+
const rows = fPoolGenesisRows(a);
|
|
1473
|
+
if (rows.some((r) => r.recipient)) {
|
|
1474
|
+
const outs = await fPoolGenesisOutputs(c, poolId, args);
|
|
1475
|
+
args = { ...args, openBuy: undefined, openBatch: rows.map((r, i) => ({ ...r, minOut: outs[i]! })) };
|
|
1476
|
+
transfers = fPoolGenesisTransfers(args, outs);
|
|
1477
|
+
}
|
|
1478
|
+
const calls = fPoolLaunchCalls(poolId, args);
|
|
1479
|
+
let hash: Hash;
|
|
1480
|
+
let delivered = transfers.length === 0;
|
|
1481
|
+
if (transfers.length > 0 && a.deliver !== "followup" && (await fPoolWalletBatchesAtomically(c, a.account))) {
|
|
1482
|
+
const wallet = requireWallet(c);
|
|
1483
|
+
const { id } = await wallet.sendCalls({
|
|
1484
|
+
account: signerFor(c, a.account),
|
|
1485
|
+
forceAtomic: true,
|
|
1486
|
+
calls: [
|
|
1487
|
+
{ to: c.addresses.fPoolManager!, data: encodeFunctionData({ abi: fPoolManagerAbi, functionName: "multicall", args: [calls] } as never) },
|
|
1488
|
+
...transfers.map((t) => ({ to: t.token, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [t.to, t.amount] }) })),
|
|
1489
|
+
],
|
|
1490
|
+
} as never);
|
|
1491
|
+
const status = await wallet.waitForCallsStatus({ id });
|
|
1492
|
+
const receipt = status.receipts?.[0];
|
|
1493
|
+
if (status.status !== "success" || !receipt) throw new Error("logswap: the launch batch did not succeed — nothing was created");
|
|
1494
|
+
hash = receipt.transactionHash;
|
|
1495
|
+
delivered = true;
|
|
1496
|
+
} else {
|
|
1497
|
+
hash = await writeFPool0(c, "multicall", [calls], a.account);
|
|
1498
|
+
}
|
|
1354
1499
|
if (a.name) await fPoolSetName(c, { poolId, name: a.name, account: a.account, policy });
|
|
1355
|
-
return { hash, poolId };
|
|
1500
|
+
return { hash, poolId, transfers, delivered };
|
|
1356
1501
|
}
|
|
1357
1502
|
|
|
1358
1503
|
// ─── the roles, on the policy ─────────────────────────────────────────────
|