@logswap/sdk 0.5.50 → 0.5.52
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 +43 -25
- package/dist/admin.js +61 -35
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/fees.d.ts +52 -0
- package/dist/fees.js +79 -0
- package/dist/fpool.d.ts +194 -38
- package/dist/fpool.js +82 -43
- package/dist/generated.d.ts +837 -264
- package/dist/generated.js +563 -176
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -1
- package/src/admin.ts +68 -37
- package/src/client.ts +1 -1
- package/src/fees.ts +103 -0
- package/src/fpool.ts +122 -77
- package/src/generated.ts +564 -175
- package/src/index.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -19,5 +19,6 @@ export * from "./onboard.js";
|
|
|
19
19
|
export * from "./indexer.js";
|
|
20
20
|
export * from "./tokencache.js";
|
|
21
21
|
export * from "./fpool.js";
|
|
22
|
+
export * from "./fees.js";
|
|
22
23
|
export { cPoolManagerAbi, fPoolManagerAbi, logswapLensAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
23
24
|
export * from "./admin.js";
|
package/dist/index.js
CHANGED
|
@@ -19,5 +19,6 @@ export * from "./onboard.js";
|
|
|
19
19
|
export * from "./indexer.js";
|
|
20
20
|
export * from "./tokencache.js";
|
|
21
21
|
export * from "./fpool.js";
|
|
22
|
+
export * from "./fees.js";
|
|
22
23
|
export { cPoolManagerAbi, fPoolManagerAbi, logswapLensAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
23
24
|
export * from "./admin.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logswap/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.52",
|
|
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",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"clean": "rm -rf dist src/generated.ts",
|
|
27
27
|
"test": "vitest run",
|
|
28
|
+
"test:isolated": "bash scripts/test-isolated.sh",
|
|
28
29
|
"test:watch": "vitest",
|
|
29
30
|
"prepublishOnly": "npm run codegen && npm run build"
|
|
30
31
|
},
|
package/src/admin.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* admin — the protocol's
|
|
3
|
-
*
|
|
4
|
-
* two-step collector handover. Plain writes to the managers
|
|
2
|
+
* admin — the protocol's writes: the C manager's cut of the mint fee (the F manager's is a
|
|
3
|
+
* constant, decisions 049), the policies a pool may be born with (the registry's), the
|
|
4
|
+
* policies' own numbers, and the two-step collector handover. Plain writes to the managers,
|
|
5
|
+
* the registry and the policies; no router.
|
|
5
6
|
*
|
|
6
7
|
* One vocabulary (decisions 040): both managers call the protocol's cut of the mint fee
|
|
7
|
-
* `MINT_FEE_PROTOCOL_CUT
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* `MINT_FEE_PROTOCOL_CUT`; C sets it through `setFeeParams(uint256)`, F holds it as a constant
|
|
9
|
+
* (049). The harvest's cut is the policy's answer at every harvest (decisions 044), set on the
|
|
10
|
+
* policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
|
|
11
|
+
* protocol share likewise (`POT_CUT_MAX`).
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import type { Address, Hash } from "viem";
|
|
14
15
|
import type { LogswapClient } from "./client.js";
|
|
15
16
|
import { cPoolManagerAbi, fPoolManagerAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, publicPolicyAbi } from "./generated.js";
|
|
16
|
-
import {
|
|
17
|
+
import { bytes32ToText, fPolicyBaseAbi, fPoolKindOfPolicy, textToBytes32, type FPoolKind } from "./fpool.js";
|
|
17
18
|
|
|
18
19
|
function wallet(c: LogswapClient) {
|
|
19
20
|
if (!c.wallet) throw new Error("a wallet client is required");
|
|
@@ -28,9 +29,9 @@ function write(c: LogswapClient, address: Address, abi: readonly unknown[], func
|
|
|
28
29
|
export async function setFeeParamsC(c: LogswapClient, mintFeeProtocolCutWad: bigint): Promise<Hash> {
|
|
29
30
|
return write(c, c.addresses.cPoolManager, cPoolManagerAbi, "setFeeParams", [mintFeeProtocolCutWad]);
|
|
30
31
|
}
|
|
31
|
-
/** The F manager's `
|
|
32
|
-
export async function
|
|
33
|
-
return
|
|
32
|
+
/** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18 (decisions 049); there is no setter. */
|
|
33
|
+
export async function fMintFeeProtocolCut(c: LogswapClient): Promise<bigint> {
|
|
34
|
+
return c.public.readContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "MINT_FEE_PROTOCOL_CUT" } as never) as Promise<bigint>;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// ─── the registry (decisions 046): the LOG authority's, not the collector's ─────────────────
|
|
@@ -47,16 +48,18 @@ export async function policyRegistryOf(c: LogswapClient): Promise<Address> {
|
|
|
47
48
|
return (await c.public.readContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "registry" } as never)) as Address;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
/** Whether `policy` is approved
|
|
51
|
-
export async function policyApproved(c: LogswapClient,
|
|
51
|
+
/** Whether `policy` is approved now — listed and not yet sunset. True everywhere on a deployment with no registry. */
|
|
52
|
+
export async function policyApproved(c: LogswapClient, policy: Address): Promise<boolean> {
|
|
52
53
|
const registry = await policyRegistryOf(c);
|
|
53
54
|
if (registry === ZERO) return true;
|
|
54
|
-
return c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "isApproved", args: [
|
|
55
|
+
return c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "isApproved", args: [policy] } as never) as Promise<boolean>;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
/** One policy on
|
|
58
|
+
/** One policy on the registry's list, with what it says it is. */
|
|
58
59
|
export interface RegistryEntry {
|
|
59
60
|
policy: Address;
|
|
61
|
+
/** The policy's own `kind()` tag (decisions 049) — what a pool born under it is. */
|
|
62
|
+
kind: FPoolKind;
|
|
60
63
|
name: string;
|
|
61
64
|
/** The block it closes to new pools from; 0 = open. */
|
|
62
65
|
until: bigint;
|
|
@@ -64,18 +67,26 @@ export interface RegistryEntry {
|
|
|
64
67
|
approved: boolean;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Every policy listed, sunset ones included — one flat list (decisions 049), each with the kind
|
|
72
|
+
* it says it is; `kind` narrows it to one, which is what a create form's dropdown reads. Empty
|
|
73
|
+
* with no registry.
|
|
74
|
+
*/
|
|
75
|
+
export async function registryPolicies(c: LogswapClient, kind?: FPoolKind): Promise<RegistryEntry[]> {
|
|
69
76
|
const registry = await policyRegistryOf(c);
|
|
70
77
|
if (registry === ZERO) return [];
|
|
71
|
-
const list = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "
|
|
78
|
+
const list = (await c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "policies" } as never)) as readonly Address[];
|
|
72
79
|
const block = await c.public.getBlockNumber();
|
|
73
|
-
|
|
80
|
+
const all = await Promise.all(
|
|
74
81
|
list.map(async (policy) => {
|
|
75
|
-
const [listed, until, , name] =
|
|
76
|
-
|
|
82
|
+
const [[listed, until, , name], tag] = await Promise.all([
|
|
83
|
+
c.public.readContract({ address: registry, abi: policyRegistryAbi, functionName: "entries", args: [policy] } as never) as Promise<[boolean, bigint, number, `0x${string}`]>,
|
|
84
|
+
fPoolKindOfPolicy(c, policy),
|
|
85
|
+
]);
|
|
86
|
+
return { policy, kind: tag, name: bytes32ToText(name), until, approved: listed && (until === 0n || block < until) };
|
|
77
87
|
}),
|
|
78
88
|
);
|
|
89
|
+
return kind ? all.filter((e) => e.kind === kind) : all;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
/** The registry's owner — the LOG authority — and the proposed next one. */
|
|
@@ -95,19 +106,19 @@ async function registryWrite(c: LogswapClient, functionName: string, args: reado
|
|
|
95
106
|
return write(c, registry, policyRegistryAbi, functionName, args);
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
/** `PolicyRegistry.approve`: list `policy`
|
|
99
|
-
export async function registryApprove(c: LogswapClient,
|
|
100
|
-
return registryWrite(c, "approve", [
|
|
109
|
+
/** `PolicyRegistry.approve`: list `policy` under `name` (at most 31 bytes). Again renames it and clears its sunset. The owner's. */
|
|
110
|
+
export async function registryApprove(c: LogswapClient, policy: Address, name: string): Promise<Hash> {
|
|
111
|
+
return registryWrite(c, "approve", [policy, textToBytes32(name)]);
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
/** `PolicyRegistry.sunset`: close `policy` to new pools
|
|
104
|
-
export async function registrySunset(c: LogswapClient,
|
|
105
|
-
return registryWrite(c, "sunset", [
|
|
114
|
+
/** `PolicyRegistry.sunset`: close `policy` to new pools from `untilBlock` on; 0 reopens. The owner's. */
|
|
115
|
+
export async function registrySunset(c: LogswapClient, policy: Address, untilBlock: bigint): Promise<Hash> {
|
|
116
|
+
return registryWrite(c, "sunset", [policy, untilBlock]);
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
/** `PolicyRegistry.revoke`: remove `policy` from
|
|
109
|
-
export async function registryRevoke(c: LogswapClient,
|
|
110
|
-
return registryWrite(c, "revoke", [
|
|
119
|
+
/** `PolicyRegistry.revoke`: remove `policy` from the list at once. Live pools are untouched. The owner's. */
|
|
120
|
+
export async function registryRevoke(c: LogswapClient, policy: Address): Promise<Hash> {
|
|
121
|
+
return registryWrite(c, "revoke", [policy]);
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
/** The registry owner's two-step handover. */
|
|
@@ -120,19 +131,20 @@ export async function registryAcceptOwner(c: LogswapClient): Promise<Hash> {
|
|
|
120
131
|
|
|
121
132
|
/**
|
|
122
133
|
* Approve or revoke, the F version 15–16 shape kept for callers: `approved` true is
|
|
123
|
-
* {@link registryApprove} under the
|
|
134
|
+
* {@link registryApprove} under the policy's own tag as its name, false is {@link registryRevoke}.
|
|
124
135
|
*/
|
|
125
|
-
export async function setPolicyApproved(c: LogswapClient,
|
|
126
|
-
return approved ? registryApprove(c,
|
|
136
|
+
export async function setPolicyApproved(c: LogswapClient, policy: Address, approved: boolean): Promise<Hash> {
|
|
137
|
+
return approved ? registryApprove(c, policy, await fPoolKindOfPolicy(c, policy)) : registryRevoke(c, policy);
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/**
|
|
130
|
-
* Move a pool to another policy the
|
|
131
|
-
* owner's alone
|
|
132
|
-
* void; no swap lands
|
|
141
|
+
* Move a pool to another approved policy of the same kind (decisions 046, 049): the registry
|
|
142
|
+
* owner's alone, orchestrated on the registry — `migrate(manager, poolId, to)`. The new policy
|
|
143
|
+
* takes the authority and the name from the old; the operator and the lists void; no swap lands
|
|
144
|
+
* in the block.
|
|
133
145
|
*/
|
|
134
146
|
export async function fPoolMigrate(c: LogswapClient, a: { poolId: `0x${string}`; to: Address }): Promise<Hash> {
|
|
135
|
-
return
|
|
147
|
+
return registryWrite(c, "migrate", [c.addresses.fPoolManager as Address, a.poolId, a.to]);
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
// ─── the policies' numbers (the collector's; each bounded where it reaches the manager) ─────
|
|
@@ -156,6 +168,25 @@ export async function polSetRules(c: LogswapClient, policy: Address, s: { keepQu
|
|
|
156
168
|
return write(c, policy, polPolicyAbi, "setRules", [s.keepQuote, s.minQuoteAtSeed]);
|
|
157
169
|
}
|
|
158
170
|
|
|
171
|
+
/** `setQuoteListed` on any protocol policy: a quote a pool of that kind may be born against while it curates quotes. The collector's. */
|
|
172
|
+
export async function policySetQuoteListed(c: LogswapClient, policy: Address, quote: Address, listed: boolean): Promise<Hash> {
|
|
173
|
+
return write(c, policy, fPolicyBaseAbi, "setQuoteListed", [quote, listed]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** `setQuoteCurated` on any protocol policy: on, a birth against an unlisted quote is refused. The collector's. */
|
|
177
|
+
export async function policySetQuoteCurated(c: LogswapClient, policy: Address, curated: boolean): Promise<Hash> {
|
|
178
|
+
return write(c, policy, fPolicyBaseAbi, "setQuoteCurated", [curated]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Whether `quote` passes `policy`'s birth check now: listed, or the policy does not curate quotes. */
|
|
182
|
+
export async function policyQuoteAllowed(c: LogswapClient, policy: Address, quote: Address): Promise<{ curated: boolean; listed: boolean; allowed: boolean }> {
|
|
183
|
+
const [curated, listed] = await Promise.all([
|
|
184
|
+
c.public.readContract({ address: policy, abi: fPolicyBaseAbi, functionName: "quoteCurated" } as never) as Promise<boolean>,
|
|
185
|
+
c.public.readContract({ address: policy, abi: fPolicyBaseAbi, functionName: "quoteListed", args: [quote] } as never) as Promise<boolean>,
|
|
186
|
+
]);
|
|
187
|
+
return { curated, listed, allowed: !curated || listed };
|
|
188
|
+
}
|
|
189
|
+
|
|
159
190
|
/** `PublicPolicy.setListed`: an asset a curated seed may hold. */
|
|
160
191
|
export async function publicSetListed(c: LogswapClient, policy: Address, token: Address, listed: boolean): Promise<Hash> {
|
|
161
192
|
return write(c, policy, publicPolicyAbi, "setListed", [token, listed]);
|
package/src/client.ts
CHANGED
|
@@ -134,7 +134,7 @@ export const EXPECTED_VERSION = 4n;
|
|
|
134
134
|
// is the policy at birth, `migrate` moves a pool for the registry's owner; `stamp` / `setAsks` are
|
|
135
135
|
// the policy's; the desk's `setAllowed` / `isAllowed` are `setMinters` / `setTakers` / `isMinter` /
|
|
136
136
|
// `isTaker`; a pad and a POL hold one base (`OneBase`).
|
|
137
|
-
export const EXPECTED_F_VERSION =
|
|
137
|
+
export const EXPECTED_F_VERSION = 18n;
|
|
138
138
|
|
|
139
139
|
export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSION): Promise<void> {
|
|
140
140
|
const { cPoolManagerAbi } = await import("./generated.js");
|
package/src/fees.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fees.ts — swap fees over a window, from `Swap` logs.
|
|
3
|
+
*
|
|
4
|
+
* `F` cannot answer "what did takers pay this week": it is per unit of L, it carries the mint-fee
|
|
5
|
+
* credits as well as swap fees, and it has no window. Each `Swap` event carries the quote leg and
|
|
6
|
+
* φ, and the fee is a fixed fraction of that leg, so the sum over a window is exact up to rounding:
|
|
7
|
+
*
|
|
8
|
+
* quote in (price up): the curve takes q/(1+φ), the fee is q·φ/(1+φ)
|
|
9
|
+
* quote out (price down): the curve releases q/(1−φ), the fee is q·φ/(1−φ)
|
|
10
|
+
*
|
|
11
|
+
* Both equal φ·L·|Δx| summed over the swap's segments — the same φ·|dx| per unit L that `F` books.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { LogswapClient } from "./client.js";
|
|
15
|
+
import { cPoolManagerAbi } from "./generated.js";
|
|
16
|
+
import type { DiscoveredMarket } from "./pools.js";
|
|
17
|
+
|
|
18
|
+
const WAD = 10n ** 18n;
|
|
19
|
+
const YEAR_SECONDS = 31_536_000;
|
|
20
|
+
|
|
21
|
+
/** The fee one swap paid, in quote, from its `Swap` event's fields. */
|
|
22
|
+
export function swapFeeOf(e: { baseIn: boolean; amountIn: bigint; amountOut: bigint }, phi: bigint): bigint {
|
|
23
|
+
return e.baseIn ? (e.amountOut * phi) / (WAD - phi) : (e.amountIn * phi) / (WAD + phi);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Swap fees paid over a window, in the market's quote units. */
|
|
27
|
+
export interface SwapFeeWindow {
|
|
28
|
+
fromBlock: bigint;
|
|
29
|
+
toBlock: bigint;
|
|
30
|
+
/** Window start, unix seconds — `since`, or the first block's timestamp, never before the market opened. */
|
|
31
|
+
from: bigint;
|
|
32
|
+
/** Window end, unix seconds — the last block's timestamp. */
|
|
33
|
+
to: bigint;
|
|
34
|
+
seconds: bigint;
|
|
35
|
+
swapFees: bigint;
|
|
36
|
+
swaps: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The first block with timestamp ≥ `t`, by bisection over `[lo, hi]`; `hi` when none is earlier. */
|
|
40
|
+
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;
|
|
45
|
+
}
|
|
46
|
+
return lo;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sum the swap fees of market `m` over a window: from `since` (unix seconds) or `fromBlock`, to
|
|
51
|
+
* `toBlock` (default latest). Either start is clamped to the market's opening, so a period longer
|
|
52
|
+
* than the market's life measures its whole life and `seconds` says how long that was.
|
|
53
|
+
*/
|
|
54
|
+
export async function swapFees(
|
|
55
|
+
c: LogswapClient,
|
|
56
|
+
m: DiscoveredMarket,
|
|
57
|
+
opts: { since?: bigint; fromBlock?: bigint; toBlock?: bigint } = {},
|
|
58
|
+
): Promise<SwapFeeWindow> {
|
|
59
|
+
const toBlock = opts.toBlock ?? (await c.public.getBlockNumber());
|
|
60
|
+
const [opened, last] = await Promise.all([
|
|
61
|
+
c.public.getBlock({ blockNumber: m.blockNumber }),
|
|
62
|
+
c.public.getBlock({ blockNumber: toBlock }),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
let fromBlock: bigint;
|
|
66
|
+
let from: bigint;
|
|
67
|
+
if (opts.since !== undefined && opts.since > opened.timestamp) {
|
|
68
|
+
fromBlock = await blockAtOrAfter(c, opts.since, m.blockNumber, toBlock);
|
|
69
|
+
from = opts.since;
|
|
70
|
+
} else if (opts.fromBlock !== undefined && opts.fromBlock > m.blockNumber) {
|
|
71
|
+
fromBlock = opts.fromBlock;
|
|
72
|
+
from = (await c.public.getBlock({ blockNumber: fromBlock })).timestamp;
|
|
73
|
+
} else {
|
|
74
|
+
fromBlock = m.blockNumber;
|
|
75
|
+
from = opened.timestamp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type Ev = Extract<(typeof cPoolManagerAbi)[number], { type: "event"; name: "Swap" }>;
|
|
79
|
+
const ev = cPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "Swap");
|
|
80
|
+
if (!ev) throw new Error("logswap: Swap event missing from the generated ABI");
|
|
81
|
+
|
|
82
|
+
const logs = fromBlock > toBlock
|
|
83
|
+
? []
|
|
84
|
+
: await c.public.getLogs({ address: c.addresses.cPoolManager, event: ev, args: { poolId: m.poolId }, fromBlock, toBlock });
|
|
85
|
+
|
|
86
|
+
let fees = 0n;
|
|
87
|
+
for (const l of logs) fees += swapFeeOf({ baseIn: l.args.baseIn!, amountIn: l.args.amountIn!, amountOut: l.args.amountOut! }, l.args.phi!);
|
|
88
|
+
|
|
89
|
+
const to = last.timestamp;
|
|
90
|
+
return { fromBlock, toBlock, from, to, seconds: to > from ? to - from : 0n, swapFees: fees, swaps: logs.length };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The window's swap fees over the market's CURRENT active L, annualised linearly over the window.
|
|
95
|
+
* A fraction: 0.12 is 12%. Linear: fees are paid out, so none compound.
|
|
96
|
+
*
|
|
97
|
+
* Per unit of L. An uncapped position is worth L·(x − ξ + 1), so its return on capital is this
|
|
98
|
+
* divided by (x − ξ + 1).
|
|
99
|
+
*/
|
|
100
|
+
export function swapFeeApy(w: Pick<SwapFeeWindow, "swapFees" | "seconds">, lActive: bigint): number {
|
|
101
|
+
if (w.seconds <= 0n || lActive <= 0n) return 0;
|
|
102
|
+
return (Number(w.swapFees) / Number(lActive)) * (YEAR_SECONDS / Number(w.seconds));
|
|
103
|
+
}
|