@elmntl/jlpd-sdk 0.2.0 → 0.13.5

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 (115) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +185 -404
  3. package/dist/common/ata.d.ts +26 -0
  4. package/dist/common/ata.js +48 -0
  5. package/dist/common/buffer.d.ts +15 -0
  6. package/dist/common/buffer.js +118 -0
  7. package/dist/common/connection.d.ts +13 -0
  8. package/dist/common/connection.js +2 -0
  9. package/dist/common/constants.d.ts +14 -0
  10. package/dist/common/constants.js +31 -0
  11. package/dist/common/index.d.ts +6 -0
  12. package/dist/common/index.js +28 -0
  13. package/dist/common/strategy-interface.d.ts +59 -0
  14. package/dist/common/strategy-interface.js +42 -0
  15. package/dist/elemental-lend/accounts.d.ts +103 -0
  16. package/dist/elemental-lend/accounts.js +354 -0
  17. package/dist/elemental-lend/constants.d.ts +25 -0
  18. package/dist/elemental-lend/constants.js +48 -0
  19. package/dist/elemental-lend/index.d.ts +8 -0
  20. package/dist/elemental-lend/index.js +24 -0
  21. package/dist/elemental-lend/instructions.d.ts +104 -0
  22. package/dist/elemental-lend/instructions.js +266 -0
  23. package/dist/elemental-lend/jupiter-lend.d.ts +91 -0
  24. package/dist/elemental-lend/jupiter-lend.js +189 -0
  25. package/dist/elemental-lend/kamino-vault.d.ts +173 -0
  26. package/dist/elemental-lend/kamino-vault.js +483 -0
  27. package/dist/elemental-lend/pda.d.ts +12 -0
  28. package/dist/elemental-lend/pda.js +24 -0
  29. package/dist/elemental-lend/protocol-actions.d.ts +56 -0
  30. package/dist/elemental-lend/protocol-actions.js +244 -0
  31. package/dist/elemental-lend/types.d.ts +113 -0
  32. package/dist/elemental-lend/types.js +2 -0
  33. package/dist/elemental-lend-v2/accounts.d.ts +14 -0
  34. package/dist/elemental-lend-v2/accounts.js +136 -0
  35. package/dist/elemental-lend-v2/adapters.d.ts +22 -0
  36. package/dist/elemental-lend-v2/adapters.js +50 -0
  37. package/dist/elemental-lend-v2/constants.d.ts +48 -0
  38. package/dist/elemental-lend-v2/constants.js +104 -0
  39. package/dist/elemental-lend-v2/index.d.ts +8 -0
  40. package/dist/elemental-lend-v2/index.js +24 -0
  41. package/dist/elemental-lend-v2/instructions.d.ts +96 -0
  42. package/dist/elemental-lend-v2/instructions.js +190 -0
  43. package/dist/elemental-lend-v2/lut.d.ts +20 -0
  44. package/dist/elemental-lend-v2/lut.js +65 -0
  45. package/dist/elemental-lend-v2/pda.d.ts +9 -0
  46. package/dist/elemental-lend-v2/pda.js +27 -0
  47. package/dist/elemental-lend-v2/types.d.ts +105 -0
  48. package/dist/elemental-lend-v2/types.js +2 -0
  49. package/dist/elemental-lend-v2/update-aum.d.ts +29 -0
  50. package/dist/elemental-lend-v2/update-aum.js +82 -0
  51. package/dist/index.d.ts +5 -4199
  52. package/dist/index.js +37 -5187
  53. package/dist/jlpd-strategy/accounts.d.ts +83 -0
  54. package/dist/jlpd-strategy/accounts.js +216 -0
  55. package/dist/jlpd-strategy/adapter.d.ts +81 -0
  56. package/dist/jlpd-strategy/adapter.js +118 -0
  57. package/dist/jlpd-strategy/base-to-base-swap.d.ts +74 -0
  58. package/dist/jlpd-strategy/base-to-base-swap.js +205 -0
  59. package/dist/jlpd-strategy/constants.d.ts +127 -0
  60. package/dist/jlpd-strategy/constants.js +174 -0
  61. package/dist/jlpd-strategy/fluid-view.d.ts +199 -0
  62. package/dist/jlpd-strategy/fluid-view.js +799 -0
  63. package/dist/jlpd-strategy/hedge-derived.d.ts +135 -0
  64. package/dist/jlpd-strategy/hedge-derived.js +231 -0
  65. package/dist/jlpd-strategy/hedge-instructions.d.ts +206 -0
  66. package/dist/jlpd-strategy/hedge-instructions.js +296 -0
  67. package/dist/jlpd-strategy/hedge-state.d.ts +88 -0
  68. package/dist/jlpd-strategy/hedge-state.js +110 -0
  69. package/dist/jlpd-strategy/index.d.ts +17 -0
  70. package/dist/jlpd-strategy/index.js +33 -0
  71. package/dist/jlpd-strategy/instructions.d.ts +159 -0
  72. package/dist/jlpd-strategy/instructions.js +234 -0
  73. package/dist/jlpd-strategy/jlp-borrow.d.ts +63 -0
  74. package/dist/jlpd-strategy/jlp-borrow.js +87 -0
  75. package/dist/jlpd-strategy/jlp-data.d.ts +166 -0
  76. package/dist/jlpd-strategy/jlp-data.js +611 -0
  77. package/dist/jlpd-strategy/jupusd-earn.d.ts +90 -0
  78. package/dist/jlpd-strategy/jupusd-earn.js +166 -0
  79. package/dist/jlpd-strategy/live-jlp-price.d.ts +46 -0
  80. package/dist/jlpd-strategy/live-jlp-price.js +267 -0
  81. package/dist/jlpd-strategy/pda.d.ts +13 -0
  82. package/dist/jlpd-strategy/pda.js +28 -0
  83. package/dist/jlpd-strategy/settle-yield.d.ts +45 -0
  84. package/dist/jlpd-strategy/settle-yield.js +113 -0
  85. package/dist/jlpd-strategy/swap-jlp.d.ts +198 -0
  86. package/dist/jlpd-strategy/swap-jlp.js +586 -0
  87. package/dist/jlpd-strategy/types.d.ts +101 -0
  88. package/dist/jlpd-strategy/types.js +29 -0
  89. package/dist/p-stv-core/accounts.d.ts +152 -0
  90. package/dist/p-stv-core/accounts.js +407 -0
  91. package/dist/p-stv-core/constants.d.ts +138 -0
  92. package/dist/p-stv-core/constants.js +181 -0
  93. package/dist/p-stv-core/events.d.ts +7 -0
  94. package/dist/p-stv-core/events.js +246 -0
  95. package/dist/p-stv-core/index.d.ts +11 -0
  96. package/dist/p-stv-core/index.js +27 -0
  97. package/dist/p-stv-core/instructions.d.ts +637 -0
  98. package/dist/p-stv-core/instructions.js +670 -0
  99. package/dist/p-stv-core/lut.d.ts +41 -0
  100. package/dist/p-stv-core/lut.js +81 -0
  101. package/dist/p-stv-core/pda.d.ts +30 -0
  102. package/dist/p-stv-core/pda.js +65 -0
  103. package/dist/p-stv-core/prices.d.ts +20 -0
  104. package/dist/p-stv-core/prices.js +77 -0
  105. package/dist/p-stv-core/remaining-accounts.d.ts +107 -0
  106. package/dist/p-stv-core/remaining-accounts.js +326 -0
  107. package/dist/p-stv-core/send-tx.d.ts +72 -0
  108. package/dist/p-stv-core/send-tx.js +290 -0
  109. package/dist/p-stv-core/sol-wrap.d.ts +34 -0
  110. package/dist/p-stv-core/sol-wrap.js +74 -0
  111. package/dist/p-stv-core/types.d.ts +361 -0
  112. package/dist/p-stv-core/types.js +2 -0
  113. package/package.json +56 -15
  114. package/dist/index.d.mts +0 -4199
  115. package/dist/index.mjs +0 -5090
@@ -0,0 +1,611 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__test__resetPricesCache = __test__resetPricesCache;
4
+ exports.fetchJlpPoolDetails = fetchJlpPoolDetails;
5
+ exports.computeAddLiquidityFee = computeAddLiquidityFee;
6
+ exports.fetchJlpCustodyData = fetchJlpCustodyData;
7
+ exports.computeEffectiveUsd = computeEffectiveUsd;
8
+ exports.fetchJlpEffectiveWeights = fetchJlpEffectiveWeights;
9
+ const web3_js_1 = require("@solana/web3.js");
10
+ const constants_1 = require("./constants");
11
+ const CUSTODIES = [
12
+ {
13
+ name: "SOL",
14
+ address: new web3_js_1.PublicKey("7xS2gz2bTp3fwCC7knJvUWTEU9Tycczu6VhJYKgi1wdz"),
15
+ decimals: 9,
16
+ isStable: false,
17
+ mintAddress: "So11111111111111111111111111111111111111112",
18
+ },
19
+ {
20
+ name: "ETH",
21
+ address: new web3_js_1.PublicKey("AQCGyheWPLeo6Qp9WpYS9m3Qj479t7R636N9ey1rEjEn"),
22
+ decimals: 8,
23
+ isStable: false,
24
+ mintAddress: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
25
+ },
26
+ {
27
+ name: "WBTC",
28
+ address: new web3_js_1.PublicKey("5Pv3gM9JrFFH883SWAhvJC9RPYmo8UNxuFtv5bMMALkm"),
29
+ decimals: 8,
30
+ isStable: false,
31
+ mintAddress: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh",
32
+ },
33
+ {
34
+ name: "USDC",
35
+ address: new web3_js_1.PublicKey("G18jKKXQwBbrHeiK3C9MRXhkHsLHf7XgCSisykV46EZa"),
36
+ decimals: 6,
37
+ isStable: true,
38
+ mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
39
+ },
40
+ {
41
+ name: "USDT",
42
+ address: new web3_js_1.PublicKey("4vkNeXiYEUizLdrpdPS1eC2mccyM4NUPRtERrk6ZETkk"),
43
+ decimals: 6,
44
+ isStable: true,
45
+ mintAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
46
+ },
47
+ ];
48
+ // ---------------------------------------------------------------------------
49
+ // Custody account byte offsets (Jupiter Perps on-chain layout)
50
+ // ---------------------------------------------------------------------------
51
+ const OFFSET_TARGET_RATIO_BPS = 206; // u64 LE, custody target weight (right before assets)
52
+ const OFFSET_ASSETS = 214;
53
+ const OFFSET_ASSETS_OWNED = OFFSET_ASSETS + 8;
54
+ const OFFSET_ASSETS_LOCKED = OFFSET_ASSETS + 16;
55
+ const OFFSET_ASSETS_GUARANTEED_USD = OFFSET_ASSETS + 24;
56
+ const OFFSET_ASSETS_GLOBAL_SHORT_SIZES = OFFSET_ASSETS + 32;
57
+ const OFFSET_ASSETS_GLOBAL_SHORT_AVG_PX = OFFSET_ASSETS + 40;
58
+ const OFFSET_DEBT = 1004;
59
+ const OFFSET_BORROW_LEND_INTERESTS_ACCRUED = 1020;
60
+ const SCALE_USD = 1e6;
61
+ const DEBT_MULTIPLIER = 1e9;
62
+ const JUPITER_PRICE_API = "https://lite-api.jup.ag/price/v3";
63
+ // ---------------------------------------------------------------------------
64
+ // Binary parsing helpers
65
+ // ---------------------------------------------------------------------------
66
+ function u64FromLE(buf, offset) {
67
+ let res = 0n;
68
+ for (let i = 0; i < 8; i++) {
69
+ res |= BigInt(buf[offset + i] ?? 0) << BigInt(8 * i);
70
+ }
71
+ return res;
72
+ }
73
+ function u128FromLE(buf, offset) {
74
+ let res = 0n;
75
+ for (let i = 0; i < 16; i++) {
76
+ res |= BigInt(buf[offset + i] ?? 0) << BigInt(8 * i);
77
+ }
78
+ return res;
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Data fetching
82
+ // ---------------------------------------------------------------------------
83
+ async function fetchAllCustodies(connection) {
84
+ const addresses = CUSTODIES.map((c) => c.address);
85
+ const infos = await connection.getMultipleAccountsInfo(addresses, "confirmed");
86
+ return CUSTODIES.map((cfg, i) => {
87
+ const info = infos[i];
88
+ if (!info || !info.data) {
89
+ throw new Error(`No account data for custody ${cfg.name}`);
90
+ }
91
+ const data = info.data;
92
+ // Read target ratio — validated post-parse by checking sum ≈ 10000
93
+ const targetRatio = data.length >= OFFSET_TARGET_RATIO_BPS + 8
94
+ ? Number(u64FromLE(data, OFFSET_TARGET_RATIO_BPS))
95
+ : 0;
96
+ return {
97
+ cfg,
98
+ owned: u64FromLE(data, OFFSET_ASSETS_OWNED),
99
+ locked: u64FromLE(data, OFFSET_ASSETS_LOCKED),
100
+ guaranteedUsd: u64FromLE(data, OFFSET_ASSETS_GUARANTEED_USD),
101
+ globalShortSizes: u64FromLE(data, OFFSET_ASSETS_GLOBAL_SHORT_SIZES),
102
+ globalShortAveragePrices: u64FromLE(data, OFFSET_ASSETS_GLOBAL_SHORT_AVG_PX),
103
+ debt: u128FromLE(data, OFFSET_DEBT),
104
+ borrowLendInterestsAccrued: u128FromLE(data, OFFSET_BORROW_LEND_INTERESTS_ACCRUED),
105
+ targetRatioBps: targetRatio <= 10000 ? targetRatio : 0,
106
+ };
107
+ });
108
+ }
109
+ // Module-level memo for Jupiter price/v3 — 3 consumers in this file
110
+ // (fetchJlpCustodyData, fetchJlpPoolDetails, fetchJlpEffectiveWeights) used to
111
+ // each hit the API independently. A single jlp-pool-snapshot route would fire
112
+ // 3 redundant calls per cache miss. 30s TTL is short enough that prices stay
113
+ // fresh for downstream NAV/weight math, long enough to coalesce snapshot loads.
114
+ const PRICES_TTL_MS = 30000;
115
+ let pricesCache = null;
116
+ let pricesInFlight = null;
117
+ /**
118
+ * Reset the price memo. Intended for unit tests that mock global fetch with
119
+ * scenario-specific prices — module-level cache pollutes across tests when
120
+ * TTL has not expired, leading to assertion failures that depend on test
121
+ * ordering.
122
+ *
123
+ * Not part of the public surface for application code; cache TTL handles
124
+ * production. Exported under `__test__*` namespace convention so consumers
125
+ * tree-shake it out and never accidentally rely on cache invalidation.
126
+ */
127
+ function __test__resetPricesCache() {
128
+ pricesCache = null;
129
+ pricesInFlight = null;
130
+ }
131
+ async function fetchPrices() {
132
+ if (pricesCache && Date.now() - pricesCache.at < PRICES_TTL_MS) {
133
+ return pricesCache.data;
134
+ }
135
+ if (pricesInFlight)
136
+ return pricesInFlight;
137
+ const ids = CUSTODIES.map((c) => c.mintAddress).join(",");
138
+ const url = `${JUPITER_PRICE_API}?ids=${ids}`;
139
+ pricesInFlight = (async () => {
140
+ try {
141
+ const response = await fetch(url);
142
+ if (!response.ok) {
143
+ throw new Error(`Jupiter Price API error: ${response.status}`);
144
+ }
145
+ const json = (await response.json());
146
+ const prices = {};
147
+ for (const [mint, data] of Object.entries(json)) {
148
+ prices[mint] = data.usdPrice;
149
+ }
150
+ pricesCache = { at: Date.now(), data: prices };
151
+ return prices;
152
+ }
153
+ finally {
154
+ pricesInFlight = null;
155
+ }
156
+ })();
157
+ return pricesInFlight;
158
+ }
159
+ function calculateDebtTokens(raw) {
160
+ if (raw.debt <= raw.borrowLendInterestsAccrued)
161
+ return 0;
162
+ const netDebt = raw.debt - raw.borrowLendInterestsAccrued;
163
+ return Number(netDebt) / DEBT_MULTIPLIER / 10 ** raw.cfg.decimals;
164
+ }
165
+ function calculateCustodyAum(raw, priceUsd) {
166
+ const { cfg } = raw;
167
+ const tokScale = 10 ** cfg.decimals;
168
+ const ownedTokens = Number(raw.owned) / tokScale;
169
+ const lockedTokens = Number(raw.locked) / tokScale;
170
+ const debtTokens = calculateDebtTokens(raw);
171
+ const guaranteedUsd = Number(raw.guaranteedUsd) / SCALE_USD;
172
+ const shortSizeUsd = Number(raw.globalShortSizes) / SCALE_USD;
173
+ const shortAvgPxUsd = Number(raw.globalShortAveragePrices) / SCALE_USD;
174
+ // theoreticallyOwned = owned + debt (debt = lent-out tokens the pool still owns)
175
+ const theoreticallyOwned = ownedTokens + debtTokens;
176
+ const netAssetsTokens = Math.max(theoreticallyOwned - lockedTokens, 0);
177
+ const netAssetsUsd = netAssetsTokens * priceUsd;
178
+ // Short PnL from pool perspective (pool is counterparty)
179
+ let shortPnLUsd = 0;
180
+ if (shortSizeUsd > 0 && shortAvgPxUsd > 0 && priceUsd > 0) {
181
+ shortPnLUsd = (shortSizeUsd * (priceUsd - shortAvgPxUsd)) / shortAvgPxUsd;
182
+ }
183
+ let aumUsd = guaranteedUsd + netAssetsUsd + shortPnLUsd;
184
+ if (aumUsd < 0)
185
+ aumUsd = 0;
186
+ return {
187
+ name: cfg.name,
188
+ aumUsd,
189
+ weightBps: 0, // set in second pass
190
+ priceUsd,
191
+ };
192
+ }
193
+ // ---------------------------------------------------------------------------
194
+ // Public API
195
+ // ---------------------------------------------------------------------------
196
+ /**
197
+ * Fetch JLP pool custody data and compute AUM breakdown with per-custody weights.
198
+ *
199
+ * Reads custody accounts on-chain and fetches prices from Jupiter's price API.
200
+ * Returns the total AUM in USD and per-custody breakdown including weight in basis points.
201
+ */
202
+ // ---------------------------------------------------------------------------
203
+ // Pool Account Fee Parsing
204
+ // ---------------------------------------------------------------------------
205
+ /**
206
+ * Parse Pool account to extract aumUsd (u128) using the dynamic offset logic
207
+ * from oracle.rs. Returns the offset just past aumUsd and the parsed value.
208
+ */
209
+ function parsePoolAumUsd(data) {
210
+ if (data.length < 28)
211
+ return null;
212
+ let offset = 8; // skip discriminator
213
+ // Skip name string (4-byte len + utf8)
214
+ if (data.length < offset + 4)
215
+ return null;
216
+ const nameLen = data.readUInt32LE(offset);
217
+ if (nameLen > 64)
218
+ return null;
219
+ offset += 4 + nameLen;
220
+ // Skip custodies vec (4-byte len + Pubkey[])
221
+ if (data.length < offset + 4)
222
+ return null;
223
+ const custodiesLen = data.readUInt32LE(offset);
224
+ if (custodiesLen > 16)
225
+ return null;
226
+ offset += 4 + custodiesLen * 32;
227
+ // Read aumUsd (u128)
228
+ if (data.length < offset + 16)
229
+ return null;
230
+ const aumUsd = u128FromLE(data, offset);
231
+ offset += 16;
232
+ return { aumUsd, afterAumOffset: offset };
233
+ }
234
+ /**
235
+ * Try to parse the Fees struct from the Pool account data.
236
+ *
237
+ * After aumUsd, the Pool struct has a Limit struct followed by a Fees struct.
238
+ * The Limit struct size varies by program version, so we try multiple skip
239
+ * sizes and validate the parsed fee values.
240
+ *
241
+ * Fees layout (8 x u64):
242
+ * [0] increasePositionBps
243
+ * [1] decreasePositionBps
244
+ * [2] addRemoveLiquidityBps
245
+ * [3] swapBps
246
+ * [4] taxBps
247
+ * [5] stableSwapBps
248
+ * [6] stableSwapTaxBps
249
+ * [7] protocolShareBps
250
+ */
251
+ function tryParsePoolFees(data, afterAumOffset) {
252
+ // Try different Limit struct sizes (2-5 u128 fields = 32-80 bytes)
253
+ for (const limitSize of [32, 48, 64, 80]) {
254
+ const feesStart = afterAumOffset + limitSize;
255
+ if (data.length < feesStart + 64)
256
+ continue;
257
+ const increasePos = Number(u64FromLE(data, feesStart));
258
+ const decreasePos = Number(u64FromLE(data, feesStart + 8));
259
+ const addRemLiq = Number(u64FromLE(data, feesStart + 16));
260
+ const swapBps = Number(u64FromLE(data, feesStart + 24));
261
+ const taxBps = Number(u64FromLE(data, feesStart + 32));
262
+ const stableSwap = Number(u64FromLE(data, feesStart + 40));
263
+ const stableSwapTax = Number(u64FromLE(data, feesStart + 48));
264
+ const protocolShare = Number(u64FromLE(data, feesStart + 56));
265
+ // All fee fields should be in [0, 1000] bps range
266
+ const allFees = [increasePos, decreasePos, addRemLiq, swapBps, taxBps,
267
+ stableSwap, stableSwapTax, protocolShare];
268
+ const allValid = allFees.every((f) => f >= 0 && f <= 1000);
269
+ // At least addRemLiq or swapBps should be non-zero
270
+ if (allValid && (addRemLiq > 0 || swapBps > 0)) {
271
+ return {
272
+ addRemoveLiquidityBps: addRemLiq,
273
+ swapBps,
274
+ taxBps,
275
+ stableSwapBps: stableSwap,
276
+ stableSwapTaxBps: stableSwapTax,
277
+ };
278
+ }
279
+ }
280
+ return null;
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // Public: fetchJlpPoolDetails
284
+ // ---------------------------------------------------------------------------
285
+ /**
286
+ * Fetch full JLP pool details: fee parameters, per-custody target/current
287
+ * weights, pool AUM, and JLP price.
288
+ *
289
+ * Reads the Pool account for fees and aumUsd, Custody accounts for per-asset
290
+ * data including target weights, and the JLP mint for total supply.
291
+ */
292
+ async function fetchJlpPoolDetails(connection) {
293
+ const poolAddress = constants_1.JLP_POOL_ACCOUNT;
294
+ const jlpMintAddress = constants_1.JLP_MINT;
295
+ const custodyAddresses = CUSTODIES.map((c) => c.address);
296
+ // Fetch Pool, JLP Mint, all Custodies, and prices in parallel
297
+ const [poolInfo, mintInfo, custodyInfos, pricesByMint] = await Promise.all([
298
+ connection.getAccountInfo(poolAddress, "confirmed"),
299
+ connection.getAccountInfo(jlpMintAddress, "confirmed"),
300
+ connection.getMultipleAccountsInfo(custodyAddresses, "confirmed"),
301
+ fetchPrices(),
302
+ ]);
303
+ // Parse Pool aumUsd
304
+ if (!poolInfo?.data)
305
+ throw new Error("Failed to fetch JLP Pool account");
306
+ const poolData = poolInfo.data;
307
+ const poolParsed = parsePoolAumUsd(poolData);
308
+ if (!poolParsed)
309
+ throw new Error("Failed to parse JLP Pool aumUsd");
310
+ // Parse Pool fees (best-effort — returns null if layout unknown)
311
+ const fees = tryParsePoolFees(poolData, poolParsed.afterAumOffset);
312
+ // Parse JLP supply from mint account (offset 36, u64)
313
+ if (!mintInfo?.data || mintInfo.data.length < 44)
314
+ throw new Error("Failed to fetch JLP mint");
315
+ const mintData = mintInfo.data;
316
+ const jlpSupplyRaw = u64FromLE(mintData, 36);
317
+ const jlpSupply = Number(jlpSupplyRaw) / 1e6; // JLP has 6 decimals
318
+ // Pool AUM in USD (u128 scaled to 6 decimals)
319
+ const poolAumUsd = Number(poolParsed.aumUsd) / SCALE_USD;
320
+ // JLP price
321
+ const jlpPrice = jlpSupply > 0 ? poolAumUsd / jlpSupply : 0;
322
+ // Parse custody data with prices
323
+ const prices = {};
324
+ for (const cfg of CUSTODIES) {
325
+ const p = pricesByMint[cfg.mintAddress];
326
+ prices[cfg.name] = p ?? (cfg.isStable ? 1 : 0);
327
+ if (!p && !cfg.isStable) {
328
+ throw new Error(`Missing price for ${cfg.name}`);
329
+ }
330
+ }
331
+ const parsedCustodies = CUSTODIES.map((cfg, i) => {
332
+ const info = custodyInfos[i];
333
+ if (!info?.data)
334
+ throw new Error(`No account data for custody ${cfg.name}`);
335
+ const data = info.data;
336
+ return {
337
+ cfg,
338
+ owned: u64FromLE(data, OFFSET_ASSETS_OWNED),
339
+ locked: u64FromLE(data, OFFSET_ASSETS_LOCKED),
340
+ guaranteedUsd: u64FromLE(data, OFFSET_ASSETS_GUARANTEED_USD),
341
+ globalShortSizes: u64FromLE(data, OFFSET_ASSETS_GLOBAL_SHORT_SIZES),
342
+ globalShortAveragePrices: u64FromLE(data, OFFSET_ASSETS_GLOBAL_SHORT_AVG_PX),
343
+ debt: u128FromLE(data, OFFSET_DEBT),
344
+ borrowLendInterestsAccrued: u128FromLE(data, OFFSET_BORROW_LEND_INTERESTS_ACCRUED),
345
+ targetRatioBps: data.length >= OFFSET_TARGET_RATIO_BPS + 8
346
+ ? Number(u64FromLE(data, OFFSET_TARGET_RATIO_BPS))
347
+ : 0,
348
+ };
349
+ });
350
+ // Compute per-custody AUM
351
+ const aumResults = parsedCustodies.map((raw) => calculateCustodyAum(raw, prices[raw.cfg.name] ?? 1));
352
+ const totalAum = aumResults.reduce((sum, r) => sum + r.aumUsd, 0);
353
+ // Validate target ratios — sum should be ~10000
354
+ const targetSum = parsedCustodies.reduce((sum, c) => sum + c.targetRatioBps, 0);
355
+ const targetRatiosValid = targetSum >= 9900 && targetSum <= 10100;
356
+ const custodies = parsedCustodies.map((raw, i) => ({
357
+ mint: new web3_js_1.PublicKey(raw.cfg.mintAddress),
358
+ name: raw.cfg.name,
359
+ targetRatioBps: targetRatiosValid ? raw.targetRatioBps : 0,
360
+ currentRatioBps: totalAum > 0 ? Math.round((aumResults[i].aumUsd / totalAum) * 10000) : 0,
361
+ aumUsd: aumResults[i].aumUsd,
362
+ isStable: raw.cfg.isStable,
363
+ }));
364
+ return { fees, custodies, poolAumUsd, jlpPrice, jlpSupply };
365
+ }
366
+ // ---------------------------------------------------------------------------
367
+ // Public: computeAddLiquidityFee
368
+ // ---------------------------------------------------------------------------
369
+ /**
370
+ * Estimate the addLiquidity fee for depositing into a specific JLP custody.
371
+ *
372
+ * Implements the Jupiter Perps fee formula:
373
+ * - If deposit moves custody weight TOWARD target: rebate (fee < baseFee)
374
+ * - If deposit moves custody weight AWAY from target: tax (fee > baseFee)
375
+ *
376
+ * Formula:
377
+ * initDiff = |currentRatio - targetRatio|
378
+ * finalDiff = |newRatio - targetRatio|
379
+ * avgDiff = (initDiff + finalDiff) / 2
380
+ * if improving: fee = max(baseFee - taxBps * avgDiff / 10000, 0)
381
+ * if worsening: fee = baseFee + taxBps * avgDiff / 10000
382
+ */
383
+ function computeAddLiquidityFee(args) {
384
+ const { custodyAumUsd, poolAumUsd, targetRatioBps, depositUsd, fees, isStable } = args;
385
+ if (poolAumUsd <= 0 || targetRatioBps <= 0 || depositUsd <= 0) {
386
+ return { feeBps: fees.addRemoveLiquidityBps, isDiscount: false };
387
+ }
388
+ const baseFee = fees.addRemoveLiquidityBps;
389
+ const effectiveTaxBps = isStable ? fees.stableSwapTaxBps : fees.taxBps;
390
+ const currentRatio = (custodyAumUsd / poolAumUsd) * 10000;
391
+ const newCustodyAum = custodyAumUsd + depositUsd;
392
+ const newPoolAum = poolAumUsd + depositUsd;
393
+ const newRatio = (newCustodyAum / newPoolAum) * 10000;
394
+ const initDiff = Math.abs(currentRatio - targetRatioBps);
395
+ const finalDiff = Math.abs(newRatio - targetRatioBps);
396
+ const avgDiff = (initDiff + finalDiff) / 2;
397
+ if (finalDiff < initDiff) {
398
+ // Improving weight → rebate
399
+ const rebate = (effectiveTaxBps * avgDiff) / 10000;
400
+ return { feeBps: Math.max(baseFee - rebate, 0), isDiscount: true };
401
+ }
402
+ else {
403
+ // Worsening weight → penalty
404
+ const penalty = (effectiveTaxBps * avgDiff) / 10000;
405
+ return { feeBps: baseFee + penalty, isDiscount: false };
406
+ }
407
+ }
408
+ // ---------------------------------------------------------------------------
409
+ // Public: fetchJlpCustodyData (existing)
410
+ // ---------------------------------------------------------------------------
411
+ async function fetchJlpCustodyData(connection) {
412
+ const [custodies, pricesByMint] = await Promise.all([
413
+ fetchAllCustodies(connection),
414
+ fetchPrices(),
415
+ ]);
416
+ // Build price lookup by custody name
417
+ const prices = {};
418
+ for (const cfg of CUSTODIES) {
419
+ const price = pricesByMint[cfg.mintAddress];
420
+ if (price === undefined || price === null) {
421
+ if (cfg.isStable) {
422
+ prices[cfg.name] = 1;
423
+ }
424
+ else {
425
+ throw new Error(`Missing price for non-stable asset ${cfg.name} (mint: ${cfg.mintAddress})`);
426
+ }
427
+ }
428
+ else {
429
+ prices[cfg.name] = price;
430
+ }
431
+ }
432
+ // Calculate per-custody AUM
433
+ const results = custodies.map((raw) => calculateCustodyAum(raw, prices[raw.cfg.name] ?? 1));
434
+ const totalAumUsd = results.reduce((sum, r) => sum + r.aumUsd, 0);
435
+ // Set weights in basis points
436
+ for (const r of results) {
437
+ r.weightBps = totalAumUsd > 0 ? Math.round((r.aumUsd / totalAumUsd) * 10000) : 0;
438
+ }
439
+ return {
440
+ timestamp: new Date().toISOString(),
441
+ totalAumUsd,
442
+ custodies: results,
443
+ };
444
+ }
445
+ /** Map JLP custody internal name to public symbol used by effective-weights API. */
446
+ function toEffSymbol(name) {
447
+ return name === "WBTC" ? "BTC" : name;
448
+ }
449
+ /** Convert a float USD price to an 8-decimal-scaled bigint.
450
+ *
451
+ * Uses 12-decimal intermediate to preserve sub-1¢ precision before truncation. */
452
+ function priceToScaled8(priceFloat) {
453
+ if (!isFinite(priceFloat) || priceFloat <= 0)
454
+ return 0n;
455
+ // Round to 12 decimals first to avoid float artifacts, then scale to 8.
456
+ const scaled12 = BigInt(Math.round(priceFloat * 1e12));
457
+ return scaled12 / 10000n; // 12 → 8 decimals
458
+ }
459
+ /** Compute effective USD (price-sensitivity-weighted) for a single custody.
460
+ *
461
+ * Formula (in 8-decimal USD):
462
+ * netDebtTokens = max(debt − borrowLendInterestsAccrued, 0) / DEBT_MULTIPLIER
463
+ * spot = (owned + netDebtTokens − locked) × price_8dec / 10^decimals
464
+ * short = shortSize_6dec × price_8dec / avgShortPrice_6dec (zeroed if either operand is 0)
465
+ * effUsd = max(spot, 0) + short
466
+ *
467
+ * Algebra for unit normalization:
468
+ * - owned/locked/netDebt is in 10^decimals base units; price is 10^8 USD.
469
+ * spot = (owned + netDebt − locked) × price_8dec / 10^decimals → 10^8 USD ✓
470
+ * - debt is stored as u128 scaled by DEBT_MULTIPLIER (1e9) on top of 10^decimals.
471
+ * netDebtTokens = (debt − borrowLendInterestsAccrued) / DEBT_MULTIPLIER
472
+ * (integer division — small rounding under one base unit, acceptable).
473
+ * - shortSize and avgShortPrice are both 10^6; ratio is dimensionless.
474
+ * short = shortSize × price_8dec / avgShortPrice → 10^8 USD ✓
475
+ */
476
+ function computeEffectiveUsd(input) {
477
+ const { owned, locked, debt, borrowLendInterestsAccrued, shortSizeUsd, avgShortPriceUsd, decimals, priceScaled8, } = input;
478
+ const tokScale = 10n ** BigInt(decimals);
479
+ // Net debt tokens: (debt − interestsAccrued) / DEBT_MULTIPLIER, clamped to 0.
480
+ // Matches `calculateDebtTokens` in `calculateCustodyAum` to keep both code paths
481
+ // consistent. DEBT_MULTIPLIER is 1e9, scaled before token decimals.
482
+ const DEBT_MULTIPLIER_BIG = 1000000000n; // 1e9, matches DEBT_MULTIPLIER constant
483
+ let netDebtTokens = 0n;
484
+ if (debt > borrowLendInterestsAccrued) {
485
+ netDebtTokens = (debt - borrowLendInterestsAccrued) / DEBT_MULTIPLIER_BIG;
486
+ }
487
+ // Spot term: (owned + netDebt − locked) × price / 10^decimals
488
+ // Use signed bigint subtraction; clamp to 0 if (owned + netDebt) < locked.
489
+ const netTokens = owned + netDebtTokens - locked; // bigint — can go negative
490
+ let spotEffUsd;
491
+ let spotClamped = false;
492
+ if (netTokens <= 0n) {
493
+ spotEffUsd = 0n;
494
+ if (netTokens < 0n)
495
+ spotClamped = true;
496
+ }
497
+ else {
498
+ spotEffUsd = (netTokens * priceScaled8) / tokScale;
499
+ }
500
+ // Short term: only contributes when both shortSize and avgShortPrice > 0.
501
+ // Numerator stays in u128 range comfortably (shortSize_6dec × price_8dec).
502
+ let shortEffUsd = 0n;
503
+ if (shortSizeUsd > 0n && avgShortPriceUsd > 0n && priceScaled8 > 0n) {
504
+ shortEffUsd = (shortSizeUsd * priceScaled8) / avgShortPriceUsd;
505
+ }
506
+ return {
507
+ spotEffUsd,
508
+ shortEffUsd,
509
+ effUsd: spotEffUsd + shortEffUsd,
510
+ spotClamped,
511
+ netDebtTokens,
512
+ };
513
+ }
514
+ /** Compute basis-point shares for each effective USD value, summing to 10000.
515
+ *
516
+ * Uses largest-remainder allocation to guarantee the bps sum equals exactly 10000
517
+ * even with rounding. (Naive `round(eff_i / total × 10000)` per-asset can drift
518
+ * by up to `n` bps cumulatively.) Returns 0s for all assets if total is 0.
519
+ */
520
+ function computeBpsShares(effs) {
521
+ const total = effs.reduce((s, e) => s + e, 0n);
522
+ if (total === 0n)
523
+ return effs.map(() => 0);
524
+ // Floor allocation
525
+ const exact = effs.map((e) => Number((e * 10000n) / total));
526
+ const allocated = exact.reduce((s, b) => s + b, 0);
527
+ let remaining = 10000 - allocated;
528
+ if (remaining <= 0)
529
+ return exact;
530
+ // Distribute remaining bps to the assets with largest fractional remainders.
531
+ // Compute remainder = (e × 10000) mod total, ranked descending.
532
+ const remainders = effs.map((e, i) => ({
533
+ i,
534
+ rem: (e * 10000n) % total,
535
+ }));
536
+ remainders.sort((a, b) => (a.rem === b.rem ? 0 : a.rem > b.rem ? -1 : 1));
537
+ const result = exact.slice();
538
+ for (let k = 0; k < remainders.length && remaining > 0; k++) {
539
+ result[remainders[k].i]++;
540
+ remaining--;
541
+ }
542
+ return result;
543
+ }
544
+ /**
545
+ * Fetch JLP pool effective-weight composition.
546
+ *
547
+ * Returns price-sensitivity-weighted shares per asset, dropping `guaranteed_usd`
548
+ * from the per-asset weight calculation. Suitable for rebalance planners that
549
+ * want to track the pool's true delta-1 composition rather than reported aumUsd.
550
+ *
551
+ * Reuses the same on-chain custody parser and Jupiter Price API source as
552
+ * `fetchJlpCustodyData` to keep numbers consistent across callers.
553
+ *
554
+ * @returns assets in fixed order: BTC, ETH, SOL, USDC, USDT.
555
+ */
556
+ async function fetchJlpEffectiveWeights(connection) {
557
+ const [custodies, pricesByMint] = await Promise.all([
558
+ fetchAllCustodies(connection),
559
+ fetchPrices(),
560
+ ]);
561
+ // Resolve price per custody (stables fall back to $1.00 when API is missing them).
562
+ const priceFloat = {};
563
+ for (const cfg of CUSTODIES) {
564
+ const p = pricesByMint[cfg.mintAddress];
565
+ if (p === undefined || p === null) {
566
+ if (cfg.isStable) {
567
+ priceFloat[cfg.name] = 1;
568
+ }
569
+ else {
570
+ throw new Error(`Missing price for non-stable asset ${cfg.name} (mint: ${cfg.mintAddress})`);
571
+ }
572
+ }
573
+ else {
574
+ priceFloat[cfg.name] = p;
575
+ }
576
+ }
577
+ // Compute effective USD per custody.
578
+ const computed = custodies.map((raw) => {
579
+ const effInput = {
580
+ owned: raw.owned,
581
+ locked: raw.locked,
582
+ debt: raw.debt,
583
+ borrowLendInterestsAccrued: raw.borrowLendInterestsAccrued,
584
+ shortSizeUsd: raw.globalShortSizes,
585
+ avgShortPriceUsd: raw.globalShortAveragePrices,
586
+ guaranteedUsd: raw.guaranteedUsd,
587
+ decimals: raw.cfg.decimals,
588
+ priceScaled8: priceToScaled8(priceFloat[raw.cfg.name]),
589
+ };
590
+ return { raw, out: computeEffectiveUsd(effInput) };
591
+ });
592
+ const effs = computed.map((c) => c.out.effUsd);
593
+ const bpsShares = computeBpsShares(effs);
594
+ const assets = computed.map((c, i) => ({
595
+ mint: new web3_js_1.PublicKey(c.raw.cfg.mintAddress),
596
+ symbol: toEffSymbol(c.raw.cfg.name),
597
+ effUsd: c.out.effUsd,
598
+ effShareBps: bpsShares[i],
599
+ spotClamped: c.out.spotClamped,
600
+ }));
601
+ const totalEffUsd = effs.reduce((s, e) => s + e, 0n);
602
+ // Total pool aumUsd in 8-decimal USD = aumUsd from `calculateCustodyAum` (float)
603
+ // re-aggregated. We reuse the existing path but normalize to bigint 8-decimals.
604
+ // Each calculateCustodyAum return is a float USD; multiply by 1e8 and round.
605
+ const totalAumFloat = custodies.reduce((sum, raw) => {
606
+ const a = calculateCustodyAum(raw, priceFloat[raw.cfg.name] ?? 1);
607
+ return sum + a.aumUsd;
608
+ }, 0);
609
+ const totalAumUsd = BigInt(Math.round(totalAumFloat * 1e8));
610
+ return { assets, totalEffUsd, totalAumUsd };
611
+ }