@logswap/sdk 0.5.57 → 0.5.58

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/fees.d.ts CHANGED
@@ -30,7 +30,17 @@ export interface SwapFeeWindow {
30
30
  swapFees: bigint;
31
31
  swaps: number;
32
32
  }
33
- /** The first block with timestamp ≥ `t`, by bisection over `[lo, hi]`; `hi` when none is earlier. */
33
+ /**
34
+ * The first block with timestamp ≥ `t` in `[lo, hi]`; `hi` when none is earlier.
35
+ *
36
+ * A PARALLEL search: each round reads {@link PROBES} evenly spaced blocks at once and keeps the
37
+ * sixteenth of the bracket that holds the answer, so 64k blocks take 4 round trips. Bisection read
38
+ * one block per round trip — ~16 sequential `getBlock`s, seconds apiece against a distant node,
39
+ * before a fee figure could render. Interpolating on timestamps was tried and did no better: a
40
+ * devnet mines on demand plus a heartbeat, so its blocks are far from evenly spaced in time.
41
+ * Exact for monotonic timestamps; a chain restored from a snapshot can carry a genesis stamped
42
+ * after its later blocks, so callers bound `lo` by the block they care about (a pool's own).
43
+ */
34
44
  export declare function blockAtOrAfter(c: LogswapClient, t: bigint, lo: bigint, hi: bigint): Promise<bigint>;
35
45
  /**
36
46
  * Sum the swap fees of market `m` over a window: from `since` (unix seconds) or `fromBlock`, to
package/dist/fees.js CHANGED
@@ -17,16 +17,42 @@ const YEAR_SECONDS = 31_536_000;
17
17
  export function swapFeeOf(e, phi) {
18
18
  return e.baseIn ? (e.amountOut * phi) / (WAD - phi) : (e.amountIn * phi) / (WAD + phi);
19
19
  }
20
- /** The first block with timestamp ≥ `t`, by bisection over `[lo, hi]`; `hi` when none is earlier. */
20
+ /** Probes per round of {@link blockAtOrAfter}: 15 splits the bracket 16 ways. */
21
+ const PROBES = 15n;
22
+ /**
23
+ * The first block with timestamp ≥ `t` in `[lo, hi]`; `hi` when none is earlier.
24
+ *
25
+ * A PARALLEL search: each round reads {@link PROBES} evenly spaced blocks at once and keeps the
26
+ * sixteenth of the bracket that holds the answer, so 64k blocks take 4 round trips. Bisection read
27
+ * one block per round trip — ~16 sequential `getBlock`s, seconds apiece against a distant node,
28
+ * before a fee figure could render. Interpolating on timestamps was tried and did no better: a
29
+ * devnet mines on demand plus a heartbeat, so its blocks are far from evenly spaced in time.
30
+ * Exact for monotonic timestamps; a chain restored from a snapshot can carry a genesis stamped
31
+ * after its later blocks, so callers bound `lo` by the block they care about (a pool's own).
32
+ */
21
33
  export async function blockAtOrAfter(c, t, lo, hi) {
22
- while (lo < hi) {
23
- const mid = (lo + hi) / 2n;
24
- if ((await c.public.getBlock({ blockNumber: mid })).timestamp < t)
25
- lo = mid + 1n;
26
- else
27
- hi = mid;
34
+ if (lo >= hi)
35
+ return hi;
36
+ const stamp = async (n) => (await c.public.getBlock({ blockNumber: n })).timestamp;
37
+ if ((await stamp(lo)) >= t)
38
+ return lo;
39
+ // invariant from here: ts(lo) < t, and the answer is in (lo, hi]
40
+ while (hi - lo > 1n) {
41
+ const width = hi - lo;
42
+ const probes = [...new Set(Array.from({ length: Number(PROBES) }, (_, i) => lo + (width * BigInt(i + 1)) / (PROBES + 1n)))].filter((n) => n > lo && n < hi);
43
+ if (probes.length === 0)
44
+ probes.push(lo + width / 2n);
45
+ const stamps = await Promise.all(probes.map(stamp));
46
+ const first = stamps.findIndex((ts) => ts >= t);
47
+ if (first === -1)
48
+ lo = probes[probes.length - 1];
49
+ else {
50
+ hi = probes[first];
51
+ if (first > 0)
52
+ lo = probes[first - 1];
53
+ }
28
54
  }
29
- return lo;
55
+ return hi;
30
56
  }
31
57
  /**
32
58
  * Sum the swap fees of market `m` over a window: from `since` (unix seconds) or `fromBlock`, to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logswap/sdk",
3
- "version": "0.5.57",
3
+ "version": "0.5.58",
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/fees.ts CHANGED
@@ -36,14 +36,40 @@ export interface SwapFeeWindow {
36
36
  swaps: number;
37
37
  }
38
38
 
39
- /** The first block with timestamp ≥ `t`, by bisection over `[lo, hi]`; `hi` when none is earlier. */
39
+ /** Probes per round of {@link blockAtOrAfter}: 15 splits the bracket 16 ways. */
40
+ const PROBES = 15n;
41
+
42
+ /**
43
+ * The first block with timestamp ≥ `t` in `[lo, hi]`; `hi` when none is earlier.
44
+ *
45
+ * A PARALLEL search: each round reads {@link PROBES} evenly spaced blocks at once and keeps the
46
+ * sixteenth of the bracket that holds the answer, so 64k blocks take 4 round trips. Bisection read
47
+ * one block per round trip — ~16 sequential `getBlock`s, seconds apiece against a distant node,
48
+ * before a fee figure could render. Interpolating on timestamps was tried and did no better: a
49
+ * devnet mines on demand plus a heartbeat, so its blocks are far from evenly spaced in time.
50
+ * Exact for monotonic timestamps; a chain restored from a snapshot can carry a genesis stamped
51
+ * after its later blocks, so callers bound `lo` by the block they care about (a pool's own).
52
+ */
40
53
  export async function blockAtOrAfter(c: LogswapClient, t: bigint, lo: bigint, hi: bigint): Promise<bigint> {
41
- while (lo < hi) {
42
- const mid = (lo + hi) / 2n;
43
- if ((await c.public.getBlock({ blockNumber: mid })).timestamp < t) lo = mid + 1n;
44
- else hi = mid;
54
+ if (lo >= hi) return hi;
55
+ const stamp = async (n: bigint) => (await c.public.getBlock({ blockNumber: n })).timestamp;
56
+ if ((await stamp(lo)) >= t) return lo;
57
+ // invariant from here: ts(lo) < t, and the answer is in (lo, hi]
58
+ while (hi - lo > 1n) {
59
+ const width = hi - lo;
60
+ const probes = [...new Set(Array.from({ length: Number(PROBES) }, (_, i) => lo + (width * BigInt(i + 1)) / (PROBES + 1n)))].filter(
61
+ (n) => n > lo && n < hi,
62
+ );
63
+ if (probes.length === 0) probes.push(lo + width / 2n);
64
+ const stamps = await Promise.all(probes.map(stamp));
65
+ const first = stamps.findIndex((ts) => ts >= t);
66
+ if (first === -1) lo = probes[probes.length - 1]!;
67
+ else {
68
+ hi = probes[first]!;
69
+ if (first > 0) lo = probes[first - 1]!;
70
+ }
45
71
  }
46
- return lo;
72
+ return hi;
47
73
  }
48
74
 
49
75
  /**