@logswap/sdk 0.5.35
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/README.md +130 -0
- package/dist/actions.d.ts +136 -0
- package/dist/actions.js +140 -0
- package/dist/admin.d.ts +11 -0
- package/dist/admin.js +29 -0
- package/dist/claims.d.ts +53 -0
- package/dist/claims.js +98 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +68 -0
- package/dist/fpool.d.ts +416 -0
- package/dist/fpool.js +653 -0
- package/dist/generated.d.ts +6522 -0
- package/dist/generated.js +3755 -0
- package/dist/ids.d.ts +42 -0
- package/dist/ids.js +72 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/indexer.d.ts +91 -0
- package/dist/indexer.js +197 -0
- package/dist/keys.d.ts +40 -0
- package/dist/keys.js +50 -0
- package/dist/liquidity.d.ts +223 -0
- package/dist/liquidity.js +305 -0
- package/dist/onboard.d.ts +56 -0
- package/dist/onboard.js +188 -0
- package/dist/pools.d.ts +182 -0
- package/dist/pools.js +322 -0
- package/dist/positions.d.ts +164 -0
- package/dist/positions.js +197 -0
- package/dist/swap.d.ts +68 -0
- package/dist/swap.js +91 -0
- package/dist/tokencache.d.ts +76 -0
- package/dist/tokencache.js +150 -0
- package/dist/write.d.ts +86 -0
- package/dist/write.js +106 -0
- package/package.json +48 -0
- package/src/actions.ts +207 -0
- package/src/admin.ts +33 -0
- package/src/claims.ts +132 -0
- package/src/client.ts +104 -0
- package/src/fpool.ts +935 -0
- package/src/generated.ts +3762 -0
- package/src/ids.ts +78 -0
- package/src/index.ts +23 -0
- package/src/indexer.ts +252 -0
- package/src/keys.ts +73 -0
- package/src/liquidity.ts +430 -0
- package/src/onboard.ts +228 -0
- package/src/pools.ts +396 -0
- package/src/positions.ts +276 -0
- package/src/swap.ts +140 -0
- package/src/tokencache.ts +169 -0
- package/src/write.ts +134 -0
package/src/actions.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* actions.ts — the router's transaction: a BATCH (decisions 010).
|
|
3
|
+
*
|
|
4
|
+
* `execute(actions, settle)` runs a list of actions — mint, burn, update, claim, swap, path, in any
|
|
5
|
+
* order, across any C markets — under ONE manager lock. Each action accrues deltas on the router
|
|
6
|
+
* and settles nothing; the batch settles once per token in `settle.tokens`, pulling at most
|
|
7
|
+
* `maxIn[i]` from the caller and taking at least `minOut[i]` to the recipient. A touched token
|
|
8
|
+
* left off the list fails the manager's own close (`TokensNotSettled`), so the list is also the
|
|
9
|
+
* caller's statement of which tokens may move.
|
|
10
|
+
*
|
|
11
|
+
* `OPEN_DELTA` as a swap amount means "the router's open delta of that token": what earlier
|
|
12
|
+
* actions left it holding (exact-in, path) or owing (exact-out). It is what lets action i+1
|
|
13
|
+
* consume action i's proceeds — a harvest funds a buy, a burn exits to one asset, a deepen is paid
|
|
14
|
+
* in base — each moving ONE token.
|
|
15
|
+
*
|
|
16
|
+
* Payloads are `abi.encode` of the router's `*Act` structs. Their tuple types are read from the
|
|
17
|
+
* generated ABI (the router's pure `encode*` helpers exist for exactly this), so nothing here is
|
|
18
|
+
* hand-written and a struct change is a `tsc`/vector failure, not a runtime surprise.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { encodeAbiParameters, type Address, type Hash, type Hex } from "viem";
|
|
22
|
+
import { logswapRouterAbi } from "./generated.js";
|
|
23
|
+
import type { LogswapClient } from "./client.js";
|
|
24
|
+
import type { PoolKey } from "./keys.js";
|
|
25
|
+
import { NO_CAP } from "./ids.js";
|
|
26
|
+
import type { Hop } from "./swap.js";
|
|
27
|
+
import { resolveDeadline, resolveRecipient, sendRouterWriteWithPermits, simulateRouterWrite, type WriteOptions } from "./write.js";
|
|
28
|
+
|
|
29
|
+
/** "The router's open delta of that token" — the swap-amount sentinel. `type(uint256).max`. */
|
|
30
|
+
export const OPEN_DELTA = (1n << 256n) - 1n;
|
|
31
|
+
/** No bound. The same word as the sentinel, kept apart because it means something else. */
|
|
32
|
+
export const UNBOUNDED = (1n << 256n) - 1n;
|
|
33
|
+
|
|
34
|
+
/** The router's op tags — `OpType` in `RouterOps.sol`, C actions only (the F ops have their own lock). */
|
|
35
|
+
export enum Op {
|
|
36
|
+
MintBlended = 0,
|
|
37
|
+
Burn = 1,
|
|
38
|
+
SwapExactIn = 2,
|
|
39
|
+
SwapExactOut = 3,
|
|
40
|
+
Claim = 4,
|
|
41
|
+
Update = 5,
|
|
42
|
+
Path = 6,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Action {
|
|
46
|
+
op: Op;
|
|
47
|
+
data: Hex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Settle {
|
|
51
|
+
tokens: readonly Address[];
|
|
52
|
+
maxIn: readonly bigint[];
|
|
53
|
+
minOut: readonly bigint[];
|
|
54
|
+
/** Receives whatever is taken, and the shares the actions mint. */
|
|
55
|
+
recipient: Address;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* payload encoding ========================================================== */
|
|
59
|
+
|
|
60
|
+
function payloadType(encoder: string) {
|
|
61
|
+
const f = (logswapRouterAbi as readonly { type: string; name?: string; inputs?: readonly unknown[] }[]).find(
|
|
62
|
+
(x) => x.type === "function" && x.name === encoder,
|
|
63
|
+
);
|
|
64
|
+
if (!f?.inputs) throw new Error(`logswap: the router ABI has no ${encoder} — SDK and contracts disagree`);
|
|
65
|
+
return f.inputs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const enc = (encoder: string, value: unknown): Hex => encodeAbiParameters(payloadType(encoder) as never, [value] as never);
|
|
69
|
+
|
|
70
|
+
/** Mint exposure `L` at `targetFloor` (off-grid blends the two adjacent rungs), `cap` optional. */
|
|
71
|
+
export function mintAction(a: { key: PoolKey; targetFloor: bigint; cap?: bigint; L: bigint }): Action {
|
|
72
|
+
return { op: Op.MintBlended, data: enc("encodeMint", { key: a.key, targetFloor: a.targetFloor, cap: a.cap ?? NO_CAP, L: a.L }) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Burn `shares[i]` of `ids[i]`; accrued fees are swept into the same batch. Needs the operator grant. */
|
|
76
|
+
export function burnAction(a: { key: PoolKey; ids: readonly bigint[]; shares: readonly bigint[] }): Action {
|
|
77
|
+
if (a.ids.length !== a.shares.length) throw new Error("logswap: ids and shares length mismatch");
|
|
78
|
+
return { op: Op.Burn, data: enc("encodeBurn", { key: a.key, ids: a.ids, shares: a.shares }) };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Exact-in swap. `amountIn` may be `OPEN_DELTA`: the input an earlier action left the router holding. */
|
|
82
|
+
export function swapExactInAction(a: { key: PoolKey; baseIn: boolean; amountIn: bigint; minOut?: bigint }): Action {
|
|
83
|
+
return { op: Op.SwapExactIn, data: enc("encodeSwap", { key: a.key, baseSide: a.baseIn, amount: a.amountIn, limit: a.minOut ?? 0n }) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Exact-out swap. `amountOut` may be `OPEN_DELTA`: the output an earlier action left the router owing. */
|
|
87
|
+
export function swapExactOutAction(a: { key: PoolKey; baseOut: boolean; amountOut: bigint; maxIn?: bigint }): Action {
|
|
88
|
+
return {
|
|
89
|
+
op: Op.SwapExactOut,
|
|
90
|
+
data: enc("encodeSwap", { key: a.key, baseSide: a.baseOut, amount: a.amountOut, limit: a.maxIn ?? UNBOUNDED }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Collect the caller's fees on `ids`; positions untouched. */
|
|
95
|
+
export function claimAction(a: { key: PoolKey; ids: readonly bigint[] }): Action {
|
|
96
|
+
return { op: Op.Claim, data: enc("encodeClaim", { key: a.key, ids: a.ids }) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The general edit — set `shares` of `fromId` to (`toFloor`, `toCap`, `newL`); `newL = 0` exits. */
|
|
100
|
+
export function updateAction(a: {
|
|
101
|
+
key: PoolKey;
|
|
102
|
+
fromId: bigint;
|
|
103
|
+
shares: bigint;
|
|
104
|
+
toFloor: bigint;
|
|
105
|
+
toCap?: bigint;
|
|
106
|
+
newL: bigint;
|
|
107
|
+
}): Action {
|
|
108
|
+
return {
|
|
109
|
+
op: Op.Update,
|
|
110
|
+
data: enc("encodeUpdate", {
|
|
111
|
+
key: a.key,
|
|
112
|
+
fromId: a.fromId,
|
|
113
|
+
shares: a.shares,
|
|
114
|
+
toFloor: a.toFloor,
|
|
115
|
+
toCap: a.toCap ?? NO_CAP,
|
|
116
|
+
newL: a.newL,
|
|
117
|
+
}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Multi-hop exact-in route; `amountIn` may be `OPEN_DELTA`. Intermediate tokens never move. */
|
|
122
|
+
export function pathAction(a: { hops: readonly Hop[]; amountIn: bigint; minOut?: bigint }): Action {
|
|
123
|
+
if (a.hops.length === 0) throw new Error("logswap: empty path");
|
|
124
|
+
return { op: Op.Path, data: enc("encodePath", { hops: a.hops, amountIn: a.amountIn, minOut: a.minOut ?? 0n }) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* settlement builders ======================================================= */
|
|
128
|
+
|
|
129
|
+
/** A settlement over explicit tokens; missing bounds default to unbounded in, zero out. */
|
|
130
|
+
export function settlement(a: {
|
|
131
|
+
tokens: readonly Address[];
|
|
132
|
+
maxIn?: readonly bigint[];
|
|
133
|
+
minOut?: readonly bigint[];
|
|
134
|
+
recipient: Address;
|
|
135
|
+
}): Settle {
|
|
136
|
+
const n = a.tokens.length;
|
|
137
|
+
const maxIn = a.maxIn ?? a.tokens.map(() => UNBOUNDED);
|
|
138
|
+
const minOut = a.minOut ?? a.tokens.map(() => 0n);
|
|
139
|
+
if (maxIn.length !== n || minOut.length !== n) throw new Error("logswap: settlement bounds length mismatch");
|
|
140
|
+
return { tokens: a.tokens, maxIn, minOut, recipient: a.recipient };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface PairBounds {
|
|
144
|
+
maxBaseIn?: bigint;
|
|
145
|
+
maxQuoteIn?: bigint;
|
|
146
|
+
minBaseOut?: bigint;
|
|
147
|
+
minQuoteOut?: bigint;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Settle both of a market's tokens, in kind. */
|
|
151
|
+
export function settleInKind(key: PoolKey, recipient: Address, b: PairBounds = {}): Settle {
|
|
152
|
+
return settlement({
|
|
153
|
+
tokens: [key.base, key.quote],
|
|
154
|
+
maxIn: [b.maxBaseIn ?? UNBOUNDED, b.maxQuoteIn ?? UNBOUNDED],
|
|
155
|
+
minOut: [b.minBaseOut ?? 0n, b.minQuoteOut ?? 0n],
|
|
156
|
+
recipient,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Settle in ONE of a market's tokens: the other is bound to zero both ways, so the batch must have
|
|
162
|
+
* netted it — which is what a trailing `OPEN_DELTA` swap does.
|
|
163
|
+
*/
|
|
164
|
+
export function settleOnly(key: PoolKey, recipient: Address, side: "base" | "quote", b: { maxIn?: bigint; minOut?: bigint } = {}): Settle {
|
|
165
|
+
const base = side === "base";
|
|
166
|
+
return settlement({
|
|
167
|
+
tokens: [key.base, key.quote],
|
|
168
|
+
maxIn: [base ? (b.maxIn ?? UNBOUNDED) : 0n, base ? 0n : (b.maxIn ?? UNBOUNDED)],
|
|
169
|
+
minOut: [base ? (b.minOut ?? 0n) : 0n, base ? 0n : (b.minOut ?? 0n)],
|
|
170
|
+
recipient,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* the batch ================================================================= */
|
|
175
|
+
|
|
176
|
+
export interface ExecuteArgs extends WriteOptions {
|
|
177
|
+
actions: readonly Action[];
|
|
178
|
+
settle: Omit<Settle, "recipient"> & { recipient?: Address };
|
|
179
|
+
/** Tokens the batch may pull, for the Permit2 signatures. Defaults to every token with `maxIn > 0`. */
|
|
180
|
+
pullTokens?: readonly Address[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Run a batch: one lock, one settlement per token, one transaction (Permit2 signatures ride inside). */
|
|
184
|
+
export async function execute(c: LogswapClient, a: ExecuteArgs): Promise<Hash> {
|
|
185
|
+
const settle: Settle = { ...a.settle, recipient: a.settle.recipient ?? resolveRecipient(c, a) };
|
|
186
|
+
const pull = a.pullTokens ?? settle.tokens.filter((_, i) => settle.maxIn[i]! > 0n);
|
|
187
|
+
return sendRouterWriteWithPermits(
|
|
188
|
+
c,
|
|
189
|
+
{ abi: logswapRouterAbi, functionName: "execute", args: [a.actions, settle, resolveDeadline(a)] },
|
|
190
|
+
[...pull],
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Simulate a batch: the per-action returns and the signed flows (+ = paid in), without sending. */
|
|
195
|
+
export async function simulateExecute(
|
|
196
|
+
c: LogswapClient,
|
|
197
|
+
a: ExecuteArgs & { account?: Address },
|
|
198
|
+
): Promise<{ results: readonly Hex[]; flows: readonly bigint[] }> {
|
|
199
|
+
const settle: Settle = { ...a.settle, recipient: a.settle.recipient ?? a.account ?? resolveRecipient(c, a) };
|
|
200
|
+
const [results, flows] = await simulateRouterWrite<readonly [readonly Hex[], readonly bigint[]]>(c, {
|
|
201
|
+
abi: logswapRouterAbi,
|
|
202
|
+
functionName: "execute",
|
|
203
|
+
args: [a.actions, settle, resolveDeadline(a)],
|
|
204
|
+
account: a.account,
|
|
205
|
+
});
|
|
206
|
+
return { results, flows };
|
|
207
|
+
}
|
package/src/admin.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin — the protocol's (feeCollector's) writes: the fee rates, the harvest-fee exemption, and
|
|
3
|
+
* the two-step collector handover, on both managers. Plain writes to the managers; no router.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Address, Hash, Hex } from "viem";
|
|
7
|
+
import type { LogswapClient } from "./client.js";
|
|
8
|
+
import { cPoolManagerAbi, fPoolManagerAbi } from "./generated.js";
|
|
9
|
+
|
|
10
|
+
function wallet(c: LogswapClient) {
|
|
11
|
+
if (!c.wallet) throw new Error("a wallet client is required");
|
|
12
|
+
return c.wallet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function setProtocolFeeCut(c: LogswapClient, cutWad: bigint): Promise<Hash> {
|
|
16
|
+
return wallet(c).writeContract({ address: c.addresses.cPoolManager, abi: cPoolManagerAbi, functionName: "setProtocolFeeCut", args: [cutWad], chain: c.wallet!.chain, account: c.wallet!.account! } as never);
|
|
17
|
+
}
|
|
18
|
+
export async function setFeeParams(c: LogswapClient, mintFeeProtocolCutWad: bigint, harvestFeeWad: bigint): Promise<Hash> {
|
|
19
|
+
return wallet(c).writeContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "setFeeParams", args: [mintFeeProtocolCutWad, harvestFeeWad], chain: c.wallet!.chain, account: c.wallet!.account! } as never);
|
|
20
|
+
}
|
|
21
|
+
export async function setHarvestFeeExempt(c: LogswapClient, poolId: Hex, exempt: boolean): Promise<Hash> {
|
|
22
|
+
return wallet(c).writeContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "setHarvestFeeExempt", args: [poolId, exempt], chain: c.wallet!.chain, account: c.wallet!.account! } as never);
|
|
23
|
+
}
|
|
24
|
+
export async function proposeCollector(c: LogswapClient, side: "C" | "F", next: Address): Promise<Hash> {
|
|
25
|
+
const address = side === "C" ? c.addresses.cPoolManager : (c.addresses.fPoolManager as Address);
|
|
26
|
+
const abi = side === "C" ? cPoolManagerAbi : fPoolManagerAbi;
|
|
27
|
+
return wallet(c).writeContract({ address, abi, functionName: "proposeCollector", args: [next], chain: c.wallet!.chain, account: c.wallet!.account! } as never);
|
|
28
|
+
}
|
|
29
|
+
export async function acceptCollector(c: LogswapClient, side: "C" | "F"): Promise<Hash> {
|
|
30
|
+
const address = side === "C" ? c.addresses.cPoolManager : (c.addresses.fPoolManager as Address);
|
|
31
|
+
const abi = side === "C" ? cPoolManagerAbi : fPoolManagerAbi;
|
|
32
|
+
return wallet(c).writeContract({ address, abi, functionName: "acceptCollector", args: [], chain: c.wallet!.chain, account: c.wallet!.account! } as never);
|
|
33
|
+
}
|
package/src/claims.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claims.ts — fee claims, and the ERC-6909 operator grant every router write depends on.
|
|
3
|
+
*
|
|
4
|
+
* Note the two unrelated meanings of "claim" in this protocol, kept apart here:
|
|
5
|
+
* - a FEE claim — `claimFees`, withdrawing accrued quote while the position stays whole
|
|
6
|
+
* - a TOKEN claim — a bearer IOU (bit-255-clear id) for inventory parked inside the manager
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Address, Hash } from "viem";
|
|
10
|
+
import { cPoolManagerAbi, logswapRouterAbi } from "./generated.js";
|
|
11
|
+
import type { LogswapClient } from "./client.js";
|
|
12
|
+
import type { PoolKey } from "./keys.js";
|
|
13
|
+
import { isPosition } from "./ids.js";
|
|
14
|
+
import { requireWallet, resolveDeadline, resolveRecipient, sendRouterWrite, type WriteOptions } from "./write.js";
|
|
15
|
+
|
|
16
|
+
export interface ClaimFeesArgs extends WriteOptions {
|
|
17
|
+
key: PoolKey;
|
|
18
|
+
ids: readonly bigint[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Claim accrued fees across one or more classes: one transfer, positions untouched.
|
|
23
|
+
*
|
|
24
|
+
* **Every id must be REGISTERED** — one the manager has seen minted. An id for a class that has
|
|
25
|
+
* never existed reverts the whole batch with `WrongPool`, because an unregistered id has
|
|
26
|
+
* `poolId == 0` and cannot be shown to belong to this pool. In a contract holding every market's
|
|
27
|
+
* assets, "cannot be shown to belong" must not be read as "belongs". A class that exists but which
|
|
28
|
+
* the caller does not hold is fine — that is a no-op, not a revert.
|
|
29
|
+
*/
|
|
30
|
+
export async function claimFees(c: LogswapClient, a: ClaimFeesArgs): Promise<Hash> {
|
|
31
|
+
for (const id of a.ids) {
|
|
32
|
+
if (!isPosition(id)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`logswap: id ${id} is a TOKEN claim (bit 255 clear), not a position — it has no fees to claim.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return sendRouterWrite(c, {
|
|
39
|
+
abi: logswapRouterAbi,
|
|
40
|
+
functionName: "claim",
|
|
41
|
+
args: [a.key, a.ids, resolveRecipient(c, a), resolveDeadline(a)],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Grant the router operator rights over your ERC-6909 positions.
|
|
47
|
+
*
|
|
48
|
+
* **Required before any burn, update, move or harvest** — those pull the shares to the router
|
|
49
|
+
* first. Without it they revert on the transfer, which reads as an unrelated balance error. Swaps
|
|
50
|
+
* and mints do not need it.
|
|
51
|
+
*
|
|
52
|
+
* One grant covers every market, because the positions all live in the one manager.
|
|
53
|
+
*/
|
|
54
|
+
export async function approveRouterAsOperator(c: LogswapClient, approved = true): Promise<Hash> {
|
|
55
|
+
const { account } = requireWallet(c);
|
|
56
|
+
return c.wallet!.writeContract({
|
|
57
|
+
address: c.addresses.cPoolManager,
|
|
58
|
+
abi: cPoolManagerAbi,
|
|
59
|
+
functionName: "setOperator",
|
|
60
|
+
args: [c.addresses.router, approved],
|
|
61
|
+
account,
|
|
62
|
+
chain: c.wallet!.chain,
|
|
63
|
+
} as never);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Is the router already an operator for `owner`? Check before prompting for a signature. */
|
|
67
|
+
export async function isRouterApproved(c: LogswapClient, owner: Address): Promise<boolean> {
|
|
68
|
+
return c.public.readContract({
|
|
69
|
+
address: c.addresses.cPoolManager,
|
|
70
|
+
abi: cPoolManagerAbi,
|
|
71
|
+
functionName: "isOperator",
|
|
72
|
+
args: [owner, c.addresses.router],
|
|
73
|
+
}) as Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* What a wallet still has to approve before a given action.
|
|
78
|
+
*
|
|
79
|
+
* Surfacing this up front is the difference between one prompt and a failed transaction: the ERC-20
|
|
80
|
+
* allowances and the 6909 operator grant are separate approvals with separate failure modes.
|
|
81
|
+
*/
|
|
82
|
+
export interface Readiness {
|
|
83
|
+
needsOperator: boolean;
|
|
84
|
+
needsBaseAllowance: boolean;
|
|
85
|
+
needsQuoteAllowance: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function checkReadiness(
|
|
89
|
+
c: LogswapClient,
|
|
90
|
+
key: PoolKey,
|
|
91
|
+
owner: Address,
|
|
92
|
+
need: { base?: bigint; quote?: bigint; positions?: boolean } = {},
|
|
93
|
+
): Promise<Readiness> {
|
|
94
|
+
const erc20 = [
|
|
95
|
+
{
|
|
96
|
+
type: "function",
|
|
97
|
+
name: "allowance",
|
|
98
|
+
stateMutability: "view",
|
|
99
|
+
inputs: [
|
|
100
|
+
{ name: "owner", type: "address" },
|
|
101
|
+
{ name: "spender", type: "address" },
|
|
102
|
+
],
|
|
103
|
+
outputs: [{ type: "uint256" }],
|
|
104
|
+
},
|
|
105
|
+
] as const;
|
|
106
|
+
|
|
107
|
+
const [operator, baseAllow, quoteAllow] = await Promise.all([
|
|
108
|
+
need.positions ? isRouterApproved(c, owner) : Promise.resolve(true),
|
|
109
|
+
need.base
|
|
110
|
+
? (c.public.readContract({
|
|
111
|
+
address: key.base,
|
|
112
|
+
abi: erc20,
|
|
113
|
+
functionName: "allowance",
|
|
114
|
+
args: [owner, c.addresses.router],
|
|
115
|
+
}) as Promise<bigint>)
|
|
116
|
+
: Promise.resolve(0n),
|
|
117
|
+
need.quote
|
|
118
|
+
? (c.public.readContract({
|
|
119
|
+
address: key.quote,
|
|
120
|
+
abi: erc20,
|
|
121
|
+
functionName: "allowance",
|
|
122
|
+
args: [owner, c.addresses.router],
|
|
123
|
+
}) as Promise<bigint>)
|
|
124
|
+
: Promise.resolve(0n),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
needsOperator: !operator,
|
|
129
|
+
needsBaseAllowance: need.base !== undefined && baseAllow < need.base,
|
|
130
|
+
needsQuoteAllowance: need.quote !== undefined && quoteAllow < need.quote,
|
|
131
|
+
};
|
|
132
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* client.ts — the SDK handle: addresses + viem clients.
|
|
3
|
+
*
|
|
4
|
+
* Construct once and pass to every helper. `wallet` is optional: read helpers need only `public`.
|
|
5
|
+
*
|
|
6
|
+
* **Three addresses, permanently.** Not three per market — every market lives in the one manager,
|
|
7
|
+
* and a market is an `initialize(key, x0)` call rather than a deployment. So this is a constant of
|
|
8
|
+
* the protocol, not configuration that grows.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Address, PublicClient, WalletClient } from "viem";
|
|
12
|
+
|
|
13
|
+
export interface LogswapAddresses {
|
|
14
|
+
/** The C singleton — a floor per position, on a tick ladder. ERC-6909 positions and token
|
|
15
|
+
* claims live here. */
|
|
16
|
+
cPoolManager: Address;
|
|
17
|
+
/** User entry for every market: multicall, blended mint, zap, routes, Permit2. */
|
|
18
|
+
router: Address;
|
|
19
|
+
/** Read-only: ladder derivations and revert-quoter previews. */
|
|
20
|
+
lens: Address;
|
|
21
|
+
/**
|
|
22
|
+
* The F-pool singleton — fungible claims, one shared floor, n bases. Optional only because a
|
|
23
|
+
* deployment predating it will not carry the address; every F-pool call needs it.
|
|
24
|
+
*/
|
|
25
|
+
fPoolManager?: Address;
|
|
26
|
+
/** Optional — the router falls back to plain approvals when absent. */
|
|
27
|
+
permit2?: Address;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LogswapClient {
|
|
31
|
+
public: PublicClient;
|
|
32
|
+
wallet?: WalletClient;
|
|
33
|
+
addresses: LogswapAddresses;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createLogswapClient(args: {
|
|
37
|
+
public: PublicClient;
|
|
38
|
+
wallet?: WalletClient;
|
|
39
|
+
addresses: LogswapAddresses;
|
|
40
|
+
}): LogswapClient {
|
|
41
|
+
return { public: args.public, wallet: args.wallet, addresses: args.addresses };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Assert the deployment matches the ABI this build was generated against.
|
|
46
|
+
*
|
|
47
|
+
* Cheap insurance worth taking at construction: without it, a manager from a different deployment
|
|
48
|
+
* decodes into plausible-looking garbage rather than failing. `version` is bumped on any tier-1 or
|
|
49
|
+
* tier-2 change (docs/app.md).
|
|
50
|
+
*
|
|
51
|
+
* **2** since 2026-09-01. It sat at 1 across a read surface that changed underneath it — the nine
|
|
52
|
+
* `public constant` getters left the manager for `CParams`, `move` gained its settlement bounds,
|
|
53
|
+
* `createMarket` arrived — so a 1 answered for two incompatible ABIs and this check passed against
|
|
54
|
+
* a manager it should have rejected. Observed, not theorised: a local chain running a pre-trim
|
|
55
|
+
* manager sailed through `assertVersion` while `FEE_MAX()` still answered on it. Move this in the
|
|
56
|
+
* same change as `CParams.VERSION`, never after it.
|
|
57
|
+
*
|
|
58
|
+
* **3** since 2026-09-03 (contracts `d8406fb`): `burnById` and `update` take an `owner`, `update`
|
|
59
|
+
* mints to `to`, `positions()` returns `aps` as uint128 — and a returned quote flow now carries the
|
|
60
|
+
* swept fees (decisions 018), which is why `exit`'s settle-to-one-asset math changed with it.
|
|
61
|
+
*/
|
|
62
|
+
export const EXPECTED_VERSION = 3n;
|
|
63
|
+
/**
|
|
64
|
+
* The F manager's own tag (contracts 25a7bcf: 5 — the key's salt, pool names, the sponsor registry;
|
|
65
|
+
* 4 was the income-only harvest cut).
|
|
66
|
+
*
|
|
67
|
+
* A FLOOR, not an equality — unlike `EXPECTED_VERSION` above. The F primitive is iterating fast and
|
|
68
|
+
* its bumps are mostly additive (6 is `multicall`, which nothing this SDK calls goes near), so an
|
|
69
|
+
* exact match meant each one bricked every app on the deployment until the SDK was republished — a
|
|
70
|
+
* version wall in front of a change that broke nothing. The cost is that a genuinely breaking bump
|
|
71
|
+
* passes it; when one lands, raise this constant in the same change, which is the discipline
|
|
72
|
+
* `EXPECTED_VERSION` enforces mechanically and this one asks for.
|
|
73
|
+
*/
|
|
74
|
+
export const EXPECTED_F_VERSION = 5n;
|
|
75
|
+
|
|
76
|
+
export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSION): Promise<void> {
|
|
77
|
+
const { cPoolManagerAbi } = await import("./generated.js");
|
|
78
|
+
const got = await c.public.readContract({
|
|
79
|
+
address: c.addresses.cPoolManager,
|
|
80
|
+
abi: cPoolManagerAbi,
|
|
81
|
+
functionName: "version",
|
|
82
|
+
});
|
|
83
|
+
if (got !== expected) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`logswap: manager at ${c.addresses.cPoolManager} reports version ${got}, but this SDK was built ` +
|
|
86
|
+
`against version ${expected}. Upgrade the SDK or point at the matching deployment.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
// the F manager carries its own tag since contracts c782811; assert it the same way when deployed
|
|
90
|
+
if (c.addresses.fPoolManager) {
|
|
91
|
+
const { fPoolManagerAbi } = await import("./generated.js");
|
|
92
|
+
const gotF = await c.public.readContract({
|
|
93
|
+
address: c.addresses.fPoolManager,
|
|
94
|
+
abi: fPoolManagerAbi,
|
|
95
|
+
functionName: "version",
|
|
96
|
+
});
|
|
97
|
+
if (gotF < EXPECTED_F_VERSION) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`logswap: F manager at ${c.addresses.fPoolManager} reports version ${gotF}, but this SDK needs ` +
|
|
100
|
+
`at least F version ${EXPECTED_F_VERSION}. Upgrade the deployment or point at a newer one.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|