@logswap/sdk 0.5.62 → 0.5.63

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/client.js CHANGED
@@ -123,30 +123,27 @@ export const EXPECTED_VERSION = 5n;
123
123
  // `isTaker`; a pad and a POL hold one base (`OneBase`).
124
124
  export const EXPECTED_F_VERSION = 19n;
125
125
  export async function assertVersion(c, expected = EXPECTED_VERSION) {
126
- const { cPoolManagerAbi } = await import("./generated.js");
127
- const got = await c.public.readContract({
128
- address: c.addresses.cPoolManager,
129
- abi: cPoolManagerAbi,
130
- functionName: "version",
131
- });
126
+ const { cPoolManagerAbi, fPoolManagerAbi } = await import("./generated.js");
127
+ // three independent reads: one round trip, not three in a row
128
+ const [got, gotF, wiring] = await Promise.all([
129
+ c.public.readContract({ address: c.addresses.cPoolManager, abi: cPoolManagerAbi, functionName: "version" }),
130
+ // the F manager carries its own tag; assert it the same way when deployed
131
+ c.addresses.fPoolManager
132
+ ? c.public.readContract({ address: c.addresses.fPoolManager, abi: fPoolManagerAbi, functionName: "version" })
133
+ : Promise.resolve(null),
134
+ assertWiring(c).then(() => null, (e) => e),
135
+ ]);
132
136
  if (got !== expected) {
133
137
  throw new Error(`logswap: manager at ${c.addresses.cPoolManager} reports version ${got}, but this SDK was built ` +
134
138
  `against version ${expected}. Upgrade the SDK or point at the matching deployment.`);
135
139
  }
136
- // the F manager carries its own tag; assert it the same way when deployed
137
- if (c.addresses.fPoolManager) {
138
- const { fPoolManagerAbi } = await import("./generated.js");
139
- const gotF = await c.public.readContract({
140
- address: c.addresses.fPoolManager,
141
- abi: fPoolManagerAbi,
142
- functionName: "version",
143
- });
144
- if (gotF < EXPECTED_F_VERSION) {
145
- throw new Error(`logswap: F manager at ${c.addresses.fPoolManager} reports version ${gotF}, but this SDK needs ` +
146
- `at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`);
147
- }
140
+ if (gotF !== null && gotF < EXPECTED_F_VERSION) {
141
+ throw new Error(`logswap: F manager at ${c.addresses.fPoolManager} reports version ${gotF}, but this SDK needs ` +
142
+ `at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`);
148
143
  }
149
- await assertWiring(c);
144
+ // the wiring's error is the less specific one, so a version mismatch is reported first
145
+ if (wiring)
146
+ throw wiring;
150
147
  }
151
148
  /**
152
149
  * The versions say the managers are the right code; this says the rest of the addresses belong
package/dist/fpool.d.ts CHANGED
@@ -1433,11 +1433,13 @@ export interface DiscoveredFPool {
1433
1433
  * Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
1434
1434
  * policy, cached. Possible only because the event carries the full key preimage (bases,
1435
1435
  * weights, phi, policy) — the poolId is a hash and could never be inverted; contracts PR #17
1436
- * exists for exactly this call.
1436
+ * exists for exactly this call. With `indexer`, the logs come from it (the node when it fails):
1437
+ * a scan from block 0 per call is what made the first load wait on the node.
1437
1438
  */
1438
1439
  export declare function discoverFPools(c: LogswapClient, opts?: {
1439
1440
  fromBlock?: bigint;
1440
1441
  toBlock?: bigint;
1442
+ indexer?: string;
1441
1443
  }): Promise<DiscoveredFPool[]>;
1442
1444
  /** A pool an authority controls, with what the policy page shows of it. */
1443
1445
  export interface FPoolControlled {
package/dist/fpool.js CHANGED
@@ -46,6 +46,7 @@ export { fPoolManagerAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPo
46
46
  */
47
47
  export const fPolicyBaseAbi = polPolicyAbi;
48
48
  import { fallBackUnlessRejected, signerFor } from "./write.js";
49
+ import { getEventLogs } from "./history.js";
49
50
  const WAD = 10n ** 18n;
50
51
  const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
51
52
  /** The optional questions a policy declared at birth, as bits of `Pool.asks`. */
@@ -1044,23 +1045,18 @@ export async function fPoolTransferShares(c, a) {
1044
1045
  * Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
1045
1046
  * policy, cached. Possible only because the event carries the full key preimage (bases,
1046
1047
  * weights, phi, policy) — the poolId is a hash and could never be inverted; contracts PR #17
1047
- * exists for exactly this call.
1048
+ * exists for exactly this call. With `indexer`, the logs come from it (the node when it fails):
1049
+ * a scan from block 0 per call is what made the first load wait on the node.
1048
1050
  */
1049
1051
  export async function discoverFPools(c, opts = {}) {
1050
1052
  const ev = fPoolManagerAbi.find((x) => x.type === "event" && x.name === "FInitialize");
1051
1053
  if (!ev)
1052
1054
  throw new Error("logswap: F Initialize event missing from the generated ABI");
1053
1055
  const seedEv = fPoolManagerAbi.find((x) => x.type === "event" && x.name === "Seeded");
1054
- const [logs, seeds] = await Promise.all([
1055
- c.public.getLogs({
1056
- address: c.addresses.fPoolManager,
1057
- event: ev,
1058
- fromBlock: opts.fromBlock ?? 0n,
1059
- toBlock: opts.toBlock ?? "latest",
1060
- }),
1061
- seedEv
1062
- ? c.public.getLogs({ address: c.addresses.fPoolManager, event: seedEv, fromBlock: opts.fromBlock ?? 0n, toBlock: opts.toBlock ?? "latest" })
1063
- : Promise.resolve([]),
1056
+ const range = { indexer: opts.indexer, address: c.addresses.fPoolManager, fromBlock: opts.fromBlock, toBlock: opts.toBlock };
1057
+ const [{ logs }, { logs: seeds }] = await Promise.all([
1058
+ getEventLogs(c, { ...range, event: ev }),
1059
+ seedEv ? getEventLogs(c, { ...range, event: seedEv }) : Promise.resolve({ logs: [] }),
1064
1060
  ]);
1065
1061
  const q0Of = new Map();
1066
1062
  for (const l of seeds) {
@@ -1080,7 +1076,7 @@ export async function discoverFPools(c, opts = {}) {
1080
1076
  kind,
1081
1077
  policy: a.policy,
1082
1078
  creator: a.creator,
1083
- block: l.blockNumber ?? 0n,
1079
+ block: l.blockNumber,
1084
1080
  seeded: q0 !== undefined,
1085
1081
  q0: q0 ?? 0n,
1086
1082
  shape: kind === "pad" ? "launch" : "basket", // the policy's word, not n = 1: a mono POL is a pool
package/dist/history.js CHANGED
@@ -66,8 +66,10 @@ function decode(type, v) {
66
66
  return getAddress(v);
67
67
  return v; // bool, bytes32 (hex string), string
68
68
  }
69
- const PAGE = 1000;
69
+ const PAGE = 1000; // postgraphile's cap
70
70
  const MAX_PAGES = 200;
71
+ /** Pages in flight at once, after the first. */
72
+ const PARALLEL_PAGES = 8;
71
73
  async function fromIndexer(c, q) {
72
74
  const { field, type } = tableFor(c, q.address, q.event.name);
73
75
  const inputs = q.event.inputs;
@@ -106,24 +108,30 @@ async function fromIndexer(c, q) {
106
108
  // postgraphile refuses a null or an empty filter, so each argument appears only when it has content
107
109
  const hasCondition = Object.keys(condition).length > 0;
108
110
  const hasFilter = Object.keys(filter).length > 0;
109
- const decl = `$after: Cursor${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
110
- const argsOf = `first: ${pageSize}, after: $after, orderBy: ${order}${hasCondition ? ", condition: $condition" : ""}${hasFilter ? ", filter: $filter" : ""}`;
111
+ const decl = `$offset: Int${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
112
+ const argsOf = `first: ${pageSize}, offset: $offset, orderBy: ${order}${hasCondition ? ", condition: $condition" : ""}${hasFilter ? ", filter: $filter" : ""}`;
111
113
  const query = `query History(${decl}) {
112
114
  ${field}(${argsOf}) {
113
- pageInfo { hasNextPage endCursor } nodes { ${selection} }
115
+ totalCount nodes { ${selection} }
114
116
  }
115
117
  }`;
116
- const rows = [];
117
- let after = null;
118
- for (let i = 0;; i++) {
119
- if (i === MAX_PAGES)
118
+ const page = async (offset) => (await gql(q.indexer, query, { offset, ...(hasCondition ? { condition } : {}), ...(hasFilter ? { filter } : {}) }, q.signal))[field];
119
+ // The first page says how many rows there are; the rest are fetched in parallel by offset, not
120
+ // one cursor after another: a day of a busy pool is a few pages, and a round trip to a distant
121
+ // indexer costs more than the page. Ascending by key, rows that land meanwhile append at the end
122
+ // and shift no earlier offset.
123
+ const first = await page(0);
124
+ const rows = [...first.nodes];
125
+ if (q.latest === undefined && first.totalCount > first.nodes.length) {
126
+ const pages = Math.ceil(first.totalCount / PAGE);
127
+ if (pages > MAX_PAGES)
120
128
  throw new Error(`history: ${field} has more than ${PAGE * MAX_PAGES} rows`);
121
- const d = await gql(q.indexer, query, { after, ...(hasCondition ? { condition } : {}), ...(hasFilter ? { filter } : {}) }, q.signal);
122
- const page = d[field];
123
- rows.push(...page.nodes);
124
- if (q.latest !== undefined || !page.pageInfo.hasNextPage || !page.pageInfo.endCursor)
125
- break;
126
- after = page.pageInfo.endCursor;
129
+ const offsets = Array.from({ length: pages - 1 }, (_, i) => (i + 1) * PAGE);
130
+ for (let i = 0; i < offsets.length; i += PARALLEL_PAGES) {
131
+ const got = await Promise.all(offsets.slice(i, i + PARALLEL_PAGES).map(page));
132
+ for (const g of got)
133
+ rows.push(...g.nodes);
134
+ }
127
135
  }
128
136
  // a table can hold a previous deployment's rows (an indexer not yet reset): keep this contract's
129
137
  const want = q.address.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logswap/sdk",
3
- "version": "0.5.62",
3
+ "version": "0.5.63",
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/client.ts CHANGED
@@ -168,34 +168,30 @@ export const EXPECTED_VERSION = 5n;
168
168
  export const EXPECTED_F_VERSION = 19n;
169
169
 
170
170
  export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSION): Promise<void> {
171
- const { cPoolManagerAbi } = await import("./generated.js");
172
- const got = await c.public.readContract({
173
- address: c.addresses.cPoolManager,
174
- abi: cPoolManagerAbi,
175
- functionName: "version",
176
- });
171
+ const { cPoolManagerAbi, fPoolManagerAbi } = await import("./generated.js");
172
+ // three independent reads: one round trip, not three in a row
173
+ const [got, gotF, wiring] = await Promise.all([
174
+ c.public.readContract({ address: c.addresses.cPoolManager, abi: cPoolManagerAbi, functionName: "version" }),
175
+ // the F manager carries its own tag; assert it the same way when deployed
176
+ c.addresses.fPoolManager
177
+ ? c.public.readContract({ address: c.addresses.fPoolManager, abi: fPoolManagerAbi, functionName: "version" })
178
+ : Promise.resolve(null),
179
+ assertWiring(c).then(() => null, (e: unknown) => e),
180
+ ]);
177
181
  if (got !== expected) {
178
182
  throw new Error(
179
183
  `logswap: manager at ${c.addresses.cPoolManager} reports version ${got}, but this SDK was built ` +
180
184
  `against version ${expected}. Upgrade the SDK or point at the matching deployment.`,
181
185
  );
182
186
  }
183
- // the F manager carries its own tag; assert it the same way when deployed
184
- if (c.addresses.fPoolManager) {
185
- const { fPoolManagerAbi } = await import("./generated.js");
186
- const gotF = await c.public.readContract({
187
- address: c.addresses.fPoolManager,
188
- abi: fPoolManagerAbi,
189
- functionName: "version",
190
- });
191
- if (gotF < EXPECTED_F_VERSION) {
192
- throw new Error(
193
- `logswap: F manager at ${c.addresses.fPoolManager} reports version ${gotF}, but this SDK needs ` +
194
- `at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`,
195
- );
196
- }
187
+ if (gotF !== null && gotF < EXPECTED_F_VERSION) {
188
+ throw new Error(
189
+ `logswap: F manager at ${c.addresses.fPoolManager} reports version ${gotF}, but this SDK needs ` +
190
+ `at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`,
191
+ );
197
192
  }
198
- await assertWiring(c);
193
+ // the wiring's error is the less specific one, so a version mismatch is reported first
194
+ if (wiring) throw wiring;
199
195
  }
200
196
 
201
197
  /**
package/src/fpool.ts CHANGED
@@ -49,6 +49,7 @@ export { fPoolManagerAbi, padPolicyAbi, polPolicyAbi, privatePolicyAbi, publicPo
49
49
  export const fPolicyBaseAbi = polPolicyAbi;
50
50
  import type { LogswapClient } from "./client.js";
51
51
  import { fallBackUnlessRejected, signerFor } from "./write.js";
52
+ import { getEventLogs } from "./history.js";
52
53
 
53
54
  const WAD = 10n ** 18n;
54
55
  const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" as const;
@@ -1474,27 +1475,22 @@ export interface DiscoveredFPool {
1474
1475
  * Every F pool ever created, from `Initialize` logs alone — plus one `kind()` read per distinct
1475
1476
  * policy, cached. Possible only because the event carries the full key preimage (bases,
1476
1477
  * weights, phi, policy) — the poolId is a hash and could never be inverted; contracts PR #17
1477
- * exists for exactly this call.
1478
+ * exists for exactly this call. With `indexer`, the logs come from it (the node when it fails):
1479
+ * a scan from block 0 per call is what made the first load wait on the node.
1478
1480
  */
1479
1481
  export async function discoverFPools(
1480
1482
  c: LogswapClient,
1481
- opts: { fromBlock?: bigint; toBlock?: bigint } = {},
1483
+ opts: { fromBlock?: bigint; toBlock?: bigint; indexer?: string } = {},
1482
1484
  ): Promise<DiscoveredFPool[]> {
1483
1485
  type Ev = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "FInitialize" }>;
1484
1486
  const ev = fPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "FInitialize");
1485
1487
  if (!ev) throw new Error("logswap: F Initialize event missing from the generated ABI");
1486
1488
  type SeedEv = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "Seeded" }>;
1487
1489
  const seedEv = fPoolManagerAbi.find((x): x is SeedEv => x.type === "event" && x.name === "Seeded");
1488
- const [logs, seeds] = await Promise.all([
1489
- c.public.getLogs({
1490
- address: c.addresses.fPoolManager!,
1491
- event: ev,
1492
- fromBlock: opts.fromBlock ?? 0n,
1493
- toBlock: opts.toBlock ?? "latest",
1494
- }),
1495
- seedEv
1496
- ? c.public.getLogs({ address: c.addresses.fPoolManager!, event: seedEv, fromBlock: opts.fromBlock ?? 0n, toBlock: opts.toBlock ?? "latest" })
1497
- : Promise.resolve([]),
1490
+ const range = { indexer: opts.indexer, address: c.addresses.fPoolManager!, fromBlock: opts.fromBlock, toBlock: opts.toBlock };
1491
+ const [{ logs }, { logs: seeds }] = await Promise.all([
1492
+ getEventLogs(c, { ...range, event: ev }),
1493
+ seedEv ? getEventLogs(c, { ...range, event: seedEv }) : Promise.resolve({ logs: [] }),
1498
1494
  ]);
1499
1495
  const q0Of = new Map<string, bigint>();
1500
1496
  for (const l of seeds) {
@@ -1518,7 +1514,7 @@ export async function discoverFPools(
1518
1514
  kind,
1519
1515
  policy: a.policy,
1520
1516
  creator: a.creator,
1521
- block: l.blockNumber ?? 0n,
1517
+ block: l.blockNumber,
1522
1518
  seeded: q0 !== undefined,
1523
1519
  q0: q0 ?? 0n,
1524
1520
  shape: kind === "pad" ? "launch": "basket", // the policy's word, not n = 1: a mono POL is a pool
package/src/history.ts CHANGED
@@ -101,8 +101,10 @@ function decode(type: string, v: unknown): unknown {
101
101
  return v; // bool, bytes32 (hex string), string
102
102
  }
103
103
 
104
- const PAGE = 1000;
104
+ const PAGE = 1000; // postgraphile's cap
105
105
  const MAX_PAGES = 200;
106
+ /** Pages in flight at once, after the first. */
107
+ const PARALLEL_PAGES = 8;
106
108
 
107
109
  async function fromIndexer<A>(c: LogswapClient, q: EventLogsQuery): Promise<HistoryLog<A>[]> {
108
110
  const { field, type } = tableFor(c, q.address, q.event.name);
@@ -135,29 +137,31 @@ async function fromIndexer<A>(c: LogswapClient, q: EventLogsQuery): Promise<Hist
135
137
  // postgraphile refuses a null or an empty filter, so each argument appears only when it has content
136
138
  const hasCondition = Object.keys(condition).length > 0;
137
139
  const hasFilter = Object.keys(filter).length > 0;
138
- const decl = `$after: Cursor${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
139
- const argsOf = `first: ${pageSize}, after: $after, orderBy: ${order}${hasCondition ? ", condition: $condition" : ""}${hasFilter ? ", filter: $filter" : ""}`;
140
+ const decl = `$offset: Int${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
141
+ const argsOf = `first: ${pageSize}, offset: $offset, orderBy: ${order}${hasCondition ? ", condition: $condition" : ""}${hasFilter ? ", filter: $filter" : ""}`;
140
142
  const query = `query History(${decl}) {
141
143
  ${field}(${argsOf}) {
142
- pageInfo { hasNextPage endCursor } nodes { ${selection} }
144
+ totalCount nodes { ${selection} }
143
145
  }
144
146
  }`;
145
147
  type Row = Record<string, unknown>;
146
- type Conn = { pageInfo: { hasNextPage: boolean; endCursor: string | null }; nodes: Row[] };
147
- const rows: Row[] = [];
148
- let after: string | null = null;
149
- for (let i = 0; ; i++) {
150
- if (i === MAX_PAGES) throw new Error(`history: ${field} has more than ${PAGE * MAX_PAGES} rows`);
151
- const d: Record<string, Conn> = await gql<Record<string, Conn>>(
152
- q.indexer!,
153
- query,
154
- { after, ...(hasCondition ? { condition } : {}), ...(hasFilter ? { filter } : {}) },
155
- q.signal,
156
- );
157
- const page: Conn = d[field]!;
158
- rows.push(...page.nodes);
159
- if (q.latest !== undefined || !page.pageInfo.hasNextPage || !page.pageInfo.endCursor) break;
160
- after = page.pageInfo.endCursor;
148
+ type Conn = { totalCount: number; nodes: Row[] };
149
+ const page = async (offset: number): Promise<Conn> =>
150
+ (await gql<Record<string, Conn>>(q.indexer!, query, { offset, ...(hasCondition ? { condition } : {}), ...(hasFilter ? { filter } : {}) }, q.signal))[field]!;
151
+ // The first page says how many rows there are; the rest are fetched in parallel by offset, not
152
+ // one cursor after another: a day of a busy pool is a few pages, and a round trip to a distant
153
+ // indexer costs more than the page. Ascending by key, rows that land meanwhile append at the end
154
+ // and shift no earlier offset.
155
+ const first = await page(0);
156
+ const rows: Row[] = [...first.nodes];
157
+ if (q.latest === undefined && first.totalCount > first.nodes.length) {
158
+ const pages = Math.ceil(first.totalCount / PAGE);
159
+ if (pages > MAX_PAGES) throw new Error(`history: ${field} has more than ${PAGE * MAX_PAGES} rows`);
160
+ const offsets = Array.from({ length: pages - 1 }, (_, i) => (i + 1) * PAGE);
161
+ for (let i = 0; i < offsets.length; i += PARALLEL_PAGES) {
162
+ const got = await Promise.all(offsets.slice(i, i + PARALLEL_PAGES).map(page));
163
+ for (const g of got) rows.push(...g.nodes);
164
+ }
161
165
  }
162
166
  // a table can hold a previous deployment's rows (an indexer not yet reset): keep this contract's
163
167
  const want = q.address.toLowerCase();