@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.
Files changed (53) hide show
  1. package/README.md +130 -0
  2. package/dist/actions.d.ts +136 -0
  3. package/dist/actions.js +140 -0
  4. package/dist/admin.d.ts +11 -0
  5. package/dist/admin.js +29 -0
  6. package/dist/claims.d.ts +53 -0
  7. package/dist/claims.js +98 -0
  8. package/dist/client.d.ts +68 -0
  9. package/dist/client.js +68 -0
  10. package/dist/fpool.d.ts +416 -0
  11. package/dist/fpool.js +653 -0
  12. package/dist/generated.d.ts +6522 -0
  13. package/dist/generated.js +3755 -0
  14. package/dist/ids.d.ts +42 -0
  15. package/dist/ids.js +72 -0
  16. package/dist/index.d.ts +23 -0
  17. package/dist/index.js +23 -0
  18. package/dist/indexer.d.ts +91 -0
  19. package/dist/indexer.js +197 -0
  20. package/dist/keys.d.ts +40 -0
  21. package/dist/keys.js +50 -0
  22. package/dist/liquidity.d.ts +223 -0
  23. package/dist/liquidity.js +305 -0
  24. package/dist/onboard.d.ts +56 -0
  25. package/dist/onboard.js +188 -0
  26. package/dist/pools.d.ts +182 -0
  27. package/dist/pools.js +322 -0
  28. package/dist/positions.d.ts +164 -0
  29. package/dist/positions.js +197 -0
  30. package/dist/swap.d.ts +68 -0
  31. package/dist/swap.js +91 -0
  32. package/dist/tokencache.d.ts +76 -0
  33. package/dist/tokencache.js +150 -0
  34. package/dist/write.d.ts +86 -0
  35. package/dist/write.js +106 -0
  36. package/package.json +48 -0
  37. package/src/actions.ts +207 -0
  38. package/src/admin.ts +33 -0
  39. package/src/claims.ts +132 -0
  40. package/src/client.ts +104 -0
  41. package/src/fpool.ts +935 -0
  42. package/src/generated.ts +3762 -0
  43. package/src/ids.ts +78 -0
  44. package/src/index.ts +23 -0
  45. package/src/indexer.ts +252 -0
  46. package/src/keys.ts +73 -0
  47. package/src/liquidity.ts +430 -0
  48. package/src/onboard.ts +228 -0
  49. package/src/pools.ts +396 -0
  50. package/src/positions.ts +276 -0
  51. package/src/swap.ts +140 -0
  52. package/src/tokencache.ts +169 -0
  53. package/src/write.ts +134 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * liquidity.ts — providing, editing and exiting a C position.
3
+ *
4
+ * A class is (ξ, μ, L): floor, cap, exposure. One primitive sets all three — `update` — and every
5
+ * edit is a constraint on it. The vocabulary follows the geometry (decisions 009): **the verb
6
+ * names the axis, the direction is a signed rung count**, and only two direction-words survive,
7
+ * because each names a wallet flow the user must see.
8
+ *
9
+ * update(ξ', μ', L') the primitive
10
+ * ├─ move(ξ', μ') L pinned
11
+ * │ ├─ floor(±n) μ pinned · ↑ harvest (receive quote) · ↓ deepen (pay quote)
12
+ * │ ├─ cap(±n) ξ pinned · no direction words
13
+ * │ └─ shift(±n) width pinned — the translation
14
+ * ├─ resize(L') ξ, μ pinned; L' = 0 is the exit
15
+ * └─ reprice(ξ') R pinned: L' = L·e^{ξ' − ξ}, dormant only
16
+ * mint · burn · claim
17
+ *
18
+ * Every edit is ONE action of the router's batch (decisions 010, `actions.ts`), which is what
19
+ * lets it settle in kind, or in base, or in quote: `settleIn` appends the one swap that nets the
20
+ * other side, and the batch moves a single token. `previewEdit` is the lens's `previewUpdate` —
21
+ * the exact flows, mint fee and swept fees included — and is what an editor shows before its one
22
+ * apply.
23
+ */
24
+ import type { Address, Hash } from "viem";
25
+ import type { LogswapClient } from "./client.js";
26
+ import type { PoolKey } from "./keys.js";
27
+ import { type PairBounds } from "./actions.js";
28
+ import { type WriteOptions } from "./write.js";
29
+ export interface MintArgs extends WriteOptions {
30
+ key: PoolKey;
31
+ /** Log-price floor. Need not be on the grid — see the blending note below. */
32
+ targetFloor: bigint;
33
+ cap?: bigint;
34
+ /** Exposure to add. */
35
+ L: bigint;
36
+ maxBaseIn?: bigint;
37
+ maxQuoteIn?: bigint;
38
+ }
39
+ /**
40
+ * Mint at `targetFloor`, blending across the two adjacent rungs when it is off-grid.
41
+ *
42
+ * This is why the floor ladder can be coarse without the UX being coarse: a blend of two adjacent
43
+ * classes IS exactly one position at the weighted-mean floor, so a continuous floor slider works
44
+ * over a discrete ladder. The router settles once per token, not once per rung.
45
+ *
46
+ * **A mint at floor == spot costs zero quote** ($Q(\xi) = 0$), and one above spot is dormant and
47
+ * costs zero quote too — that is what makes single-asset entry possible in both directions.
48
+ */
49
+ export declare function mint(c: LogswapClient, a: MintArgs): Promise<Hash>;
50
+ export interface CreateMarketArgs extends WriteOptions {
51
+ key: PoolKey;
52
+ /** Launch log-price. Bounded by the manager to ±46 (the price domain, decisions 012). */
53
+ x0: bigint;
54
+ /** The backstop's floor — the first mint's rung, permanent for the pool's life. */
55
+ targetFloor: bigint;
56
+ /** The founding exposure; must exceed the manager's `minBackstopL` lock. */
57
+ L: bigint;
58
+ maxBaseIn?: bigint;
59
+ maxQuoteIn?: bigint;
60
+ }
61
+ /**
62
+ * Create a market ATOMICALLY: `initialize` at `x0` plus the backstop-establishing first mint in
63
+ * one transaction — the canonical creation path (decisions 013, security-review H-02). The
64
+ * two-step path let an attacker seat the PERMANENT backstop at a hostile rung between an honest
65
+ * `initialize` and its first mint; here either both land or neither, and a front-run of the whole
66
+ * call reverts it without moving a wei. No cap: the first mint IS the backstop and must be
67
+ * uncapped.
68
+ */
69
+ export declare function createMarket(c: LogswapClient, a: CreateMarketArgs): Promise<Hash>;
70
+ export interface ZapArgs extends WriteOptions {
71
+ key: PoolKey;
72
+ /** Fund entirely from base (true) or entirely from quote (false). */
73
+ fundWithBase: boolean;
74
+ amountIn: bigint;
75
+ targetFloor: bigint;
76
+ cap?: bigint;
77
+ L: bigint;
78
+ maxIn?: bigint;
79
+ }
80
+ /**
81
+ * Fund a mint from a single asset: the router swaps the imbalanced part through the curve, then
82
+ * mints, all inside one lock — the two-action batch `[swapExactIn, mint]`.
83
+ *
84
+ * Routing the imbalanced part through the CURVE is the point, not an implementation detail. A
85
+ * pro-rata deposit of one asset would be a zero-slippage, zero-fee swap at stale spot — a subsidy
86
+ * paid by the incumbent LPs (`logswap-docs` §*Quote-only capital*). Paying the curve removes it.
87
+ */
88
+ export declare function zapIn(c: LogswapClient, a: ZapArgs): Promise<Hash>;
89
+ export interface UpdateArgs extends WriteOptions {
90
+ key: PoolKey;
91
+ fromId: bigint;
92
+ shares: bigint;
93
+ toFloor: bigint;
94
+ toCap?: bigint;
95
+ /** New exposure. `0n` exits the position entirely. */
96
+ newL: bigint;
97
+ maxBaseIn?: bigint;
98
+ maxQuoteIn?: bigint;
99
+ }
100
+ /** The primitive, through the router's named entry: resize and reposition in one call, in kind. */
101
+ export declare function update(c: LogswapClient, a: UpdateArgs): Promise<Hash>;
102
+ export interface MoveArgs extends WriteOptions {
103
+ key: PoolKey;
104
+ fromId: bigint;
105
+ shares: bigint;
106
+ toFloor: bigint;
107
+ toCap?: bigint;
108
+ maxBaseIn?: bigint;
109
+ maxQuoteIn?: bigint;
110
+ }
111
+ /**
112
+ * Reposition at constant exposure — the L-pinned parent of `floor`, `cap` and `shift`; in kind.
113
+ *
114
+ * **Bound it.** The router used to settle this entry with no limit, which meant a reposition that
115
+ * priced differently by inclusion time could pull the whole allowance (security-review2 M-01).
116
+ * The defaults below are unbounded for callers who genuinely want that; pass `maxBaseIn` /
117
+ * `maxQuoteIn` from a preview for anything user-facing.
118
+ */
119
+ export declare function move(c: LogswapClient, a: MoveArgs): Promise<Hash>;
120
+ /** Which token the edit settles in. `kind` moves both legs; `base` / `quote` nets the other side through the curve. */
121
+ export type SettleIn = "kind" | "base" | "quote";
122
+ export interface EditArgs extends WriteOptions, PairBounds {
123
+ key: PoolKey;
124
+ fromId: bigint;
125
+ /** The shares to edit — all of a holding, or part of it. */
126
+ shares: bigint;
127
+ /** Default `kind`. */
128
+ settleIn?: SettleIn;
129
+ }
130
+ export interface EditPreview {
131
+ /** Signed, + = the caller deposits; the mint fee is inside `dQuote`. */
132
+ dBase: bigint;
133
+ dQuote: bigint;
134
+ /** Accrued fees the edit sweeps, delivered in the same settlement. */
135
+ fees: bigint;
136
+ }
137
+ /** The exact flows of an edit before it is sent — the lens's `previewUpdate`. */
138
+ export declare function previewEdit(c: LogswapClient, a: {
139
+ key: PoolKey;
140
+ fromId: bigint;
141
+ holder?: Address;
142
+ shares: bigint;
143
+ toFloor: bigint;
144
+ toCap?: bigint;
145
+ newL: bigint;
146
+ }): Promise<EditPreview>;
147
+ /**
148
+ * The general edit as a batch: `[update]`, plus — when `settleIn` is one token — the one swap that
149
+ * nets the other. The other side's flow is read off the preview: released → sold with an exact-in
150
+ * of `OPEN_DELTA`; owed → bought with an exact-out of `OPEN_DELTA`. The settlement then binds the
151
+ * other token to zero both ways, so a stale preview fails loudly rather than moving it.
152
+ */
153
+ export declare function edit(c: LogswapClient, a: EditArgs & {
154
+ toFloor: bigint;
155
+ toCap?: bigint;
156
+ newL: bigint;
157
+ }): Promise<Hash>;
158
+ /** Move the floor by `rungs` (signed), cap and L pinned. Positive = harvest, negative = deepen. */
159
+ export declare function floor(c: LogswapClient, a: EditArgs & {
160
+ rungs: bigint;
161
+ }): Promise<Hash>;
162
+ /**
163
+ * Raise the floor by `rungs` (default 1) and withdraw PURE QUOTE — the base leg untouched.
164
+ *
165
+ * A burn is in kind: it returns base and quote pro rata, so taking α of the proceeds also takes α
166
+ * of the unsold inventory off the market. Raising the floor instead releases
167
+ *
168
+ * ΔQ = L (ξ' − ξ), ΔR = 0
169
+ *
170
+ * because $R = Le^{-x}$ depends on neither the floor nor the withdrawal. No base moves, the price
171
+ * does not move, and depth is unchanged. **The cost, which a UI must show:** the position stops
172
+ * bidding below ξ'. `settleIn: "base"` swaps the proceeds into base in the same batch.
173
+ */
174
+ export declare function harvest(c: LogswapClient, a: EditArgs & {
175
+ rungs?: bigint;
176
+ }): Promise<Hash>;
177
+ /**
178
+ * Lower the floor by `rungs` (default 1), paying L·Δξ of quote for the deeper bid — harvest's
179
+ * mirror. `settleIn: "base"` pays it in base instead (an exact-out of the quote owed).
180
+ */
181
+ export declare function deepen(c: LogswapClient, a: EditArgs & {
182
+ rungs?: bigint;
183
+ }): Promise<Hash>;
184
+ /** Move the cap by `rungs` (signed), floor and L pinned. Base flows while the class converts; quote once capped out. */
185
+ export declare function cap(c: LogswapClient, a: EditArgs & {
186
+ rungs: bigint;
187
+ }): Promise<Hash>;
188
+ /** Translate the class by `rungs` (signed): floor and cap together, width and L pinned. */
189
+ export declare function shift(c: LogswapClient, a: EditArgs & {
190
+ rungs: bigint;
191
+ }): Promise<Hash>;
192
+ /** Set the exposure to `newL`, floor and cap pinned; both legs pro rata. The mint fee bites on an increase only. */
193
+ export declare function resize(c: LogswapClient, a: EditArgs & {
194
+ newL: bigint;
195
+ }): Promise<Hash>;
196
+ /** Exit `shares` — `resize(0)`. `settleIn: "quote"` (or `"base"`) leaves with one asset. */
197
+ export declare function exit(c: LogswapClient, a: EditArgs): Promise<Hash>;
198
+ /**
199
+ * Re-express a DORMANT class's base at a new trigger price: the base is pinned, so
200
+ * L' = L·e^{ξ' − ξ} and nothing moves but the trigger. `toFloor` must stay above spot.
201
+ *
202
+ * The exponential is evaluated in double precision (~1e-16 relative), which is far inside the
203
+ * wei-rounding of the class it produces; an exact L' is not what a trigger edit is about.
204
+ */
205
+ export declare function reprice(c: LogswapClient, a: EditArgs & {
206
+ toFloor: bigint;
207
+ }): Promise<Hash>;
208
+ /** Quote released by a harvest, exactly: ΔQ = L(ξ' − ξ). Base is untouched. */
209
+ export declare function harvestProceeds(L: bigint, fromFloor: bigint, toFloor: bigint): bigint;
210
+ export interface BurnArgs extends WriteOptions {
211
+ key: PoolKey;
212
+ ids: readonly bigint[];
213
+ shares: readonly bigint[];
214
+ minBaseOut?: bigint;
215
+ minQuoteOut?: bigint;
216
+ }
217
+ /**
218
+ * Exit, in kind, across one or more classes. Accrued fees are swept into the same settlement.
219
+ *
220
+ * In kind means both legs, pro rata. To take proceeds while leaving inventory in the market, that
221
+ * is {@link harvest}; to leave with one asset, {@link exit} with `settleIn`.
222
+ */
223
+ export declare function burn(c: LogswapClient, a: BurnArgs): Promise<Hash>;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * liquidity.ts — providing, editing and exiting a C position.
3
+ *
4
+ * A class is (ξ, μ, L): floor, cap, exposure. One primitive sets all three — `update` — and every
5
+ * edit is a constraint on it. The vocabulary follows the geometry (decisions 009): **the verb
6
+ * names the axis, the direction is a signed rung count**, and only two direction-words survive,
7
+ * because each names a wallet flow the user must see.
8
+ *
9
+ * update(ξ', μ', L') the primitive
10
+ * ├─ move(ξ', μ') L pinned
11
+ * │ ├─ floor(±n) μ pinned · ↑ harvest (receive quote) · ↓ deepen (pay quote)
12
+ * │ ├─ cap(±n) ξ pinned · no direction words
13
+ * │ └─ shift(±n) width pinned — the translation
14
+ * ├─ resize(L') ξ, μ pinned; L' = 0 is the exit
15
+ * └─ reprice(ξ') R pinned: L' = L·e^{ξ' − ξ}, dormant only
16
+ * mint · burn · claim
17
+ *
18
+ * Every edit is ONE action of the router's batch (decisions 010, `actions.ts`), which is what
19
+ * lets it settle in kind, or in base, or in quote: `settleIn` appends the one swap that nets the
20
+ * other side, and the batch moves a single token. `previewEdit` is the lens's `previewUpdate` —
21
+ * the exact flows, mint fee and swept fees included — and is what an editor shows before its one
22
+ * apply.
23
+ */
24
+ import { logswapLensAbi, logswapRouterAbi } from "./generated.js";
25
+ import { NO_CAP } from "./ids.js";
26
+ import { describeId, getPositionClass } from "./positions.js";
27
+ import { execute, OPEN_DELTA, settleInKind, settleOnly, swapExactInAction, swapExactOutAction, updateAction, } from "./actions.js";
28
+ import { requireWallet, resolveDeadline, resolveRecipient, sendRouterWrite, sendRouterWriteWithPermits, } from "./write.js";
29
+ /**
30
+ * Mint at `targetFloor`, blending across the two adjacent rungs when it is off-grid.
31
+ *
32
+ * This is why the floor ladder can be coarse without the UX being coarse: a blend of two adjacent
33
+ * classes IS exactly one position at the weighted-mean floor, so a continuous floor slider works
34
+ * over a discrete ladder. The router settles once per token, not once per rung.
35
+ *
36
+ * **A mint at floor == spot costs zero quote** ($Q(\xi) = 0$), and one above spot is dormant and
37
+ * costs zero quote too — that is what makes single-asset entry possible in both directions.
38
+ */
39
+ export async function mint(c, a) {
40
+ const MAX = (1n << 256n) - 1n;
41
+ return sendRouterWriteWithPermits(c, {
42
+ abi: logswapRouterAbi,
43
+ functionName: "mintBlended",
44
+ args: [
45
+ a.key,
46
+ a.targetFloor,
47
+ a.cap ?? NO_CAP,
48
+ a.L,
49
+ a.maxBaseIn ?? MAX,
50
+ a.maxQuoteIn ?? MAX,
51
+ resolveRecipient(c, a),
52
+ resolveDeadline(a),
53
+ ],
54
+ }, [a.key.base, a.key.quote]);
55
+ }
56
+ /**
57
+ * Create a market ATOMICALLY: `initialize` at `x0` plus the backstop-establishing first mint in
58
+ * one transaction — the canonical creation path (decisions 013, security-review H-02). The
59
+ * two-step path let an attacker seat the PERMANENT backstop at a hostile rung between an honest
60
+ * `initialize` and its first mint; here either both land or neither, and a front-run of the whole
61
+ * call reverts it without moving a wei. No cap: the first mint IS the backstop and must be
62
+ * uncapped.
63
+ */
64
+ export async function createMarket(c, a) {
65
+ const MAX = (1n << 256n) - 1n;
66
+ return sendRouterWriteWithPermits(c, {
67
+ abi: logswapRouterAbi,
68
+ functionName: "createMarket",
69
+ args: [
70
+ a.key,
71
+ a.x0,
72
+ a.targetFloor,
73
+ a.L,
74
+ a.maxBaseIn ?? MAX,
75
+ a.maxQuoteIn ?? MAX,
76
+ resolveRecipient(c, a),
77
+ resolveDeadline(a),
78
+ ],
79
+ }, [a.key.base, a.key.quote]);
80
+ }
81
+ /**
82
+ * Fund a mint from a single asset: the router swaps the imbalanced part through the curve, then
83
+ * mints, all inside one lock — the two-action batch `[swapExactIn, mint]`.
84
+ *
85
+ * Routing the imbalanced part through the CURVE is the point, not an implementation detail. A
86
+ * pro-rata deposit of one asset would be a zero-slippage, zero-fee swap at stale spot — a subsidy
87
+ * paid by the incumbent LPs (`logswap-docs` §*Quote-only capital*). Paying the curve removes it.
88
+ */
89
+ export async function zapIn(c, a) {
90
+ return sendRouterWriteWithPermits(c, {
91
+ abi: logswapRouterAbi,
92
+ functionName: "zapIn",
93
+ args: [
94
+ a.key,
95
+ a.fundWithBase,
96
+ a.amountIn,
97
+ a.targetFloor,
98
+ a.cap ?? NO_CAP,
99
+ a.L,
100
+ a.maxIn ?? (1n << 256n) - 1n,
101
+ resolveRecipient(c, a),
102
+ resolveDeadline(a),
103
+ ],
104
+ }, [a.fundWithBase ? a.key.base : a.key.quote]);
105
+ }
106
+ /** The primitive, through the router's named entry: resize and reposition in one call, in kind. */
107
+ export async function update(c, a) {
108
+ const MAX = (1n << 256n) - 1n;
109
+ return sendRouterWriteWithPermits(c, {
110
+ abi: logswapRouterAbi,
111
+ functionName: "update",
112
+ args: [
113
+ a.key,
114
+ a.fromId,
115
+ a.shares,
116
+ a.toFloor,
117
+ a.toCap ?? NO_CAP,
118
+ a.newL,
119
+ a.maxBaseIn ?? MAX,
120
+ a.maxQuoteIn ?? MAX,
121
+ resolveRecipient(c, a),
122
+ resolveDeadline(a),
123
+ ],
124
+ }, [a.key.base, a.key.quote]);
125
+ }
126
+ /**
127
+ * Reposition at constant exposure — the L-pinned parent of `floor`, `cap` and `shift`; in kind.
128
+ *
129
+ * **Bound it.** The router used to settle this entry with no limit, which meant a reposition that
130
+ * priced differently by inclusion time could pull the whole allowance (security-review2 M-01).
131
+ * The defaults below are unbounded for callers who genuinely want that; pass `maxBaseIn` /
132
+ * `maxQuoteIn` from a preview for anything user-facing.
133
+ */
134
+ export async function move(c, a) {
135
+ const MAX = (1n << 256n) - 1n;
136
+ return sendRouterWrite(c, {
137
+ abi: logswapRouterAbi,
138
+ functionName: "move",
139
+ args: [
140
+ a.key,
141
+ a.fromId,
142
+ a.shares,
143
+ a.toFloor,
144
+ a.toCap ?? NO_CAP,
145
+ a.maxBaseIn ?? MAX,
146
+ a.maxQuoteIn ?? MAX,
147
+ resolveRecipient(c, a),
148
+ resolveDeadline(a),
149
+ ],
150
+ });
151
+ }
152
+ /** The exact flows of an edit before it is sent — the lens's `previewUpdate`. */
153
+ export async function previewEdit(c, a) {
154
+ const holder = a.holder ?? requireWallet(c).address;
155
+ const r = (await c.public.readContract({
156
+ address: c.addresses.lens,
157
+ abi: logswapLensAbi,
158
+ functionName: "previewUpdate",
159
+ args: [a.key, a.fromId, holder, a.shares, a.toFloor, a.toCap ?? NO_CAP, a.newL],
160
+ }));
161
+ return { dBase: r[0], dQuote: r[1], fees: r[2] };
162
+ }
163
+ /**
164
+ * The general edit as a batch: `[update]`, plus — when `settleIn` is one token — the one swap that
165
+ * nets the other. The other side's flow is read off the preview: released → sold with an exact-in
166
+ * of `OPEN_DELTA`; owed → bought with an exact-out of `OPEN_DELTA`. The settlement then binds the
167
+ * other token to zero both ways, so a stale preview fails loudly rather than moving it.
168
+ */
169
+ export async function edit(c, a) {
170
+ const { key } = a;
171
+ const recipient = resolveRecipient(c, a);
172
+ const actions = [
173
+ updateAction({ key, fromId: a.fromId, shares: a.shares, toFloor: a.toFloor, toCap: a.toCap, newL: a.newL }),
174
+ ];
175
+ const settleIn = a.settleIn ?? "kind";
176
+ let settle;
177
+ if (settleIn === "kind") {
178
+ settle = settleInKind(key, recipient, a);
179
+ }
180
+ else {
181
+ const p = await previewEdit(c, { key, fromId: a.fromId, shares: a.shares, toFloor: a.toFloor, toCap: a.toCap, newL: a.newL });
182
+ // the swept fees ride INSIDE dQuote (the manager burns by owner and nets them; decisions 018),
183
+ // so quote's open delta after the update is dQuote itself — `fees` is reported for display only
184
+ const otherFlow = settleIn === "base" ? p.dQuote : p.dBase; // + = owed, − = released
185
+ const otherIsBase = settleIn === "quote";
186
+ if (otherFlow < 0n)
187
+ actions.push(swapExactInAction({ key, baseIn: otherIsBase, amountIn: OPEN_DELTA }));
188
+ else if (otherFlow > 0n)
189
+ actions.push(swapExactOutAction({ key, baseOut: otherIsBase, amountOut: OPEN_DELTA }));
190
+ settle = settleOnly(key, recipient, settleIn, {
191
+ maxIn: settleIn === "base" ? a.maxBaseIn : a.maxQuoteIn,
192
+ minOut: settleIn === "base" ? a.minBaseOut : a.minQuoteOut,
193
+ });
194
+ }
195
+ return execute(c, { actions, settle, deadline: a.deadline, recipient });
196
+ }
197
+ /** The class behind `shares` of `id`: its floor, cap, and the exposure those shares carry. */
198
+ async function classOf(c, key, id, shares) {
199
+ const [d, cls] = await Promise.all([describeId(c, id, key.tickSpacing), getPositionClass(c, id)]);
200
+ if (d.poolId === `0x${"0".repeat(64)}`)
201
+ throw new Error(`logswap: id ${id} was never minted`);
202
+ const L = cls.shares === 0n ? 0n : (cls.L * shares) / cls.shares;
203
+ return { floor: d.floor, cap: d.cap, capped: d.capped, L };
204
+ }
205
+ /** Move the floor by `rungs` (signed), cap and L pinned. Positive = harvest, negative = deepen. */
206
+ export async function floor(c, a) {
207
+ const k = await classOf(c, a.key, a.fromId, a.shares);
208
+ return edit(c, { ...a, toFloor: k.floor + a.rungs * a.key.tickSpacing, toCap: k.capped ? k.cap : undefined, newL: k.L });
209
+ }
210
+ /**
211
+ * Raise the floor by `rungs` (default 1) and withdraw PURE QUOTE — the base leg untouched.
212
+ *
213
+ * A burn is in kind: it returns base and quote pro rata, so taking α of the proceeds also takes α
214
+ * of the unsold inventory off the market. Raising the floor instead releases
215
+ *
216
+ * ΔQ = L (ξ' − ξ), ΔR = 0
217
+ *
218
+ * because $R = Le^{-x}$ depends on neither the floor nor the withdrawal. No base moves, the price
219
+ * does not move, and depth is unchanged. **The cost, which a UI must show:** the position stops
220
+ * bidding below ξ'. `settleIn: "base"` swaps the proceeds into base in the same batch.
221
+ */
222
+ export async function harvest(c, a) {
223
+ const n = a.rungs ?? 1n;
224
+ if (n <= 0n)
225
+ throw new Error("logswap: harvest raises the floor — rungs must be positive; to lower it, deepen()");
226
+ return floor(c, { ...a, rungs: n });
227
+ }
228
+ /**
229
+ * Lower the floor by `rungs` (default 1), paying L·Δξ of quote for the deeper bid — harvest's
230
+ * mirror. `settleIn: "base"` pays it in base instead (an exact-out of the quote owed).
231
+ */
232
+ export async function deepen(c, a) {
233
+ const n = a.rungs ?? 1n;
234
+ if (n <= 0n)
235
+ throw new Error("logswap: deepen lowers the floor — rungs must be positive; to raise it, harvest()");
236
+ return floor(c, { ...a, rungs: -n });
237
+ }
238
+ /** Move the cap by `rungs` (signed), floor and L pinned. Base flows while the class converts; quote once capped out. */
239
+ export async function cap(c, a) {
240
+ const k = await classOf(c, a.key, a.fromId, a.shares);
241
+ if (!k.capped)
242
+ throw new Error("logswap: cap() needs a capped class — an uncapped one has no cap to move");
243
+ const toCap = k.cap + a.rungs * a.key.tickSpacing;
244
+ if (toCap <= k.floor)
245
+ throw new Error("logswap: the cap cannot sit at or below the floor");
246
+ return edit(c, { ...a, toFloor: k.floor, toCap, newL: k.L });
247
+ }
248
+ /** Translate the class by `rungs` (signed): floor and cap together, width and L pinned. */
249
+ export async function shift(c, a) {
250
+ const k = await classOf(c, a.key, a.fromId, a.shares);
251
+ const d = a.rungs * a.key.tickSpacing;
252
+ return edit(c, { ...a, toFloor: k.floor + d, toCap: k.capped ? k.cap + d : undefined, newL: k.L });
253
+ }
254
+ /** Set the exposure to `newL`, floor and cap pinned; both legs pro rata. The mint fee bites on an increase only. */
255
+ export async function resize(c, a) {
256
+ const k = await classOf(c, a.key, a.fromId, a.shares);
257
+ return edit(c, { ...a, toFloor: k.floor, toCap: k.capped ? k.cap : undefined, newL: a.newL });
258
+ }
259
+ /** Exit `shares` — `resize(0)`. `settleIn: "quote"` (or `"base"`) leaves with one asset. */
260
+ export async function exit(c, a) {
261
+ const k = await classOf(c, a.key, a.fromId, a.shares);
262
+ return edit(c, { ...a, toFloor: k.floor, toCap: k.capped ? k.cap : undefined, newL: 0n });
263
+ }
264
+ /**
265
+ * Re-express a DORMANT class's base at a new trigger price: the base is pinned, so
266
+ * L' = L·e^{ξ' − ξ} and nothing moves but the trigger. `toFloor` must stay above spot.
267
+ *
268
+ * The exponential is evaluated in double precision (~1e-16 relative), which is far inside the
269
+ * wei-rounding of the class it produces; an exact L' is not what a trigger edit is about.
270
+ */
271
+ export async function reprice(c, a) {
272
+ const k = await classOf(c, a.key, a.fromId, a.shares);
273
+ const ratio = Math.exp(Number(a.toFloor - k.floor) / 1e18);
274
+ const newL = (k.L * BigInt(Math.round(ratio * 1e18))) / 10n ** 18n;
275
+ return edit(c, { ...a, toFloor: a.toFloor, toCap: k.capped ? k.cap : undefined, newL });
276
+ }
277
+ /** Quote released by a harvest, exactly: ΔQ = L(ξ' − ξ). Base is untouched. */
278
+ export function harvestProceeds(L, fromFloor, toFloor) {
279
+ if (toFloor <= fromFloor)
280
+ return 0n;
281
+ return (L * (toFloor - fromFloor)) / 10n ** 18n;
282
+ }
283
+ /**
284
+ * Exit, in kind, across one or more classes. Accrued fees are swept into the same settlement.
285
+ *
286
+ * In kind means both legs, pro rata. To take proceeds while leaving inventory in the market, that
287
+ * is {@link harvest}; to leave with one asset, {@link exit} with `settleIn`.
288
+ */
289
+ export async function burn(c, a) {
290
+ if (a.ids.length !== a.shares.length)
291
+ throw new Error("logswap: ids and shares length mismatch");
292
+ return sendRouterWrite(c, {
293
+ abi: logswapRouterAbi,
294
+ functionName: "burn",
295
+ args: [
296
+ a.key,
297
+ a.ids,
298
+ a.shares,
299
+ a.minBaseOut ?? 0n,
300
+ a.minQuoteOut ?? 0n,
301
+ resolveRecipient(c, a),
302
+ resolveDeadline(a),
303
+ ],
304
+ });
305
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * onboard.ts — getting a wallet to the point where router writes actually work.
3
+ *
4
+ * There are **three separate approvals** with three separate failure modes, and a UI that surfaces
5
+ * them late produces failed transactions instead of prompts:
6
+ *
7
+ * 1. ERC-20 → Permit2 once per token, forever. Only when the router is Permit2-wired.
8
+ * 2. Permit2 → router a standing allowance, by transaction or by signature.
9
+ * 3. 6909 operator grant once per wallet, covers every market. Needed by burn/update/move.
10
+ *
11
+ * **The trap this module exists for:** when the router is deployed with Permit2, approving the
12
+ * ERC-20 to the *router* is not enough and not used — the pull goes through Permit2, and the
13
+ * failure is `NotAllowed()` from Permit2 rather than anything naming an allowance. Approving the
14
+ * router directly looks correct and does nothing.
15
+ */
16
+ import { type Address, type Hash, type Hex } from "viem";
17
+ import type { LogswapClient } from "./client.js";
18
+ export declare const MAX_UINT256: bigint;
19
+ export declare const MAX_UINT160: bigint;
20
+ export declare const MAX_UINT48: bigint;
21
+ /** Is this deployment's router wired to Permit2? Decides which approval path applies. */
22
+ export declare function usesPermit2(c: LogswapClient): boolean;
23
+ /** The contract that will actually pull tokens: Permit2 when wired, otherwise the router. */
24
+ export declare function spenderFor(c: LogswapClient): Address;
25
+ /** Step 1 — approve the *puller* on an ERC-20. Once per token. */
26
+ export declare function approveToken(c: LogswapClient, token: Address, amount?: bigint): Promise<Hash>;
27
+ /**
28
+ * Step 2 — the standing Permit2 allowance for the router.
29
+ *
30
+ * No-op when the deployment has no Permit2. Prefer the signature flow (`applyPermit2` on the
31
+ * router, bundled through `multicall`) in production; this transaction form is what a test or a
32
+ * simple UI wants.
33
+ */
34
+ export declare function approvePermit2ForRouter(c: LogswapClient, token: Address, amount?: bigint, expiration?: bigint): Promise<Hash | null>;
35
+ /**
36
+ * Both token-side steps in order, skipping any already satisfied.
37
+ *
38
+ * Returns the hashes actually sent, so a UI can report "2 approvals" honestly rather than always
39
+ * prompting twice.
40
+ */
41
+ export declare function onboardToken(c: LogswapClient, token: Address, need: bigint): Promise<Hash[]>;
42
+ /** What still stands between this wallet and a working write. */
43
+ export interface TokenReadiness {
44
+ usesPermit2: boolean;
45
+ /** ERC-20 allowance to whoever pulls (Permit2 when wired, else the router). */
46
+ needsTokenApproval: boolean;
47
+ /** Permit2's standing allowance for the router. Always false when Permit2 is not wired. */
48
+ needsPermit2Approval: boolean;
49
+ }
50
+ export declare function checkTokenReadiness(c: LogswapClient, token: Address, owner: Address, need: bigint): Promise<TokenReadiness>;
51
+ /**
52
+ * For each token whose Permit2→router allowance is missing: read the nonce, have the wallet SIGN
53
+ * a PermitSingle (a signature prompt, not a transaction), and return the encoded `applyPermit2`
54
+ * calls ready to ride in a router multicall. Tokens already allowed produce nothing.
55
+ */
56
+ export declare function permit2SigCalls(c: LogswapClient, tokens: Address[]): Promise<Hex[]>;