@elmntl/jlpd-sdk 0.13.5 → 0.13.7

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.
@@ -60,6 +60,14 @@ export interface FluidTick {
60
60
  * program — byte-identical to the on-chain `derive_fluid_tick_pda` (golden-vector tested).
61
61
  */
62
62
  export declare function deriveFluidTickPda(vaultId: number, tick: number): PublicKey;
63
+ /**
64
+ * Net RAW debt of a decoded position: tick-implied minus dust — the `net_debt_raw` the deployed
65
+ * partial-repay acceptance predicate is evaluated against (hedge_repay.rs). Supply-only positions
66
+ * read at the COLD tick and therefore return 0. Dust exceeding the tick-implied debt means the
67
+ * decoded (tick, collateral, dust) triple is inconsistent — a STRUCTURAL error the caller must
68
+ * treat as terminal, never as "no debt".
69
+ */
70
+ export declare function fluidNetDebtRaw(position: FluidPosition): bigint;
63
71
  /**
64
72
  * The Tick account settle_yield's per-slot triple must carry for `position`: the canonical
65
73
  * `Tick(vaultId, position.tick)` for an ACTIVE debt position, or the `Tick(vaultId, MIN_TICK)`
@@ -20,6 +20,7 @@ exports.FLUID_ZERO_TICK_SCALED_RATIO = exports.JUP_LEND_EARN_MAX_SANE_RATE = exp
20
20
  exports.getRatioAtTick = getRatioAtTick;
21
21
  exports.decodeFluidPosition = decodeFluidPosition;
22
22
  exports.deriveFluidTickPda = deriveFluidTickPda;
23
+ exports.fluidNetDebtRaw = fluidNetDebtRaw;
23
24
  exports.settleTickPdaForPosition = settleTickPdaForPosition;
24
25
  exports.decodeFluidTick = decodeFluidTick;
25
26
  exports.requireFluidPositionLiquidationCurrent = requireFluidPositionLiquidationCurrent;
@@ -296,6 +297,21 @@ function deriveFluidTickPda(vaultId, tick) {
296
297
  normalizedLe.writeInt32LE(tick - FLUID_MIN_TICK);
297
298
  return web3_js_1.PublicKey.findProgramAddressSync([TICK_SEED, vaultIdLe, normalizedLe], constants_1.JUP_LEND_BORROW_PROGRAM_ID)[0];
298
299
  }
300
+ /**
301
+ * Net RAW debt of a decoded position: tick-implied minus dust — the `net_debt_raw` the deployed
302
+ * partial-repay acceptance predicate is evaluated against (hedge_repay.rs). Supply-only positions
303
+ * read at the COLD tick and therefore return 0. Dust exceeding the tick-implied debt means the
304
+ * decoded (tick, collateral, dust) triple is inconsistent — a STRUCTURAL error the caller must
305
+ * treat as terminal, never as "no debt".
306
+ */
307
+ function fluidNetDebtRaw(position) {
308
+ const tick = position.isSupplyOnly ? FLUID_COLD_TICK : position.tick;
309
+ const tickDebt = tickImpliedDebtRaw(tick, position.collateralRaw);
310
+ if (position.dustDebtRaw > tickDebt) {
311
+ throw new Error(`fluidNetDebtRaw: dust ${position.dustDebtRaw} exceeds tick-implied debt ${tickDebt} — inconsistent position`);
312
+ }
313
+ return tickDebt - position.dustDebtRaw;
314
+ }
299
315
  /**
300
316
  * The Tick account settle_yield's per-slot triple must carry for `position`: the canonical
301
317
  * `Tick(vaultId, position.tick)` for an ACTIVE debt position, or the `Tick(vaultId, MIN_TICK)`
@@ -5,12 +5,12 @@
5
5
  * `hedge-state.ts` that answers the same questions the on-chain guards answer:
6
6
  * - per-slot LTV, valued on that slot's OWN pinned Fluid oracle (matches `hedge_borrow`'s /
7
7
  * `hedge_withdraw_collateral`'s post-op guard basis exactly — see `fluid-view.ts`)
8
- * - pledged collateral summed across every ENABLED slot vs `HedgeState.maxPledgedCollateralJlp`
9
- * - swap-churn budget remaining (`maxSwapNotionalPerSettle - swapNotionalSinceSettle`)
8
+ * - each slot's pledged collateral vs its `HedgeState.maxPledgedBps` share of total controlled JLP
10
9
  */
11
10
  import { PublicKey } from "@solana/web3.js";
12
11
  import type { SolanaConnection } from "../common/connection";
13
12
  import { type HedgeState, type HedgePositionSlot } from "./hedge-state";
13
+ import { type FluidPosition } from "./fluid-view";
14
14
  /**
15
15
  * Native decimals for each known hedge debt mint — mirrors `utils/oracle.rs`'s
16
16
  * `TOKEN_DECIMALS_{BTC,ETH,SOL,JUPUSD}` constants (the four mints
@@ -27,13 +27,13 @@ export declare function resolveDebtManifestAta(state: HedgeState, debtMint: Publ
27
27
  /** Sum already-decoded per-slot collateral (pure, never throws — for read-only dashboards). */
28
28
  export declare function sumPledgedCollateral(perSlotCollateralJlp: bigint[]): bigint;
29
29
  /**
30
- * Sum already-decoded per-slot collateral and enforce the total against
31
- * `HedgeState.maxPledgedCollateralJlp` mirrors `sum_and_check_pledged_collateral` exactly
32
- * (checked addition; throws rather than wrapping past the cap). For DRIVER preflight (reject
33
- * before submitting a tx that would revert on-chain); dashboards should use
34
- * `sumPledgedCollateral` instead so an (unexpected) over-cap reading still renders.
30
+ * Mirror of `check_pledged_bps`: for each slot i, `collateral_i * 10000 <= maxPledgedBps[i] *
31
+ * (vaultJlpFree + Σ pledged)`. `perSlotCollateralJlp` is indexed BY SLOT (0 for a disabled/absent
32
+ * slot). Returns total pledged; throws on the first slot over its bps share. For DRIVER preflight
33
+ * (reject before a tx that would revert); dashboards should use `sumPledgedCollateral` instead so an
34
+ * (unexpected) over-cap reading still renders.
35
35
  */
36
- export declare function sumAndCheckPledgedCollateral(perSlotCollateralJlp: bigint[], maxPledgedCollateralJlp: bigint): bigint;
36
+ export declare function checkPledgedBps(perSlotCollateralJlp: bigint[], maxPledgedBps: number[], vaultJlpFree: bigint): bigint;
37
37
  /**
38
38
  * `hedge_repay` repay-all sentinel (u64::MAX) — the only mode that guarantees `debt == 0`.
39
39
  * Fund the debt ATA to the on-chain budget (ceiled reading + 0.1% + 2 units) and crank the
@@ -63,6 +63,43 @@ export declare function fluidPartialRepayCreditRaw(xNative: bigint, debtDecimals
63
63
  * minimum position debt). `netRawDebt` = tick-implied − dust (fluid-view's `netDebtRaw` basis).
64
64
  */
65
65
  export declare function isPartialRepayAcceptable(xNative: bigint, netRawDebt: bigint, debtDecimals: number, borrowExPrice: bigint): boolean;
66
+ export interface FluidPartialRepayVerdict {
67
+ accepted: boolean;
68
+ /** Human reason when refused; null when accepted. */
69
+ reason: string | null;
70
+ creditRaw: bigint | null;
71
+ netRaw: bigint;
72
+ minDebtRaw: bigint;
73
+ }
74
+ /**
75
+ * The DEPLOYED acceptance predicate for a partial repay of `x`, evaluated from a decoded
76
+ * position, extended with the driver's balance precondition (Fluid pulls `x + 1` on payback):
77
+ *
78
+ * x ≥ MIN_OPERATE ∧ C(x) ≥ 1 ∧ netRaw ≥ C(x) + minDebtRaw ∧ debtAtaBalance ≥ x + 1
79
+ *
80
+ * An EXACT payoff must use the repay-all sentinel — the residual bound enforces that. Throws only
81
+ * on an INCONSISTENT position (dust > tick-implied), which is structural and terminal.
82
+ */
83
+ export declare function evaluateFluidPartialRepay(args: {
84
+ xNative: bigint;
85
+ position: FluidPosition;
86
+ borrowExPrice: bigint;
87
+ debtDecimals: number;
88
+ debtAtaBalance: bigint;
89
+ }): FluidPartialRepayVerdict;
90
+ /**
91
+ * Largest acceptable partial repay ≤ `desiredNative` given the position AND the debt-ATA balance
92
+ * (Fluid's `x + 1` over-pull). Built on `maxPartialRepayNative`'s invert-and-verify, then
93
+ * re-verified through the full predicate so the ATA bound participates. Null = no acceptable
94
+ * partial exists — the caller escalates to repay-all (if fundable) or reports top-up-required.
95
+ */
96
+ export declare function maxAcceptedFluidPartialRepay(args: {
97
+ desiredNative: bigint;
98
+ position: FluidPosition;
99
+ borrowExPrice: bigint;
100
+ debtDecimals: number;
101
+ debtAtaBalance: bigint;
102
+ }): bigint | null;
66
103
  /**
67
104
  * Largest partial repay (native) the on-chain acceptance admits for `netRawDebt` at the given
68
105
  * stored price — the driver's sizing cap (anything larger reverts; anything at/below is safe at
@@ -70,18 +107,6 @@ export declare function isPartialRepayAcceptable(xNative: bigint, netRawDebt: bi
70
107
  * use the repay-all sentinel instead).
71
108
  */
72
109
  export declare function maxPartialRepayNative(netRawDebt: bigint, debtDecimals: number, borrowExPrice: bigint): bigint;
73
- /**
74
- * Swap-churn budget remaining before the next `settle_yield` resets the accumulator — mirrors
75
- * `HedgeState.maxSwapNotionalPerSettle - HedgeState.swapNotionalSinceSettle` (USD, 6-dec),
76
- * floored at 0 (the accumulator should never on-chain exceed the cap, but this stays
77
- * fail-safe against a stale/racing read).
78
- */
79
- export declare function churnBudgetRemainingUsd(state: Pick<HedgeState, "maxSwapNotionalPerSettle" | "swapNotionalSinceSettle">): bigint;
80
- /**
81
- * `HedgeState.maxPledgedCollateralJlp` as a bigint — headroom convenience: pass the result of
82
- * `sumAndCheckPledgedCollateral`'s successful total (or a raw sum) to get remaining pledge room.
83
- */
84
- export declare function pledgeHeadroomJlp(state: Pick<HedgeState, "maxPledgedCollateralJlp">, totalPledgedJlp: bigint): bigint;
85
110
  /** One enabled `HedgeState.positions` slot's derived valuation. */
86
111
  export interface HedgeSlotDerived {
87
112
  slotIndex: number;
@@ -109,27 +134,33 @@ export declare function fetchHedgeSlotDerived(connection: SolanaConnection, stat
109
134
  */
110
135
  export declare function fetchAllHedgeSlotsDerived(connection: SolanaConnection, state: HedgeState): Promise<HedgeSlotDerived[]>;
111
136
  /**
112
- * Portfolio-level hedge exposure: total pledged JLP collateral across every enabled slot (vs
113
- * `HedgeState.maxPledgedCollateralJlp`), and per-debt-mint net debt exposure (native units,
114
- * keyed by the debt mint's base58 — different debt mints are never summed together, since
137
+ * Portfolio-level hedge exposure: total pledged JLP collateral, each slot's pledged SHARE of total
138
+ * controlled JLP (bps, vs `HedgeState.maxPledgedBps`), and per-debt-mint net debt exposure (native
139
+ * units, keyed by the debt mint's base58 — different debt mints are never summed together, since
115
140
  * they are different tokens).
116
141
  */
117
142
  export interface HedgeExposure {
118
143
  /** Σ `collateralJlpNative` across every enabled slot. */
119
144
  totalPledgedCollateralJlp: bigint;
120
- /** `HedgeState.maxPledgedCollateralJlp - totalPledgedCollateralJlp`, floored at 0. */
121
- pledgeHeadroomJlp: bigint;
145
+ /** Per-slot (indexed by slot) pledged share of total controlled JLP (free vault JLP + Σ pledged), bps. */
146
+ pledgedShareBps: number[];
122
147
  /**
123
- * `totalPledgedCollateralJlp > HedgeState.maxPledgedCollateralJlp` — should never be true given
124
- * the on-chain guard (`hedge_supply_collateral` re-scans + rejects this every supply), but
125
- * surfaced rather than thrown so a stale/racing read still renders on a dashboard.
148
+ * True if any slot's share exceeds its `HedgeState.maxPledgedBps[i]` — should never be true given
149
+ * the on-chain admission guard (`hedge_supply_collateral` rejects it every supply), but surfaced
150
+ * rather than thrown so a stale/racing read still renders on a dashboard.
126
151
  */
127
152
  pledgeCapExceeded: boolean;
128
153
  /** Per-debt-mint (base58) → summed native debt across every slot borrowing that mint. */
129
154
  debtByMint: Record<string, bigint>;
130
- /** Swap-churn budget remaining (USD, 6-dec). */
131
- churnBudgetRemainingUsd: bigint;
132
155
  slots: HedgeSlotDerived[];
133
156
  }
134
- /** Compute portfolio-level hedge exposure from a fully-fetched `HedgeState`. */
135
- export declare function fetchHedgeExposure(connection: SolanaConnection, state: HedgeState): Promise<HedgeExposure>;
157
+ /**
158
+ * Compute portfolio-level hedge exposure. `vaultJlpFree` is the config-owned vault JLP ATA balance
159
+ * (base units) — the free-JLP term of the pledge-cap denominator; fetch it alongside the state.
160
+ *
161
+ * NOTE: this reads each position's stored collateral WITHOUT applying the Fluid
162
+ * liquidation-staleness gate (`requireFluidPositionLiquidationCurrent`), so during a
163
+ * liquidation-stale window a slot's collateral (and thus `pledgedShareBps` / `pledgeCapExceeded`)
164
+ * can be phantom. Best-effort for dashboards; the on-chain guards are authoritative.
165
+ */
166
+ export declare function fetchHedgeExposure(connection: SolanaConnection, state: HedgeState, vaultJlpFree: bigint): Promise<HedgeExposure>;
@@ -6,22 +6,21 @@
6
6
  * `hedge-state.ts` that answers the same questions the on-chain guards answer:
7
7
  * - per-slot LTV, valued on that slot's OWN pinned Fluid oracle (matches `hedge_borrow`'s /
8
8
  * `hedge_withdraw_collateral`'s post-op guard basis exactly — see `fluid-view.ts`)
9
- * - pledged collateral summed across every ENABLED slot vs `HedgeState.maxPledgedCollateralJlp`
10
- * - swap-churn budget remaining (`maxSwapNotionalPerSettle - swapNotionalSinceSettle`)
9
+ * - each slot's pledged collateral vs its `HedgeState.maxPledgedBps` share of total controlled JLP
11
10
  */
12
11
  Object.defineProperty(exports, "__esModule", { value: true });
13
12
  exports.FLUID_MIN_OPERATE_NATIVE = exports.REPAY_ALL_SENTINEL = void 0;
14
13
  exports.debtMintDecimals = debtMintDecimals;
15
14
  exports.resolveDebtManifestAta = resolveDebtManifestAta;
16
15
  exports.sumPledgedCollateral = sumPledgedCollateral;
17
- exports.sumAndCheckPledgedCollateral = sumAndCheckPledgedCollateral;
16
+ exports.checkPledgedBps = checkPledgedBps;
18
17
  exports.clampRepayAmount = clampRepayAmount;
19
18
  exports.fluidMinDebtRaw = fluidMinDebtRaw;
20
19
  exports.fluidPartialRepayCreditRaw = fluidPartialRepayCreditRaw;
21
20
  exports.isPartialRepayAcceptable = isPartialRepayAcceptable;
21
+ exports.evaluateFluidPartialRepay = evaluateFluidPartialRepay;
22
+ exports.maxAcceptedFluidPartialRepay = maxAcceptedFluidPartialRepay;
22
23
  exports.maxPartialRepayNative = maxPartialRepayNative;
23
- exports.churnBudgetRemainingUsd = churnBudgetRemainingUsd;
24
- exports.pledgeHeadroomJlp = pledgeHeadroomJlp;
25
24
  exports.fetchHedgeSlotDerived = fetchHedgeSlotDerived;
26
25
  exports.fetchAllHedgeSlotsDerived = fetchAllHedgeSlotsDerived;
27
26
  exports.fetchHedgeExposure = fetchHedgeExposure;
@@ -73,18 +72,33 @@ function sumPledgedCollateral(perSlotCollateralJlp) {
73
72
  return total;
74
73
  }
75
74
  /**
76
- * Sum already-decoded per-slot collateral and enforce the total against
77
- * `HedgeState.maxPledgedCollateralJlp` mirrors `sum_and_check_pledged_collateral` exactly
78
- * (checked addition; throws rather than wrapping past the cap). For DRIVER preflight (reject
79
- * before submitting a tx that would revert on-chain); dashboards should use
80
- * `sumPledgedCollateral` instead so an (unexpected) over-cap reading still renders.
75
+ * Mirror of `check_pledged_bps`: for each slot i, `collateral_i * 10000 <= maxPledgedBps[i] *
76
+ * (vaultJlpFree + Σ pledged)`. `perSlotCollateralJlp` is indexed BY SLOT (0 for a disabled/absent
77
+ * slot). Returns total pledged; throws on the first slot over its bps share. For DRIVER preflight
78
+ * (reject before a tx that would revert); dashboards should use `sumPledgedCollateral` instead so an
79
+ * (unexpected) over-cap reading still renders.
81
80
  */
82
- function sumAndCheckPledgedCollateral(perSlotCollateralJlp, maxPledgedCollateralJlp) {
83
- const total = sumPledgedCollateral(perSlotCollateralJlp);
84
- if (total > maxPledgedCollateralJlp) {
85
- throw new Error(`sumAndCheckPledgedCollateral: total pledged ${total} exceeds cap ${maxPledgedCollateralJlp}`);
81
+ function checkPledgedBps(perSlotCollateralJlp, maxPledgedBps, vaultJlpFree) {
82
+ // Mirror the program's fixed [_; MAX_HEDGE_POSITIONS] arrays: validate EVERY slot's bps, so a
83
+ // short array can't skip an out-of-range later entry the way the on-chain check never would.
84
+ if (perSlotCollateralJlp.length !== constants_1.MAX_HEDGE_POSITIONS ||
85
+ maxPledgedBps.length !== constants_1.MAX_HEDGE_POSITIONS) {
86
+ throw new Error(`checkPledgedBps: expected ${constants_1.MAX_HEDGE_POSITIONS} slot entries, got ` +
87
+ `collateral=${perSlotCollateralJlp.length} bps=${maxPledgedBps.length}`);
86
88
  }
87
- return total;
89
+ const totalPledged = sumPledgedCollateral(perSlotCollateralJlp);
90
+ const totalControlled = vaultJlpFree + totalPledged;
91
+ for (let i = 0; i < perSlotCollateralJlp.length; i++) {
92
+ const bps = maxPledgedBps[i] ?? 0;
93
+ if (!Number.isInteger(bps) || bps < 0 || bps > 10000) {
94
+ throw new Error(`checkPledgedBps: maxPledgedBps[${i}] out of range: ${bps}`);
95
+ }
96
+ if (perSlotCollateralJlp[i] * 10000n > BigInt(bps) * totalControlled) {
97
+ throw new Error(`checkPledgedBps: slot ${i} pledged ${perSlotCollateralJlp[i]} exceeds ${bps}bps of ` +
98
+ `controlled JLP ${totalControlled}`);
99
+ }
100
+ }
101
+ return totalPledged;
88
102
  }
89
103
  /**
90
104
  * `hedge_repay` repay-all sentinel (u64::MAX) — the only mode that guarantees `debt == 0`.
@@ -145,6 +159,70 @@ function isPartialRepayAcceptable(xNative, netRawDebt, debtDecimals, borrowExPri
145
159
  }
146
160
  return credit >= 1n && credit + fluidMinDebtRaw(debtDecimals) <= netRawDebt;
147
161
  }
162
+ /**
163
+ * The DEPLOYED acceptance predicate for a partial repay of `x`, evaluated from a decoded
164
+ * position, extended with the driver's balance precondition (Fluid pulls `x + 1` on payback):
165
+ *
166
+ * x ≥ MIN_OPERATE ∧ C(x) ≥ 1 ∧ netRaw ≥ C(x) + minDebtRaw ∧ debtAtaBalance ≥ x + 1
167
+ *
168
+ * An EXACT payoff must use the repay-all sentinel — the residual bound enforces that. Throws only
169
+ * on an INCONSISTENT position (dust > tick-implied), which is structural and terminal.
170
+ */
171
+ function evaluateFluidPartialRepay(args) {
172
+ const netRaw = (0, fluid_view_1.fluidNetDebtRaw)(args.position);
173
+ const minDebtRaw = fluidMinDebtRaw(args.debtDecimals);
174
+ const base = { netRaw, minDebtRaw };
175
+ if (args.xNative < exports.FLUID_MIN_OPERATE_NATIVE) {
176
+ return { accepted: false, reason: `x ${args.xNative} < Fluid minimum ${exports.FLUID_MIN_OPERATE_NATIVE}`, creditRaw: null, ...base };
177
+ }
178
+ if (args.debtAtaBalance < args.xNative + 1n) {
179
+ return {
180
+ accepted: false,
181
+ reason: `debt ATA ${args.debtAtaBalance} < x+1 (${args.xNative + 1n}) — Fluid over-pulls one native unit`,
182
+ creditRaw: null,
183
+ ...base,
184
+ };
185
+ }
186
+ let creditRaw;
187
+ try {
188
+ creditRaw = fluidPartialRepayCreditRaw(args.xNative, args.debtDecimals, args.borrowExPrice);
189
+ }
190
+ catch (e) {
191
+ return { accepted: false, reason: e instanceof Error ? e.message : String(e), creditRaw: null, ...base };
192
+ }
193
+ if (creditRaw < 1n || netRaw < creditRaw + minDebtRaw) {
194
+ return {
195
+ accepted: false,
196
+ reason: `residual bound: net ${netRaw} < credit ${creditRaw} + min ${minDebtRaw}`,
197
+ creditRaw,
198
+ ...base,
199
+ };
200
+ }
201
+ return { accepted: true, reason: null, creditRaw, ...base };
202
+ }
203
+ /**
204
+ * Largest acceptable partial repay ≤ `desiredNative` given the position AND the debt-ATA balance
205
+ * (Fluid's `x + 1` over-pull). Built on `maxPartialRepayNative`'s invert-and-verify, then
206
+ * re-verified through the full predicate so the ATA bound participates. Null = no acceptable
207
+ * partial exists — the caller escalates to repay-all (if fundable) or reports top-up-required.
208
+ */
209
+ function maxAcceptedFluidPartialRepay(args) {
210
+ const netRaw = (0, fluid_view_1.fluidNetDebtRaw)(args.position);
211
+ let x = maxPartialRepayNative(netRaw, args.debtDecimals, args.borrowExPrice);
212
+ if (x <= 0n)
213
+ return null;
214
+ if (x > args.desiredNative)
215
+ x = args.desiredNative;
216
+ if (args.debtAtaBalance < 2n)
217
+ return null;
218
+ if (x > args.debtAtaBalance - 1n)
219
+ x = args.debtAtaBalance - 1n;
220
+ for (let i = 0; i < 4 && x >= exports.FLUID_MIN_OPERATE_NATIVE; i++, x -= 1n) {
221
+ if (evaluateFluidPartialRepay({ ...args, xNative: x }).accepted)
222
+ return x;
223
+ }
224
+ return null;
225
+ }
148
226
  /**
149
227
  * Largest partial repay (native) the on-chain acceptance admits for `netRawDebt` at the given
150
228
  * stored price — the driver's sizing cap (anything larger reverts; anything at/below is safe at
@@ -164,27 +242,6 @@ function maxPartialRepayNative(netRawDebt, debtDecimals, borrowExPrice) {
164
242
  }
165
243
  return x >= exports.FLUID_MIN_OPERATE_NATIVE ? x : 0n;
166
244
  }
167
- /**
168
- * Swap-churn budget remaining before the next `settle_yield` resets the accumulator — mirrors
169
- * `HedgeState.maxSwapNotionalPerSettle - HedgeState.swapNotionalSinceSettle` (USD, 6-dec),
170
- * floored at 0 (the accumulator should never on-chain exceed the cap, but this stays
171
- * fail-safe against a stale/racing read).
172
- */
173
- function churnBudgetRemainingUsd(state) {
174
- const max = BigInt(state.maxSwapNotionalPerSettle.toString());
175
- const since = BigInt(state.swapNotionalSinceSettle.toString());
176
- const remaining = max - since;
177
- return remaining > 0n ? remaining : 0n;
178
- }
179
- /**
180
- * `HedgeState.maxPledgedCollateralJlp` as a bigint — headroom convenience: pass the result of
181
- * `sumAndCheckPledgedCollateral`'s successful total (or a raw sum) to get remaining pledge room.
182
- */
183
- function pledgeHeadroomJlp(state, totalPledgedJlp) {
184
- const cap = BigInt(state.maxPledgedCollateralJlp.toString());
185
- const headroom = cap - totalPledgedJlp;
186
- return headroom > 0n ? headroom : 0n;
187
- }
188
245
  /**
189
246
  * Fetch + value ONE `HedgeState.positions[slotIndex]` slot — the async convenience wrapper
190
247
  * matching `hedge_borrow`'s / `hedge_withdraw_collateral`'s post-op LTV guard basis exactly
@@ -210,22 +267,29 @@ async function fetchAllHedgeSlotsDerived(connection, state) {
210
267
  const results = await Promise.all(state.positions.map((slot, slotIndex) => (0, hedge_state_1.isHedgePositionSlotDisabled)(slot) ? Promise.resolve(null) : fetchHedgeSlotDerived(connection, state, slotIndex)));
211
268
  return results.filter((r) => r !== null);
212
269
  }
213
- /** Compute portfolio-level hedge exposure from a fully-fetched `HedgeState`. */
214
- async function fetchHedgeExposure(connection, state) {
270
+ /**
271
+ * Compute portfolio-level hedge exposure. `vaultJlpFree` is the config-owned vault JLP ATA balance
272
+ * (base units) — the free-JLP term of the pledge-cap denominator; fetch it alongside the state.
273
+ *
274
+ * NOTE: this reads each position's stored collateral WITHOUT applying the Fluid
275
+ * liquidation-staleness gate (`requireFluidPositionLiquidationCurrent`), so during a
276
+ * liquidation-stale window a slot's collateral (and thus `pledgedShareBps` / `pledgeCapExceeded`)
277
+ * can be phantom. Best-effort for dashboards; the on-chain guards are authoritative.
278
+ */
279
+ async function fetchHedgeExposure(connection, state, vaultJlpFree) {
215
280
  const slots = await fetchAllHedgeSlotsDerived(connection, state);
216
- const totalPledgedCollateralJlp = sumPledgedCollateral(slots.map((s) => s.collateralJlpNative));
217
- const cap = BigInt(state.maxPledgedCollateralJlp.toString());
281
+ // Per-slot collateral indexed BY SLOT (0 for disabled) so it aligns with maxPledgedBps.
282
+ const perSlot = state.positions.map(() => 0n);
283
+ for (const s of slots)
284
+ perSlot[s.slotIndex] = s.collateralJlpNative;
285
+ const totalPledgedCollateralJlp = sumPledgedCollateral(perSlot);
286
+ const totalControlled = vaultJlpFree + totalPledgedCollateralJlp;
287
+ const pledgedShareBps = perSlot.map((c) => totalControlled > 0n ? Number((c * 10000n) / totalControlled) : 0);
288
+ const pledgeCapExceeded = perSlot.some((c, i) => c * 10000n > BigInt(state.maxPledgedBps[i] ?? 0) * totalControlled);
218
289
  const debtByMint = {};
219
290
  for (const s of slots) {
220
291
  const key = s.slot.debtMint.toBase58();
221
292
  debtByMint[key] = (debtByMint[key] ?? 0n) + s.debtNative;
222
293
  }
223
- return {
224
- totalPledgedCollateralJlp,
225
- pledgeHeadroomJlp: cap - totalPledgedCollateralJlp > 0n ? cap - totalPledgedCollateralJlp : 0n,
226
- pledgeCapExceeded: totalPledgedCollateralJlp > cap,
227
- debtByMint,
228
- churnBudgetRemainingUsd: churnBudgetRemainingUsd(state),
229
- slots,
230
- };
294
+ return { totalPledgedCollateralJlp, pledgedShareBps, pledgeCapExceeded, debtByMint, slots };
231
295
  }
@@ -184,10 +184,11 @@ export interface InitOrUpdateHedgeStateArgs {
184
184
  jlWbtcLending?: PublicKey | null;
185
185
  /** wETH Earn `lending` rate-source account. `null`/omitted = unchanged. */
186
186
  jlWethLending?: PublicKey | null;
187
- /** JLP-native pledge cap (base units, 6 decimals). `null`/omitted = unchanged (init: 0). */
188
- maxPledgedCollateralJlp?: bigint | BN | null;
189
- /** Per-settle swap-churn budget (USD, 6-dec). `null`/omitted = unchanged (init: 0). */
190
- maxSwapNotionalPerSettle?: bigint | BN | null;
187
+ /**
188
+ * Per-slot pledged-collateral cap, bps of total controlled JLP, one entry per positions slot
189
+ * (each 0..=10000, Σ 0..=10000). `null`/omitted = unchanged (init: [0,0,0,0] = fail-closed).
190
+ */
191
+ maxPledgedBps?: number[] | null;
191
192
  /** Pair-native per-position LTV cap, bps (0..=`MAX_HEDGE_LTV_BPS`). `null`/omitted = unchanged (init: 0). */
192
193
  maxLtvBps?: number | null;
193
194
  /** Hedge master switch. `null`/omitted = unchanged (init: false). */
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.HedgeManifestRole = void 0;
7
4
  exports.createHedgeSupplyCollateralIx = createHedgeSupplyCollateralIx;
@@ -12,7 +9,6 @@ exports.createHedgeSwapIx = createHedgeSwapIx;
12
9
  exports.createInitOrUpdateHedgeStateIx = createInitOrUpdateHedgeStateIx;
13
10
  const web3_js_1 = require("@solana/web3.js");
14
11
  const spl_token_1 = require("@solana/spl-token");
15
- const bn_js_1 = __importDefault(require("bn.js"));
16
12
  const buffer_1 = require("../common/buffer");
17
13
  const constants_1 = require("./constants");
18
14
  function toBigIntAmount(v) {
@@ -92,7 +88,7 @@ function createHedgeWithdrawCollateralIx(args, programId = constants_1.PROGRAM_I
92
88
  // 0. manager [signer]
93
89
  // 1. config [] JLPD Config PDA (read-only — this ix never writes it)
94
90
  // 2. manager_role [] ManagerRole PDA
95
- // 3. hedge_state [writable] writes swap_notional_since_settle
91
+ // 3. hedge_state [] HedgeState PDA (read-only — this ix never writes it)
96
92
  // 4. src_ata [writable] == manifest_role_ata(hedge_state, params.src_role)
97
93
  // 5. dst_ata [writable] == manifest_role_ata(hedge_state, params.dst_role)
98
94
  // 6. price_oracle_src [] Doves oracle for src_role, if Doves-priced; else unused
@@ -160,7 +156,7 @@ function createHedgeSwapIx(args, programId = constants_1.PROGRAM_ID) {
160
156
  { pubkey: manager, isSigner: true, isWritable: false },
161
157
  { pubkey: config, isSigner: false, isWritable: false },
162
158
  { pubkey: managerRole, isSigner: false, isWritable: false },
163
- { pubkey: hedgeState, isSigner: false, isWritable: true },
159
+ { pubkey: hedgeState, isSigner: false, isWritable: false },
164
160
  { pubkey: srcAta, isSigner: false, isWritable: true },
165
161
  { pubkey: dstAta, isSigner: false, isWritable: true },
166
162
  { pubkey: priceOracleSrc, isSigner: false, isWritable: false },
@@ -206,8 +202,7 @@ function createHedgeSwapIx(args, programId = constants_1.PROGRAM_ID) {
206
202
  // jl_jupusd / jl_wbtc / jl_weth: Option<Pubkey> (3) — Earn fToken ATAs
207
203
  // jl_jupusd_mint / jl_wbtc_mint / jl_weth_mint: Option<Pubkey> (3) — admin-set fToken mints
208
204
  // jl_jupusd_lending / jl_wbtc_lending / jl_weth_lending: Option<Pubkey> (3) — Earn rate sources
209
- // max_pledged_collateral_jlp: Option<u64>
210
- // max_swap_notional_per_settle: Option<u64>
205
+ // max_pledged_bps: Option<[u16; MAX_HEDGE_POSITIONS(4)]>
211
206
  // max_ltv_bps: Option<u16>
212
207
  // enabled: Option<bool>
213
208
  // ---------------------------------------------------------------------------
@@ -240,6 +235,33 @@ function writeOptionalBool(parts, value) {
240
235
  parts.push(0x00);
241
236
  }
242
237
  }
238
+ /** Serialize `Option<[u16; n]>` — 1-byte tag, then n u16 LE if `Some` (a fixed-size Rust array, no
239
+ * length prefix). Mirrors the program's `validate_max_pledged_bps`: throws if the array is the
240
+ * wrong length, an entry exceeds 10000 (100%), or the entries sum above 10000 — so a bad governance
241
+ * proposal fails locally instead of on-chain. */
242
+ function writeOptionalU16Array(parts, value, n) {
243
+ if (value == null) {
244
+ parts.push(0x00);
245
+ return;
246
+ }
247
+ if (value.length !== n) {
248
+ throw new RangeError(`maxPledgedBps must have exactly ${n} entries, got ${value.length}`);
249
+ }
250
+ let sum = 0;
251
+ for (const v of value) {
252
+ if (!Number.isInteger(v) || v < 0 || v > 10000) {
253
+ throw new RangeError(`maxPledgedBps entry must be an integer in 0..=10000, got: ${v}`);
254
+ }
255
+ sum += v;
256
+ }
257
+ if (sum > 10000) {
258
+ throw new RangeError(`maxPledgedBps entries sum to ${sum}, exceeds 10000 (100%)`);
259
+ }
260
+ parts.push(0x01);
261
+ for (const v of value) {
262
+ parts.push(v & 0xff, (v >> 8) & 0xff);
263
+ }
264
+ }
243
265
  /**
244
266
  * Build the on-chain `init_or_update_hedge_state` instruction (provisions/updates the singleton
245
267
  * `HedgeState` PDA). See this section's module doc comment above for the full account list and
@@ -278,9 +300,9 @@ function createInitOrUpdateHedgeStateIx(args, programId = constants_1.PROGRAM_ID
278
300
  (0, buffer_1.writeOptionalPubkey)(parts, args.jlJupusdLending ?? null);
279
301
  (0, buffer_1.writeOptionalPubkey)(parts, args.jlWbtcLending ?? null);
280
302
  (0, buffer_1.writeOptionalPubkey)(parts, args.jlWethLending ?? null);
281
- // Guards.
282
- (0, buffer_1.writeOptionalU64)(parts, args.maxPledgedCollateralJlp != null ? new bn_js_1.default(toBigIntAmount(args.maxPledgedCollateralJlp).toString()) : null);
283
- (0, buffer_1.writeOptionalU64)(parts, args.maxSwapNotionalPerSettle != null ? new bn_js_1.default(toBigIntAmount(args.maxSwapNotionalPerSettle).toString()) : null);
303
+ // Guards. max_pledged_bps: Option<[u16; MAX_HEDGE_POSITIONS]> (1-byte tag + 4*u16 LE if Some),
304
+ // then max_ltv_bps: Option<u16>, then enabled: Option<bool>.
305
+ writeOptionalU16Array(parts, args.maxPledgedBps ?? null, constants_1.MAX_HEDGE_POSITIONS);
284
306
  (0, buffer_1.writeOptionalU16)(parts, args.maxLtvBps ?? null);
285
307
  writeOptionalBool(parts, args.enabled ?? null);
286
308
  const data = Buffer.from(parts);
@@ -1,5 +1,4 @@
1
1
  import { PublicKey } from "@solana/web3.js";
2
- import BN from "bn.js";
3
2
  import type { SolanaConnection } from "../common/connection";
4
3
  /**
5
4
  * One Fluid borrow position: the position NFT, that vault's pinned
@@ -55,12 +54,12 @@ export interface HedgeState {
55
54
  jlWbtcLending: PublicKey;
56
55
  /** Canonical `lending` account for the wETH pool (disabled until `jlWethMint` is set). */
57
56
  jlWethLending: PublicKey;
58
- /** Max JLP (base units) that may be pledged as Fluid collateral, summed across every enabled slot. */
59
- maxPledgedCollateralJlp: BN;
60
- /** Swap-churn budget: max input notional (USD, 6-dec) `hedge_swap` may spend between settles. */
61
- maxSwapNotionalPerSettle: BN;
62
- /** Swap-churn accumulator: input notional spent since the last settle. Reset only by `settle_yield`. */
63
- swapNotionalSinceSettle: BN;
57
+ /**
58
+ * Per-slot pledged-collateral cap, bps of total controlled JLP (one entry per positions slot,
59
+ * each 0..=10000). Lives in the reserved tail at bytes [1120..1128]. Replaces the removed absolute
60
+ * pledge cap + the swap-churn cap/accumulator (bytes [1088..1112] are now reserved, unread).
61
+ */
62
+ maxPledgedBps: number[];
64
63
  /** Liquidation-probability cap in basis points, valued on each position's own pinned Fluid oracle. */
65
64
  maxLtvBps: number;
66
65
  /** Hedge switch bitfield; bit 0 (`HEDGE_FLAG_ENABLED`) is the master switch. */
@@ -84,9 +84,14 @@ function deserializeHedgeState(data) {
84
84
  jlJupusdLending: (0, buffer_1.readPubkey)(data, afterPositions + 352),
85
85
  jlWbtcLending: (0, buffer_1.readPubkey)(data, afterPositions + 384),
86
86
  jlWethLending: (0, buffer_1.readPubkey)(data, afterPositions + 416),
87
- maxPledgedCollateralJlp: (0, buffer_1.readU64)(data, afterPositions + 448),
88
- maxSwapNotionalPerSettle: (0, buffer_1.readU64)(data, afterPositions + 456),
89
- swapNotionalSinceSettle: (0, buffer_1.readU64)(data, afterPositions + 464),
87
+ // bytes [1088..1112] (afterPositions + 448..472) are reserved (removed guards), unread.
88
+ // max_pledged_bps: [u16; 4] in the reserved tail at byte 1120 (afterPositions + 480).
89
+ maxPledgedBps: [
90
+ (0, buffer_1.readU16)(data, afterPositions + 480),
91
+ (0, buffer_1.readU16)(data, afterPositions + 482),
92
+ (0, buffer_1.readU16)(data, afterPositions + 484),
93
+ (0, buffer_1.readU16)(data, afterPositions + 486),
94
+ ],
90
95
  maxLtvBps: (0, buffer_1.readU16)(data, afterPositions + 472),
91
96
  flags: (0, buffer_1.readU16)(data, afterPositions + 474),
92
97
  version: (0, buffer_1.readU8)(data, afterPositions + 476),
@@ -8,10 +8,10 @@ export interface CreateSettleYieldIxArgs {
8
8
  tokenProgram?: PublicKey;
9
9
  programId?: PublicKey;
10
10
  /**
11
- * Hedge block accounts (position/vault_state pairs per enabled hedge slot,
12
- * plus the manifest + earn block), appended after the 15 fixed accounts and
13
- * BEFORE `custodyAccounts`. Defaults to `[]` -- for now callers pass nothing
14
- * while the hedge is disabled.
11
+ * Hedge block accounts (position/vault_state/tick triples per enabled hedge
12
+ * slot, plus the manifest + earn block), appended after the 15 fixed accounts
13
+ * and BEFORE `custodyAccounts`. Defaults to `[]` -- for now callers pass
14
+ * nothing while the hedge is disabled.
15
15
  */
16
16
  hedgeAccounts?: AccountMeta[];
17
17
  /**
@@ -17,7 +17,7 @@ const ata_1 = require("../common/ata");
17
17
  * Accounts (15 total):
18
18
  * 0: config (JlpdConfig PDA)
19
19
  * 1: manager_role (ManagerRole PDA)
20
- * 2: hedge_state (HedgeState PDA, mut -- resets swap-churn accumulator)
20
+ * 2: hedge_state (HedgeState PDA, read-only)
21
21
  * 3: vault_jlp_ata (JLP ATA owned by config)
22
22
  * 4: stv_0 (BTC strategy_state, mut)
23
23
  * 5: stv_1 (ETH strategy_state, mut)
@@ -31,7 +31,7 @@ const ata_1 = require("../common/ata");
31
31
  * 13: jlp_mint_account
32
32
  * 14: manager (signer)
33
33
  * 15+: remaining_accounts, in this EXACT order:
34
- * [0 .. 2*enabled_hedge_slots) -- (position, vault_state) pairs per
34
+ * [0 .. 3*enabled_hedge_slots) -- (position, vault_state, tick) triples per
35
35
  * ENABLED hedge slot, then the manifest + earn block (one account per
36
36
  * non-default hedge manifest slot). Empty while the hedge is disabled.
37
37
  * [.. remainder) -- the JLP pool custody accounts, in pool `custodies`
@@ -46,7 +46,7 @@ function buildRawSettleYieldIx(args, programId = constants_1.PROGRAM_ID) {
46
46
  const keys = [
47
47
  { pubkey: config, isSigner: false, isWritable: true },
48
48
  { pubkey: managerRole, isSigner: false, isWritable: false },
49
- { pubkey: hedgeState, isSigner: false, isWritable: true },
49
+ { pubkey: hedgeState, isSigner: false, isWritable: false },
50
50
  { pubkey: vaultJlpAta, isSigner: false, isWritable: false },
51
51
  { pubkey: stv0, isSigner: false, isWritable: true },
52
52
  { pubkey: stv1, isSigner: false, isWritable: true },
@@ -99,11 +99,17 @@ export declare function warmAdminEffective(stv: Stv): PublicKey;
99
99
  * [ 92.. 96] claimAvailableAfter u32 (unix seconds)
100
100
  * [ 96.. 97] bump u8
101
101
  * [ 97.. 98] version u8
102
- * [ 98.. 99] requestFlags u8 (bit0 WR_FLAG_MIGRATE: 0=withdraw, 1=migrate)
102
+ * [ 98.. 99] requestFlags u8 (mask 0x0F: bit0 WR_FLAG_MIGRATE,
103
+ * bit1 WR_FLAG_FROZEN, bit2
104
+ * WR_FLAG_RECIPIENT_USER_OK, bit3
105
+ * WR_FLAG_RECIPIENT_COLD_OK)
103
106
  * [ 99..104] _padding [u8; 5]
104
107
  * [104..112] destVaultId u64 (migrate: dest STV vault_id; withdraw: 0)
105
108
  * [112..120] minDestShares u64 (migrate: slippage floor; withdraw: 0)
106
- * [120..168] _reserved0 [u8; 48]
109
+ * [120..152] claimRecipient Pubkey (withdrawal-freeze co-signed
110
+ * redirect target; [0;32] = pay
111
+ * `user`, today's default)
112
+ * [152..168] _reserved [u8; 16]
107
113
  */
108
114
  export declare function deserializeWithdrawRequest(data: Buffer): WithdrawRequest;
109
115
  /**
@@ -111,6 +117,16 @@ export declare function deserializeWithdrawRequest(data: Buffer): WithdrawReques
111
117
  * Returns `"migrate"` when WR_FLAG_MIGRATE (0x01) is set, else `"withdraw"`.
112
118
  */
113
119
  export declare function withdrawRequestKind(wr: WithdrawRequest): "withdraw" | "migrate";
120
+ /** True if `WR_FLAG_FROZEN` is set on `requestFlags`. Mirrors `WithdrawRequest::is_frozen`. */
121
+ export declare function isWithdrawRequestFrozen(wr: WithdrawRequest): boolean;
122
+ /**
123
+ * A redirect is ARMED iff `claimRecipient` is nonzero AND both the user's and
124
+ * cold's approval bits are set on the same stored value. Mirrors the on-chain
125
+ * `WithdrawRequest::is_redirect_armed()` (state/withdraw_request.rs) exactly —
126
+ * when armed, `execute_claim` pays the canonical `ATA(claimRecipient, baseMint)`
127
+ * instead of `user`.
128
+ */
129
+ export declare function isRedirectArmed(wr: WithdrawRequest): boolean;
114
130
  /**
115
131
  * DelayedDepositRequest — per-(STV, user, epoch) pending deposit. Total 152 bytes.
116
132
  *
@@ -5,6 +5,8 @@ exports.deserializeStv = deserializeStv;
5
5
  exports.warmAdminEffective = warmAdminEffective;
6
6
  exports.deserializeWithdrawRequest = deserializeWithdrawRequest;
7
7
  exports.withdrawRequestKind = withdrawRequestKind;
8
+ exports.isWithdrawRequestFrozen = isWithdrawRequestFrozen;
9
+ exports.isRedirectArmed = isRedirectArmed;
8
10
  exports.deserializeDelayedDepositRequest = deserializeDelayedDepositRequest;
9
11
  exports.deserializeManagerRole = deserializeManagerRole;
10
12
  exports.fetchGlobalConfig = fetchGlobalConfig;
@@ -209,11 +211,17 @@ function warmAdminEffective(stv) {
209
211
  * [ 92.. 96] claimAvailableAfter u32 (unix seconds)
210
212
  * [ 96.. 97] bump u8
211
213
  * [ 97.. 98] version u8
212
- * [ 98.. 99] requestFlags u8 (bit0 WR_FLAG_MIGRATE: 0=withdraw, 1=migrate)
214
+ * [ 98.. 99] requestFlags u8 (mask 0x0F: bit0 WR_FLAG_MIGRATE,
215
+ * bit1 WR_FLAG_FROZEN, bit2
216
+ * WR_FLAG_RECIPIENT_USER_OK, bit3
217
+ * WR_FLAG_RECIPIENT_COLD_OK)
213
218
  * [ 99..104] _padding [u8; 5]
214
219
  * [104..112] destVaultId u64 (migrate: dest STV vault_id; withdraw: 0)
215
220
  * [112..120] minDestShares u64 (migrate: slippage floor; withdraw: 0)
216
- * [120..168] _reserved0 [u8; 48]
221
+ * [120..152] claimRecipient Pubkey (withdrawal-freeze co-signed
222
+ * redirect target; [0;32] = pay
223
+ * `user`, today's default)
224
+ * [152..168] _reserved [u8; 16]
217
225
  */
218
226
  function deserializeWithdrawRequest(data) {
219
227
  if (data.length < constants_1.WITHDRAW_REQUEST_SIZE) {
@@ -236,6 +244,7 @@ function deserializeWithdrawRequest(data) {
236
244
  requestFlags: (0, buffer_1.readU8)(data, 98),
237
245
  destVaultId: (0, buffer_1.readU64)(data, 104),
238
246
  minDestShares: (0, buffer_1.readU64)(data, 112),
247
+ claimRecipient: (0, buffer_1.readPubkey)(data, 120),
239
248
  };
240
249
  }
241
250
  /**
@@ -245,6 +254,22 @@ function deserializeWithdrawRequest(data) {
245
254
  function withdrawRequestKind(wr) {
246
255
  return (wr.requestFlags & 0x01) !== 0 ? "migrate" : "withdraw";
247
256
  }
257
+ /** True if `WR_FLAG_FROZEN` is set on `requestFlags`. Mirrors `WithdrawRequest::is_frozen`. */
258
+ function isWithdrawRequestFrozen(wr) {
259
+ return (wr.requestFlags & constants_1.WR_FLAG_FROZEN) !== 0;
260
+ }
261
+ /**
262
+ * A redirect is ARMED iff `claimRecipient` is nonzero AND both the user's and
263
+ * cold's approval bits are set on the same stored value. Mirrors the on-chain
264
+ * `WithdrawRequest::is_redirect_armed()` (state/withdraw_request.rs) exactly —
265
+ * when armed, `execute_claim` pays the canonical `ATA(claimRecipient, baseMint)`
266
+ * instead of `user`.
267
+ */
268
+ function isRedirectArmed(wr) {
269
+ return (!wr.claimRecipient.equals(web3_js_1.PublicKey.default) &&
270
+ (wr.requestFlags & constants_1.WR_FLAG_RECIPIENT_USER_OK) !== 0 &&
271
+ (wr.requestFlags & constants_1.WR_FLAG_RECIPIENT_COLD_OK) !== 0);
272
+ }
248
273
  /**
249
274
  * DelayedDepositRequest — per-(STV, user, epoch) pending deposit. Total 152 bytes.
250
275
  *
@@ -42,6 +42,25 @@ export declare const IX_ACCEPT_VAULT_ADMIN = 20;
42
42
  * silently freezing a vault. See docs/spec-warm-admin-guardrails.md §4.3.
43
43
  */
44
44
  export declare const IX_TRIP_CIRCUIT_BREAKER = 21;
45
+ /**
46
+ * freeze_withdraw_request (disc 0x16 = 22) — incident-response freeze on a
47
+ * single WithdrawRequest. OR-auth: the WR's own `user` (self-freeze) OR a
48
+ * valid manager for the STV. FIXED 4-account ABI (see
49
+ * `createFreezeWithdrawRequestIx`) — pause-exempt.
50
+ */
51
+ export declare const IX_FREEZE_WITHDRAW_REQUEST = 22;
52
+ /**
53
+ * unfreeze_withdraw_request (disc 0x17 = 23) — clears `WR_FLAG_FROZEN`.
54
+ * `stv.coldAdmin` may always; `stv.warmAdminEffective()` may only when the
55
+ * redirect is ARMED. Pause-exempt.
56
+ */
57
+ export declare const IX_UNFREEZE_WITHDRAW_REQUEST = 23;
58
+ /**
59
+ * set_claim_recipient (disc 0x18 = 24) — the 2-of-2 COLD-LOCK redirect.
60
+ * Signer is the WR's `user` OR `stv.coldAdmin` (never warm). WR must be
61
+ * frozen. Pause-exempt.
62
+ */
63
+ export declare const IX_SET_CLAIM_RECIPIENT = 24;
45
64
  export declare const EVT_CONFIG_INITIALIZED = 0;
46
65
  export declare const EVT_CONFIG_UPDATED = 1;
47
66
  export declare const EVT_STV_CREATED = 2;
@@ -91,6 +110,31 @@ export declare const EVT_CIRCUIT_BREAKER_TRIPPED = 27;
91
110
  export declare const TRIP_AUTHORITY_KIND_COLD = 0;
92
111
  export declare const TRIP_AUTHORITY_KIND_WARM = 1;
93
112
  export declare const TRIP_AUTHORITY_KIND_MANAGER = 2;
113
+ /**
114
+ * WithdrawRequestFrozen — emitted by freeze_withdraw_request (disc 22).
115
+ * Fields: wr(32), stv(32), user(32), epochId(4), authority(32), authorityKind(1).
116
+ */
117
+ export declare const EVT_WITHDRAW_REQUEST_FROZEN = 28;
118
+ /**
119
+ * WithdrawRequestUnfrozen — emitted by unfreeze_withdraw_request (disc 23).
120
+ * Fields: wr(32), stv(32), user(32), epochId(4), authority(32).
121
+ */
122
+ export declare const EVT_WITHDRAW_REQUEST_UNFROZEN = 29;
123
+ /**
124
+ * ClaimRecipientSet — emitted by set_claim_recipient (disc 24).
125
+ * Fields: wr(32), stv(32), user(32), epochId(4), recipient(32), approverKind(1), armed(1).
126
+ */
127
+ export declare const EVT_CLAIM_RECIPIENT_SET = 30;
128
+ /**
129
+ * freeze_withdraw_request / WithdrawRequestFrozenEvent authorityKind values.
130
+ */
131
+ export declare const FREEZE_AUTHORITY_KIND_USER = 0;
132
+ export declare const FREEZE_AUTHORITY_KIND_MANAGER = 1;
133
+ /**
134
+ * set_claim_recipient / ClaimRecipientSetEvent approverKind values.
135
+ */
136
+ export declare const RECIPIENT_APPROVER_KIND_USER = 0;
137
+ export declare const RECIPIENT_APPROVER_KIND_COLD = 1;
94
138
  export declare const FLAG_PAUSED = 1;
95
139
  export declare const FLAG_DEPOSITS_DISABLED = 2;
96
140
  export declare const FLAG_WITHDRAWALS_DISABLED = 4;
@@ -131,6 +175,17 @@ export declare const FEE_CAP_BPS = 9900;
131
175
  export declare const MAX_FEE_BPS = 1000;
132
176
  /** WithdrawRequest.request_flags bit 0: 0 = withdraw, 1 = migrate. */
133
177
  export declare const WR_FLAG_MIGRATE = 1;
178
+ /**
179
+ * WithdrawRequest.request_flags bit 1: the WR is frozen (incident response).
180
+ * Blocks payout in execute_claim unless a redirect is ARMED (exit-only).
181
+ */
182
+ export declare const WR_FLAG_FROZEN = 2;
183
+ /** WithdrawRequest.request_flags bit 2: the WR's `user` approved `claimRecipient`. */
184
+ export declare const WR_FLAG_RECIPIENT_USER_OK = 4;
185
+ /** WithdrawRequest.request_flags bit 3: `stv.coldAdmin` approved `claimRecipient`. */
186
+ export declare const WR_FLAG_RECIPIENT_COLD_OK = 8;
187
+ /** All bits outside this mask are reserved and must be zero. */
188
+ export declare const WR_VALID_FLAGS_MASK = 15;
134
189
  export declare const GLOBAL_CONFIG_SIZE = 56;
135
190
  export declare const STV_SIZE = 664;
136
191
  export declare const WITHDRAW_REQUEST_SIZE = 168;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVT_MANAGER_REMOVED = exports.EVT_MANAGER_ADDED = exports.EVT_STV_CLOSED = exports.EVT_FEES_SETTLED = exports.EVT_EPOCH_ADVANCED = exports.EVT_EPOCH_PROCESSED = exports.EVT_WITHDRAWN_FROM_STRATEGY = exports.EVT_DEPOSITED_TO_STRATEGY = exports.EVT_WITHDRAW_CLAIMED = exports.EVT_WITHDRAW_REQUEST_INCREASED = exports.EVT_WITHDRAW_REQUESTED = exports.EVT_DEPOSITED = exports.EVT_STV_UPDATED = exports.EVT_STV_CREATED = exports.EVT_CONFIG_UPDATED = exports.EVT_CONFIG_INITIALIZED = exports.IX_TRIP_CIRCUIT_BREAKER = exports.IX_ACCEPT_VAULT_ADMIN = exports.IX_SEED_STV = exports.IX_INSTANT_WITHDRAW = exports.IX_MIGRATE_CANCEL = exports.IX_PROCESS_DELAYED_DEPOSIT = exports.IX_MIGRATE_EXECUTE = exports.IX_MIGRATE_REQUEST = exports.IX_MIGRATE_LEND = exports.IX_OVERRIDE_CLAIM_WITHDRAW = exports.IX_REMOVE_MANAGER = exports.IX_ADD_MANAGER = exports.IX_CLOSE_STV = exports.IX_WITHDRAW_FROM_STRATEGY = exports.IX_DEPOSIT_TO_STRATEGY = exports.IX_PROCESS_EPOCH = exports.IX_CLAIM_WITHDRAW = exports.IX_REQUEST_WITHDRAW = exports.IX_DEPOSIT = exports.IX_INIT_OR_UPDATE_STV = exports.IX_INIT_OR_UPDATE_CONFIG = exports.DISC_MANAGER_ROLE = exports.DISC_DELAYED_DEPOSIT_REQUEST = exports.DISC_WITHDRAW_REQUEST = exports.DISC_STV = exports.DISC_GLOBAL_CONFIG = exports.MANAGER_SEED = exports.DELAYED_DEPOSIT_SEED = exports.MIGRATE_REQUEST_SEED = exports.WITHDRAW_REQUEST_SEED = exports.EV_MINT_SEED = exports.STV_SEED = exports.CONFIG_SEED = exports.PROGRAM_ID = void 0;
4
- exports.MANAGER_ROLE_SIZE = exports.DELAYED_DEPOSIT_REQUEST_SIZE = exports.WITHDRAW_REQUEST_SIZE = exports.STV_SIZE = exports.GLOBAL_CONFIG_SIZE = exports.WR_FLAG_MIGRATE = exports.MAX_FEE_BPS = exports.FEE_CAP_BPS = exports.MAX_STRATEGIES = exports.MAX_CHILD_VAULTS = exports.STALENESS_THRESHOLD = exports.BPS_DENOMINATOR = exports.PPS_DECIMALS = exports.CFG_FLAG_GLOBAL_PAUSED = exports.FLAG_SEEDED = exports.FLAG_BYPASS_MIGRATE_DST_WAITOUT = exports.FLAG_BYPASS_MIGRATE_SRC_WAITOUT = exports.FLAG_DELAYED_DEPOSIT = exports.FLAG_DEPOSIT_FEE_ON_MIGRATE = exports.FLAG_WITHDRAWAL_FEE_ON_MIGRATE = exports.FLAG_BYPASS_WITHDRAW_LIMIT = exports.FLAG_ALLOCATOR = exports.FLAG_REBALANCE_DISABLED = exports.FLAG_WITHDRAWALS_DISABLED = exports.FLAG_DEPOSITS_DISABLED = exports.FLAG_PAUSED = exports.TRIP_AUTHORITY_KIND_MANAGER = exports.TRIP_AUTHORITY_KIND_WARM = exports.TRIP_AUTHORITY_KIND_COLD = exports.EVT_CIRCUIT_BREAKER_TRIPPED = exports.EVT_WARM_ADMIN_SET = exports.EVT_ADMIN_ROTATION_ACCEPTED = exports.EVT_ADMIN_ROTATION_STARTED = exports.EVT_STV_SEEDED = exports.EVT_INSTANT_WITHDRAWN = exports.EVT_DELAYED_DEPOSIT_PROCESSED = exports.EVT_MIGRATE_EXECUTED = exports.EVT_MIGRATE_REQUESTED = exports.EVT_LEND_MIGRATED = exports.EVT_WITHDRAW_OVERRIDE_CLAIMED = void 0;
3
+ exports.EVT_FEES_SETTLED = exports.EVT_EPOCH_ADVANCED = exports.EVT_EPOCH_PROCESSED = exports.EVT_WITHDRAWN_FROM_STRATEGY = exports.EVT_DEPOSITED_TO_STRATEGY = exports.EVT_WITHDRAW_CLAIMED = exports.EVT_WITHDRAW_REQUEST_INCREASED = exports.EVT_WITHDRAW_REQUESTED = exports.EVT_DEPOSITED = exports.EVT_STV_UPDATED = exports.EVT_STV_CREATED = exports.EVT_CONFIG_UPDATED = exports.EVT_CONFIG_INITIALIZED = exports.IX_SET_CLAIM_RECIPIENT = exports.IX_UNFREEZE_WITHDRAW_REQUEST = exports.IX_FREEZE_WITHDRAW_REQUEST = exports.IX_TRIP_CIRCUIT_BREAKER = exports.IX_ACCEPT_VAULT_ADMIN = exports.IX_SEED_STV = exports.IX_INSTANT_WITHDRAW = exports.IX_MIGRATE_CANCEL = exports.IX_PROCESS_DELAYED_DEPOSIT = exports.IX_MIGRATE_EXECUTE = exports.IX_MIGRATE_REQUEST = exports.IX_MIGRATE_LEND = exports.IX_OVERRIDE_CLAIM_WITHDRAW = exports.IX_REMOVE_MANAGER = exports.IX_ADD_MANAGER = exports.IX_CLOSE_STV = exports.IX_WITHDRAW_FROM_STRATEGY = exports.IX_DEPOSIT_TO_STRATEGY = exports.IX_PROCESS_EPOCH = exports.IX_CLAIM_WITHDRAW = exports.IX_REQUEST_WITHDRAW = exports.IX_DEPOSIT = exports.IX_INIT_OR_UPDATE_STV = exports.IX_INIT_OR_UPDATE_CONFIG = exports.DISC_MANAGER_ROLE = exports.DISC_DELAYED_DEPOSIT_REQUEST = exports.DISC_WITHDRAW_REQUEST = exports.DISC_STV = exports.DISC_GLOBAL_CONFIG = exports.MANAGER_SEED = exports.DELAYED_DEPOSIT_SEED = exports.MIGRATE_REQUEST_SEED = exports.WITHDRAW_REQUEST_SEED = exports.EV_MINT_SEED = exports.STV_SEED = exports.CONFIG_SEED = exports.PROGRAM_ID = void 0;
4
+ exports.GLOBAL_CONFIG_SIZE = exports.WR_VALID_FLAGS_MASK = exports.WR_FLAG_RECIPIENT_COLD_OK = exports.WR_FLAG_RECIPIENT_USER_OK = exports.WR_FLAG_FROZEN = exports.WR_FLAG_MIGRATE = exports.MAX_FEE_BPS = exports.FEE_CAP_BPS = exports.MAX_STRATEGIES = exports.MAX_CHILD_VAULTS = exports.STALENESS_THRESHOLD = exports.BPS_DENOMINATOR = exports.PPS_DECIMALS = exports.CFG_FLAG_GLOBAL_PAUSED = exports.FLAG_SEEDED = exports.FLAG_BYPASS_MIGRATE_DST_WAITOUT = exports.FLAG_BYPASS_MIGRATE_SRC_WAITOUT = exports.FLAG_DELAYED_DEPOSIT = exports.FLAG_DEPOSIT_FEE_ON_MIGRATE = exports.FLAG_WITHDRAWAL_FEE_ON_MIGRATE = exports.FLAG_BYPASS_WITHDRAW_LIMIT = exports.FLAG_ALLOCATOR = exports.FLAG_REBALANCE_DISABLED = exports.FLAG_WITHDRAWALS_DISABLED = exports.FLAG_DEPOSITS_DISABLED = exports.FLAG_PAUSED = exports.RECIPIENT_APPROVER_KIND_COLD = exports.RECIPIENT_APPROVER_KIND_USER = exports.FREEZE_AUTHORITY_KIND_MANAGER = exports.FREEZE_AUTHORITY_KIND_USER = exports.EVT_CLAIM_RECIPIENT_SET = exports.EVT_WITHDRAW_REQUEST_UNFROZEN = exports.EVT_WITHDRAW_REQUEST_FROZEN = exports.TRIP_AUTHORITY_KIND_MANAGER = exports.TRIP_AUTHORITY_KIND_WARM = exports.TRIP_AUTHORITY_KIND_COLD = exports.EVT_CIRCUIT_BREAKER_TRIPPED = exports.EVT_WARM_ADMIN_SET = exports.EVT_ADMIN_ROTATION_ACCEPTED = exports.EVT_ADMIN_ROTATION_STARTED = exports.EVT_STV_SEEDED = exports.EVT_INSTANT_WITHDRAWN = exports.EVT_DELAYED_DEPOSIT_PROCESSED = exports.EVT_MIGRATE_EXECUTED = exports.EVT_MIGRATE_REQUESTED = exports.EVT_LEND_MIGRATED = exports.EVT_WITHDRAW_OVERRIDE_CLAIMED = exports.EVT_MANAGER_REMOVED = exports.EVT_MANAGER_ADDED = exports.EVT_STV_CLOSED = void 0;
5
+ exports.MANAGER_ROLE_SIZE = exports.DELAYED_DEPOSIT_REQUEST_SIZE = exports.WITHDRAW_REQUEST_SIZE = exports.STV_SIZE = void 0;
5
6
  const web3_js_1 = require("@solana/web3.js");
6
7
  // ---------------------------------------------------------------------------
7
8
  // Program ID
@@ -68,6 +69,25 @@ exports.IX_ACCEPT_VAULT_ADMIN = 20;
68
69
  * silently freezing a vault. See docs/spec-warm-admin-guardrails.md §4.3.
69
70
  */
70
71
  exports.IX_TRIP_CIRCUIT_BREAKER = 21;
72
+ /**
73
+ * freeze_withdraw_request (disc 0x16 = 22) — incident-response freeze on a
74
+ * single WithdrawRequest. OR-auth: the WR's own `user` (self-freeze) OR a
75
+ * valid manager for the STV. FIXED 4-account ABI (see
76
+ * `createFreezeWithdrawRequestIx`) — pause-exempt.
77
+ */
78
+ exports.IX_FREEZE_WITHDRAW_REQUEST = 22;
79
+ /**
80
+ * unfreeze_withdraw_request (disc 0x17 = 23) — clears `WR_FLAG_FROZEN`.
81
+ * `stv.coldAdmin` may always; `stv.warmAdminEffective()` may only when the
82
+ * redirect is ARMED. Pause-exempt.
83
+ */
84
+ exports.IX_UNFREEZE_WITHDRAW_REQUEST = 23;
85
+ /**
86
+ * set_claim_recipient (disc 0x18 = 24) — the 2-of-2 COLD-LOCK redirect.
87
+ * Signer is the WR's `user` OR `stv.coldAdmin` (never warm). WR must be
88
+ * frozen. Pause-exempt.
89
+ */
90
+ exports.IX_SET_CLAIM_RECIPIENT = 24;
71
91
  // ---------------------------------------------------------------------------
72
92
  // Event Discriminators (1-byte)
73
93
  // ---------------------------------------------------------------------------
@@ -120,6 +140,31 @@ exports.EVT_CIRCUIT_BREAKER_TRIPPED = 27;
120
140
  exports.TRIP_AUTHORITY_KIND_COLD = 0;
121
141
  exports.TRIP_AUTHORITY_KIND_WARM = 1;
122
142
  exports.TRIP_AUTHORITY_KIND_MANAGER = 2;
143
+ /**
144
+ * WithdrawRequestFrozen — emitted by freeze_withdraw_request (disc 22).
145
+ * Fields: wr(32), stv(32), user(32), epochId(4), authority(32), authorityKind(1).
146
+ */
147
+ exports.EVT_WITHDRAW_REQUEST_FROZEN = 28;
148
+ /**
149
+ * WithdrawRequestUnfrozen — emitted by unfreeze_withdraw_request (disc 23).
150
+ * Fields: wr(32), stv(32), user(32), epochId(4), authority(32).
151
+ */
152
+ exports.EVT_WITHDRAW_REQUEST_UNFROZEN = 29;
153
+ /**
154
+ * ClaimRecipientSet — emitted by set_claim_recipient (disc 24).
155
+ * Fields: wr(32), stv(32), user(32), epochId(4), recipient(32), approverKind(1), armed(1).
156
+ */
157
+ exports.EVT_CLAIM_RECIPIENT_SET = 30;
158
+ /**
159
+ * freeze_withdraw_request / WithdrawRequestFrozenEvent authorityKind values.
160
+ */
161
+ exports.FREEZE_AUTHORITY_KIND_USER = 0;
162
+ exports.FREEZE_AUTHORITY_KIND_MANAGER = 1;
163
+ /**
164
+ * set_claim_recipient / ClaimRecipientSetEvent approverKind values.
165
+ */
166
+ exports.RECIPIENT_APPROVER_KIND_USER = 0;
167
+ exports.RECIPIENT_APPROVER_KIND_COLD = 1;
123
168
  // ---------------------------------------------------------------------------
124
169
  // Vault Flags
125
170
  // ---------------------------------------------------------------------------
@@ -171,6 +216,17 @@ exports.FEE_CAP_BPS = 9900;
171
216
  exports.MAX_FEE_BPS = 1000;
172
217
  /** WithdrawRequest.request_flags bit 0: 0 = withdraw, 1 = migrate. */
173
218
  exports.WR_FLAG_MIGRATE = 0x01;
219
+ /**
220
+ * WithdrawRequest.request_flags bit 1: the WR is frozen (incident response).
221
+ * Blocks payout in execute_claim unless a redirect is ARMED (exit-only).
222
+ */
223
+ exports.WR_FLAG_FROZEN = 0x02;
224
+ /** WithdrawRequest.request_flags bit 2: the WR's `user` approved `claimRecipient`. */
225
+ exports.WR_FLAG_RECIPIENT_USER_OK = 0x04;
226
+ /** WithdrawRequest.request_flags bit 3: `stv.coldAdmin` approved `claimRecipient`. */
227
+ exports.WR_FLAG_RECIPIENT_COLD_OK = 0x08;
228
+ /** All bits outside this mask are reserved and must be zero. */
229
+ exports.WR_VALID_FLAGS_MASK = 0x0f;
174
230
  // ---------------------------------------------------------------------------
175
231
  // Account Sizes
176
232
  // ---------------------------------------------------------------------------
@@ -59,12 +59,14 @@ function parseEventsFromLogs(logs, programId = constants_1.PROGRAM_ID) {
59
59
  * [41..49] amount | [49..57] sharesMinted | [57..65] depositFeeShares |
60
60
  * [65..73] pps
61
61
  * WithdrawRequested [0..1] disc | [1..9] vaultId | [9..41] user |
62
- * [41..49] shares | [49..53] epochId
62
+ * [41..49] shares | [49..53] epochId | [53..85] wr
63
63
  * WithdrawRequestIncreased [0..1] disc | [1..9] vaultId | [9..41] user |
64
64
  * [41..49] additionalShares | [49..57] totalShares |
65
- * [57..61] epochId
65
+ * [57..61] epochId | [61..93] wr
66
66
  * WithdrawClaimed [0..1] disc | [1..9] vaultId | [9..41] user |
67
- * [41..49] shares | [49..57] baseAmount | [57..61] epochId
67
+ * [41..49] shares | [49..57] baseAmount | [57..65] withdrawalFeeBase |
68
+ * [65..69] epochId | [69..101] wr | [101..133] recipient |
69
+ * [133..165] destination
68
70
  * DepositedToStrategy [0..1] disc | [1..9] vaultId | [9..41] strategy |
69
71
  * [41..49] amount | [49..57] sharesReceived
70
72
  * WithdrawnFromStrategy [0..1] disc | [1..9] vaultId | [9..41] strategy |
@@ -117,21 +119,28 @@ function parseEventBuffer(data) {
117
119
  depositFeeShares: (0, buffer_1.readU64)(data, 57), pps: (0, buffer_1.readU64)(data, 65),
118
120
  } : null;
119
121
  case constants_1.EVT_WITHDRAW_REQUESTED:
120
- return data.length >= 53 ? {
122
+ // Rust: vault_id(8), user(32), shares(8), epoch_id(4), wr(32) — wr appended (withdrawal-freeze feature).
123
+ return data.length >= 85 ? {
121
124
  name: "WithdrawRequested", vaultId: (0, buffer_1.readU64)(data, 1), user: (0, buffer_1.readPubkey)(data, 9),
122
- shares: (0, buffer_1.readU64)(data, 41), epochId: (0, buffer_1.readU32)(data, 49),
125
+ shares: (0, buffer_1.readU64)(data, 41), epochId: (0, buffer_1.readU32)(data, 49), wr: (0, buffer_1.readPubkey)(data, 53),
123
126
  } : null;
124
127
  case constants_1.EVT_WITHDRAW_REQUEST_INCREASED:
125
- return data.length >= 61 ? {
128
+ // Rust: vault_id(8), user(32), additional_shares(8), total_shares(8), epoch_id(4), wr(32) — wr appended.
129
+ return data.length >= 93 ? {
126
130
  name: "WithdrawRequestIncreased", vaultId: (0, buffer_1.readU64)(data, 1), user: (0, buffer_1.readPubkey)(data, 9),
127
131
  additionalShares: (0, buffer_1.readU64)(data, 41), totalShares: (0, buffer_1.readU64)(data, 49), epochId: (0, buffer_1.readU32)(data, 57),
132
+ wr: (0, buffer_1.readPubkey)(data, 61),
128
133
  } : null;
129
134
  case constants_1.EVT_WITHDRAW_CLAIMED:
130
- // Rust: vault_id(8), user(32), shares(8), net_base(8), withdrawal_fee_base(8), epoch_id(4)
131
- return data.length >= 69 ? {
135
+ // Rust: vault_id(8), user(32), shares(8), net_base(8), withdrawal_fee_base(8), epoch_id(4),
136
+ // wr(32), recipient(32), destination(32) wr/recipient/destination appended
137
+ // (withdrawal-freeze feature). recipient/destination equal user/vaultAta
138
+ // unless the redirect was armed at claim time.
139
+ return data.length >= 165 ? {
132
140
  name: "WithdrawClaimed", vaultId: (0, buffer_1.readU64)(data, 1), user: (0, buffer_1.readPubkey)(data, 9),
133
141
  shares: (0, buffer_1.readU64)(data, 41), baseAmount: (0, buffer_1.readU64)(data, 49),
134
142
  withdrawalFeeBase: (0, buffer_1.readU64)(data, 57), epochId: (0, buffer_1.readU32)(data, 65),
143
+ wr: (0, buffer_1.readPubkey)(data, 69), recipient: (0, buffer_1.readPubkey)(data, 101), destination: (0, buffer_1.readPubkey)(data, 133),
135
144
  } : null;
136
145
  case constants_1.EVT_DEPOSITED_TO_STRATEGY:
137
146
  return data.length >= 57 ? {
@@ -172,12 +181,15 @@ function parseEventBuffer(data) {
172
181
  } : null;
173
182
  case constants_1.EVT_WITHDRAW_OVERRIDE_CLAIMED:
174
183
  // Rust: vault_id(8), manager(32), user(32), shares(8), net_base(8),
175
- // withdrawal_fee_base(8), epoch_id(4), daily_withdrawn(8), daily_limit(8)
176
- return data.length >= 117 ? {
184
+ // withdrawal_fee_base(8), epoch_id(4), daily_withdrawn(8), daily_limit(8),
185
+ // wr(32), recipient(32), destination(32) wr/recipient/destination
186
+ // appended (withdrawal-freeze feature).
187
+ return data.length >= 213 ? {
177
188
  name: "WithdrawOverrideClaimed", vaultId: (0, buffer_1.readU64)(data, 1), manager: (0, buffer_1.readPubkey)(data, 9),
178
189
  user: (0, buffer_1.readPubkey)(data, 41), shares: (0, buffer_1.readU64)(data, 73), baseAmount: (0, buffer_1.readU64)(data, 81),
179
190
  withdrawalFeeBase: (0, buffer_1.readU64)(data, 89), epochId: (0, buffer_1.readU32)(data, 97),
180
191
  dailyWithdrawnBase: (0, buffer_1.readU64)(data, 101), dailyLimit: (0, buffer_1.readU64)(data, 109),
192
+ wr: (0, buffer_1.readPubkey)(data, 117), recipient: (0, buffer_1.readPubkey)(data, 149), destination: (0, buffer_1.readPubkey)(data, 181),
181
193
  } : null;
182
194
  case constants_1.EVT_LEND_MIGRATED:
183
195
  return data.length >= 89 ? {
@@ -185,19 +197,21 @@ function parseEventBuffer(data) {
185
197
  newLendProgram: (0, buffer_1.readPubkey)(data, 41), sharesWithdrawn: (0, buffer_1.readU64)(data, 73), baseMigrated: (0, buffer_1.readU64)(data, 81),
186
198
  } : null;
187
199
  case constants_1.EVT_MIGRATE_REQUESTED:
188
- // Rust: src_vault_id(8), dest_vault_id(8), user(32), shares(8), epoch_id(4)
189
- return data.length >= 61 ? {
200
+ // Rust: src_vault_id(8), dest_vault_id(8), user(32), shares(8), epoch_id(4), wr(32) — wr appended.
201
+ return data.length >= 93 ? {
190
202
  name: "MigrateRequested", srcVaultId: (0, buffer_1.readU64)(data, 1), destVaultId: (0, buffer_1.readU64)(data, 9),
191
203
  user: (0, buffer_1.readPubkey)(data, 17), shares: (0, buffer_1.readU64)(data, 49), epochId: (0, buffer_1.readU32)(data, 57),
204
+ wr: (0, buffer_1.readPubkey)(data, 61),
192
205
  } : null;
193
206
  case constants_1.EVT_MIGRATE_EXECUTED:
194
207
  // Rust: src_vault_id(8), dst_vault_id(8), user(32), shares_burned(8), base_moved(8),
195
- // withdrawal_fee_base(8), deposit_fee_base(8), user_dest_shares(8), epoch_id(4)
196
- return data.length >= 93 ? {
208
+ // withdrawal_fee_base(8), deposit_fee_base(8), user_dest_shares(8), epoch_id(4),
209
+ // wr(32) wr appended.
210
+ return data.length >= 125 ? {
197
211
  name: "MigrateExecuted", srcVaultId: (0, buffer_1.readU64)(data, 1), destVaultId: (0, buffer_1.readU64)(data, 9),
198
212
  user: (0, buffer_1.readPubkey)(data, 17), sharesBurned: (0, buffer_1.readU64)(data, 49), baseMoved: (0, buffer_1.readU64)(data, 57),
199
213
  withdrawalFeeBase: (0, buffer_1.readU64)(data, 65), depositFeeBase: (0, buffer_1.readU64)(data, 73),
200
- userDestShares: (0, buffer_1.readU64)(data, 81), epochId: (0, buffer_1.readU32)(data, 89),
214
+ userDestShares: (0, buffer_1.readU64)(data, 81), epochId: (0, buffer_1.readU32)(data, 89), wr: (0, buffer_1.readPubkey)(data, 93),
201
215
  } : null;
202
216
  case constants_1.EVT_DELAYED_DEPOSIT_PROCESSED:
203
217
  return data.length >= 77 ? {
@@ -240,6 +254,30 @@ function parseEventBuffer(data) {
240
254
  name: "CircuitBreakerTripped", vaultId: (0, buffer_1.readU64)(data, 1), trippedBy: (0, buffer_1.readPubkey)(data, 9),
241
255
  authorityKind: data[41],
242
256
  } : null;
257
+ case constants_1.EVT_WITHDRAW_REQUEST_FROZEN:
258
+ // Rust: wr(32), stv(32), user(32), epoch_id(4), authority(32), authority_kind(1)
259
+ // → total payload 134 bytes. authorityKind: 0=user, 1=manager
260
+ // (FREEZE_AUTHORITY_KIND_* in constants.ts).
261
+ return data.length >= 134 ? {
262
+ name: "WithdrawRequestFrozen", wr: (0, buffer_1.readPubkey)(data, 1), stv: (0, buffer_1.readPubkey)(data, 33),
263
+ user: (0, buffer_1.readPubkey)(data, 65), epochId: (0, buffer_1.readU32)(data, 97), authority: (0, buffer_1.readPubkey)(data, 101),
264
+ authorityKind: data[133],
265
+ } : null;
266
+ case constants_1.EVT_WITHDRAW_REQUEST_UNFROZEN:
267
+ // Rust: wr(32), stv(32), user(32), epoch_id(4), authority(32) → total payload 133 bytes.
268
+ return data.length >= 133 ? {
269
+ name: "WithdrawRequestUnfrozen", wr: (0, buffer_1.readPubkey)(data, 1), stv: (0, buffer_1.readPubkey)(data, 33),
270
+ user: (0, buffer_1.readPubkey)(data, 65), epochId: (0, buffer_1.readU32)(data, 97), authority: (0, buffer_1.readPubkey)(data, 101),
271
+ } : null;
272
+ case constants_1.EVT_CLAIM_RECIPIENT_SET:
273
+ // Rust: wr(32), stv(32), user(32), epoch_id(4), recipient(32), approver_kind(1), armed(1)
274
+ // → total payload 135 bytes. approverKind: 0=user, 1=cold
275
+ // (RECIPIENT_APPROVER_KIND_* in constants.ts).
276
+ return data.length >= 135 ? {
277
+ name: "ClaimRecipientSet", wr: (0, buffer_1.readPubkey)(data, 1), stv: (0, buffer_1.readPubkey)(data, 33),
278
+ user: (0, buffer_1.readPubkey)(data, 65), epochId: (0, buffer_1.readU32)(data, 97), recipient: (0, buffer_1.readPubkey)(data, 101),
279
+ approverKind: data[133], armed: data[134] !== 0,
280
+ } : null;
243
281
  default:
244
282
  return null;
245
283
  }
@@ -216,7 +216,7 @@ export interface OverrideClaimWithdrawArgs {
216
216
  authority: PublicKey;
217
217
  /** GlobalConfig PDA — checked for CFG_FLAG_GLOBAL_PAUSED */
218
218
  config: PublicKey;
219
- /** WithdrawRequest owner. NOT a signer authority acts on their behalf. */
219
+ /** WithdrawRequest owner. Signer MUST be distinct from `authority` (the on-chain handler rejects authority == user). */
220
220
  user: PublicKey;
221
221
  stv: PublicKey;
222
222
  withdrawRequest: PublicKey;
@@ -635,3 +635,34 @@ export interface TripCircuitBreakerArgs {
635
635
  managerRole?: PublicKey;
636
636
  }
637
637
  export declare function createTripCircuitBreakerIx(args: TripCircuitBreakerArgs, programId?: PublicKey): TransactionInstruction;
638
+ export interface FreezeWithdrawRequestArgs {
639
+ /** Self-freeze: the WR's own `user`. Manager path: any valid manager for the STV. */
640
+ authority: PublicKey;
641
+ stv: PublicKey;
642
+ withdrawRequest: PublicKey;
643
+ /**
644
+ * ManagerRole PDA for (stv, authority). ALWAYS required on the wire (FIXED
645
+ * 4-account ABI) — the handler destructures exactly 4 accounts regardless
646
+ * of path. Pass a real ManagerRole PDA when `authority` is a manager;
647
+ * pass any account (e.g. `findStvManagerRolePda(stv, authority)`) as an
648
+ * unread placeholder when self-freezing (`authority === wr.user`).
649
+ */
650
+ managerRole: PublicKey;
651
+ }
652
+ export declare function createFreezeWithdrawRequestIx(args: FreezeWithdrawRequestArgs, programId?: PublicKey): TransactionInstruction;
653
+ export interface UnfreezeWithdrawRequestArgs {
654
+ /** stv.coldAdmin (always) or stv.warmAdminEffective() (only when the redirect is armed). */
655
+ authority: PublicKey;
656
+ stv: PublicKey;
657
+ withdrawRequest: PublicKey;
658
+ }
659
+ export declare function createUnfreezeWithdrawRequestIx(args: UnfreezeWithdrawRequestArgs, programId?: PublicKey): TransactionInstruction;
660
+ export interface SetClaimRecipientArgs {
661
+ /** wr.user OR stv.coldAdmin. Never warm — see module doc. */
662
+ authority: PublicKey;
663
+ stv: PublicKey;
664
+ withdrawRequest: PublicKey;
665
+ /** Recipient to propose/approve. `PublicKey.default` resets (cold-only). */
666
+ recipient: PublicKey;
667
+ }
668
+ export declare function createSetClaimRecipientIx(args: SetClaimRecipientArgs, programId?: PublicKey): TransactionInstruction;
@@ -24,6 +24,9 @@ exports.createInstantWithdrawIx = createInstantWithdrawIx;
24
24
  exports.createSeedStvIx = createSeedStvIx;
25
25
  exports.createAcceptVaultAdminIx = createAcceptVaultAdminIx;
26
26
  exports.createTripCircuitBreakerIx = createTripCircuitBreakerIx;
27
+ exports.createFreezeWithdrawRequestIx = createFreezeWithdrawRequestIx;
28
+ exports.createUnfreezeWithdrawRequestIx = createUnfreezeWithdrawRequestIx;
29
+ exports.createSetClaimRecipientIx = createSetClaimRecipientIx;
27
30
  const web3_js_1 = require("@solana/web3.js");
28
31
  const spl_token_1 = require("@solana/spl-token");
29
32
  const buffer_1 = require("../common/buffer");
@@ -217,13 +220,15 @@ function createOverrideClaimWithdrawIx(args, programId = constants_1.PROGRAM_ID)
217
220
  const { authority, config, user, stv, withdrawRequest, vaultAta, userBaseAta, feeReceiverBaseAta, baseMint, tokenProgram, remainingAccounts = [], autoUnrouteCount = 0, protocolAumCount = 0, } = args;
218
221
  // Data: auto_unroute_count(1) + protocol_aum_count(1) — protocol_aum_count is data[1],
219
222
  // so always serialize both bytes (handler reads data[0]/data[1]). Data layout
220
- // is unchanged by warm-admin-guardrails — only the account list changed.
223
+ // is unchanged by warm-admin-guardrails / withdrawal-freeze — only the
224
+ // account list (and user's signer flag) changed.
221
225
  const data = Buffer.from([constants_1.IX_OVERRIDE_CLAIM_WITHDRAW, autoUnrouteCount & 0xff, protocolAumCount & 0xff]);
222
226
  return new web3_js_1.TransactionInstruction({
223
227
  keys: [
224
228
  { pubkey: authority, isSigner: true, isWritable: false },
225
229
  { pubkey: config, isSigner: false, isWritable: false },
226
- { pubkey: user, isSigner: false, isWritable: true },
230
+ // user is now a required signer, distinct from authority — see module doc.
231
+ { pubkey: user, isSigner: true, isWritable: true },
227
232
  { pubkey: stv, isSigner: false, isWritable: true },
228
233
  { pubkey: withdrawRequest, isSigner: false, isWritable: true },
229
234
  { pubkey: vaultAta, isSigner: false, isWritable: true },
@@ -668,3 +673,44 @@ function createTripCircuitBreakerIx(args, programId = constants_1.PROGRAM_ID) {
668
673
  data: Buffer.from([constants_1.IX_TRIP_CIRCUIT_BREAKER]),
669
674
  });
670
675
  }
676
+ function createFreezeWithdrawRequestIx(args, programId = constants_1.PROGRAM_ID) {
677
+ const { authority, stv, withdrawRequest, managerRole } = args;
678
+ return new web3_js_1.TransactionInstruction({
679
+ keys: [
680
+ { pubkey: authority, isSigner: true, isWritable: false },
681
+ { pubkey: stv, isSigner: false, isWritable: false },
682
+ { pubkey: withdrawRequest, isSigner: false, isWritable: true },
683
+ { pubkey: managerRole, isSigner: false, isWritable: false },
684
+ ],
685
+ programId,
686
+ data: Buffer.from([constants_1.IX_FREEZE_WITHDRAW_REQUEST]),
687
+ });
688
+ }
689
+ function createUnfreezeWithdrawRequestIx(args, programId = constants_1.PROGRAM_ID) {
690
+ const { authority, stv, withdrawRequest } = args;
691
+ return new web3_js_1.TransactionInstruction({
692
+ keys: [
693
+ { pubkey: authority, isSigner: true, isWritable: false },
694
+ { pubkey: stv, isSigner: false, isWritable: false },
695
+ { pubkey: withdrawRequest, isSigner: false, isWritable: true },
696
+ ],
697
+ programId,
698
+ data: Buffer.from([constants_1.IX_UNFREEZE_WITHDRAW_REQUEST]),
699
+ });
700
+ }
701
+ function createSetClaimRecipientIx(args, programId = constants_1.PROGRAM_ID) {
702
+ const { authority, stv, withdrawRequest, recipient } = args;
703
+ const data = Buffer.concat([
704
+ Buffer.from([constants_1.IX_SET_CLAIM_RECIPIENT]),
705
+ recipient.toBuffer(),
706
+ ]);
707
+ return new web3_js_1.TransactionInstruction({
708
+ keys: [
709
+ { pubkey: authority, isSigner: true, isWritable: false },
710
+ { pubkey: stv, isSigner: false, isWritable: false },
711
+ { pubkey: withdrawRequest, isSigner: false, isWritable: true },
712
+ ],
713
+ programId,
714
+ data,
715
+ });
716
+ }
@@ -125,6 +125,14 @@ export interface WithdrawRequest {
125
125
  destVaultId: BN;
126
126
  /** Migrate kind: minimum dest shares (slippage protection). Withdraw kind: 0. */
127
127
  minDestShares: BN;
128
+ /**
129
+ * Incident-response redirect target for `execute_claim` payout (offset 120).
130
+ * `PublicKey.default` (`[0;32]`, the deployed zero state) = pay `user`,
131
+ * exactly today's behavior. A redirect is ARMED iff this is nonzero AND both
132
+ * `WR_FLAG_RECIPIENT_USER_OK` and `WR_FLAG_RECIPIENT_COLD_OK` are set on
133
+ * `requestFlags` — see `isRedirectArmed` (accounts.ts).
134
+ */
135
+ claimRecipient: PublicKey;
128
136
  }
129
137
  export interface DelayedDepositRequest {
130
138
  stv: PublicKey;
@@ -183,6 +191,8 @@ export interface WithdrawRequestedEvent {
183
191
  user: PublicKey;
184
192
  shares: BN;
185
193
  epochId: number;
194
+ /** WithdrawRequest PDA (appended field, withdrawal-freeze feature). */
195
+ wr: PublicKey;
186
196
  }
187
197
  export interface WithdrawRequestIncreasedEvent {
188
198
  name: "WithdrawRequestIncreased";
@@ -191,6 +201,8 @@ export interface WithdrawRequestIncreasedEvent {
191
201
  additionalShares: BN;
192
202
  totalShares: BN;
193
203
  epochId: number;
204
+ /** WithdrawRequest PDA (appended field, withdrawal-freeze feature). */
205
+ wr: PublicKey;
194
206
  }
195
207
  export interface WithdrawClaimedEvent {
196
208
  name: "WithdrawClaimed";
@@ -200,6 +212,15 @@ export interface WithdrawClaimedEvent {
200
212
  baseAmount: BN;
201
213
  withdrawalFeeBase: BN;
202
214
  epochId: number;
215
+ /** WithdrawRequest PDA (appended field, withdrawal-freeze feature). */
216
+ wr: PublicKey;
217
+ /**
218
+ * Payout target: `user` unless the redirect was ARMED at claim time, in
219
+ * which case this is the stored `claimRecipient`.
220
+ */
221
+ recipient: PublicKey;
222
+ /** ATA the payout was actually sent to: `ATA(recipient, baseMint)`. */
223
+ destination: PublicKey;
203
224
  }
204
225
  export interface DepositedToStrategyEvent {
205
226
  name: "DepositedToStrategy";
@@ -268,6 +289,15 @@ export interface WithdrawOverrideClaimedEvent {
268
289
  epochId: number;
269
290
  dailyWithdrawnBase: BN;
270
291
  dailyLimit: BN;
292
+ /** WithdrawRequest PDA (appended field, withdrawal-freeze feature). */
293
+ wr: PublicKey;
294
+ /**
295
+ * Payout target: `user` unless the redirect was ARMED at claim time, in
296
+ * which case this is the stored `claimRecipient`.
297
+ */
298
+ recipient: PublicKey;
299
+ /** ATA the payout was actually sent to: `ATA(recipient, baseMint)`. */
300
+ destination: PublicKey;
271
301
  }
272
302
  export interface LendMigratedEvent {
273
303
  name: "LendMigrated";
@@ -284,6 +314,8 @@ export interface MigrateRequestedEvent {
284
314
  user: PublicKey;
285
315
  shares: BN;
286
316
  epochId: number;
317
+ /** WithdrawRequest (migrate-kind) PDA (appended field, withdrawal-freeze feature). */
318
+ wr: PublicKey;
287
319
  }
288
320
  export interface MigrateExecutedEvent {
289
321
  name: "MigrateExecuted";
@@ -296,6 +328,8 @@ export interface MigrateExecutedEvent {
296
328
  depositFeeBase: BN;
297
329
  userDestShares: BN;
298
330
  epochId: number;
331
+ /** WithdrawRequest (migrate-kind) PDA (appended field, withdrawal-freeze feature). */
332
+ wr: PublicKey;
299
333
  }
300
334
  export interface DelayedDepositProcessedEvent {
301
335
  name: "DelayedDepositProcessed";
@@ -358,4 +392,43 @@ export interface CircuitBreakerTrippedEvent {
358
392
  trippedBy: PublicKey;
359
393
  authorityKind: number;
360
394
  }
361
- export type StvEvent = ConfigInitializedEvent | ConfigUpdatedEvent | StvCreatedEvent | StvUpdatedEvent | DepositedEvent | WithdrawRequestedEvent | WithdrawRequestIncreasedEvent | WithdrawClaimedEvent | DepositedToStrategyEvent | WithdrawnFromStrategyEvent | EpochProcessedEvent | EpochAdvancedEvent | FeesSettledEvent | StvClosedEvent | ManagerAddedEvent | ManagerRemovedEvent | WithdrawOverrideClaimedEvent | LendMigratedEvent | MigrateRequestedEvent | MigrateExecutedEvent | DelayedDepositProcessedEvent | InstantWithdrawnEvent | StvSeededEvent | AdminRotationStartedEvent | AdminRotationAcceptedEvent | WarmAdminSetEvent | CircuitBreakerTrippedEvent;
395
+ /**
396
+ * Emitted by `freeze_withdraw_request` (disc 22).
397
+ * `authorityKind`: 0 = user (self-freeze), 1 = manager
398
+ * (see `FREEZE_AUTHORITY_KIND_*` in constants.ts).
399
+ */
400
+ export interface WithdrawRequestFrozenEvent {
401
+ name: "WithdrawRequestFrozen";
402
+ wr: PublicKey;
403
+ stv: PublicKey;
404
+ user: PublicKey;
405
+ epochId: number;
406
+ authority: PublicKey;
407
+ authorityKind: number;
408
+ }
409
+ /** Emitted by `unfreeze_withdraw_request` (disc 23). */
410
+ export interface WithdrawRequestUnfrozenEvent {
411
+ name: "WithdrawRequestUnfrozen";
412
+ wr: PublicKey;
413
+ stv: PublicKey;
414
+ user: PublicKey;
415
+ epochId: number;
416
+ authority: PublicKey;
417
+ }
418
+ /**
419
+ * Emitted by `set_claim_recipient` (disc 24).
420
+ * `approverKind`: 0 = user, 1 = cold (see `RECIPIENT_APPROVER_KIND_*` in
421
+ * constants.ts). `armed` mirrors `WithdrawRequest.isRedirectArmed()` as of
422
+ * this call.
423
+ */
424
+ export interface ClaimRecipientSetEvent {
425
+ name: "ClaimRecipientSet";
426
+ wr: PublicKey;
427
+ stv: PublicKey;
428
+ user: PublicKey;
429
+ epochId: number;
430
+ recipient: PublicKey;
431
+ approverKind: number;
432
+ armed: boolean;
433
+ }
434
+ export type StvEvent = ConfigInitializedEvent | ConfigUpdatedEvent | StvCreatedEvent | StvUpdatedEvent | DepositedEvent | WithdrawRequestedEvent | WithdrawRequestIncreasedEvent | WithdrawClaimedEvent | DepositedToStrategyEvent | WithdrawnFromStrategyEvent | EpochProcessedEvent | EpochAdvancedEvent | FeesSettledEvent | StvClosedEvent | ManagerAddedEvent | ManagerRemovedEvent | WithdrawOverrideClaimedEvent | LendMigratedEvent | MigrateRequestedEvent | MigrateExecutedEvent | DelayedDepositProcessedEvent | InstantWithdrawnEvent | StvSeededEvent | AdminRotationStartedEvent | AdminRotationAcceptedEvent | WarmAdminSetEvent | CircuitBreakerTrippedEvent | WithdrawRequestFrozenEvent | WithdrawRequestUnfrozenEvent | ClaimRecipientSetEvent;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elmntl/jlpd-sdk",
3
- "version": "0.13.5",
4
- "description": "SDK for JLP.D (JLP Deconstructed) by Elemental \u2014 p-stv-core, jlpd-strategy, and elemental-lend clients",
3
+ "version": "0.13.7",
4
+ "description": "SDK for JLP.D (JLP Deconstructed) by Elemental p-stv-core, jlpd-strategy, and elemental-lend clients",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
7
7
  "solana",
@@ -70,4 +70,4 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  }
73
- }
73
+ }