@percolatorct/sdk 0.5.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -93,6 +93,7 @@ function concatBytes(...arrays) {
93
93
  }
94
94
 
95
95
  // src/abi/instructions.ts
96
+ var MAX_ORACLE_PRICE = 1000000000000n;
96
97
  var IX_TAG = {
97
98
  InitMarket: 0,
98
99
  InitUser: 1,
@@ -199,8 +200,17 @@ var IX_TAG = {
199
200
  /** PERC-8111: Set per-wallet position cap (admin only, cap_e6=0 disables). */
200
201
  SetWalletCap: 70,
201
202
  /** PERC-8110: Set OI imbalance hard-block threshold (admin only). */
202
- SetOiImbalanceHardBlock: 71
203
+ SetOiImbalanceHardBlock: 71,
204
+ /** PERC-8270: Rescue orphan vault — recover tokens from a closed market's vault (admin). */
205
+ RescueOrphanVault: 72,
206
+ /** PERC-8270: Close orphan slab — reclaim rent from a slab whose market closed unexpectedly (admin). */
207
+ CloseOrphanSlab: 73,
208
+ /** PERC-SetDexPool: Pin admin-approved DEX pool address for a HYPERP market (admin). */
209
+ SetDexPool: 74,
210
+ /** CPI to the matcher program to initialize a matcher context account for an LP slot. Admin-only. */
211
+ InitMatcherCtx: 75
203
212
  };
213
+ Object.freeze(IX_TAG);
204
214
  var HEX_RE = /^[0-9a-fA-F]{64}$/;
205
215
  function encodeFeedId(feedId) {
206
216
  const hex = feedId.startsWith("0x") ? feedId.slice(2) : feedId;
@@ -211,7 +221,13 @@ function encodeFeedId(feedId) {
211
221
  }
212
222
  const bytes = new Uint8Array(32);
213
223
  for (let i = 0; i < 64; i += 2) {
214
- bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
224
+ const byte = parseInt(hex.substring(i, i + 2), 16);
225
+ if (Number.isNaN(byte)) {
226
+ throw new Error(
227
+ `Failed to parse hex byte at position ${i}: "${hex.substring(i, i + 2)}"`
228
+ );
229
+ }
230
+ bytes[i / 2] = byte;
215
231
  }
216
232
  return bytes;
217
233
  }
@@ -362,9 +378,16 @@ function encodeSetOracleAuthority(args) {
362
378
  );
363
379
  }
364
380
  function encodePushOraclePrice(args) {
381
+ const price = typeof args.priceE6 === "string" ? BigInt(args.priceE6) : args.priceE6;
382
+ if (price === 0n) {
383
+ throw new Error("encodePushOraclePrice: price cannot be zero (division by zero in engine)");
384
+ }
385
+ if (price > MAX_ORACLE_PRICE) {
386
+ throw new Error(`encodePushOraclePrice: price exceeds maximum (${MAX_ORACLE_PRICE}), got ${price}`);
387
+ }
365
388
  return concatBytes(
366
389
  encU8(IX_TAG.PushOraclePrice),
367
- encU64(args.priceE6),
390
+ encU64(price),
368
391
  encI64(args.timestamp)
369
392
  );
370
393
  }
@@ -441,17 +464,15 @@ function encodeSetPythOracle(args) {
441
464
  }
442
465
  var PYTH_RECEIVER_PROGRAM_ID = "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ";
443
466
  async function derivePythPriceUpdateAccount(feedId, shardId = 0) {
444
- const { PublicKey: PublicKey12 } = await import("@solana/web3.js");
467
+ const { PublicKey: PublicKey15 } = await import("@solana/web3.js");
445
468
  const shardBuf = new Uint8Array(2);
446
469
  new DataView(shardBuf.buffer).setUint16(0, shardId, true);
447
- const [pda] = PublicKey12.findProgramAddressSync(
470
+ const [pda] = PublicKey15.findProgramAddressSync(
448
471
  [shardBuf, feedId],
449
- new PublicKey12(PYTH_RECEIVER_PROGRAM_ID)
472
+ new PublicKey15(PYTH_RECEIVER_PROGRAM_ID)
450
473
  );
451
474
  return pda.toBase58();
452
475
  }
453
- IX_TAG["SetPythOracle"] = 32;
454
- IX_TAG["UpdateMarkPrice"] = 33;
455
476
  function encodeUpdateMarkPrice() {
456
477
  return new Uint8Array([33]);
457
478
  }
@@ -462,7 +483,7 @@ function computeEmaMarkPrice(markPrevE6, oracleE6, dtSlots, alphaE6 = MARK_PRICE
462
483
  if (markPrevE6 === 0n || dtSlots === 0n) return oracleE6;
463
484
  let oracleClamped = oracleE6;
464
485
  if (capE2bps > 0n) {
465
- const maxDelta = markPrevE6 * capE2bps * dtSlots / 1000000n;
486
+ const maxDelta = markPrevE6 * capE2bps / 1000000n * dtSlots;
466
487
  const lo = markPrevE6 > maxDelta ? markPrevE6 - maxDelta : 0n;
467
488
  const hi = markPrevE6 + maxDelta;
468
489
  if (oracleClamped < lo) oracleClamped = lo;
@@ -472,7 +493,6 @@ function computeEmaMarkPrice(markPrevE6, oracleE6, dtSlots, alphaE6 = MARK_PRICE
472
493
  const oneMinusAlpha = 1000000n - effectiveAlpha;
473
494
  return (oracleClamped * effectiveAlpha + markPrevE6 * oneMinusAlpha) / 1000000n;
474
495
  }
475
- IX_TAG["UpdateHyperpMark"] = 34;
476
496
  function encodeUpdateHyperpMark() {
477
497
  return new Uint8Array([34]);
478
498
  }
@@ -616,6 +636,22 @@ function encodeTransferOwnershipCpi(args) {
616
636
  function encodeSetWalletCap(args) {
617
637
  return concatBytes(encU8(IX_TAG.SetWalletCap), encU64(args.capE6));
618
638
  }
639
+ function encodeInitMatcherCtx(args) {
640
+ return concatBytes(
641
+ encU8(IX_TAG.InitMatcherCtx),
642
+ encU16(args.lpIdx),
643
+ encU8(args.kind),
644
+ encU32(args.tradingFeeBps),
645
+ encU32(args.baseSpreadBps),
646
+ encU32(args.maxTotalBps),
647
+ encU32(args.impactKBps),
648
+ encU128(args.liquidityNotionalE6),
649
+ encU128(args.maxFillAbs),
650
+ encU128(args.maxInventoryAbs),
651
+ encU16(args.feeToInsuranceBps),
652
+ encU16(args.skewSpreadMultBps)
653
+ );
654
+ }
619
655
 
620
656
  // src/abi/accounts.ts
621
657
  import {
@@ -942,6 +978,13 @@ var ACCOUNTS_SET_WALLET_CAP = [
942
978
  { name: "admin", signer: true, writable: false },
943
979
  { name: "slab", signer: false, writable: true }
944
980
  ];
981
+ var ACCOUNTS_INIT_MATCHER_CTX = [
982
+ { name: "admin", signer: true, writable: false },
983
+ { name: "slab", signer: false, writable: false },
984
+ { name: "matcherCtx", signer: false, writable: true },
985
+ { name: "matcherProg", signer: false, writable: false },
986
+ { name: "lpPda", signer: false, writable: false }
987
+ ];
945
988
  var WELL_KNOWN = {
946
989
  tokenProgram: TOKEN_PROGRAM_ID,
947
990
  clock: SYSVAR_CLOCK_PUBKEY,
@@ -1226,11 +1269,24 @@ function getErrorName(code) {
1226
1269
  function getErrorHint(code) {
1227
1270
  return PERCOLATOR_ERRORS[code]?.hint;
1228
1271
  }
1272
+ var LIGHTHOUSE_PROGRAM_ID_STR = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
1273
+ var ANCHOR_ERROR_RANGE_START = 6e3;
1274
+ var ANCHOR_ERROR_RANGE_END = 8191;
1275
+ var ANCHOR_ERROR_NAMES = {
1276
+ 6032: "ConstraintMut",
1277
+ 6036: "ConstraintOwner",
1278
+ 6038: "ConstraintSeeds",
1279
+ 6400: "ConstraintAddress"
1280
+ };
1281
+ function isAnchorErrorCode(code) {
1282
+ return code >= ANCHOR_ERROR_RANGE_START && code <= ANCHOR_ERROR_RANGE_END;
1283
+ }
1229
1284
  var CUSTOM_ERROR_HEX_MAX_LEN = 8;
1230
1285
  function parseErrorFromLogs(logs) {
1231
1286
  if (!Array.isArray(logs)) {
1232
1287
  return null;
1233
1288
  }
1289
+ let insideLighthouse = false;
1234
1290
  const re = new RegExp(
1235
1291
  `custom program error: 0x([0-9a-fA-F]{1,${CUSTOM_ERROR_HEX_MAX_LEN}})(?![0-9a-fA-F])`,
1236
1292
  "i"
@@ -1239,17 +1295,32 @@ function parseErrorFromLogs(logs) {
1239
1295
  if (typeof log !== "string") {
1240
1296
  continue;
1241
1297
  }
1298
+ if (log.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR} invoke`)) {
1299
+ insideLighthouse = true;
1300
+ } else if (log.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR} success`)) {
1301
+ insideLighthouse = false;
1302
+ }
1242
1303
  const match = log.match(re);
1243
1304
  if (match) {
1244
1305
  const code = parseInt(match[1], 16);
1245
1306
  if (!Number.isFinite(code) || code < 0 || code > 4294967295) {
1246
1307
  continue;
1247
1308
  }
1309
+ if (isAnchorErrorCode(code) || insideLighthouse) {
1310
+ const anchorName = ANCHOR_ERROR_NAMES[code] ?? `AnchorError(0x${code.toString(16)})`;
1311
+ return {
1312
+ code,
1313
+ name: `Lighthouse:${anchorName}`,
1314
+ hint: "This error comes from the Lighthouse/Blowfish wallet guard, not from Percolator. The transaction itself is valid. Disable transaction simulation in your wallet settings, or use a wallet without Blowfish protection (e.g., Backpack, Solflare).",
1315
+ source: "lighthouse"
1316
+ };
1317
+ }
1248
1318
  const info = decodeError(code);
1249
1319
  return {
1250
1320
  code,
1251
1321
  name: info?.name ?? `Unknown(${code})`,
1252
- hint: info?.hint
1322
+ hint: info?.hint,
1323
+ source: info ? "percolator" : "unknown"
1253
1324
  };
1254
1325
  }
1255
1326
  }
@@ -1416,6 +1487,8 @@ var V2_ENGINE_LP_MAX_ABS_OFF = 536;
1416
1487
  var V2_ENGINE_LP_MAX_ABS_SWEEP_OFF = 552;
1417
1488
  var V_ADL_ENGINE_OFF = 624;
1418
1489
  var V_ADL_CONFIG_LEN = 520;
1490
+ var V_SETDEXPOOL_CONFIG_LEN = 544;
1491
+ var V_SETDEXPOOL_ENGINE_OFF = 648;
1419
1492
  var V_ADL_ACCOUNT_SIZE = 312;
1420
1493
  var V_ADL_ENGINE_PARAMS_OFF = 96;
1421
1494
  var V_ADL_PARAMS_SIZE = 336;
@@ -1446,7 +1519,7 @@ var V_ADL_ENGINE_LP_MAX_ABS_SWEEP_OFF = 952;
1446
1519
  var V_ADL_ENGINE_EMERGENCY_OI_MODE_OFF = 968;
1447
1520
  var V_ADL_ENGINE_EMERGENCY_START_SLOT_OFF = 976;
1448
1521
  var V_ADL_ENGINE_LAST_BREAKER_SLOT_OFF = 984;
1449
- var V_ADL_ENGINE_BITMAP_OFF = 1006;
1522
+ var V_ADL_ENGINE_BITMAP_OFF = 1008;
1450
1523
  var V_ADL_ACCT_WARMUP_STARTED_OFF = 64;
1451
1524
  var V_ADL_ACCT_WARMUP_SLOPE_OFF = 72;
1452
1525
  var V_ADL_ACCT_POSITION_SIZE_OFF = 88;
@@ -1457,6 +1530,46 @@ var V_ADL_ACCT_MATCHER_CONTEXT_OFF = 160;
1457
1530
  var V_ADL_ACCT_OWNER_OFF = 192;
1458
1531
  var V_ADL_ACCT_FEE_CREDITS_OFF = 224;
1459
1532
  var V_ADL_ACCT_LAST_FEE_SLOT_OFF = 240;
1533
+ var V12_1_ENGINE_OFF = 648;
1534
+ var V12_1_ACCOUNT_SIZE = 320;
1535
+ var V12_1_ENGINE_BITMAP_OFF = 1016;
1536
+ var V12_1_ENGINE_PARAMS_OFF = 96;
1537
+ var V12_1_PARAMS_SIZE = 352;
1538
+ var V12_1_ENGINE_CURRENT_SLOT_OFF = 448;
1539
+ var V12_1_ENGINE_FUNDING_RATE_BPS_OFF = 456;
1540
+ var V12_1_ENGINE_LAST_CRANK_SLOT_OFF = 464;
1541
+ var V12_1_ENGINE_MAX_CRANK_STALENESS_OFF = 472;
1542
+ var V12_1_ENGINE_C_TOT_OFF = 480;
1543
+ var V12_1_ENGINE_PNL_POS_TOT_OFF = 496;
1544
+ var V12_1_ENGINE_LIQ_CURSOR_OFF = 528;
1545
+ var V12_1_ENGINE_GC_CURSOR_OFF = 530;
1546
+ var V12_1_ENGINE_LAST_SWEEP_START_OFF = 536;
1547
+ var V12_1_ENGINE_LAST_SWEEP_COMPLETE_OFF = 544;
1548
+ var V12_1_ENGINE_CRANK_CURSOR_OFF = 552;
1549
+ var V12_1_ENGINE_SWEEP_START_IDX_OFF = 554;
1550
+ var V12_1_ENGINE_LIFETIME_LIQUIDATIONS_OFF = 560;
1551
+ var V12_1_ENGINE_TOTAL_OI_OFF = 816;
1552
+ var V12_1_ENGINE_LONG_OI_OFF = 832;
1553
+ var V12_1_ENGINE_SHORT_OI_OFF = 848;
1554
+ var V12_1_ENGINE_NET_LP_POS_OFF = 864;
1555
+ var V12_1_ENGINE_LP_SUM_ABS_OFF = 880;
1556
+ var V12_1_ENGINE_LP_MAX_ABS_OFF = 896;
1557
+ var V12_1_ENGINE_LP_MAX_ABS_SWEEP_OFF = 912;
1558
+ var V12_1_ENGINE_MARK_PRICE_OFF = 928;
1559
+ var V12_1_ENGINE_FUNDING_INDEX_OFF = 936;
1560
+ var V12_1_ENGINE_LAST_FUNDING_SLOT_OFF = 944;
1561
+ var V12_1_ENGINE_EMERGENCY_OI_MODE_OFF = 968;
1562
+ var V12_1_ENGINE_EMERGENCY_START_SLOT_OFF = 976;
1563
+ var V12_1_ENGINE_LAST_BREAKER_SLOT_OFF = 984;
1564
+ var V12_1_ENGINE_LIFETIME_FORCE_CLOSES_OFF = 1008;
1565
+ var V12_1_ACCT_MATCHER_PROGRAM_OFF = 144;
1566
+ var V12_1_ACCT_MATCHER_CONTEXT_OFF = 176;
1567
+ var V12_1_ACCT_OWNER_OFF = 208;
1568
+ var V12_1_ACCT_FEE_CREDITS_OFF = 240;
1569
+ var V12_1_ACCT_LAST_FEE_SLOT_OFF = 256;
1570
+ var V12_1_ACCT_POSITION_SIZE_OFF = 296;
1571
+ var V12_1_ACCT_ENTRY_PRICE_OFF = 280;
1572
+ var V12_1_ACCT_FUNDING_INDEX_OFF = 288;
1460
1573
  var V1M_ENGINE_OFF = 640;
1461
1574
  var V1M_CONFIG_LEN = 536;
1462
1575
  var V1M_ACCOUNT_SIZE = 248;
@@ -1494,15 +1607,7 @@ var V1M_ENGINE_EMERGENCY_START_SLOT_OFF = 688;
1494
1607
  var V1M_ENGINE_LAST_BREAKER_SLOT_OFF = 696;
1495
1608
  var V1M_ENGINE_BITMAP_OFF = 720;
1496
1609
  var V1M2_ACCOUNT_SIZE = 312;
1497
- var V1M2_ENGINE_BITMAP_OFF = 990;
1498
- var V1M2_ENGINE_CURRENT_SLOT_OFF = 408;
1499
- var V1M2_ENGINE_FUNDING_INDEX_OFF = 416;
1500
- var V1M2_ENGINE_LAST_FUNDING_SLOT_OFF = 432;
1501
- var V1M2_ENGINE_FUNDING_RATE_BPS_OFF = 440;
1502
- var V1M2_ENGINE_MARK_PRICE_OFF = 480;
1503
- var V1M2_ENGINE_LAST_CRANK_SLOT_OFF = 504;
1504
- var V1M2_ENGINE_MAX_CRANK_STALENESS_OFF = 512;
1505
- var V1M2_RUNTIME_SHIFT = 32;
1610
+ var V1M2_ENGINE_BITMAP_OFF = 1008;
1506
1611
  var ENGINE_OFF = V1_ENGINE_OFF;
1507
1612
  var ENGINE_MARK_PRICE_OFF = V1_ENGINE_MARK_PRICE_OFF;
1508
1613
  function computeSlabSize(engineOff, bitmapOff, accountSize, maxAccounts, postBitmap = 18) {
@@ -1521,6 +1626,9 @@ var V1D_SIZES = /* @__PURE__ */ new Map();
1521
1626
  var V2_SIZES = /* @__PURE__ */ new Map();
1522
1627
  var V1M_SIZES = /* @__PURE__ */ new Map();
1523
1628
  var V_ADL_SIZES = /* @__PURE__ */ new Map();
1629
+ var V1M2_SIZES = /* @__PURE__ */ new Map();
1630
+ var V_SETDEXPOOL_SIZES = /* @__PURE__ */ new Map();
1631
+ var V12_1_SIZES = /* @__PURE__ */ new Map();
1524
1632
  var V1D_SIZES_LEGACY = /* @__PURE__ */ new Map();
1525
1633
  for (const n of TIERS) {
1526
1634
  V0_SIZES.set(computeSlabSize(V0_ENGINE_OFF, V0_ENGINE_BITMAP_OFF, V0_ACCOUNT_SIZE, n), n);
@@ -1531,6 +1639,9 @@ for (const n of TIERS) {
1531
1639
  V2_SIZES.set(computeSlabSize(V2_ENGINE_OFF, V2_ENGINE_BITMAP_OFF, V2_ACCOUNT_SIZE, n, 18), n);
1532
1640
  V1M_SIZES.set(computeSlabSize(V1M_ENGINE_OFF, V1M_ENGINE_BITMAP_OFF, V1M_ACCOUNT_SIZE, n, 18), n);
1533
1641
  V_ADL_SIZES.set(computeSlabSize(V_ADL_ENGINE_OFF, V_ADL_ENGINE_BITMAP_OFF, V_ADL_ACCOUNT_SIZE, n, 18), n);
1642
+ V1M2_SIZES.set(computeSlabSize(V1M2_ENGINE_OFF, V1M2_ENGINE_BITMAP_OFF, V1M2_ACCOUNT_SIZE, n, 18), n);
1643
+ V_SETDEXPOOL_SIZES.set(computeSlabSize(V_SETDEXPOOL_ENGINE_OFF, V_ADL_ENGINE_BITMAP_OFF, V_ADL_ACCOUNT_SIZE, n, 18), n);
1644
+ V12_1_SIZES.set(computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18), n);
1534
1645
  }
1535
1646
  var SLAB_TIERS_V2 = {
1536
1647
  small: { maxAccounts: 256, dataSize: 65088, label: "Small", description: "256 slots (V2 BPF intermediate)" },
@@ -1826,41 +1937,68 @@ function buildLayoutV1M2(maxAccounts) {
1826
1937
  accountsOff: engineOff + accountsOffRel,
1827
1938
  engineInsuranceOff: 16,
1828
1939
  engineParamsOff: V1M2_ENGINE_PARAMS_OFF,
1829
- // 96 — expanded InsuranceFund
1830
- paramsSize: V1M_PARAMS_SIZE,
1831
- // 336 — same as V1M
1832
- // Runtime fields: same as V1M up to fundingRateBps, then +32 shift
1833
- engineCurrentSlotOff: V1M2_ENGINE_CURRENT_SLOT_OFF,
1834
- engineFundingIndexOff: V1M2_ENGINE_FUNDING_INDEX_OFF,
1835
- engineLastFundingSlotOff: V1M2_ENGINE_LAST_FUNDING_SLOT_OFF,
1836
- engineFundingRateBpsOff: V1M2_ENGINE_FUNDING_RATE_BPS_OFF,
1837
- engineMarkPriceOff: V1M2_ENGINE_MARK_PRICE_OFF,
1838
- engineLastCrankSlotOff: V1M2_ENGINE_LAST_CRANK_SLOT_OFF,
1839
- engineMaxCrankStalenessOff: V1M2_ENGINE_MAX_CRANK_STALENESS_OFF,
1840
- // Fields after maxCrankStaleness: apply same +32 shift from V1M
1841
- engineTotalOiOff: V1M_ENGINE_TOTAL_OI_OFF + V1M2_RUNTIME_SHIFT,
1842
- engineLongOiOff: V1M_ENGINE_LONG_OI_OFF + V1M2_RUNTIME_SHIFT,
1843
- engineShortOiOff: V1M_ENGINE_SHORT_OI_OFF + V1M2_RUNTIME_SHIFT,
1844
- engineCTotOff: V1M_ENGINE_C_TOT_OFF + V1M2_RUNTIME_SHIFT,
1845
- enginePnlPosTotOff: V1M_ENGINE_PNL_POS_TOT_OFF + V1M2_RUNTIME_SHIFT,
1846
- engineLiqCursorOff: V1M_ENGINE_LIQ_CURSOR_OFF + V1M2_RUNTIME_SHIFT,
1847
- engineGcCursorOff: V1M_ENGINE_GC_CURSOR_OFF + V1M2_RUNTIME_SHIFT,
1848
- engineLastSweepStartOff: V1M_ENGINE_LAST_SWEEP_START_OFF + V1M2_RUNTIME_SHIFT,
1849
- engineLastSweepCompleteOff: V1M_ENGINE_LAST_SWEEP_COMPLETE_OFF + V1M2_RUNTIME_SHIFT,
1850
- engineCrankCursorOff: V1M_ENGINE_CRANK_CURSOR_OFF + V1M2_RUNTIME_SHIFT,
1851
- engineSweepStartIdxOff: V1M_ENGINE_SWEEP_START_IDX_OFF + V1M2_RUNTIME_SHIFT,
1852
- engineLifetimeLiquidationsOff: V1M_ENGINE_LIFETIME_LIQUIDATIONS_OFF + V1M2_RUNTIME_SHIFT,
1853
- engineLifetimeForceClosesOff: V1M_ENGINE_LIFETIME_FORCE_CLOSES_OFF + V1M2_RUNTIME_SHIFT,
1854
- engineNetLpPosOff: V1M_ENGINE_NET_LP_POS_OFF + V1M2_RUNTIME_SHIFT,
1855
- engineLpSumAbsOff: V1M_ENGINE_LP_SUM_ABS_OFF + V1M2_RUNTIME_SHIFT,
1856
- engineLpMaxAbsOff: V1M_ENGINE_LP_MAX_ABS_OFF + V1M2_RUNTIME_SHIFT,
1857
- engineLpMaxAbsSweepOff: V1M_ENGINE_LP_MAX_ABS_SWEEP_OFF + V1M2_RUNTIME_SHIFT,
1858
- engineEmergencyOiModeOff: V1M_ENGINE_EMERGENCY_OI_MODE_OFF + V1M2_RUNTIME_SHIFT,
1859
- engineEmergencyStartSlotOff: V1M_ENGINE_EMERGENCY_START_SLOT_OFF + V1M2_RUNTIME_SHIFT,
1860
- engineLastBreakerSlotOff: V1M_ENGINE_LAST_BREAKER_SLOT_OFF + V1M2_RUNTIME_SHIFT,
1940
+ // 96 — expanded InsuranceFund (same as V_ADL)
1941
+ paramsSize: V_ADL_PARAMS_SIZE,
1942
+ // 336 — same as V_ADL
1943
+ // Runtime fields: V1M2 engine struct is layout-identical to V_ADL reuse V_ADL constants.
1944
+ engineCurrentSlotOff: V_ADL_ENGINE_CURRENT_SLOT_OFF,
1945
+ // 432
1946
+ engineFundingIndexOff: V_ADL_ENGINE_FUNDING_INDEX_OFF,
1947
+ // 440
1948
+ engineLastFundingSlotOff: V_ADL_ENGINE_LAST_FUNDING_SLOT_OFF,
1949
+ // 456
1950
+ engineFundingRateBpsOff: V_ADL_ENGINE_FUNDING_RATE_BPS_OFF,
1951
+ // 464
1952
+ engineMarkPriceOff: V_ADL_ENGINE_MARK_PRICE_OFF,
1953
+ // 504
1954
+ engineLastCrankSlotOff: V_ADL_ENGINE_LAST_CRANK_SLOT_OFF,
1955
+ // 528
1956
+ engineMaxCrankStalenessOff: V_ADL_ENGINE_MAX_CRANK_STALENESS_OFF,
1957
+ // 536
1958
+ engineTotalOiOff: V_ADL_ENGINE_TOTAL_OI_OFF,
1959
+ // 544
1960
+ engineLongOiOff: V_ADL_ENGINE_LONG_OI_OFF,
1961
+ // 560
1962
+ engineShortOiOff: V_ADL_ENGINE_SHORT_OI_OFF,
1963
+ // 576
1964
+ engineCTotOff: V_ADL_ENGINE_C_TOT_OFF,
1965
+ // 592
1966
+ enginePnlPosTotOff: V_ADL_ENGINE_PNL_POS_TOT_OFF,
1967
+ // 608
1968
+ engineLiqCursorOff: V_ADL_ENGINE_LIQ_CURSOR_OFF,
1969
+ // 640
1970
+ engineGcCursorOff: V_ADL_ENGINE_GC_CURSOR_OFF,
1971
+ // 642
1972
+ engineLastSweepStartOff: V_ADL_ENGINE_LAST_SWEEP_START_OFF,
1973
+ // 648
1974
+ engineLastSweepCompleteOff: V_ADL_ENGINE_LAST_SWEEP_COMPLETE_OFF,
1975
+ // 656
1976
+ engineCrankCursorOff: V_ADL_ENGINE_CRANK_CURSOR_OFF,
1977
+ // 664
1978
+ engineSweepStartIdxOff: V_ADL_ENGINE_SWEEP_START_IDX_OFF,
1979
+ // 666
1980
+ engineLifetimeLiquidationsOff: V_ADL_ENGINE_LIFETIME_LIQUIDATIONS_OFF,
1981
+ // 672
1982
+ engineLifetimeForceClosesOff: V_ADL_ENGINE_LIFETIME_FORCE_CLOSES_OFF,
1983
+ // 680
1984
+ engineNetLpPosOff: V_ADL_ENGINE_NET_LP_POS_OFF,
1985
+ // 904
1986
+ engineLpSumAbsOff: V_ADL_ENGINE_LP_SUM_ABS_OFF,
1987
+ // 920
1988
+ engineLpMaxAbsOff: V_ADL_ENGINE_LP_MAX_ABS_OFF,
1989
+ // 936
1990
+ engineLpMaxAbsSweepOff: V_ADL_ENGINE_LP_MAX_ABS_SWEEP_OFF,
1991
+ // 952
1992
+ engineEmergencyOiModeOff: V_ADL_ENGINE_EMERGENCY_OI_MODE_OFF,
1993
+ // 968
1994
+ engineEmergencyStartSlotOff: V_ADL_ENGINE_EMERGENCY_START_SLOT_OFF,
1995
+ // 976
1996
+ engineLastBreakerSlotOff: V_ADL_ENGINE_LAST_BREAKER_SLOT_OFF,
1997
+ // 984
1861
1998
  engineBitmapOff: V1M2_ENGINE_BITMAP_OFF,
1862
1999
  postBitmap: 18,
1863
- acctOwnerOff: ACCT_OWNER_OFF,
2000
+ acctOwnerOff: V_ADL_ACCT_OWNER_OFF,
2001
+ // 192 — same shift as V_ADL (reserved_pnl u64→u128)
1864
2002
  hasInsuranceIsolation: true,
1865
2003
  engineInsuranceIsolatedOff: 48,
1866
2004
  engineInsuranceIsolationBpsOff: 64
@@ -1950,7 +2088,7 @@ function buildLayoutVADL(maxAccounts) {
1950
2088
  engineLastBreakerSlotOff: V_ADL_ENGINE_LAST_BREAKER_SLOT_OFF,
1951
2089
  // 984
1952
2090
  engineBitmapOff: V_ADL_ENGINE_BITMAP_OFF,
1953
- // 1006
2091
+ // 1008
1954
2092
  postBitmap: 18,
1955
2093
  acctOwnerOff: V_ADL_ACCT_OWNER_OFF,
1956
2094
  // 192
@@ -1959,17 +2097,145 @@ function buildLayoutVADL(maxAccounts) {
1959
2097
  engineInsuranceIsolationBpsOff: 64
1960
2098
  };
1961
2099
  }
2100
+ var SLAB_TIERS_V_SETDEXPOOL = {};
2101
+ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Large", 4096]]) {
2102
+ const size = computeSlabSize(V_SETDEXPOOL_ENGINE_OFF, V_ADL_ENGINE_BITMAP_OFF, V_ADL_ACCOUNT_SIZE, n, 18);
2103
+ SLAB_TIERS_V_SETDEXPOOL[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (V_SETDEXPOOL PERC-SetDexPool)` };
2104
+ }
2105
+ var SLAB_TIERS_V12_1 = {};
2106
+ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Large", 4096]]) {
2107
+ const size = computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18);
2108
+ SLAB_TIERS_V12_1[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (v12.1)` };
2109
+ }
2110
+ function buildLayoutVSetDexPool(maxAccounts) {
2111
+ const engineOff = V_SETDEXPOOL_ENGINE_OFF;
2112
+ const bitmapOff = V_ADL_ENGINE_BITMAP_OFF;
2113
+ const accountSize = V_ADL_ACCOUNT_SIZE;
2114
+ const bitmapWords = Math.ceil(maxAccounts / 64);
2115
+ const bitmapBytes = bitmapWords * 8;
2116
+ const postBitmap = 18;
2117
+ const nextFreeBytes = maxAccounts * 2;
2118
+ const preAccountsLen = bitmapOff + bitmapBytes + postBitmap + nextFreeBytes;
2119
+ const accountsOffRel = Math.ceil(preAccountsLen / 8) * 8;
2120
+ return {
2121
+ version: 1,
2122
+ headerLen: V1_HEADER_LEN,
2123
+ configOffset: V1_HEADER_LEN,
2124
+ configLen: V_SETDEXPOOL_CONFIG_LEN,
2125
+ // 544
2126
+ reservedOff: V1_RESERVED_OFF,
2127
+ engineOff,
2128
+ accountSize,
2129
+ maxAccounts,
2130
+ bitmapWords,
2131
+ accountsOff: engineOff + accountsOffRel,
2132
+ engineInsuranceOff: 16,
2133
+ engineParamsOff: V_ADL_ENGINE_PARAMS_OFF,
2134
+ paramsSize: V_ADL_PARAMS_SIZE,
2135
+ engineCurrentSlotOff: V_ADL_ENGINE_CURRENT_SLOT_OFF,
2136
+ engineFundingIndexOff: V_ADL_ENGINE_FUNDING_INDEX_OFF,
2137
+ engineLastFundingSlotOff: V_ADL_ENGINE_LAST_FUNDING_SLOT_OFF,
2138
+ engineFundingRateBpsOff: V_ADL_ENGINE_FUNDING_RATE_BPS_OFF,
2139
+ engineMarkPriceOff: V_ADL_ENGINE_MARK_PRICE_OFF,
2140
+ engineLastCrankSlotOff: V_ADL_ENGINE_LAST_CRANK_SLOT_OFF,
2141
+ engineMaxCrankStalenessOff: V_ADL_ENGINE_MAX_CRANK_STALENESS_OFF,
2142
+ engineTotalOiOff: V_ADL_ENGINE_TOTAL_OI_OFF,
2143
+ engineLongOiOff: V_ADL_ENGINE_LONG_OI_OFF,
2144
+ engineShortOiOff: V_ADL_ENGINE_SHORT_OI_OFF,
2145
+ engineCTotOff: V_ADL_ENGINE_C_TOT_OFF,
2146
+ enginePnlPosTotOff: V_ADL_ENGINE_PNL_POS_TOT_OFF,
2147
+ engineLiqCursorOff: V_ADL_ENGINE_LIQ_CURSOR_OFF,
2148
+ engineGcCursorOff: V_ADL_ENGINE_GC_CURSOR_OFF,
2149
+ engineLastSweepStartOff: V_ADL_ENGINE_LAST_SWEEP_START_OFF,
2150
+ engineLastSweepCompleteOff: V_ADL_ENGINE_LAST_SWEEP_COMPLETE_OFF,
2151
+ engineCrankCursorOff: V_ADL_ENGINE_CRANK_CURSOR_OFF,
2152
+ engineSweepStartIdxOff: V_ADL_ENGINE_SWEEP_START_IDX_OFF,
2153
+ engineLifetimeLiquidationsOff: V_ADL_ENGINE_LIFETIME_LIQUIDATIONS_OFF,
2154
+ engineLifetimeForceClosesOff: V_ADL_ENGINE_LIFETIME_FORCE_CLOSES_OFF,
2155
+ engineNetLpPosOff: V_ADL_ENGINE_NET_LP_POS_OFF,
2156
+ engineLpSumAbsOff: V_ADL_ENGINE_LP_SUM_ABS_OFF,
2157
+ engineLpMaxAbsOff: V_ADL_ENGINE_LP_MAX_ABS_OFF,
2158
+ engineLpMaxAbsSweepOff: V_ADL_ENGINE_LP_MAX_ABS_SWEEP_OFF,
2159
+ engineEmergencyOiModeOff: V_ADL_ENGINE_EMERGENCY_OI_MODE_OFF,
2160
+ engineEmergencyStartSlotOff: V_ADL_ENGINE_EMERGENCY_START_SLOT_OFF,
2161
+ engineLastBreakerSlotOff: V_ADL_ENGINE_LAST_BREAKER_SLOT_OFF,
2162
+ engineBitmapOff: V_ADL_ENGINE_BITMAP_OFF,
2163
+ postBitmap: 18,
2164
+ acctOwnerOff: V_ADL_ACCT_OWNER_OFF,
2165
+ hasInsuranceIsolation: true,
2166
+ engineInsuranceIsolatedOff: 48,
2167
+ engineInsuranceIsolationBpsOff: 64
2168
+ };
2169
+ }
2170
+ function buildLayoutV12_1(maxAccounts) {
2171
+ const engineOff = V12_1_ENGINE_OFF;
2172
+ const bitmapOff = V12_1_ENGINE_BITMAP_OFF;
2173
+ const accountSize = V12_1_ACCOUNT_SIZE;
2174
+ const bitmapWords = Math.ceil(maxAccounts / 64);
2175
+ const bitmapBytes = bitmapWords * 8;
2176
+ const postBitmap = 18;
2177
+ const nextFreeBytes = maxAccounts * 2;
2178
+ const preAccountsLen = bitmapOff + bitmapBytes + postBitmap + nextFreeBytes;
2179
+ const accountsOffRel = Math.ceil(preAccountsLen / 8) * 8;
2180
+ return {
2181
+ version: 1,
2182
+ headerLen: V1_HEADER_LEN,
2183
+ configOffset: V1_HEADER_LEN,
2184
+ configLen: V_SETDEXPOOL_CONFIG_LEN,
2185
+ // 544 (same as V_SETDEXPOOL)
2186
+ reservedOff: V1_RESERVED_OFF,
2187
+ engineOff,
2188
+ accountSize,
2189
+ maxAccounts,
2190
+ bitmapWords,
2191
+ accountsOff: engineOff + accountsOffRel,
2192
+ engineInsuranceOff: 16,
2193
+ engineParamsOff: V12_1_ENGINE_PARAMS_OFF,
2194
+ paramsSize: V12_1_PARAMS_SIZE,
2195
+ engineCurrentSlotOff: V12_1_ENGINE_CURRENT_SLOT_OFF,
2196
+ engineFundingIndexOff: V12_1_ENGINE_FUNDING_INDEX_OFF,
2197
+ engineLastFundingSlotOff: V12_1_ENGINE_LAST_FUNDING_SLOT_OFF,
2198
+ engineFundingRateBpsOff: V12_1_ENGINE_FUNDING_RATE_BPS_OFF,
2199
+ engineMarkPriceOff: V12_1_ENGINE_MARK_PRICE_OFF,
2200
+ engineLastCrankSlotOff: V12_1_ENGINE_LAST_CRANK_SLOT_OFF,
2201
+ engineMaxCrankStalenessOff: V12_1_ENGINE_MAX_CRANK_STALENESS_OFF,
2202
+ engineTotalOiOff: V12_1_ENGINE_TOTAL_OI_OFF,
2203
+ engineLongOiOff: V12_1_ENGINE_LONG_OI_OFF,
2204
+ engineShortOiOff: V12_1_ENGINE_SHORT_OI_OFF,
2205
+ engineCTotOff: V12_1_ENGINE_C_TOT_OFF,
2206
+ enginePnlPosTotOff: V12_1_ENGINE_PNL_POS_TOT_OFF,
2207
+ engineLiqCursorOff: V12_1_ENGINE_LIQ_CURSOR_OFF,
2208
+ engineGcCursorOff: V12_1_ENGINE_GC_CURSOR_OFF,
2209
+ engineLastSweepStartOff: V12_1_ENGINE_LAST_SWEEP_START_OFF,
2210
+ engineLastSweepCompleteOff: V12_1_ENGINE_LAST_SWEEP_COMPLETE_OFF,
2211
+ engineCrankCursorOff: V12_1_ENGINE_CRANK_CURSOR_OFF,
2212
+ engineSweepStartIdxOff: V12_1_ENGINE_SWEEP_START_IDX_OFF,
2213
+ engineLifetimeLiquidationsOff: V12_1_ENGINE_LIFETIME_LIQUIDATIONS_OFF,
2214
+ engineLifetimeForceClosesOff: V12_1_ENGINE_LIFETIME_FORCE_CLOSES_OFF,
2215
+ engineNetLpPosOff: V12_1_ENGINE_NET_LP_POS_OFF,
2216
+ engineLpSumAbsOff: V12_1_ENGINE_LP_SUM_ABS_OFF,
2217
+ engineLpMaxAbsOff: V12_1_ENGINE_LP_MAX_ABS_OFF,
2218
+ engineLpMaxAbsSweepOff: V12_1_ENGINE_LP_MAX_ABS_SWEEP_OFF,
2219
+ engineEmergencyOiModeOff: V12_1_ENGINE_EMERGENCY_OI_MODE_OFF,
2220
+ engineEmergencyStartSlotOff: V12_1_ENGINE_EMERGENCY_START_SLOT_OFF,
2221
+ engineLastBreakerSlotOff: V12_1_ENGINE_LAST_BREAKER_SLOT_OFF,
2222
+ engineBitmapOff: V12_1_ENGINE_BITMAP_OFF,
2223
+ postBitmap: 18,
2224
+ acctOwnerOff: V12_1_ACCT_OWNER_OFF,
2225
+ hasInsuranceIsolation: true,
2226
+ engineInsuranceIsolatedOff: 48,
2227
+ engineInsuranceIsolationBpsOff: 64
2228
+ };
2229
+ }
1962
2230
  function detectSlabLayout(dataLen, data) {
2231
+ const v121n = V12_1_SIZES.get(dataLen);
2232
+ if (v121n !== void 0) return buildLayoutV12_1(v121n);
2233
+ const vsdpn = V_SETDEXPOOL_SIZES.get(dataLen);
2234
+ if (vsdpn !== void 0) return buildLayoutVSetDexPool(vsdpn);
2235
+ const v1m2n = V1M2_SIZES.get(dataLen);
2236
+ if (v1m2n !== void 0) return buildLayoutV1M2(v1m2n);
1963
2237
  const vadln = V_ADL_SIZES.get(dataLen);
1964
- if (vadln !== void 0) {
1965
- if (data && data.length >= 752) {
1966
- const maxAcctsV1M2 = readU64LE(data, V1M2_ENGINE_OFF + V1M2_ENGINE_PARAMS_OFF + 32);
1967
- if (maxAcctsV1M2 === BigInt(vadln)) {
1968
- return buildLayoutV1M2(vadln);
1969
- }
1970
- }
1971
- return buildLayoutVADL(vadln);
1972
- }
2238
+ if (vadln !== void 0) return buildLayoutVADL(vadln);
1973
2239
  const v1mn = V1M_SIZES.get(dataLen);
1974
2240
  if (v1mn !== void 0) return buildLayoutV1M(v1mn);
1975
2241
  const v0n = V0_SIZES.get(dataLen);
@@ -2202,6 +2468,14 @@ function parseConfig(data, layoutHint) {
2202
2468
  }
2203
2469
  }
2204
2470
  }
2471
+ let dexPool = null;
2472
+ const DEX_POOL_REL_OFF = 512;
2473
+ if (configLen >= DEX_POOL_REL_OFF + 32 && data.length >= configOff + DEX_POOL_REL_OFF + 32) {
2474
+ const dexPoolBytes = data.subarray(configOff + DEX_POOL_REL_OFF, configOff + DEX_POOL_REL_OFF + 32);
2475
+ if (dexPoolBytes.some((b) => b !== 0)) {
2476
+ dexPool = new PublicKey3(dexPoolBytes);
2477
+ }
2478
+ }
2205
2479
  return {
2206
2480
  collateralMint,
2207
2481
  vaultPubkey,
@@ -2244,7 +2518,8 @@ function parseConfig(data, layoutHint) {
2244
2518
  insuranceIsolationBps,
2245
2519
  oraclePhase,
2246
2520
  cumulativeVolumeE6,
2247
- phase2DeltaSlots
2521
+ phase2DeltaSlots,
2522
+ dexPool
2248
2523
  };
2249
2524
  }
2250
2525
  function parseParams(data, layoutHint) {
@@ -2384,16 +2659,17 @@ function parseAccount(data, idx) {
2384
2659
  if (data.length < base + layout.accountSize) {
2385
2660
  throw new Error("Slab data too short for account");
2386
2661
  }
2662
+ const isV12_1 = layout.accountSize >= 320;
2387
2663
  const isAdl = layout.accountSize >= 312;
2388
2664
  const warmupStartedOff = isAdl ? V_ADL_ACCT_WARMUP_STARTED_OFF : ACCT_WARMUP_STARTED_OFF;
2389
2665
  const warmupSlopeOff = isAdl ? V_ADL_ACCT_WARMUP_SLOPE_OFF : ACCT_WARMUP_SLOPE_OFF;
2390
- const positionSizeOff = isAdl ? V_ADL_ACCT_POSITION_SIZE_OFF : ACCT_POSITION_SIZE_OFF;
2391
- const entryPriceOff = isAdl ? V_ADL_ACCT_ENTRY_PRICE_OFF : ACCT_ENTRY_PRICE_OFF;
2392
- const fundingIndexOff = isAdl ? V_ADL_ACCT_FUNDING_INDEX_OFF : ACCT_FUNDING_INDEX_OFF;
2393
- const matcherProgOff = isAdl ? V_ADL_ACCT_MATCHER_PROGRAM_OFF : ACCT_MATCHER_PROGRAM_OFF;
2394
- const matcherCtxOff = isAdl ? V_ADL_ACCT_MATCHER_CONTEXT_OFF : ACCT_MATCHER_CONTEXT_OFF;
2395
- const feeCreditsOff = isAdl ? V_ADL_ACCT_FEE_CREDITS_OFF : ACCT_FEE_CREDITS_OFF;
2396
- const lastFeeSlotOff = isAdl ? V_ADL_ACCT_LAST_FEE_SLOT_OFF : ACCT_LAST_FEE_SLOT_OFF;
2666
+ const positionSizeOff = isV12_1 ? V12_1_ACCT_POSITION_SIZE_OFF : isAdl ? V_ADL_ACCT_POSITION_SIZE_OFF : ACCT_POSITION_SIZE_OFF;
2667
+ const entryPriceOff = isV12_1 ? V12_1_ACCT_ENTRY_PRICE_OFF : isAdl ? V_ADL_ACCT_ENTRY_PRICE_OFF : ACCT_ENTRY_PRICE_OFF;
2668
+ const fundingIndexOff = isV12_1 ? V12_1_ACCT_FUNDING_INDEX_OFF : isAdl ? V_ADL_ACCT_FUNDING_INDEX_OFF : ACCT_FUNDING_INDEX_OFF;
2669
+ const matcherProgOff = isV12_1 ? V12_1_ACCT_MATCHER_PROGRAM_OFF : isAdl ? V_ADL_ACCT_MATCHER_PROGRAM_OFF : ACCT_MATCHER_PROGRAM_OFF;
2670
+ const matcherCtxOff = isV12_1 ? V12_1_ACCT_MATCHER_CONTEXT_OFF : isAdl ? V_ADL_ACCT_MATCHER_CONTEXT_OFF : ACCT_MATCHER_CONTEXT_OFF;
2671
+ const feeCreditsOff = isV12_1 ? V12_1_ACCT_FEE_CREDITS_OFF : isAdl ? V_ADL_ACCT_FEE_CREDITS_OFF : ACCT_FEE_CREDITS_OFF;
2672
+ const lastFeeSlotOff = isV12_1 ? V12_1_ACCT_LAST_FEE_SLOT_OFF : isAdl ? V_ADL_ACCT_LAST_FEE_SLOT_OFF : ACCT_LAST_FEE_SLOT_OFF;
2397
2673
  const kindByte = readU8(data, base + ACCT_KIND_OFF);
2398
2674
  const kind = kindByte === 1 ? 1 /* LP */ : 0 /* User */;
2399
2675
  return {
@@ -2406,7 +2682,8 @@ function parseAccount(data, idx) {
2406
2682
  warmupSlopePerStep: readU128LE(data, base + warmupSlopeOff),
2407
2683
  positionSize: readI128LE(data, base + positionSizeOff),
2408
2684
  entryPrice: readU64LE(data, base + entryPriceOff),
2409
- fundingIndex: readI128LE(data, base + fundingIndexOff),
2685
+ // V12_1 changed funding_index from i128 to i64 (legacy field moved to end of account)
2686
+ fundingIndex: isV12_1 ? BigInt(readI64LE(data, base + fundingIndexOff)) : readI128LE(data, base + fundingIndexOff),
2410
2687
  matcherProgram: new PublicKey3(data.subarray(base + matcherProgOff, base + matcherProgOff + 32)),
2411
2688
  matcherContext: new PublicKey3(data.subarray(base + matcherCtxOff, base + matcherCtxOff + 32)),
2412
2689
  owner: new PublicKey3(data.subarray(base + layout.acctOwnerOff, base + layout.acctOwnerOff + 32)),
@@ -2501,7 +2778,14 @@ function derivePythPushOraclePDA(feedIdHex) {
2501
2778
  }
2502
2779
  const feedId = new Uint8Array(32);
2503
2780
  for (let i = 0; i < 32; i++) {
2504
- feedId[i] = parseInt(normalized.substring(i * 2, i * 2 + 2), 16);
2781
+ const hexPair = normalized.substring(i * 2, i * 2 + 2);
2782
+ const byte = parseInt(hexPair, 16);
2783
+ if (Number.isNaN(byte)) {
2784
+ throw new Error(
2785
+ `derivePythPushOraclePDA: failed to parse hex byte at position ${i}: "${hexPair}"`
2786
+ );
2787
+ }
2788
+ feedId[i] = byte;
2505
2789
  }
2506
2790
  const shardBuf = new Uint8Array(2);
2507
2791
  return PublicKey4.findProgramAddressSync(
@@ -2527,13 +2811,76 @@ async function fetchTokenAccount(connection, address, tokenProgramId = TOKEN_PRO
2527
2811
  return getAccount(connection, address, void 0, tokenProgramId);
2528
2812
  }
2529
2813
 
2814
+ // src/solana/discovery.ts
2815
+ import { PublicKey as PublicKey6 } from "@solana/web3.js";
2816
+
2817
+ // src/solana/static-markets.ts
2818
+ import { PublicKey as PublicKey5 } from "@solana/web3.js";
2819
+ var MAINNET_MARKETS = [
2820
+ // Populated at mainnet launch — currently empty.
2821
+ // To add entries:
2822
+ // { slabAddress: "ABC123...", symbol: "SOL-PERP", name: "SOL Perpetual" },
2823
+ ];
2824
+ var DEVNET_MARKETS = [
2825
+ // Populated from prior discoverMarkets() runs on devnet.
2826
+ // These serve as the tier-3 safety net for devnet users.
2827
+ ];
2828
+ var STATIC_REGISTRY = {
2829
+ mainnet: MAINNET_MARKETS,
2830
+ devnet: DEVNET_MARKETS
2831
+ };
2832
+ var USER_MARKETS = {
2833
+ mainnet: [],
2834
+ devnet: []
2835
+ };
2836
+ function getStaticMarkets(network) {
2837
+ const builtin = STATIC_REGISTRY[network] ?? [];
2838
+ const user = USER_MARKETS[network] ?? [];
2839
+ if (user.length === 0) return [...builtin];
2840
+ const seen = /* @__PURE__ */ new Map();
2841
+ for (const entry of builtin) {
2842
+ seen.set(entry.slabAddress, entry);
2843
+ }
2844
+ for (const entry of user) {
2845
+ seen.set(entry.slabAddress, entry);
2846
+ }
2847
+ return [...seen.values()];
2848
+ }
2849
+ function registerStaticMarkets(network, entries) {
2850
+ const existing = USER_MARKETS[network];
2851
+ const seen = new Set(existing.map((e) => e.slabAddress));
2852
+ for (const entry of entries) {
2853
+ if (!entry.slabAddress) continue;
2854
+ if (seen.has(entry.slabAddress)) continue;
2855
+ try {
2856
+ new PublicKey5(entry.slabAddress);
2857
+ } catch {
2858
+ console.warn(
2859
+ `[registerStaticMarkets] Skipping invalid slabAddress: ${entry.slabAddress}`
2860
+ );
2861
+ continue;
2862
+ }
2863
+ seen.add(entry.slabAddress);
2864
+ existing.push(entry);
2865
+ }
2866
+ }
2867
+ function clearStaticMarkets(network) {
2868
+ if (network) {
2869
+ USER_MARKETS[network] = [];
2870
+ } else {
2871
+ USER_MARKETS.mainnet = [];
2872
+ USER_MARKETS.devnet = [];
2873
+ }
2874
+ }
2875
+
2530
2876
  // src/solana/discovery.ts
2531
2877
  var ENGINE_BITMAP_OFF_V0 = 320;
2532
2878
  var MAGIC_BYTES = new Uint8Array([84, 65, 76, 79, 67, 82, 69, 80]);
2533
2879
  var SLAB_TIERS = {
2534
- small: { maxAccounts: 256, dataSize: 65352, label: "Small", description: "256 slots \xB7 ~0.45 SOL" },
2535
- medium: { maxAccounts: 1024, dataSize: 257448, label: "Medium", description: "1,024 slots \xB7 ~1.79 SOL" },
2536
- large: { maxAccounts: 4096, dataSize: 1025832, label: "Large", description: "4,096 slots \xB7 ~7.14 SOL" }
2880
+ micro: SLAB_TIERS_V12_1["micro"],
2881
+ small: SLAB_TIERS_V12_1["small"],
2882
+ medium: SLAB_TIERS_V12_1["medium"],
2883
+ large: SLAB_TIERS_V12_1["large"]
2537
2884
  };
2538
2885
  var SLAB_TIERS_V0 = {
2539
2886
  small: { maxAccounts: 256, dataSize: 62808, label: "Small", description: "256 slots \xB7 ~0.44 SOL" },
@@ -2911,19 +3258,74 @@ async function discoverMarkets(connection, programId, options = {}) {
2911
3258
  "[discoverMarkets] dataSize filters failed, falling back to memcmp:",
2912
3259
  err instanceof Error ? err.message : err
2913
3260
  );
2914
- const fallback = await connection.getProgramAccounts(programId, {
2915
- filters: [
2916
- {
2917
- memcmp: {
2918
- offset: 0,
2919
- bytes: "F6P2QNqpQV5"
2920
- // base58 of TALOCREP (u64 LE magic)
3261
+ try {
3262
+ const fallback = await connection.getProgramAccounts(programId, {
3263
+ filters: [
3264
+ {
3265
+ memcmp: {
3266
+ offset: 0,
3267
+ bytes: "F6P2QNqpQV5"
3268
+ // base58 of TALOCREP (u64 LE magic)
3269
+ }
2921
3270
  }
2922
- }
2923
- ],
2924
- dataSlice: { offset: 0, length: HEADER_SLICE_LENGTH }
2925
- });
2926
- rawAccounts = [...fallback].map((e) => ({ ...e, maxAccounts: 4096, dataSize: SLAB_TIERS.large.dataSize }));
3271
+ ],
3272
+ dataSlice: { offset: 0, length: HEADER_SLICE_LENGTH }
3273
+ });
3274
+ rawAccounts = [...fallback].map((e) => ({ ...e, maxAccounts: 4096, dataSize: SLAB_TIERS.large.dataSize }));
3275
+ } catch (memcmpErr) {
3276
+ console.warn(
3277
+ "[discoverMarkets] memcmp fallback also failed:",
3278
+ memcmpErr instanceof Error ? memcmpErr.message : memcmpErr
3279
+ );
3280
+ }
3281
+ }
3282
+ if (rawAccounts.length === 0 && options.apiBaseUrl) {
3283
+ console.warn(
3284
+ "[discoverMarkets] RPC discovery returned 0 markets, falling back to REST API"
3285
+ );
3286
+ try {
3287
+ const apiResult = await discoverMarketsViaApi(
3288
+ connection,
3289
+ programId,
3290
+ options.apiBaseUrl,
3291
+ { timeoutMs: options.apiTimeoutMs }
3292
+ );
3293
+ if (apiResult.length > 0) {
3294
+ return apiResult;
3295
+ }
3296
+ console.warn(
3297
+ "[discoverMarkets] REST API returned 0 markets, checking tier-3 static bundle"
3298
+ );
3299
+ } catch (apiErr) {
3300
+ console.warn(
3301
+ "[discoverMarkets] API fallback also failed:",
3302
+ apiErr instanceof Error ? apiErr.message : apiErr
3303
+ );
3304
+ }
3305
+ }
3306
+ if (rawAccounts.length === 0 && options.network) {
3307
+ const staticEntries = getStaticMarkets(options.network);
3308
+ if (staticEntries.length > 0) {
3309
+ console.warn(
3310
+ `[discoverMarkets] Tier 1+2 failed, falling back to static bundle (${staticEntries.length} addresses for ${options.network})`
3311
+ );
3312
+ try {
3313
+ return await discoverMarketsViaStaticBundle(
3314
+ connection,
3315
+ programId,
3316
+ staticEntries
3317
+ );
3318
+ } catch (staticErr) {
3319
+ console.warn(
3320
+ "[discoverMarkets] Static bundle fallback also failed:",
3321
+ staticErr instanceof Error ? staticErr.message : staticErr
3322
+ );
3323
+ }
3324
+ } else {
3325
+ console.warn(
3326
+ `[discoverMarkets] Static bundle has 0 entries for ${options.network} \u2014 skipping tier 3`
3327
+ );
3328
+ }
2927
3329
  }
2928
3330
  const accounts = rawAccounts;
2929
3331
  const markets = [];
@@ -2963,30 +3365,166 @@ async function discoverMarkets(connection, programId, options = {}) {
2963
3365
  }
2964
3366
  return markets;
2965
3367
  }
2966
-
2967
- // src/solana/dex-oracle.ts
2968
- import { PublicKey as PublicKey5 } from "@solana/web3.js";
2969
- function detectDexType(ownerProgramId) {
2970
- if (ownerProgramId.equals(PUMPSWAP_PROGRAM_ID)) return "pumpswap";
2971
- if (ownerProgramId.equals(RAYDIUM_CLMM_PROGRAM_ID)) return "raydium-clmm";
2972
- if (ownerProgramId.equals(METEORA_DLMM_PROGRAM_ID)) return "meteora-dlmm";
2973
- return null;
2974
- }
2975
- function parseDexPool(dexType, poolAddress, data) {
2976
- switch (dexType) {
2977
- case "pumpswap":
2978
- return parsePumpSwapPool(poolAddress, data);
2979
- case "raydium-clmm":
2980
- return parseRaydiumClmmPool(poolAddress, data);
2981
- case "meteora-dlmm":
2982
- return parseMeteoraPool(poolAddress, data);
2983
- }
2984
- }
2985
- function computeDexSpotPriceE6(dexType, data, vaultData) {
2986
- switch (dexType) {
2987
- case "pumpswap":
2988
- if (!vaultData) throw new Error("PumpSwap requires vaultData (base and quote vault accounts)");
2989
- return computePumpSwapPriceE6(data, vaultData);
3368
+ async function getMarketsByAddress(connection, programId, addresses, options = {}) {
3369
+ if (addresses.length === 0) return [];
3370
+ const {
3371
+ batchSize = 100,
3372
+ interBatchDelayMs = 0
3373
+ } = options;
3374
+ const effectiveBatchSize = Math.max(1, Math.min(batchSize, 100));
3375
+ const fetched = [];
3376
+ for (let offset = 0; offset < addresses.length; offset += effectiveBatchSize) {
3377
+ const batch = addresses.slice(offset, offset + effectiveBatchSize);
3378
+ const response = await connection.getMultipleAccountsInfo(batch);
3379
+ for (let i = 0; i < batch.length; i++) {
3380
+ const info = response[i];
3381
+ if (info && info.data) {
3382
+ if (!info.owner.equals(programId)) {
3383
+ console.warn(
3384
+ `[getMarketsByAddress] Skipping ${batch[i].toBase58()}: owner mismatch (expected ${programId.toBase58()}, got ${info.owner.toBase58()})`
3385
+ );
3386
+ continue;
3387
+ }
3388
+ fetched.push({ pubkey: batch[i], data: info.data });
3389
+ }
3390
+ }
3391
+ if (interBatchDelayMs > 0 && offset + effectiveBatchSize < addresses.length) {
3392
+ await new Promise((r) => setTimeout(r, interBatchDelayMs));
3393
+ }
3394
+ }
3395
+ const markets = [];
3396
+ for (const entry of fetched) {
3397
+ if (!entry) continue;
3398
+ const { pubkey, data: rawData } = entry;
3399
+ const data = new Uint8Array(rawData);
3400
+ let valid = true;
3401
+ for (let i = 0; i < MAGIC_BYTES.length; i++) {
3402
+ if (data[i] !== MAGIC_BYTES[i]) {
3403
+ valid = false;
3404
+ break;
3405
+ }
3406
+ }
3407
+ if (!valid) {
3408
+ console.warn(
3409
+ `[getMarketsByAddress] Skipping ${pubkey.toBase58()}: invalid magic bytes`
3410
+ );
3411
+ continue;
3412
+ }
3413
+ const layout = detectSlabLayout(data.length, data);
3414
+ if (!layout) {
3415
+ console.warn(
3416
+ `[getMarketsByAddress] Skipping ${pubkey.toBase58()}: unrecognized layout for dataSize=${data.length}`
3417
+ );
3418
+ continue;
3419
+ }
3420
+ try {
3421
+ const header = parseHeader(data);
3422
+ const config = parseConfig(data, layout);
3423
+ const engine = parseEngineLight(data, layout, layout.maxAccounts);
3424
+ const params = parseParams(data, layout);
3425
+ markets.push({ slabAddress: pubkey, programId, header, config, engine, params });
3426
+ } catch (err) {
3427
+ console.warn(
3428
+ `[getMarketsByAddress] Failed to parse account ${pubkey.toBase58()}:`,
3429
+ err instanceof Error ? err.message : err
3430
+ );
3431
+ }
3432
+ }
3433
+ return markets;
3434
+ }
3435
+ async function discoverMarketsViaApi(connection, programId, apiBaseUrl, options = {}) {
3436
+ const { timeoutMs = 1e4, onChainOptions } = options;
3437
+ const base = apiBaseUrl.replace(/\/+$/, "");
3438
+ const url = `${base}/markets`;
3439
+ const controller = new AbortController();
3440
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3441
+ let response;
3442
+ try {
3443
+ response = await fetch(url, {
3444
+ method: "GET",
3445
+ headers: { Accept: "application/json" },
3446
+ signal: controller.signal
3447
+ });
3448
+ } finally {
3449
+ clearTimeout(timer);
3450
+ }
3451
+ if (!response.ok) {
3452
+ throw new Error(
3453
+ `[discoverMarketsViaApi] API returned ${response.status} ${response.statusText} from ${url}`
3454
+ );
3455
+ }
3456
+ const body = await response.json();
3457
+ const apiMarkets = body.markets;
3458
+ if (!Array.isArray(apiMarkets) || apiMarkets.length === 0) {
3459
+ console.warn("[discoverMarketsViaApi] API returned 0 markets");
3460
+ return [];
3461
+ }
3462
+ const addresses = [];
3463
+ for (const entry of apiMarkets) {
3464
+ if (!entry.slab_address || typeof entry.slab_address !== "string") continue;
3465
+ try {
3466
+ addresses.push(new PublicKey6(entry.slab_address));
3467
+ } catch {
3468
+ console.warn(
3469
+ `[discoverMarketsViaApi] Skipping invalid slab address: ${entry.slab_address}`
3470
+ );
3471
+ }
3472
+ }
3473
+ if (addresses.length === 0) {
3474
+ console.warn("[discoverMarketsViaApi] No valid slab addresses from API");
3475
+ return [];
3476
+ }
3477
+ console.log(
3478
+ `[discoverMarketsViaApi] API returned ${addresses.length} slab addresses, fetching on-chain data`
3479
+ );
3480
+ return getMarketsByAddress(connection, programId, addresses, onChainOptions);
3481
+ }
3482
+ async function discoverMarketsViaStaticBundle(connection, programId, entries, options = {}) {
3483
+ if (entries.length === 0) return [];
3484
+ const addresses = [];
3485
+ for (const entry of entries) {
3486
+ if (!entry.slabAddress || typeof entry.slabAddress !== "string") continue;
3487
+ try {
3488
+ addresses.push(new PublicKey6(entry.slabAddress));
3489
+ } catch {
3490
+ console.warn(
3491
+ `[discoverMarketsViaStaticBundle] Skipping invalid slab address: ${entry.slabAddress}`
3492
+ );
3493
+ }
3494
+ }
3495
+ if (addresses.length === 0) {
3496
+ console.warn("[discoverMarketsViaStaticBundle] No valid slab addresses in static bundle");
3497
+ return [];
3498
+ }
3499
+ console.log(
3500
+ `[discoverMarketsViaStaticBundle] Fetching ${addresses.length} slab addresses on-chain`
3501
+ );
3502
+ return getMarketsByAddress(connection, programId, addresses, options.onChainOptions);
3503
+ }
3504
+
3505
+ // src/solana/dex-oracle.ts
3506
+ import { PublicKey as PublicKey7 } from "@solana/web3.js";
3507
+ function detectDexType(ownerProgramId) {
3508
+ if (ownerProgramId.equals(PUMPSWAP_PROGRAM_ID)) return "pumpswap";
3509
+ if (ownerProgramId.equals(RAYDIUM_CLMM_PROGRAM_ID)) return "raydium-clmm";
3510
+ if (ownerProgramId.equals(METEORA_DLMM_PROGRAM_ID)) return "meteora-dlmm";
3511
+ return null;
3512
+ }
3513
+ function parseDexPool(dexType, poolAddress, data) {
3514
+ switch (dexType) {
3515
+ case "pumpswap":
3516
+ return parsePumpSwapPool(poolAddress, data);
3517
+ case "raydium-clmm":
3518
+ return parseRaydiumClmmPool(poolAddress, data);
3519
+ case "meteora-dlmm":
3520
+ return parseMeteoraPool(poolAddress, data);
3521
+ }
3522
+ }
3523
+ function computeDexSpotPriceE6(dexType, data, vaultData) {
3524
+ switch (dexType) {
3525
+ case "pumpswap":
3526
+ if (!vaultData) throw new Error("PumpSwap requires vaultData (base and quote vault accounts)");
3527
+ return computePumpSwapPriceE6(data, vaultData);
2990
3528
  case "raydium-clmm":
2991
3529
  return computeRaydiumClmmPriceE6(data);
2992
3530
  case "meteora-dlmm":
@@ -3001,10 +3539,10 @@ function parsePumpSwapPool(poolAddress, data) {
3001
3539
  return {
3002
3540
  dexType: "pumpswap",
3003
3541
  poolAddress,
3004
- baseMint: new PublicKey5(data.slice(35, 67)),
3005
- quoteMint: new PublicKey5(data.slice(67, 99)),
3006
- baseVault: new PublicKey5(data.slice(131, 163)),
3007
- quoteVault: new PublicKey5(data.slice(163, 195))
3542
+ baseMint: new PublicKey7(data.slice(35, 67)),
3543
+ quoteMint: new PublicKey7(data.slice(67, 99)),
3544
+ baseVault: new PublicKey7(data.slice(131, 163)),
3545
+ quoteVault: new PublicKey7(data.slice(163, 195))
3008
3546
  };
3009
3547
  }
3010
3548
  var SPL_TOKEN_AMOUNT_MIN_LEN = 72;
@@ -3030,8 +3568,8 @@ function parseRaydiumClmmPool(poolAddress, data) {
3030
3568
  return {
3031
3569
  dexType: "raydium-clmm",
3032
3570
  poolAddress,
3033
- baseMint: new PublicKey5(data.slice(73, 105)),
3034
- quoteMint: new PublicKey5(data.slice(105, 137))
3571
+ baseMint: new PublicKey7(data.slice(73, 105)),
3572
+ quoteMint: new PublicKey7(data.slice(105, 137))
3035
3573
  };
3036
3574
  }
3037
3575
  var MAX_TOKEN_DECIMALS = 24;
@@ -3049,9 +3587,7 @@ function computeRaydiumClmmPriceE6(data) {
3049
3587
  }
3050
3588
  const sqrtPriceX64 = readU128LE3(dv3, 253);
3051
3589
  if (sqrtPriceX64 === 0n) return 0n;
3052
- const scaledSqrt = sqrtPriceX64 * 1000000n;
3053
- const term = scaledSqrt >> 64n;
3054
- const priceE6Raw = term * sqrtPriceX64 >> 64n;
3590
+ const priceE6Raw = sqrtPriceX64 * sqrtPriceX64 * 1000000n >> 128n;
3055
3591
  const decimalDiff = 6 + decimals0 - decimals1;
3056
3592
  const adjustedDiff = decimalDiff - 6;
3057
3593
  if (adjustedDiff >= 0) {
@@ -3070,8 +3606,8 @@ function parseMeteoraPool(poolAddress, data) {
3070
3606
  return {
3071
3607
  dexType: "meteora-dlmm",
3072
3608
  poolAddress,
3073
- baseMint: new PublicKey5(data.slice(81, 113)),
3074
- quoteMint: new PublicKey5(data.slice(113, 145))
3609
+ baseMint: new PublicKey7(data.slice(81, 113)),
3610
+ quoteMint: new PublicKey7(data.slice(113, 145))
3075
3611
  };
3076
3612
  }
3077
3613
  var MAX_BIN_STEP = 1e4;
@@ -3104,7 +3640,14 @@ function computeMeteoraDlmmPriceE6(data) {
3104
3640
  let exp = isNeg ? BigInt(-activeId) : BigInt(activeId);
3105
3641
  let result = SCALE;
3106
3642
  let b = base;
3643
+ let iterations = 0;
3644
+ const MAX_ITERATIONS = 25;
3107
3645
  while (exp > 0n) {
3646
+ if (iterations++ >= MAX_ITERATIONS) {
3647
+ throw new Error(
3648
+ `Meteora DLMM: exponentiation loop exceeded ${MAX_ITERATIONS} iterations (activeId=${activeId})`
3649
+ );
3650
+ }
3108
3651
  if (exp & 1n) {
3109
3652
  result = result * b / SCALE;
3110
3653
  }
@@ -3135,6 +3678,7 @@ function readU128LE3(dv3, offset) {
3135
3678
  var CHAINLINK_MIN_SIZE = 224;
3136
3679
  var MAX_DECIMALS = 18;
3137
3680
  var CHAINLINK_DECIMALS_OFFSET = 138;
3681
+ var CHAINLINK_TIMESTAMP_OFFSET = 168;
3138
3682
  var CHAINLINK_ANSWER_OFFSET = 216;
3139
3683
  function readU82(data, off) {
3140
3684
  return data[off];
@@ -3142,7 +3686,7 @@ function readU82(data, off) {
3142
3686
  function readBigInt64LE(data, off) {
3143
3687
  return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigInt64(off, true);
3144
3688
  }
3145
- function parseChainlinkPrice(data) {
3689
+ function parseChainlinkPrice(data, options) {
3146
3690
  if (data.length < CHAINLINK_MIN_SIZE) {
3147
3691
  throw new Error(
3148
3692
  `Oracle account data too small: ${data.length} bytes (need at least ${CHAINLINK_MIN_SIZE})`
@@ -3160,7 +3704,18 @@ function parseChainlinkPrice(data) {
3160
3704
  `Oracle price is non-positive: ${price}`
3161
3705
  );
3162
3706
  }
3163
- return { price, decimals };
3707
+ const updatedAtBig = readBigInt64LE(data, CHAINLINK_TIMESTAMP_OFFSET);
3708
+ const updatedAt = Number(updatedAtBig);
3709
+ if (options?.maxStalenessSeconds !== void 0 && updatedAt > 0) {
3710
+ const now = Math.floor(Date.now() / 1e3);
3711
+ const age = now - updatedAt;
3712
+ if (age > options.maxStalenessSeconds) {
3713
+ throw new Error(
3714
+ `Oracle price is stale: last updated ${age}s ago (max ${options.maxStalenessSeconds}s)`
3715
+ );
3716
+ }
3717
+ }
3718
+ return { price, decimals, updatedAt: updatedAt > 0 ? updatedAt : void 0 };
3164
3719
  }
3165
3720
  function isValidChainlinkOracle(data) {
3166
3721
  try {
@@ -3172,15 +3727,19 @@ function isValidChainlinkOracle(data) {
3172
3727
  }
3173
3728
 
3174
3729
  // src/solana/token-program.ts
3175
- import { PublicKey as PublicKey6 } from "@solana/web3.js";
3730
+ import { PublicKey as PublicKey8 } from "@solana/web3.js";
3176
3731
  import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID3 } from "@solana/spl-token";
3177
- var TOKEN_2022_PROGRAM_ID = new PublicKey6(
3732
+ var TOKEN_2022_PROGRAM_ID = new PublicKey8(
3178
3733
  "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
3179
3734
  );
3180
3735
  async function detectTokenProgram(connection, mint) {
3181
3736
  const info = await connection.getAccountInfo(mint);
3182
3737
  if (!info) throw new Error(`Mint account not found: ${mint.toBase58()}`);
3183
- return info.owner;
3738
+ if (info.owner.equals(TOKEN_PROGRAM_ID3)) return TOKEN_PROGRAM_ID3;
3739
+ if (info.owner.equals(TOKEN_2022_PROGRAM_ID)) return TOKEN_2022_PROGRAM_ID;
3740
+ throw new Error(
3741
+ `Mint ${mint.toBase58()} is owned by ${info.owner.toBase58()}, which is neither TOKEN_PROGRAM_ID nor TOKEN_2022_PROGRAM_ID`
3742
+ );
3184
3743
  }
3185
3744
  function isToken2022(tokenProgramId) {
3186
3745
  return tokenProgramId.equals(TOKEN_2022_PROGRAM_ID);
@@ -3190,11 +3749,11 @@ function isStandardToken(tokenProgramId) {
3190
3749
  }
3191
3750
 
3192
3751
  // src/solana/stake.ts
3193
- import { PublicKey as PublicKey8, SystemProgram as SystemProgram2, SYSVAR_RENT_PUBKEY as SYSVAR_RENT_PUBKEY2, SYSVAR_CLOCK_PUBKEY as SYSVAR_CLOCK_PUBKEY2 } from "@solana/web3.js";
3752
+ import { PublicKey as PublicKey10, SystemProgram as SystemProgram2, SYSVAR_RENT_PUBKEY as SYSVAR_RENT_PUBKEY2, SYSVAR_CLOCK_PUBKEY as SYSVAR_CLOCK_PUBKEY2 } from "@solana/web3.js";
3194
3753
  import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID4 } from "@solana/spl-token";
3195
3754
 
3196
3755
  // src/config/program-ids.ts
3197
- import { PublicKey as PublicKey7 } from "@solana/web3.js";
3756
+ import { PublicKey as PublicKey9 } from "@solana/web3.js";
3198
3757
  function safeEnv(key) {
3199
3758
  try {
3200
3759
  return typeof process !== "undefined" && process?.env ? process.env[key] : void 0;
@@ -3213,25 +3772,29 @@ var PROGRAM_IDS = {
3213
3772
  }
3214
3773
  };
3215
3774
  function getProgramId(network) {
3216
- const override = safeEnv("PROGRAM_ID");
3217
- if (override) {
3218
- console.warn(
3219
- `[percolator-sdk] PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3220
- );
3221
- return new PublicKey7(override);
3775
+ if (!network) {
3776
+ const override = safeEnv("PROGRAM_ID");
3777
+ if (override) {
3778
+ console.warn(
3779
+ `[percolator-sdk] PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3780
+ );
3781
+ return new PublicKey9(override);
3782
+ }
3222
3783
  }
3223
3784
  const detectedNetwork = getCurrentNetwork();
3224
3785
  const targetNetwork = network ?? detectedNetwork;
3225
3786
  const programId = PROGRAM_IDS[targetNetwork].percolator;
3226
- return new PublicKey7(programId);
3787
+ return new PublicKey9(programId);
3227
3788
  }
3228
3789
  function getMatcherProgramId(network) {
3229
- const override = safeEnv("MATCHER_PROGRAM_ID");
3230
- if (override) {
3231
- console.warn(
3232
- `[percolator-sdk] MATCHER_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3233
- );
3234
- return new PublicKey7(override);
3790
+ if (!network) {
3791
+ const override = safeEnv("MATCHER_PROGRAM_ID");
3792
+ if (override) {
3793
+ console.warn(
3794
+ `[percolator-sdk] MATCHER_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3795
+ );
3796
+ return new PublicKey9(override);
3797
+ }
3235
3798
  }
3236
3799
  const detectedNetwork = getCurrentNetwork();
3237
3800
  const targetNetwork = network ?? detectedNetwork;
@@ -3239,7 +3802,7 @@ function getMatcherProgramId(network) {
3239
3802
  if (!programId) {
3240
3803
  throw new Error(`Matcher program not deployed on ${targetNetwork}`);
3241
3804
  }
3242
- return new PublicKey7(programId);
3805
+ return new PublicKey9(programId);
3243
3806
  }
3244
3807
  function getCurrentNetwork() {
3245
3808
  const network = safeEnv("NETWORK")?.toLowerCase();
@@ -3261,7 +3824,7 @@ function getStakeProgramId(network) {
3261
3824
  console.warn(
3262
3825
  `[percolator-sdk] STAKE_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3263
3826
  );
3264
- return new PublicKey8(override);
3827
+ return new PublicKey10(override);
3265
3828
  }
3266
3829
  const detectedNetwork = network ?? (() => {
3267
3830
  const n = safeEnv("NEXT_PUBLIC_DEFAULT_NETWORK")?.toLowerCase() ?? safeEnv("NETWORK")?.toLowerCase() ?? "";
@@ -3273,9 +3836,9 @@ function getStakeProgramId(network) {
3273
3836
  `Stake program not deployed on ${detectedNetwork}. Set STAKE_PROGRAM_ID env var or wait for DevOps to deploy and update STAKE_PROGRAM_IDS.mainnet.`
3274
3837
  );
3275
3838
  }
3276
- return new PublicKey8(id);
3839
+ return new PublicKey10(id);
3277
3840
  }
3278
- var STAKE_PROGRAM_ID = new PublicKey8(STAKE_PROGRAM_IDS.devnet);
3841
+ var STAKE_PROGRAM_ID = new PublicKey10(STAKE_PROGRAM_IDS.devnet);
3279
3842
  var STAKE_IX = {
3280
3843
  InitPool: 0,
3281
3844
  Deposit: 1,
@@ -3302,19 +3865,19 @@ var STAKE_IX = {
3302
3865
  };
3303
3866
  var TEXT = new TextEncoder();
3304
3867
  function deriveStakePool(slab, programId) {
3305
- return PublicKey8.findProgramAddressSync(
3868
+ return PublicKey10.findProgramAddressSync(
3306
3869
  [TEXT.encode("stake_pool"), slab.toBytes()],
3307
3870
  programId ?? getStakeProgramId()
3308
3871
  );
3309
3872
  }
3310
3873
  function deriveStakeVaultAuth(pool, programId) {
3311
- return PublicKey8.findProgramAddressSync(
3874
+ return PublicKey10.findProgramAddressSync(
3312
3875
  [TEXT.encode("vault_auth"), pool.toBytes()],
3313
3876
  programId ?? getStakeProgramId()
3314
3877
  );
3315
3878
  }
3316
3879
  function deriveDepositPda(pool, user, programId) {
3317
- return PublicKey8.findProgramAddressSync(
3880
+ return PublicKey10.findProgramAddressSync(
3318
3881
  [TEXT.encode("deposit"), pool.toBytes(), user.toBytes()],
3319
3882
  programId ?? getStakeProgramId()
3320
3883
  );
@@ -3336,6 +3899,9 @@ function readU16LE3(data, off) {
3336
3899
  );
3337
3900
  }
3338
3901
  function u64Le(v) {
3902
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
3903
+ throw new Error(`u64Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
3904
+ }
3339
3905
  const big = BigInt(v);
3340
3906
  if (big < 0n) throw new Error(`u64Le: value must be non-negative, got ${big}`);
3341
3907
  if (big > 0xFFFFFFFFFFFFFFFFn) throw new Error(`u64Le: value exceeds u64 max`);
@@ -3344,6 +3910,9 @@ function u64Le(v) {
3344
3910
  return arr;
3345
3911
  }
3346
3912
  function u128Le(v) {
3913
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
3914
+ throw new Error(`u128Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
3915
+ }
3347
3916
  const big = BigInt(v);
3348
3917
  if (big < 0n) throw new Error(`u128Le: value must be non-negative, got ${big}`);
3349
3918
  if (big > (1n << 128n) - 1n) throw new Error(`u128Le: value exceeds u128 max`);
@@ -3354,7 +3923,7 @@ function u128Le(v) {
3354
3923
  return arr;
3355
3924
  }
3356
3925
  function u16Le(v) {
3357
- if (v < 0 || v > 65535) throw new Error(`u16Le: value out of u16 range (0..65535), got ${v}`);
3926
+ if (!Number.isInteger(v) || v < 0 || v > 65535) throw new Error(`u16Le: value must be integer in range 0..65535, got ${v}`);
3358
3927
  const arr = new Uint8Array(2);
3359
3928
  new DataView(arr.buffer).setUint16(0, v, true);
3360
3929
  return arr;
@@ -3465,15 +4034,15 @@ function decodeStakePool(data) {
3465
4034
  const adminTransferred = bytes[off] === 1;
3466
4035
  off += 1;
3467
4036
  off += 4;
3468
- const slab = new PublicKey8(bytes.subarray(off, off + 32));
4037
+ const slab = new PublicKey10(bytes.subarray(off, off + 32));
3469
4038
  off += 32;
3470
- const admin = new PublicKey8(bytes.subarray(off, off + 32));
4039
+ const admin = new PublicKey10(bytes.subarray(off, off + 32));
3471
4040
  off += 32;
3472
- const collateralMint = new PublicKey8(bytes.subarray(off, off + 32));
4041
+ const collateralMint = new PublicKey10(bytes.subarray(off, off + 32));
3473
4042
  off += 32;
3474
- const lpMint = new PublicKey8(bytes.subarray(off, off + 32));
4043
+ const lpMint = new PublicKey10(bytes.subarray(off, off + 32));
3475
4044
  off += 32;
3476
- const vault = new PublicKey8(bytes.subarray(off, off + 32));
4045
+ const vault = new PublicKey10(bytes.subarray(off, off + 32));
3477
4046
  off += 32;
3478
4047
  const totalDeposited = readU64LE4(bytes, off);
3479
4048
  off += 8;
@@ -3489,7 +4058,7 @@ function decodeStakePool(data) {
3489
4058
  off += 8;
3490
4059
  const totalWithdrawn = readU64LE4(bytes, off);
3491
4060
  off += 8;
3492
- const percolatorProgram = new PublicKey8(bytes.subarray(off, off + 32));
4061
+ const percolatorProgram = new PublicKey10(bytes.subarray(off, off + 32));
3493
4062
  off += 32;
3494
4063
  const totalFeesEarned = readU64LE4(bytes, off);
3495
4064
  off += 8;
@@ -3609,7 +4178,9 @@ function computePnlPct(pnl, capital) {
3609
4178
  }
3610
4179
  function isAdlTriggered(slabData) {
3611
4180
  const layout = detectSlabLayout(slabData.length);
3612
- if (!layout) return false;
4181
+ if (!layout) {
4182
+ return false;
4183
+ }
3613
4184
  try {
3614
4185
  const engine = parseEngine(slabData);
3615
4186
  if (engine.pnlPosTot === 0n) return false;
@@ -3652,6 +4223,14 @@ function rankAdlPositions(slabData) {
3652
4223
  if (account.kind !== 0 /* User */) continue;
3653
4224
  if (account.positionSize === 0n) continue;
3654
4225
  const side = account.positionSize > 0n ? "long" : "short";
4226
+ if (side === "long" && account.positionSize <= 0n) {
4227
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=long but positionSize=${account.positionSize}`);
4228
+ continue;
4229
+ }
4230
+ if (side === "short" && account.positionSize >= 0n) {
4231
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=short but positionSize=${account.positionSize}`);
4232
+ continue;
4233
+ }
3655
4234
  const pnlPct = computePnlPct(account.pnl, account.capital);
3656
4235
  positions.push({
3657
4236
  idx,
@@ -3725,7 +4304,11 @@ function parseAdlEvent(logs) {
3725
4304
  }
3726
4305
  if (tag !== ADL_EVENT_TAG) continue;
3727
4306
  try {
3728
- const targetIdx = Number(BigInt(match[2]));
4307
+ const targetIdxBig = BigInt(match[2]);
4308
+ if (targetIdxBig < 0n || targetIdxBig > 65535n) {
4309
+ continue;
4310
+ }
4311
+ const targetIdx = Number(targetIdxBig);
3729
4312
  const price = BigInt(match[3]);
3730
4313
  const closedLo = BigInt(match[4]);
3731
4314
  const closedHi = BigInt(match[5]);
@@ -3753,9 +4336,387 @@ async function fetchAdlRankings(apiBase, slab, fetchFn = fetch) {
3753
4336
  );
3754
4337
  }
3755
4338
  const json = await res.json();
4339
+ if (typeof json !== "object" || json === null) {
4340
+ throw new Error("fetchAdlRankings: API returned non-object response");
4341
+ }
4342
+ const obj = json;
4343
+ if (!Array.isArray(obj.rankings)) {
4344
+ throw new Error("fetchAdlRankings: API response missing rankings array");
4345
+ }
4346
+ for (const entry of obj.rankings) {
4347
+ if (typeof entry !== "object" || entry === null) {
4348
+ throw new Error("fetchAdlRankings: invalid ranking entry (not an object)");
4349
+ }
4350
+ const r = entry;
4351
+ if (typeof r.idx !== "number" || !Number.isInteger(r.idx) || r.idx < 0) {
4352
+ throw new Error(`fetchAdlRankings: invalid ranking idx: ${r.idx}`);
4353
+ }
4354
+ }
3756
4355
  return json;
3757
4356
  }
3758
4357
 
4358
+ // src/solana/rpc-pool.ts
4359
+ import {
4360
+ Connection as Connection5
4361
+ } from "@solana/web3.js";
4362
+ async function checkRpcHealth(endpoint, timeoutMs = 5e3) {
4363
+ const conn = new Connection5(endpoint, { commitment: "processed" });
4364
+ const start = performance.now();
4365
+ const timeout = rejectAfter(timeoutMs, `Health probe timed out after ${timeoutMs}ms`);
4366
+ try {
4367
+ const slot = await Promise.race([
4368
+ conn.getSlot("processed"),
4369
+ timeout.promise
4370
+ ]);
4371
+ const latencyMs = Math.round(performance.now() - start);
4372
+ return { endpoint, healthy: true, latencyMs, slot };
4373
+ } catch (err) {
4374
+ const latencyMs = Math.round(performance.now() - start);
4375
+ return {
4376
+ endpoint,
4377
+ healthy: false,
4378
+ latencyMs,
4379
+ slot: 0,
4380
+ error: err instanceof Error ? err.message : String(err)
4381
+ };
4382
+ } finally {
4383
+ timeout.cancel();
4384
+ }
4385
+ }
4386
+ function resolveRetryConfig(cfg) {
4387
+ if (cfg === false) return null;
4388
+ const c = cfg ?? {};
4389
+ return {
4390
+ maxRetries: c.maxRetries ?? 3,
4391
+ baseDelayMs: c.baseDelayMs ?? 500,
4392
+ maxDelayMs: c.maxDelayMs ?? 1e4,
4393
+ jitterFactor: Math.max(0, Math.min(1, c.jitterFactor ?? 0.25)),
4394
+ retryableStatusCodes: c.retryableStatusCodes ?? [429, 502, 503, 504]
4395
+ };
4396
+ }
4397
+ function normalizeEndpoint(ep) {
4398
+ if (typeof ep === "string") return { url: ep };
4399
+ return ep;
4400
+ }
4401
+ function endpointLabel(ep) {
4402
+ if (ep.label) return ep.label;
4403
+ try {
4404
+ return new URL(ep.url).hostname;
4405
+ } catch {
4406
+ return ep.url.slice(0, 40);
4407
+ }
4408
+ }
4409
+ function isRetryable(err, codes) {
4410
+ if (!err) return false;
4411
+ const msg = err instanceof Error ? err.message : String(err);
4412
+ for (const code of codes) {
4413
+ if (msg.includes(String(code))) return true;
4414
+ }
4415
+ if (msg.toLowerCase().includes("rate limit") || msg.toLowerCase().includes("too many requests") || msg.toLowerCase().includes("econnreset") || msg.toLowerCase().includes("econnrefused") || msg.toLowerCase().includes("socket hang up") || msg.toLowerCase().includes("network") || msg.toLowerCase().includes("timeout") || msg.toLowerCase().includes("abort")) {
4416
+ return true;
4417
+ }
4418
+ return false;
4419
+ }
4420
+ function computeDelay(attempt, config) {
4421
+ const raw = Math.min(
4422
+ config.baseDelayMs * Math.pow(2, attempt),
4423
+ config.maxDelayMs
4424
+ );
4425
+ const jitter = Math.floor(Math.random() * raw * config.jitterFactor);
4426
+ return raw + jitter;
4427
+ }
4428
+ function rejectAfter(ms, message) {
4429
+ let timer;
4430
+ const promise = new Promise((_, reject) => {
4431
+ timer = setTimeout(() => reject(new Error(message)), ms);
4432
+ });
4433
+ return { promise, cancel: () => clearTimeout(timer) };
4434
+ }
4435
+ function sleep(ms) {
4436
+ return new Promise((resolve) => setTimeout(resolve, ms));
4437
+ }
4438
+ function redactUrl(raw) {
4439
+ try {
4440
+ const u = new URL(raw);
4441
+ const sensitive = /^(api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|key|password|bearer|credential|jwt)$/i;
4442
+ for (const k of [...u.searchParams.keys()]) {
4443
+ if (sensitive.test(k)) {
4444
+ u.searchParams.set(k, "***");
4445
+ }
4446
+ }
4447
+ return u.toString();
4448
+ } catch {
4449
+ return raw;
4450
+ }
4451
+ }
4452
+ var RpcPool = class _RpcPool {
4453
+ endpoints;
4454
+ strategy;
4455
+ retryConfig;
4456
+ requestTimeoutMs;
4457
+ verbose;
4458
+ /** Round-robin index tracker. */
4459
+ rrIndex = 0;
4460
+ /** Consecutive failure threshold before marking an endpoint unhealthy. */
4461
+ static UNHEALTHY_THRESHOLD = 3;
4462
+ /** Minimum endpoints before auto-recovery is attempted. */
4463
+ static MIN_HEALTHY = 1;
4464
+ constructor(config) {
4465
+ if (!config.endpoints || config.endpoints.length === 0) {
4466
+ throw new Error("RpcPool: at least one endpoint is required");
4467
+ }
4468
+ this.strategy = config.strategy ?? "failover";
4469
+ this.retryConfig = resolveRetryConfig(config.retry);
4470
+ this.requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
4471
+ this.verbose = config.verbose ?? true;
4472
+ const commitment = config.commitment ?? "confirmed";
4473
+ this.endpoints = config.endpoints.map((raw) => {
4474
+ const ep = normalizeEndpoint(raw);
4475
+ const connConfig = {
4476
+ commitment,
4477
+ ...ep.connectionConfig
4478
+ };
4479
+ return {
4480
+ config: ep,
4481
+ connection: new Connection5(ep.url, connConfig),
4482
+ label: endpointLabel(ep),
4483
+ weight: Math.max(1, ep.weight ?? 1),
4484
+ failures: 0,
4485
+ healthy: true,
4486
+ lastLatencyMs: -1
4487
+ };
4488
+ });
4489
+ }
4490
+ // -----------------------------------------------------------------------
4491
+ // Public API
4492
+ // -----------------------------------------------------------------------
4493
+ /**
4494
+ * Execute a function against a pooled connection with automatic retry
4495
+ * and failover.
4496
+ *
4497
+ * @param fn - Async function that receives a `Connection` and returns a result.
4498
+ * @returns The result of `fn`.
4499
+ * @throws The last error if all retries and failovers are exhausted.
4500
+ *
4501
+ * @example
4502
+ * ```ts
4503
+ * const balance = await pool.call(c => c.getBalance(pubkey));
4504
+ * const markets = await pool.call(c => discoverMarkets(c, programId, opts));
4505
+ * ```
4506
+ */
4507
+ async call(fn) {
4508
+ const maxAttempts = this.retryConfig ? this.retryConfig.maxRetries + 1 : 1;
4509
+ let lastError;
4510
+ const triedEndpoints = /* @__PURE__ */ new Set();
4511
+ const maxTotalIterations = maxAttempts + this.endpoints.length;
4512
+ let totalIterations = 0;
4513
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4514
+ if (++totalIterations > maxTotalIterations) break;
4515
+ const epIdx = this.selectEndpoint(triedEndpoints);
4516
+ if (epIdx === -1) {
4517
+ break;
4518
+ }
4519
+ const ep = this.endpoints[epIdx];
4520
+ const timeout = rejectAfter(this.requestTimeoutMs, `RPC request timed out after ${this.requestTimeoutMs}ms (${ep.label})`);
4521
+ try {
4522
+ const result = await Promise.race([
4523
+ fn(ep.connection),
4524
+ timeout.promise
4525
+ ]);
4526
+ timeout.cancel();
4527
+ ep.failures = 0;
4528
+ ep.healthy = true;
4529
+ return result;
4530
+ } catch (err) {
4531
+ timeout.cancel();
4532
+ lastError = err;
4533
+ ep.failures++;
4534
+ if (ep.failures >= _RpcPool.UNHEALTHY_THRESHOLD) {
4535
+ ep.healthy = false;
4536
+ if (this.verbose) {
4537
+ console.warn(
4538
+ `[RpcPool] Endpoint ${ep.label} marked unhealthy after ${ep.failures} consecutive failures`
4539
+ );
4540
+ }
4541
+ }
4542
+ const retryable = this.retryConfig ? isRetryable(err, this.retryConfig.retryableStatusCodes) : false;
4543
+ if (!retryable) {
4544
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4545
+ triedEndpoints.add(epIdx);
4546
+ attempt--;
4547
+ if (triedEndpoints.size >= this.endpoints.length) break;
4548
+ continue;
4549
+ }
4550
+ throw err;
4551
+ }
4552
+ if (this.verbose) {
4553
+ console.warn(
4554
+ `[RpcPool] Retryable error on ${ep.label} (attempt ${attempt + 1}/${maxAttempts}):`,
4555
+ err instanceof Error ? err.message : err
4556
+ );
4557
+ }
4558
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4559
+ triedEndpoints.add(epIdx);
4560
+ }
4561
+ if (attempt < maxAttempts - 1 && this.retryConfig) {
4562
+ const delay = computeDelay(attempt, this.retryConfig);
4563
+ await sleep(delay);
4564
+ }
4565
+ }
4566
+ }
4567
+ this.maybeRecoverEndpoints();
4568
+ throw lastError ?? new Error("RpcPool: all endpoints exhausted");
4569
+ }
4570
+ /**
4571
+ * Get a raw `Connection` from the current preferred endpoint.
4572
+ * Useful when you need to pass a Connection to external code.
4573
+ *
4574
+ * NOTE: This bypasses retry and failover logic. Prefer `call()`.
4575
+ *
4576
+ * @returns Solana Connection from the current preferred endpoint.
4577
+ *
4578
+ * @example
4579
+ * ```ts
4580
+ * const conn = pool.getConnection();
4581
+ * const balance = await conn.getBalance(pubkey);
4582
+ * ```
4583
+ */
4584
+ getConnection() {
4585
+ const idx = this.selectEndpoint();
4586
+ if (idx === -1) {
4587
+ this.maybeRecoverEndpoints();
4588
+ return this.endpoints[0].connection;
4589
+ }
4590
+ return this.endpoints[idx].connection;
4591
+ }
4592
+ /**
4593
+ * Run a health check against all endpoints in the pool.
4594
+ *
4595
+ * @param timeoutMs - Per-endpoint probe timeout (default: 5000)
4596
+ * @returns Array of health results, one per endpoint.
4597
+ *
4598
+ * @example
4599
+ * ```ts
4600
+ * const results = await pool.healthCheck();
4601
+ * for (const r of results) {
4602
+ * console.log(`${r.endpoint}: ${r.healthy ? 'UP' : 'DOWN'} (${r.latencyMs}ms, slot ${r.slot})`);
4603
+ * }
4604
+ * ```
4605
+ */
4606
+ async healthCheck(timeoutMs = 5e3) {
4607
+ const results = await Promise.all(
4608
+ this.endpoints.map(async (ep) => {
4609
+ const result = await checkRpcHealth(ep.config.url, timeoutMs);
4610
+ ep.lastLatencyMs = result.latencyMs;
4611
+ ep.healthy = result.healthy;
4612
+ if (result.healthy) ep.failures = 0;
4613
+ result.endpoint = redactUrl(result.endpoint);
4614
+ return result;
4615
+ })
4616
+ );
4617
+ return results;
4618
+ }
4619
+ /**
4620
+ * Get the number of endpoints in the pool.
4621
+ */
4622
+ get size() {
4623
+ return this.endpoints.length;
4624
+ }
4625
+ /**
4626
+ * Get the number of currently healthy endpoints.
4627
+ */
4628
+ get healthyCount() {
4629
+ return this.endpoints.filter((ep) => ep.healthy).length;
4630
+ }
4631
+ /**
4632
+ * Get endpoint labels and their current status.
4633
+ *
4634
+ * @returns Array of `{ label, url, healthy, failures, lastLatencyMs }`.
4635
+ */
4636
+ status() {
4637
+ return this.endpoints.map((ep) => ({
4638
+ label: ep.label,
4639
+ url: redactUrl(ep.config.url),
4640
+ healthy: ep.healthy,
4641
+ failures: ep.failures,
4642
+ lastLatencyMs: ep.lastLatencyMs
4643
+ }));
4644
+ }
4645
+ // -----------------------------------------------------------------------
4646
+ // Internals
4647
+ // -----------------------------------------------------------------------
4648
+ /**
4649
+ * Select the next endpoint based on strategy.
4650
+ * Returns -1 if no endpoint is available.
4651
+ */
4652
+ selectEndpoint(exclude) {
4653
+ const healthy = this.endpoints.map((ep, i) => ({ ep, i })).filter(({ ep, i }) => ep.healthy && !exclude?.has(i));
4654
+ if (healthy.length === 0) {
4655
+ const remaining = this.endpoints.map((_, i) => i).filter((i) => !exclude?.has(i));
4656
+ return remaining.length > 0 ? remaining[0] : -1;
4657
+ }
4658
+ if (this.strategy === "failover") {
4659
+ return healthy[0].i;
4660
+ }
4661
+ const totalWeight = healthy.reduce((sum, { ep }) => sum + ep.weight, 0);
4662
+ this.rrIndex = (this.rrIndex + 1) % totalWeight;
4663
+ let cumulative = 0;
4664
+ for (const { ep, i } of healthy) {
4665
+ cumulative += ep.weight;
4666
+ if (this.rrIndex < cumulative) return i;
4667
+ }
4668
+ return healthy[healthy.length - 1].i;
4669
+ }
4670
+ /**
4671
+ * If all endpoints are unhealthy, reset them so we at least try again.
4672
+ */
4673
+ maybeRecoverEndpoints() {
4674
+ const healthyCount = this.endpoints.filter((ep) => ep.healthy).length;
4675
+ if (healthyCount < _RpcPool.MIN_HEALTHY) {
4676
+ if (this.verbose) {
4677
+ console.warn("[RpcPool] All endpoints unhealthy \u2014 resetting for recovery");
4678
+ }
4679
+ for (const ep of this.endpoints) {
4680
+ ep.healthy = true;
4681
+ ep.failures = 0;
4682
+ }
4683
+ }
4684
+ }
4685
+ };
4686
+ async function withRetry(fn, config) {
4687
+ const resolved = resolveRetryConfig(config) ?? {
4688
+ maxRetries: 3,
4689
+ baseDelayMs: 500,
4690
+ maxDelayMs: 1e4,
4691
+ jitterFactor: 0.25,
4692
+ retryableStatusCodes: [429, 502, 503, 504]
4693
+ };
4694
+ let lastError;
4695
+ const maxAttempts = resolved.maxRetries + 1;
4696
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4697
+ try {
4698
+ return await fn();
4699
+ } catch (err) {
4700
+ lastError = err;
4701
+ if (!isRetryable(err, resolved.retryableStatusCodes)) {
4702
+ throw err;
4703
+ }
4704
+ if (attempt < maxAttempts - 1) {
4705
+ const delay = computeDelay(attempt, resolved);
4706
+ await sleep(delay);
4707
+ }
4708
+ }
4709
+ }
4710
+ throw lastError ?? new Error("withRetry: all attempts exhausted");
4711
+ }
4712
+ var _internal = {
4713
+ isRetryable,
4714
+ computeDelay,
4715
+ resolveRetryConfig,
4716
+ normalizeEndpoint,
4717
+ endpointLabel
4718
+ };
4719
+
3759
4720
  // src/runtime/tx.ts
3760
4721
  import {
3761
4722
  TransactionInstruction as TransactionInstruction2,
@@ -3907,6 +4868,139 @@ function formatResult(result, jsonMode) {
3907
4868
  return lines.join("\n");
3908
4869
  }
3909
4870
 
4871
+ // src/runtime/lighthouse.ts
4872
+ import { PublicKey as PublicKey13, Transaction as Transaction2 } from "@solana/web3.js";
4873
+ var LIGHTHOUSE_PROGRAM_ID = new PublicKey13(
4874
+ "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95"
4875
+ );
4876
+ var LIGHTHOUSE_PROGRAM_ID_STR2 = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
4877
+ var LIGHTHOUSE_CONSTRAINT_ADDRESS = 6400;
4878
+ var LIGHTHOUSE_ERROR_CODES = /* @__PURE__ */ new Set([
4879
+ 6e3,
4880
+ // InstructionMissing
4881
+ 6001,
4882
+ // InstructionFallbackNotFound
4883
+ 6002,
4884
+ // InstructionDidNotDeserialize
4885
+ 6003,
4886
+ // InstructionDidNotSerialize
4887
+ 6016,
4888
+ // IdlInstructionStub
4889
+ 6032,
4890
+ // ConstraintMut
4891
+ 6033,
4892
+ // ConstraintHasOne
4893
+ 6034,
4894
+ // ConstraintSigner
4895
+ 6035,
4896
+ // ConstraintRaw
4897
+ 6036,
4898
+ // ConstraintOwner
4899
+ 6037,
4900
+ // ConstraintRentExempt
4901
+ 6038,
4902
+ // ConstraintSeeds
4903
+ 6039,
4904
+ // ConstraintExecutable
4905
+ 6040,
4906
+ // ConstraintState
4907
+ 6041,
4908
+ // ConstraintAssociated
4909
+ 6042,
4910
+ // ConstraintAssociatedInit
4911
+ 6043,
4912
+ // ConstraintClose
4913
+ 6400
4914
+ // ConstraintAddress (the one we hit most often)
4915
+ ]);
4916
+ function isLighthouseInstruction(ix) {
4917
+ return ix.programId.equals(LIGHTHOUSE_PROGRAM_ID);
4918
+ }
4919
+ function isLighthouseError(error) {
4920
+ const msg = extractErrorMessage(error);
4921
+ if (!msg) return false;
4922
+ if (msg.includes(LIGHTHOUSE_PROGRAM_ID_STR2)) return true;
4923
+ if (/custom\s+program\s+error:\s*0x1900\b/i.test(msg)) return true;
4924
+ if (/"Custom"\s*:\s*6400\b/.test(msg) && /InstructionError/i.test(msg)) return true;
4925
+ return false;
4926
+ }
4927
+ function isLighthouseFailureInLogs(logs) {
4928
+ if (!Array.isArray(logs)) return false;
4929
+ let insideLighthouse = false;
4930
+ for (const line of logs) {
4931
+ if (typeof line !== "string") continue;
4932
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} invoke`)) {
4933
+ insideLighthouse = true;
4934
+ continue;
4935
+ }
4936
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} success`)) {
4937
+ insideLighthouse = false;
4938
+ continue;
4939
+ }
4940
+ if (insideLighthouse && /failed/i.test(line)) {
4941
+ return true;
4942
+ }
4943
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} failed`)) {
4944
+ return true;
4945
+ }
4946
+ }
4947
+ return false;
4948
+ }
4949
+ function stripLighthouseInstructions(instructions, percolatorProgramId) {
4950
+ if (percolatorProgramId) {
4951
+ const hasPercolatorIx = instructions.some(
4952
+ (ix) => ix.programId.equals(percolatorProgramId)
4953
+ );
4954
+ if (!hasPercolatorIx) {
4955
+ return instructions;
4956
+ }
4957
+ }
4958
+ return instructions.filter((ix) => !isLighthouseInstruction(ix));
4959
+ }
4960
+ function stripLighthouseFromTransaction(transaction, percolatorProgramId) {
4961
+ if (percolatorProgramId) {
4962
+ const hasPercolatorIx = transaction.instructions.some(
4963
+ (ix) => ix.programId.equals(percolatorProgramId)
4964
+ );
4965
+ if (!hasPercolatorIx) return transaction;
4966
+ }
4967
+ const hasLighthouse = transaction.instructions.some(isLighthouseInstruction);
4968
+ if (!hasLighthouse) return transaction;
4969
+ const clean = new Transaction2();
4970
+ clean.recentBlockhash = transaction.recentBlockhash;
4971
+ clean.feePayer = transaction.feePayer;
4972
+ for (const ix of transaction.instructions) {
4973
+ if (!isLighthouseInstruction(ix)) {
4974
+ clean.add(ix);
4975
+ }
4976
+ }
4977
+ return clean;
4978
+ }
4979
+ function countLighthouseInstructions(ixsOrTx) {
4980
+ const instructions = Array.isArray(ixsOrTx) ? ixsOrTx : ixsOrTx.instructions;
4981
+ return instructions.filter(isLighthouseInstruction).length;
4982
+ }
4983
+ var LIGHTHOUSE_USER_MESSAGE = "Your wallet's transaction guard (Blowfish/Lighthouse) is blocking this transaction. This is a known compatibility issue \u2014 the transaction itself is valid. Try one of these workarounds:\n1. Disable transaction simulation in your wallet settings\n2. Use a wallet without Blowfish protection (e.g., Backpack, Solflare)\n3. The SDK will automatically retry without the guard";
4984
+ function classifyLighthouseError(error) {
4985
+ if (isLighthouseError(error)) {
4986
+ return LIGHTHOUSE_USER_MESSAGE;
4987
+ }
4988
+ return null;
4989
+ }
4990
+ function extractErrorMessage(error) {
4991
+ if (!error) return null;
4992
+ if (typeof error === "string") return error;
4993
+ if (error instanceof Error) return error.message;
4994
+ if (typeof error === "object" && "message" in error) {
4995
+ return String(error.message);
4996
+ }
4997
+ try {
4998
+ return JSON.stringify(error);
4999
+ } catch {
5000
+ return null;
5001
+ }
5002
+ }
5003
+
3910
5004
  // src/math/trading.ts
3911
5005
  function computeMarkPnl(positionSize, entryPrice, oraclePrice) {
3912
5006
  if (positionSize === 0n || oraclePrice === 0n) return 0n;
@@ -3954,9 +5048,20 @@ function computeFeeSplit(totalFee, config) {
3954
5048
  if (config.lpBps === 0n && config.protocolBps === 0n && config.creatorBps === 0n) {
3955
5049
  return [totalFee, 0n, 0n];
3956
5050
  }
5051
+ const totalBps = config.lpBps + config.protocolBps + config.creatorBps;
5052
+ if (totalBps !== 10000n) {
5053
+ throw new Error(
5054
+ `Fee split must equal exactly 10000 bps (100%): lpBps=${config.lpBps} + protocolBps=${config.protocolBps} + creatorBps=${config.creatorBps} = ${totalBps}`
5055
+ );
5056
+ }
3957
5057
  const lp = totalFee * config.lpBps / 10000n;
3958
5058
  const protocol = totalFee * config.protocolBps / 10000n;
3959
5059
  const creator = totalFee - lp - protocol;
5060
+ if (creator < 0n) {
5061
+ throw new Error(
5062
+ `Internal error: creator fee is negative (${creator}). This should not happen if lpBps + protocolBps + creatorBps <= 10000.`
5063
+ );
5064
+ }
3960
5065
  return [lp, protocol, creator];
3961
5066
  }
3962
5067
  function computePnlPercent(pnlTokens, capital) {
@@ -3971,8 +5076,17 @@ function computePnlPercent(pnlTokens, capital) {
3971
5076
  }
3972
5077
  function computeEstimatedEntryPrice(oracleE6, tradingFeeBps, direction) {
3973
5078
  if (oracleE6 === 0n) return 0n;
5079
+ if (tradingFeeBps < 0n) {
5080
+ throw new Error(`computeEstimatedEntryPrice: tradingFeeBps must be non-negative, got ${tradingFeeBps}`);
5081
+ }
3974
5082
  const feeImpact = oracleE6 * tradingFeeBps / 10000n;
3975
- return direction === "long" ? oracleE6 + feeImpact : oracleE6 - feeImpact;
5083
+ const result = direction === "long" ? oracleE6 + feeImpact : oracleE6 - feeImpact;
5084
+ if (result <= 0n) {
5085
+ throw new Error(
5086
+ `computeEstimatedEntryPrice: result ${result} is non-positive (tradingFeeBps=${tradingFeeBps} too high for oracle=${oracleE6})`
5087
+ );
5088
+ }
5089
+ return result;
3976
5090
  }
3977
5091
  var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
3978
5092
  var MIN_SAFE_BIGINT = BigInt(-Number.MAX_SAFE_INTEGER);
@@ -3993,7 +5107,12 @@ function computeMaxLeverage(initialMarginBps) {
3993
5107
  if (initialMarginBps <= 0n) {
3994
5108
  throw new Error("computeMaxLeverage: initialMarginBps must be positive");
3995
5109
  }
3996
- return Number(10000n / initialMarginBps);
5110
+ const scaledResult = 10000n * 1000000n / initialMarginBps;
5111
+ return Number(scaledResult) / 1e6;
5112
+ }
5113
+ function computeMaxWithdrawable(capital, pnl, reservedPnl) {
5114
+ const maturedPnl = pnl - reservedPnl;
5115
+ return capital + (maturedPnl > 0n ? maturedPnl : 0n);
3997
5116
  }
3998
5117
 
3999
5118
  // src/math/warmup.ts
@@ -4005,6 +5124,9 @@ function computeWarmupUnlockedCapital(totalCapital, currentSlot, warmupStartSlot
4005
5124
  return totalCapital * elapsed / warmupPeriodSlots;
4006
5125
  }
4007
5126
  function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
5127
+ if (initialMarginBps <= 0n) {
5128
+ throw new Error("computeWarmupLeverageCap: initialMarginBps must be positive");
5129
+ }
4008
5130
  const maxLev = computeMaxLeverage(initialMarginBps);
4009
5131
  if (warmupPeriodSlots === 0n || warmupStartSlot === 0n) return maxLev;
4010
5132
  if (totalCapital <= 0n) return 1;
@@ -4015,7 +5137,14 @@ function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, w
4015
5137
  warmupPeriodSlots
4016
5138
  );
4017
5139
  if (unlocked <= 0n) return 1;
4018
- const effectiveLev = Number(BigInt(maxLev) * unlocked / totalCapital);
5140
+ const scaledResult = BigInt(maxLev) * unlocked / totalCapital;
5141
+ if (scaledResult > BigInt(Number.MAX_SAFE_INTEGER)) {
5142
+ console.warn(
5143
+ `[computeWarmupLeverageCap] Warning: effective leverage ${scaledResult} exceeds MAX_SAFE_INTEGER, returning MAX_SAFE_INTEGER as a safety bound`
5144
+ );
5145
+ return Number.MAX_SAFE_INTEGER;
5146
+ }
5147
+ const effectiveLev = Number(scaledResult);
4019
5148
  return Math.max(1, effectiveLev);
4020
5149
  }
4021
5150
  function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
@@ -4028,9 +5157,40 @@ function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlo
4028
5157
  );
4029
5158
  return unlocked * BigInt(maxLev);
4030
5159
  }
5160
+ function computeWarmupProgress(currentSlot, warmupStartedAtSlot, warmupPeriodSlots, pnl, reservedPnl) {
5161
+ if (warmupPeriodSlots === 0n || warmupStartedAtSlot === 0n) {
5162
+ return {
5163
+ maturedPnl: pnl > 0n ? pnl : 0n,
5164
+ reservedPnl: 0n,
5165
+ progressBps: 10000n,
5166
+ // 100%
5167
+ slotsRemaining: 0n
5168
+ };
5169
+ }
5170
+ const elapsed = currentSlot >= warmupStartedAtSlot ? currentSlot - warmupStartedAtSlot : 0n;
5171
+ if (elapsed >= warmupPeriodSlots) {
5172
+ return {
5173
+ maturedPnl: pnl > 0n ? pnl : 0n,
5174
+ reservedPnl: 0n,
5175
+ progressBps: 10000n,
5176
+ // 100%
5177
+ slotsRemaining: 0n
5178
+ };
5179
+ }
5180
+ const progressBps = elapsed * 10000n / warmupPeriodSlots;
5181
+ const slotsRemaining = warmupPeriodSlots - elapsed;
5182
+ const maturedPnl = pnl > 0n ? pnl * progressBps / 10000n : 0n;
5183
+ const locked = reservedPnl > 0n ? reservedPnl : 0n;
5184
+ return {
5185
+ maturedPnl,
5186
+ reservedPnl: locked,
5187
+ progressBps,
5188
+ slotsRemaining
5189
+ };
5190
+ }
4031
5191
 
4032
5192
  // src/validation.ts
4033
- import { PublicKey as PublicKey11 } from "@solana/web3.js";
5193
+ import { PublicKey as PublicKey14 } from "@solana/web3.js";
4034
5194
  var U16_MAX = 65535;
4035
5195
  var U64_MAX = BigInt("18446744073709551615");
4036
5196
  var I64_MIN = BigInt("-9223372036854775808");
@@ -4060,7 +5220,7 @@ var ValidationError = class extends Error {
4060
5220
  };
4061
5221
  function validatePublicKey(value, field) {
4062
5222
  try {
4063
- return new PublicKey11(value);
5223
+ return new PublicKey14(value);
4064
5224
  } catch {
4065
5225
  throw new ValidationError(
4066
5226
  field,
@@ -4077,18 +5237,20 @@ function validateIndex(value, field) {
4077
5237
  `must be <= ${U16_MAX} (u16 max), got ${t}`
4078
5238
  );
4079
5239
  }
5240
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5241
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5242
+ }
4080
5243
  return Number(bi);
4081
5244
  }
4082
5245
  function validateAmount(value, field) {
4083
- let num;
4084
- try {
4085
- num = BigInt(value);
4086
- } catch {
5246
+ const t = value.trim();
5247
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4087
5248
  throw new ValidationError(
4088
5249
  field,
4089
- `"${value}" is not a valid number. Use decimal digits only.`
5250
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4090
5251
  );
4091
5252
  }
5253
+ const num = BigInt(t);
4092
5254
  if (num < 0n) {
4093
5255
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4094
5256
  }
@@ -4101,15 +5263,14 @@ function validateAmount(value, field) {
4101
5263
  return num;
4102
5264
  }
4103
5265
  function validateU128(value, field) {
4104
- let num;
4105
- try {
4106
- num = BigInt(value);
4107
- } catch {
5266
+ const t = value.trim();
5267
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4108
5268
  throw new ValidationError(
4109
5269
  field,
4110
- `"${value}" is not a valid number. Use decimal digits only.`
5270
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4111
5271
  );
4112
5272
  }
5273
+ const num = BigInt(t);
4113
5274
  if (num < 0n) {
4114
5275
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4115
5276
  }
@@ -4122,15 +5283,14 @@ function validateU128(value, field) {
4122
5283
  return num;
4123
5284
  }
4124
5285
  function validateI64(value, field) {
4125
- let num;
4126
- try {
4127
- num = BigInt(value);
4128
- } catch {
5286
+ const t = value.trim();
5287
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4129
5288
  throw new ValidationError(
4130
5289
  field,
4131
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5290
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4132
5291
  );
4133
5292
  }
5293
+ const num = BigInt(t);
4134
5294
  if (num < I64_MIN) {
4135
5295
  throw new ValidationError(
4136
5296
  field,
@@ -4146,15 +5306,14 @@ function validateI64(value, field) {
4146
5306
  return num;
4147
5307
  }
4148
5308
  function validateI128(value, field) {
4149
- let num;
4150
- try {
4151
- num = BigInt(value);
4152
- } catch {
5309
+ const t = value.trim();
5310
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4153
5311
  throw new ValidationError(
4154
5312
  field,
4155
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5313
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4156
5314
  );
4157
5315
  }
5316
+ const num = BigInt(t);
4158
5317
  if (num < I128_MIN) {
4159
5318
  throw new ValidationError(
4160
5319
  field,
@@ -4178,6 +5337,9 @@ function validateBps(value, field) {
4178
5337
  `must be <= 10000 (100%), got ${t}`
4179
5338
  );
4180
5339
  }
5340
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5341
+ throw new ValidationError(field, `internal error: bps value exceeds MAX_SAFE_INTEGER`);
5342
+ }
4181
5343
  return Number(bi);
4182
5344
  }
4183
5345
  function validateU64(value, field) {
@@ -4192,6 +5354,9 @@ function validateU16(value, field) {
4192
5354
  `must be <= ${U16_MAX} (u16 max), got ${t}`
4193
5355
  );
4194
5356
  }
5357
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5358
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5359
+ }
4195
5360
  return Number(bi);
4196
5361
  }
4197
5362
 
@@ -4242,7 +5407,9 @@ function parseDexScreenerPairs(json) {
4242
5407
  else if (liquidity > 1e4) confidence = 60;
4243
5408
  else if (liquidity > 1e3) confidence = 45;
4244
5409
  const priceUsd = pair.priceUsd;
4245
- const price = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) || 0 : 0;
5410
+ const rawPrice = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) : NaN;
5411
+ if (!Number.isFinite(rawPrice) || rawPrice <= 0) continue;
5412
+ const price = rawPrice;
4246
5413
  let baseSym = "?";
4247
5414
  let quoteSym = "?";
4248
5415
  if (isRecord(pair.baseToken) && typeof pair.baseToken.symbol === "string") {
@@ -4273,8 +5440,8 @@ function parseJupiterMintEntry(json, mint) {
4273
5440
  if (!isRecord(row)) return null;
4274
5441
  const rawPrice = row.price;
4275
5442
  if (rawPrice === void 0 || rawPrice === null) return null;
4276
- const price = parseFloat(String(rawPrice)) || 0;
4277
- if (price <= 0) return null;
5443
+ const price = parseFloat(String(rawPrice));
5444
+ if (!Number.isFinite(price) || price <= 0) return null;
4278
5445
  let mintSymbol = "?";
4279
5446
  if (typeof row.mintSymbol === "string") mintSymbol = row.mintSymbol;
4280
5447
  return { price, mintSymbol };
@@ -4340,10 +5507,17 @@ async function fetchDexSources(mint, signal) {
4340
5507
  headers: { "User-Agent": "percolator/1.0" }
4341
5508
  }
4342
5509
  );
4343
- if (!resp.ok) return [];
5510
+ if (!resp.ok) {
5511
+ console.debug(`[fetchDexSources] HTTP ${resp.status} for mint ${mint}`);
5512
+ return [];
5513
+ }
4344
5514
  const json = await resp.json();
4345
5515
  return parseDexScreenerPairs(json);
4346
- } catch {
5516
+ } catch (err) {
5517
+ console.warn(
5518
+ `[fetchDexSources] Error fetching DexScreener data for mint ${mint}:`,
5519
+ err instanceof Error ? err.message : String(err)
5520
+ );
4347
5521
  return [];
4348
5522
  }
4349
5523
  }
@@ -4354,7 +5528,7 @@ function lookupPythSource(mint) {
4354
5528
  type: "pyth",
4355
5529
  address: entry.feedId,
4356
5530
  pairLabel: `${entry.symbol} / USD (Pyth)`,
4357
- liquidity: Infinity,
5531
+ liquidity: Number.MAX_SAFE_INTEGER,
4358
5532
  // Pyth is considered deep liquidity
4359
5533
  price: 0,
4360
5534
  // We don't fetch live price here; caller can enrich
@@ -4371,10 +5545,16 @@ async function fetchJupiterSource(mint, signal) {
4371
5545
  headers: { "User-Agent": "percolator/1.0" }
4372
5546
  }
4373
5547
  );
4374
- if (!resp.ok) return null;
5548
+ if (!resp.ok) {
5549
+ console.debug(`[fetchJupiterSource] HTTP ${resp.status} for mint ${mint}`);
5550
+ return null;
5551
+ }
4375
5552
  const json = await resp.json();
4376
5553
  const row = parseJupiterMintEntry(json, mint);
4377
- if (!row) return null;
5554
+ if (!row) {
5555
+ console.debug(`[fetchJupiterSource] No price data from Jupiter for mint ${mint}`);
5556
+ return null;
5557
+ }
4378
5558
  return {
4379
5559
  type: "jupiter",
4380
5560
  address: mint,
@@ -4385,7 +5565,11 @@ async function fetchJupiterSource(mint, signal) {
4385
5565
  confidence: 40
4386
5566
  // Fallback — lower confidence
4387
5567
  };
4388
- } catch {
5568
+ } catch (err) {
5569
+ console.warn(
5570
+ `[fetchJupiterSource] Error fetching Jupiter data for mint ${mint}:`,
5571
+ err instanceof Error ? err.message : String(err)
5572
+ );
4389
5573
  return null;
4390
5574
  }
4391
5575
  }
@@ -4400,8 +5584,20 @@ async function resolvePrice(mint, signal, options) {
4400
5584
  const pythSource = lookupPythSource(mint);
4401
5585
  const allSources = [];
4402
5586
  if (pythSource) {
4403
- const refPrice = dexSources[0]?.price || jupiterSource?.price || 0;
4404
- pythSource.price = refPrice;
5587
+ const dexPrice = dexSources[0]?.price ?? 0;
5588
+ const jupPrice = jupiterSource?.price ?? 0;
5589
+ if (dexPrice > 0 && jupPrice > 0) {
5590
+ const mid = (dexPrice + jupPrice) / 2;
5591
+ const deviation = Math.abs(dexPrice - jupPrice) / mid;
5592
+ if (deviation > 0.5) {
5593
+ pythSource.price = 0;
5594
+ pythSource.confidence = 20;
5595
+ } else {
5596
+ pythSource.price = mid;
5597
+ }
5598
+ } else {
5599
+ pythSource.price = dexPrice || jupPrice || 0;
5600
+ }
4405
5601
  allSources.push(pythSource);
4406
5602
  }
4407
5603
  allSources.push(...dexSources);
@@ -4433,6 +5629,7 @@ export {
4433
5629
  ACCOUNTS_FUND_MARKET_INSURANCE,
4434
5630
  ACCOUNTS_INIT_LP,
4435
5631
  ACCOUNTS_INIT_MARKET,
5632
+ ACCOUNTS_INIT_MATCHER_CTX,
4436
5633
  ACCOUNTS_INIT_USER,
4437
5634
  ACCOUNTS_KEEPER_CRANK,
4438
5635
  ACCOUNTS_LIQUIDATE_AT_ORACLE,
@@ -4466,15 +5663,22 @@ export {
4466
5663
  CHAINLINK_ANSWER_OFFSET,
4467
5664
  CHAINLINK_DECIMALS_OFFSET,
4468
5665
  CHAINLINK_MIN_SIZE,
5666
+ CHAINLINK_TIMESTAMP_OFFSET,
4469
5667
  CREATOR_LOCK_SEED,
4470
5668
  CTX_VAMM_OFFSET,
4471
5669
  DEFAULT_OI_RAMP_SLOTS,
4472
5670
  ENGINE_MARK_PRICE_OFF,
4473
5671
  ENGINE_OFF,
4474
5672
  IX_TAG,
5673
+ LIGHTHOUSE_CONSTRAINT_ADDRESS,
5674
+ LIGHTHOUSE_ERROR_CODES,
5675
+ LIGHTHOUSE_PROGRAM_ID,
5676
+ LIGHTHOUSE_PROGRAM_ID_STR2 as LIGHTHOUSE_PROGRAM_ID_STR,
5677
+ LIGHTHOUSE_USER_MESSAGE,
4475
5678
  MARK_PRICE_EMA_ALPHA_E6,
4476
5679
  MARK_PRICE_EMA_WINDOW_SLOTS,
4477
5680
  MAX_DECIMALS,
5681
+ MAX_ORACLE_PRICE,
4478
5682
  METEORA_DLMM_PROGRAM_ID,
4479
5683
  ORACLE_PHASE_GROWING,
4480
5684
  ORACLE_PHASE_MATURE,
@@ -4492,9 +5696,11 @@ export {
4492
5696
  RAMP_START_BPS,
4493
5697
  RAYDIUM_CLMM_PROGRAM_ID,
4494
5698
  RENOUNCE_ADMIN_CONFIRMATION,
5699
+ RpcPool,
4495
5700
  SLAB_TIERS,
4496
5701
  SLAB_TIERS_V0,
4497
5702
  SLAB_TIERS_V1,
5703
+ SLAB_TIERS_V12_1,
4498
5704
  SLAB_TIERS_V1D,
4499
5705
  SLAB_TIERS_V1D_LEGACY,
4500
5706
  SLAB_TIERS_V1M,
@@ -4502,6 +5708,7 @@ export {
4502
5708
  SLAB_TIERS_V2,
4503
5709
  SLAB_TIERS_V_ADL,
4504
5710
  SLAB_TIERS_V_ADL_DISCOVERY,
5711
+ SLAB_TIERS_V_SETDEXPOOL,
4505
5712
  STAKE_IX,
4506
5713
  STAKE_POOL_SIZE,
4507
5714
  STAKE_PROGRAM_ID,
@@ -4511,11 +5718,15 @@ export {
4511
5718
  VAMM_MAGIC,
4512
5719
  ValidationError,
4513
5720
  WELL_KNOWN,
5721
+ _internal,
4514
5722
  buildAccountMetas,
4515
5723
  buildAdlInstruction,
4516
5724
  buildAdlTransaction,
4517
5725
  buildIx,
4518
5726
  checkPhaseTransition,
5727
+ checkRpcHealth,
5728
+ classifyLighthouseError,
5729
+ clearStaticMarkets,
4519
5730
  computeDexSpotPriceE6,
4520
5731
  computeDynamicFeeBps,
4521
5732
  computeDynamicTradingFee,
@@ -4527,6 +5738,7 @@ export {
4527
5738
  computeLiqPrice,
4528
5739
  computeMarkPnl,
4529
5740
  computeMaxLeverage,
5741
+ computeMaxWithdrawable,
4530
5742
  computePnlPercent,
4531
5743
  computePreTradeLiqPrice,
4532
5744
  computeRequiredMargin,
@@ -4534,8 +5746,10 @@ export {
4534
5746
  computeVammQuote,
4535
5747
  computeWarmupLeverageCap,
4536
5748
  computeWarmupMaxPositionSize,
5749
+ computeWarmupProgress,
4537
5750
  computeWarmupUnlockedCapital,
4538
5751
  concatBytes,
5752
+ countLighthouseInstructions,
4539
5753
  decodeError,
4540
5754
  decodeStakePool,
4541
5755
  depositAccounts,
@@ -4554,6 +5768,8 @@ export {
4554
5768
  detectSlabLayout,
4555
5769
  detectTokenProgram,
4556
5770
  discoverMarkets,
5771
+ discoverMarketsViaApi,
5772
+ discoverMarketsViaStaticBundle,
4557
5773
  encBool,
4558
5774
  encI128,
4559
5775
  encI64,
@@ -4583,6 +5799,7 @@ export {
4583
5799
  encodeFundMarketInsurance,
4584
5800
  encodeInitLP,
4585
5801
  encodeInitMarket,
5802
+ encodeInitMatcherCtx,
4586
5803
  encodeInitSharedVault,
4587
5804
  encodeInitUser,
4588
5805
  encodeKeeperCrank,
@@ -4650,12 +5867,18 @@ export {
4650
5867
  getCurrentNetwork,
4651
5868
  getErrorHint,
4652
5869
  getErrorName,
5870
+ getMarketsByAddress,
4653
5871
  getMatcherProgramId,
4654
5872
  getProgramId,
4655
5873
  getStakeProgramId,
5874
+ getStaticMarkets,
4656
5875
  initPoolAccounts,
4657
5876
  isAccountUsed,
4658
5877
  isAdlTriggered,
5878
+ isAnchorErrorCode,
5879
+ isLighthouseError,
5880
+ isLighthouseFailureInLogs,
5881
+ isLighthouseInstruction,
4659
5882
  isStandardToken,
4660
5883
  isToken2022,
4661
5884
  isValidChainlinkOracle,
@@ -4674,11 +5897,14 @@ export {
4674
5897
  rankAdlPositions,
4675
5898
  readLastThrUpdateSlot,
4676
5899
  readNonce,
5900
+ registerStaticMarkets,
4677
5901
  resolvePrice,
4678
5902
  safeEnv,
4679
5903
  simulateOrSend,
4680
5904
  slabDataSize,
4681
5905
  slabDataSizeV1,
5906
+ stripLighthouseFromTransaction,
5907
+ stripLighthouseInstructions,
4682
5908
  validateAmount,
4683
5909
  validateBps,
4684
5910
  validateI128,
@@ -4689,6 +5915,7 @@ export {
4689
5915
  validateU128,
4690
5916
  validateU16,
4691
5917
  validateU64,
5918
+ withRetry,
4692
5919
  withdrawAccounts
4693
5920
  };
4694
5921
  //# sourceMappingURL=index.js.map