@elmntl/jlpd-sdk 0.13.6 → 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)`
@@ -10,6 +10,7 @@
10
10
  import { PublicKey } from "@solana/web3.js";
11
11
  import type { SolanaConnection } from "../common/connection";
12
12
  import { type HedgeState, type HedgePositionSlot } from "./hedge-state";
13
+ import { type FluidPosition } from "./fluid-view";
13
14
  /**
14
15
  * Native decimals for each known hedge debt mint — mirrors `utils/oracle.rs`'s
15
16
  * `TOKEN_DECIMALS_{BTC,ETH,SOL,JUPUSD}` constants (the four mints
@@ -62,6 +63,43 @@ export declare function fluidPartialRepayCreditRaw(xNative: bigint, debtDecimals
62
63
  * minimum position debt). `netRawDebt` = tick-implied − dust (fluid-view's `netDebtRaw` basis).
63
64
  */
64
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;
65
103
  /**
66
104
  * Largest partial repay (native) the on-chain acceptance admits for `netRawDebt` at the given
67
105
  * stored price — the driver's sizing cap (anything larger reverts; anything at/below is safe at
@@ -18,6 +18,8 @@ exports.clampRepayAmount = clampRepayAmount;
18
18
  exports.fluidMinDebtRaw = fluidMinDebtRaw;
19
19
  exports.fluidPartialRepayCreditRaw = fluidPartialRepayCreditRaw;
20
20
  exports.isPartialRepayAcceptable = isPartialRepayAcceptable;
21
+ exports.evaluateFluidPartialRepay = evaluateFluidPartialRepay;
22
+ exports.maxAcceptedFluidPartialRepay = maxAcceptedFluidPartialRepay;
21
23
  exports.maxPartialRepayNative = maxPartialRepayNative;
22
24
  exports.fetchHedgeSlotDerived = fetchHedgeSlotDerived;
23
25
  exports.fetchAllHedgeSlotsDerived = fetchAllHedgeSlotsDerived;
@@ -157,6 +159,70 @@ function isPartialRepayAcceptable(xNative, netRawDebt, debtDecimals, borrowExPri
157
159
  }
158
160
  return credit >= 1n && credit + fluidMinDebtRaw(debtDecimals) <= netRawDebt;
159
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
+ }
160
226
  /**
161
227
  * Largest partial repay (native) the on-chain acceptance admits for `netRawDebt` at the given
162
228
  * stored price — the driver's sizing cap (anything larger reverts; anything at/below is safe at
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elmntl/jlpd-sdk",
3
- "version": "0.13.6",
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
+ }