@percolatorct/sdk 1.0.0-beta.15 → 1.0.0-beta.17

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
@@ -1,16 +1,13 @@
1
1
  // src/abi/encode.ts
2
2
  import { PublicKey } from "@solana/web3.js";
3
- var U8_MAX = 255;
4
- var U16_MAX = 65535;
5
- var U32_MAX = 4294967295;
6
3
  function encU8(val) {
7
- if (!Number.isInteger(val) || val < 0 || val > U8_MAX) {
4
+ if (!Number.isInteger(val) || val < 0 || val > 255) {
8
5
  throw new Error(`encU8: value out of range (0..255), got ${val}`);
9
6
  }
10
7
  return new Uint8Array([val]);
11
8
  }
12
9
  function encU16(val) {
13
- if (!Number.isInteger(val) || val < 0 || val > U16_MAX) {
10
+ if (!Number.isInteger(val) || val < 0 || val > 65535) {
14
11
  throw new Error(`encU16: value out of range (0..65535), got ${val}`);
15
12
  }
16
13
  const buf = new Uint8Array(2);
@@ -18,7 +15,7 @@ function encU16(val) {
18
15
  return buf;
19
16
  }
20
17
  function encU32(val) {
21
- if (!Number.isInteger(val) || val < 0 || val > U32_MAX) {
18
+ if (!Number.isInteger(val) || val < 0 || val > 4294967295) {
22
19
  throw new Error(`encU32: value out of range (0..4294967295), got ${val}`);
23
20
  }
24
21
  const buf = new Uint8Array(4);
@@ -134,8 +131,13 @@ var IX_TAG = {
134
131
  ReclaimEmptyAccount: 25,
135
132
  SettleAccount: 26,
136
133
  // Tags 27-28: on-chain = DepositFeeCredits/ConvertReleasedPnl.
134
+ // Legacy aliases (PauseMarket/UnpauseMarket) kept — those instructions don't exist on-chain.
137
135
  DepositFeeCredits: 27,
136
+ /** @deprecated No on-chain PauseMarket instruction */
137
+ PauseMarket: 27,
138
138
  ConvertReleasedPnl: 28,
139
+ /** @deprecated No on-chain UnpauseMarket instruction */
140
+ UnpauseMarket: 28,
139
141
  // Tags 29-30: on-chain = ResolvePermissionless/ForceCloseResolved.
140
142
  ResolvePermissionless: 29,
141
143
  /** @deprecated Use ResolvePermissionless */
@@ -186,8 +188,7 @@ var IX_TAG = {
186
188
  AttestCrossMargin: 55,
187
189
  /** PERC-622: Advance oracle phase (permissionless crank) */
188
190
  AdvanceOraclePhase: 56,
189
- /** PERC-623: Top up a market's keeper fund (permissionless) */
190
- TopUpKeeperFund: 57,
191
+ // 57: removed (keeper fund)
191
192
  /** PERC-629: Slash a market creator's deposit (permissionless) */
192
193
  SlashCreationDeposit: 58,
193
194
  /** PERC-628: Initialize the global shared vault (admin) */
@@ -223,13 +224,7 @@ var IX_TAG = {
223
224
  /** PERC-SetDexPool: Pin admin-approved DEX pool address for a HYPERP market (admin). */
224
225
  SetDexPool: 74,
225
226
  /** CPI to the matcher program to initialize a matcher context account for an LP slot. Admin-only. */
226
- InitMatcherCtx: 75,
227
- /** PauseMarket (tag 76): admin emergency pause. Blocks Trade/Deposit/Withdraw/InitUser. */
228
- PauseMarket: 76,
229
- /** UnpauseMarket (tag 77): admin unpause. Re-enables all operations. */
230
- UnpauseMarket: 77,
231
- /** CloseKeeperFund (tag 78): close keeper fund PDA and recover lamports to admin. */
232
- CloseKeeperFund: 78
227
+ InitMatcherCtx: 75
233
228
  };
234
229
  Object.freeze(IX_TAG);
235
230
  var HEX_RE = /^[0-9a-fA-F]{64}$/;
@@ -252,8 +247,10 @@ function encodeFeedId(feedId) {
252
247
  }
253
248
  return bytes;
254
249
  }
255
- var INIT_MARKET_DATA_LEN = 352;
250
+ var INIT_MARKET_DATA_LEN = 360;
256
251
  function encodeInitMarket(args) {
252
+ const hMin = args.hMin ?? args.warmupPeriodSlots ?? 0n;
253
+ const hMax = args.hMax ?? args.warmupPeriodSlots ?? 0n;
257
254
  const data = concatBytes(
258
255
  encU8(IX_TAG.InitMarket),
259
256
  encPubkey(args.admin),
@@ -268,27 +265,25 @@ function encodeInitMarket(args) {
268
265
  encU128(args.maxMaintenanceFeePerSlot ?? 0n),
269
266
  encU128(args.maxInsuranceFloor ?? 0n),
270
267
  encU64(args.minOraclePriceCap ?? 0n),
271
- // RiskParams wire formatmust match read_risk_params() in percolator.rs
272
- // Note: insurance_floor occupies the old riskReductionThreshold slot,
273
- // and liquidationBufferBps is read but discarded (kept for wire compat).
274
- encU64(args.warmupPeriodSlots),
268
+ // RiskParams block (16 fields h_min replaces warmup_period_slots, h_max added at end)
269
+ encU64(hMin),
275
270
  encU64(args.maintenanceMarginBps),
276
271
  encU64(args.initialMarginBps),
277
272
  encU64(args.tradingFeeBps),
278
273
  encU64(args.maxAccounts),
279
274
  encU128(args.newAccountFee),
280
- encU128(args.insuranceFloor ?? 0n),
281
- // wire slot: old riskReductionThreshold → now insurance_floor
275
+ encU128(args.riskReductionThreshold),
282
276
  encU128(args.maintenanceFeePerSlot),
283
277
  encU64(args.maxCrankStalenessSlots),
284
278
  encU64(args.liquidationFeeBps),
285
279
  encU128(args.liquidationFeeCap),
286
- encU64(args.liquidationBufferBps ?? 0n),
287
- // wire slot: read and discarded by program
280
+ encU64(args.liquidationBufferBps),
288
281
  encU128(args.minLiquidationAbs),
289
282
  encU128(args.minInitialDeposit),
290
283
  encU128(args.minNonzeroMmReq),
291
- encU128(args.minNonzeroImReq)
284
+ encU128(args.minNonzeroImReq),
285
+ encU64(hMax)
286
+ // h_max — new in v12.15, appended after minNonzeroImReq
292
287
  );
293
288
  if (data.length !== INIT_MARKET_DATA_LEN) {
294
289
  throw new Error(
@@ -488,12 +483,12 @@ function encodeSetPythOracle(args) {
488
483
  }
489
484
  var PYTH_RECEIVER_PROGRAM_ID = "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ";
490
485
  async function derivePythPriceUpdateAccount(feedId, shardId = 0) {
491
- const { PublicKey: PublicKey14 } = await import("@solana/web3.js");
486
+ const { PublicKey: PublicKey15 } = await import("@solana/web3.js");
492
487
  const shardBuf = new Uint8Array(2);
493
488
  new DataView(shardBuf.buffer).setUint16(0, shardId, true);
494
- const [pda] = PublicKey14.findProgramAddressSync(
489
+ const [pda] = PublicKey15.findProgramAddressSync(
495
490
  [shardBuf, feedId],
496
- new PublicKey14(PYTH_RECEIVER_PROGRAM_ID)
491
+ new PublicKey15(PYTH_RECEIVER_PROGRAM_ID)
497
492
  );
498
493
  return pda.toBase58();
499
494
  }
@@ -604,9 +599,6 @@ function checkPhaseTransition(currentSlot, marketCreatedSlot, oraclePhase, cumul
604
599
  return [ORACLE_PHASE_MATURE, false];
605
600
  }
606
601
  }
607
- function encodeTopUpKeeperFund(args) {
608
- return concatBytes(encU8(IX_TAG.TopUpKeeperFund), encU64(args.amount));
609
- }
610
602
  function encodeSlashCreationDeposit() {
611
603
  return encU8(IX_TAG.SlashCreationDeposit);
612
604
  }
@@ -728,18 +720,6 @@ function encodeCloseOrphanSlab() {
728
720
  function encodeSetDexPool(args) {
729
721
  return concatBytes(encU8(IX_TAG.SetDexPool), encPubkey(args.pool));
730
722
  }
731
- function encodeCloseKeeperFund() {
732
- return concatBytes(encU8(IX_TAG.CloseKeeperFund));
733
- }
734
- function encodeCreateInsuranceMint() {
735
- return encodeCreateLpVault({ feeShareBps: 0n });
736
- }
737
- function encodeDepositInsuranceLP(args) {
738
- return encodeLpVaultDeposit({ amount: args.amount });
739
- }
740
- function encodeWithdrawInsuranceLP(args) {
741
- return encodeLpVaultWithdraw({ lpAmount: args.lpAmount });
742
- }
743
723
 
744
724
  // src/abi/accounts.ts
745
725
  import {
@@ -764,8 +744,7 @@ var ACCOUNTS_INIT_USER = [
764
744
  { name: "slab", signer: false, writable: true },
765
745
  { name: "userAta", signer: false, writable: true },
766
746
  { name: "vault", signer: false, writable: true },
767
- { name: "tokenProgram", signer: false, writable: false },
768
- { name: "clock", signer: false, writable: false }
747
+ { name: "tokenProgram", signer: false, writable: false }
769
748
  ];
770
749
  var ACCOUNTS_INIT_LP = [
771
750
  { name: "user", signer: true, writable: true },
@@ -832,7 +811,6 @@ var ACCOUNTS_TRADE_CPI = [
832
811
  { name: "lpOwner", signer: false, writable: false },
833
812
  // LP delegated to matcher - no signature needed
834
813
  { name: "slab", signer: false, writable: true },
835
- { name: "clock", signer: false, writable: false },
836
814
  { name: "oracle", signer: false, writable: false },
837
815
  { name: "matcherProg", signer: false, writable: false },
838
816
  { name: "matcherCtx", signer: false, writable: true },
@@ -916,37 +894,6 @@ function buildAccountMetas(spec, keys) {
916
894
  isWritable: s.writable
917
895
  }));
918
896
  }
919
- var ACCOUNTS_CREATE_INSURANCE_MINT = [
920
- { name: "admin", signer: true, writable: false },
921
- { name: "slab", signer: false, writable: false },
922
- { name: "insLpMint", signer: false, writable: true },
923
- { name: "vaultAuthority", signer: false, writable: false },
924
- { name: "collateralMint", signer: false, writable: false },
925
- { name: "systemProgram", signer: false, writable: false },
926
- { name: "tokenProgram", signer: false, writable: false },
927
- { name: "rent", signer: false, writable: false },
928
- { name: "payer", signer: true, writable: true }
929
- ];
930
- var ACCOUNTS_DEPOSIT_INSURANCE_LP = [
931
- { name: "depositor", signer: true, writable: false },
932
- { name: "slab", signer: false, writable: true },
933
- { name: "depositorAta", signer: false, writable: true },
934
- { name: "vault", signer: false, writable: true },
935
- { name: "tokenProgram", signer: false, writable: false },
936
- { name: "insLpMint", signer: false, writable: true },
937
- { name: "depositorLpAta", signer: false, writable: true },
938
- { name: "vaultAuthority", signer: false, writable: false }
939
- ];
940
- var ACCOUNTS_WITHDRAW_INSURANCE_LP = [
941
- { name: "withdrawer", signer: true, writable: false },
942
- { name: "slab", signer: false, writable: true },
943
- { name: "withdrawerAta", signer: false, writable: true },
944
- { name: "vault", signer: false, writable: true },
945
- { name: "tokenProgram", signer: false, writable: false },
946
- { name: "insLpMint", signer: false, writable: true },
947
- { name: "withdrawerLpAta", signer: false, writable: true },
948
- { name: "vaultAuthority", signer: false, writable: false }
949
- ];
950
897
  var ACCOUNTS_LP_VAULT_WITHDRAW = [
951
898
  { name: "withdrawer", signer: true, writable: false },
952
899
  { name: "slab", signer: false, writable: true },
@@ -1014,12 +961,6 @@ var ACCOUNTS_AUDIT_CRANK = [
1014
961
  var ACCOUNTS_ADVANCE_ORACLE_PHASE = [
1015
962
  { name: "slab", signer: false, writable: true }
1016
963
  ];
1017
- var ACCOUNTS_TOPUP_KEEPER_FUND = [
1018
- { name: "funder", signer: true, writable: true },
1019
- { name: "slab", signer: false, writable: true },
1020
- { name: "keeperFund", signer: false, writable: true },
1021
- { name: "systemProgram", signer: false, writable: false }
1022
- ];
1023
964
  var ACCOUNTS_SET_OI_IMBALANCE_HARD_BLOCK = [
1024
965
  { name: "admin", signer: true, writable: false },
1025
966
  { name: "slab", signer: false, writable: true }
@@ -1069,11 +1010,6 @@ var ACCOUNTS_SET_WALLET_CAP = [
1069
1010
  { name: "admin", signer: true, writable: false },
1070
1011
  { name: "slab", signer: false, writable: true }
1071
1012
  ];
1072
- var ACCOUNTS_SET_DEX_POOL = [
1073
- { name: "admin", signer: true, writable: false },
1074
- { name: "slab", signer: false, writable: true },
1075
- { name: "poolAccount", signer: false, writable: false }
1076
- ];
1077
1013
  var ACCOUNTS_INIT_MATCHER_CTX = [
1078
1014
  { name: "admin", signer: true, writable: false },
1079
1015
  { name: "slab", signer: false, writable: false },
@@ -1365,11 +1301,24 @@ function getErrorName(code) {
1365
1301
  function getErrorHint(code) {
1366
1302
  return PERCOLATOR_ERRORS[code]?.hint;
1367
1303
  }
1304
+ var LIGHTHOUSE_PROGRAM_ID_STR = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
1305
+ var ANCHOR_ERROR_RANGE_START = 6e3;
1306
+ var ANCHOR_ERROR_RANGE_END = 8191;
1307
+ var ANCHOR_ERROR_NAMES = {
1308
+ 6032: "ConstraintMut",
1309
+ 6036: "ConstraintOwner",
1310
+ 6038: "ConstraintSeeds",
1311
+ 6400: "ConstraintAddress"
1312
+ };
1313
+ function isAnchorErrorCode(code) {
1314
+ return code >= ANCHOR_ERROR_RANGE_START && code <= ANCHOR_ERROR_RANGE_END;
1315
+ }
1368
1316
  var CUSTOM_ERROR_HEX_MAX_LEN = 8;
1369
1317
  function parseErrorFromLogs(logs) {
1370
1318
  if (!Array.isArray(logs)) {
1371
1319
  return null;
1372
1320
  }
1321
+ let insideLighthouse = false;
1373
1322
  const re = new RegExp(
1374
1323
  `custom program error: 0x([0-9a-fA-F]{1,${CUSTOM_ERROR_HEX_MAX_LEN}})(?![0-9a-fA-F])`,
1375
1324
  "i"
@@ -1378,17 +1327,32 @@ function parseErrorFromLogs(logs) {
1378
1327
  if (typeof log !== "string") {
1379
1328
  continue;
1380
1329
  }
1330
+ if (log.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR} invoke`)) {
1331
+ insideLighthouse = true;
1332
+ } else if (log.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR} success`)) {
1333
+ insideLighthouse = false;
1334
+ }
1381
1335
  const match = log.match(re);
1382
1336
  if (match) {
1383
1337
  const code = parseInt(match[1], 16);
1384
1338
  if (!Number.isFinite(code) || code < 0 || code > 4294967295) {
1385
1339
  continue;
1386
1340
  }
1341
+ if (isAnchorErrorCode(code) || insideLighthouse) {
1342
+ const anchorName = ANCHOR_ERROR_NAMES[code] ?? `AnchorError(0x${code.toString(16)})`;
1343
+ return {
1344
+ code,
1345
+ name: `Lighthouse:${anchorName}`,
1346
+ 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).",
1347
+ source: "lighthouse"
1348
+ };
1349
+ }
1387
1350
  const info = decodeError(code);
1388
1351
  return {
1389
1352
  code,
1390
1353
  name: info?.name ?? `Unknown(${code})`,
1391
- hint: info?.hint
1354
+ hint: info?.hint,
1355
+ source: info ? "percolator" : "unknown"
1392
1356
  };
1393
1357
  }
1394
1358
  }
@@ -1619,10 +1583,6 @@ var V12_1_SBF_OFF_LAST_SWEEP_COMPLETE = 312;
1619
1583
  var V12_1_SBF_OFF_CRANK_CURSOR = 320;
1620
1584
  var V12_1_SBF_OFF_SWEEP_START_IDX = 322;
1621
1585
  var V12_1_SBF_OFF_LIFETIME_LIQUIDATIONS = 328;
1622
- var V12_1_SBF_OFF_TOTAL_OI = 448;
1623
- var V12_1_SBF_OFF_LONG_OI = 464;
1624
- var V12_1_SBF_OFF_SHORT_OI = 480;
1625
- var V12_1_SBF_OFF_MARK_PRICE_E6 = 560;
1626
1586
  var V12_1_ENGINE_CURRENT_SLOT_OFF = 448;
1627
1587
  var V12_1_ENGINE_FUNDING_RATE_BPS_OFF = 456;
1628
1588
  var V12_1_ENGINE_LAST_CRANK_SLOT_OFF = 464;
@@ -1657,13 +1617,41 @@ var V12_1_ACCT_FEE_CREDITS_OFF = 240;
1657
1617
  var V12_1_ACCT_LAST_FEE_SLOT_OFF = 256;
1658
1618
  var V12_1_ACCT_POSITION_SIZE_OFF = 88;
1659
1619
  var V12_1_ACCT_ENTRY_PRICE_OFF = -1;
1660
- var V12_1_EP_SBF_ACCOUNT_SIZE = 288;
1661
- var V12_1_EP_ACCT_ENTRY_PRICE_OFF = 144;
1662
- var V12_1_EP_ACCT_MATCHER_PROGRAM_OFF = 152;
1663
- var V12_1_EP_ACCT_MATCHER_CONTEXT_OFF = 184;
1664
- var V12_1_EP_ACCT_OWNER_OFF = 216;
1665
- var V12_1_EP_ACCT_FEE_CREDITS_OFF = 248;
1666
- var V12_1_EP_ACCT_LAST_FEE_SLOT_OFF = 264;
1620
+ var V12_1_ACCT_FUNDING_INDEX_OFF = 288;
1621
+ var V12_15_ENGINE_OFF = 624;
1622
+ var V12_15_ACCOUNT_SIZE = 4400;
1623
+ var V12_15_ACCOUNT_SIZE_SMALL = 944;
1624
+ var V12_15_ACCT_ACCOUNT_ID_OFF = 0;
1625
+ var V12_15_ACCT_CAPITAL_OFF = 8;
1626
+ var V12_15_ACCT_KIND_OFF = 24;
1627
+ var V12_15_ACCT_PNL_OFF = 32;
1628
+ var V12_15_ACCT_RESERVED_PNL_OFF = 48;
1629
+ var V12_15_ACCT_POSITION_BASIS_Q_OFF = 64;
1630
+ var V12_15_ACCT_ENTRY_PRICE_OFF = 120;
1631
+ var V12_15_ACCT_MATCHER_PROGRAM_OFF = 128;
1632
+ var V12_15_ACCT_MATCHER_CONTEXT_OFF = 160;
1633
+ var V12_15_ACCT_OWNER_OFF = 192;
1634
+ var V12_15_ACCT_FEE_CREDITS_OFF = 224;
1635
+ var V12_15_ACCT_FEES_EARNED_TOTAL_OFF = 240;
1636
+ var V12_15_ACCT_EXACT_RESERVE_COHORTS_OFF = 256;
1637
+ var V12_15_ACCT_EXACT_COHORT_COUNT_OFF = 4224;
1638
+ var V12_15_ACCT_OVERFLOW_OLDER_OFF = 4240;
1639
+ var V12_15_ACCT_OVERFLOW_OLDER_PRESENT_OFF = 4304;
1640
+ var V12_15_ACCT_OVERFLOW_NEWEST_OFF = 4320;
1641
+ var V12_15_ACCT_OVERFLOW_NEWEST_PRESENT_OFF = 4384;
1642
+ var V12_15_PARAMS_SIZE = 192;
1643
+ var V12_15_PARAMS_MAX_ACCOUNTS_OFF = 24;
1644
+ var V12_15_PARAMS_H_MIN_OFF = 160;
1645
+ var V12_15_PARAMS_H_MAX_OFF = 168;
1646
+ var V12_15_ENGINE_PARAMS_OFF = 32;
1647
+ var V12_15_ENGINE_CURRENT_SLOT_OFF = 224;
1648
+ var V12_15_ENGINE_FUNDING_RATE_E9_OFF = 240;
1649
+ var V12_15_ENGINE_MARKET_MODE_OFF = 256;
1650
+ var V12_15_ENGINE_C_TOT_OFF = 344;
1651
+ var V12_15_ENGINE_PNL_POS_TOT_OFF = 368;
1652
+ var V12_15_ENGINE_PNL_MATURED_POS_TOT_OFF = 384;
1653
+ var V12_15_ENGINE_BITMAP_OFF = 862;
1654
+ var V12_15_SIZES = /* @__PURE__ */ new Map();
1667
1655
  var V1M_ENGINE_OFF = 640;
1668
1656
  var V1M_CONFIG_LEN = 536;
1669
1657
  var V1M_ACCOUNT_SIZE = 248;
@@ -1736,7 +1724,10 @@ for (const n of TIERS) {
1736
1724
  V1M2_SIZES.set(computeSlabSize(V1M2_ENGINE_OFF, V1M2_ENGINE_BITMAP_OFF, V1M2_ACCOUNT_SIZE, n, 18), n);
1737
1725
  V_SETDEXPOOL_SIZES.set(computeSlabSize(V_SETDEXPOOL_ENGINE_OFF, V_ADL_ENGINE_BITMAP_OFF, V_ADL_ACCOUNT_SIZE, n, 18), n);
1738
1726
  V12_1_SIZES.set(computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18), n);
1727
+ V12_15_SIZES.set(computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, n, 18), n);
1739
1728
  }
1729
+ V12_15_SIZES.set(computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, 2048, 18), 2048);
1730
+ V12_15_SIZES.set(237512, 256);
1740
1731
  var V12_1_SBF_ACCOUNT_SIZE = 280;
1741
1732
  var V12_1_SBF_ENGINE_OFF = 616;
1742
1733
  var V12_1_SBF_BITMAP_OFF = 584;
@@ -1747,14 +1738,6 @@ for (const [, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Large",
1747
1738
  const total = V12_1_SBF_ENGINE_OFF + accountsOff + n * V12_1_SBF_ACCOUNT_SIZE;
1748
1739
  V12_1_SIZES.set(total, n);
1749
1740
  }
1750
- var V12_1_EP_SIZES = /* @__PURE__ */ new Map();
1751
- for (const [, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Large", 4096]]) {
1752
- const bitmapBytes = Math.ceil(n / 64) * 8;
1753
- const preAccLen = V12_1_SBF_BITMAP_OFF + bitmapBytes + 18 + n * 2;
1754
- const accountsOff = Math.ceil(preAccLen / 8) * 8;
1755
- const total = V12_1_SBF_ENGINE_OFF + accountsOff + n * V12_1_EP_SBF_ACCOUNT_SIZE;
1756
- V12_1_EP_SIZES.set(total, n);
1757
- }
1758
1741
  var SLAB_TIERS_V2 = {
1759
1742
  small: { maxAccounts: 256, dataSize: 65088, label: "Small", description: "256 slots (V2 BPF intermediate)" },
1760
1743
  large: { maxAccounts: 4096, dataSize: 1025568, label: "Large", description: "4,096 slots (V2 BPF intermediate)" }
@@ -2219,6 +2202,11 @@ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Lar
2219
2202
  const size = computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18);
2220
2203
  SLAB_TIERS_V12_1[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (v12.1)` };
2221
2204
  }
2205
+ var SLAB_TIERS_V12_15 = {};
2206
+ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Medium2048", 2048], ["Large", 4096]]) {
2207
+ const size = computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, n, 18);
2208
+ SLAB_TIERS_V12_15[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (v12.15)` };
2209
+ }
2222
2210
  function buildLayoutVSetDexPool(maxAccounts) {
2223
2211
  const engineOff = V_SETDEXPOOL_ENGINE_OFF;
2224
2212
  const bitmapOff = V_ADL_ENGINE_BITMAP_OFF;
@@ -2315,12 +2303,16 @@ function buildLayoutV12_1(maxAccounts, dataLen) {
2315
2303
  engineLastFundingSlotOff: isSbf ? -1 : V12_1_ENGINE_LAST_FUNDING_SLOT_OFF,
2316
2304
  // not in deployed struct
2317
2305
  engineFundingRateBpsOff: isSbf ? V12_1_SBF_OFF_FUNDING_RATE : V12_1_ENGINE_FUNDING_RATE_BPS_OFF,
2318
- engineMarkPriceOff: isSbf ? V12_1_SBF_OFF_MARK_PRICE_E6 : V12_1_ENGINE_MARK_PRICE_OFF,
2306
+ engineMarkPriceOff: isSbf ? -1 : V12_1_ENGINE_MARK_PRICE_OFF,
2307
+ // not in deployed struct
2319
2308
  engineLastCrankSlotOff: isSbf ? V12_1_SBF_OFF_LAST_CRANK_SLOT : V12_1_ENGINE_LAST_CRANK_SLOT_OFF,
2320
2309
  engineMaxCrankStalenessOff: isSbf ? V12_1_SBF_OFF_MAX_CRANK_STALENESS : V12_1_ENGINE_MAX_CRANK_STALENESS_OFF,
2321
- engineTotalOiOff: isSbf ? V12_1_SBF_OFF_TOTAL_OI : V12_1_ENGINE_TOTAL_OI_OFF,
2322
- engineLongOiOff: isSbf ? V12_1_SBF_OFF_LONG_OI : V12_1_ENGINE_LONG_OI_OFF,
2323
- engineShortOiOff: isSbf ? V12_1_SBF_OFF_SHORT_OI : V12_1_ENGINE_SHORT_OI_OFF,
2310
+ engineTotalOiOff: isSbf ? -1 : V12_1_ENGINE_TOTAL_OI_OFF,
2311
+ // not in deployed struct
2312
+ engineLongOiOff: isSbf ? -1 : V12_1_ENGINE_LONG_OI_OFF,
2313
+ // not in deployed struct
2314
+ engineShortOiOff: isSbf ? -1 : V12_1_ENGINE_SHORT_OI_OFF,
2315
+ // not in deployed struct
2324
2316
  engineCTotOff: isSbf ? V12_1_SBF_OFF_C_TOT : V12_1_ENGINE_C_TOT_OFF,
2325
2317
  enginePnlPosTotOff: isSbf ? V12_1_SBF_OFF_PNL_POS_TOT : V12_1_ENGINE_PNL_POS_TOT_OFF,
2326
2318
  engineLiqCursorOff: isSbf ? V12_1_SBF_OFF_LIQ_CURSOR : V12_1_ENGINE_LIQ_CURSOR_OFF,
@@ -2356,10 +2348,10 @@ function buildLayoutV12_1(maxAccounts, dataLen) {
2356
2348
  engineInsuranceIsolationBpsOff: isSbf ? -1 : 64
2357
2349
  };
2358
2350
  }
2359
- function buildLayoutV12_1EP(maxAccounts) {
2360
- const engineOff = V12_1_SBF_ENGINE_OFF;
2361
- const bitmapOff = V12_1_SBF_BITMAP_OFF;
2362
- const accountSize = V12_1_EP_SBF_ACCOUNT_SIZE;
2351
+ function buildLayoutV12_15(maxAccounts) {
2352
+ const engineOff = V12_15_ENGINE_OFF;
2353
+ const bitmapOff = V12_15_ENGINE_BITMAP_OFF;
2354
+ const accountSize = V12_15_ACCOUNT_SIZE;
2363
2355
  const bitmapWords = Math.ceil(maxAccounts / 64);
2364
2356
  const bitmapBytes = bitmapWords * 8;
2365
2357
  const postBitmap = 18;
@@ -2367,63 +2359,94 @@ function buildLayoutV12_1EP(maxAccounts) {
2367
2359
  const preAccountsLen = bitmapOff + bitmapBytes + postBitmap + nextFreeBytes;
2368
2360
  const accountsOffRel = Math.ceil(preAccountsLen / 8) * 8;
2369
2361
  return {
2370
- version: 1,
2371
- headerLen: 72,
2372
- configOffset: 72,
2373
- configLen: 544,
2374
- reservedOff: 80,
2375
- // V1_RESERVED_OFF
2362
+ version: 2,
2363
+ headerLen: V0_HEADER_LEN,
2364
+ // 72 (same as V12_1)
2365
+ configOffset: V0_HEADER_LEN,
2366
+ // 72
2367
+ configLen: 552,
2368
+ // SBF CONFIG_LEN for v12.15
2369
+ reservedOff: V1_RESERVED_OFF,
2370
+ // 80
2376
2371
  engineOff,
2377
2372
  accountSize,
2378
2373
  maxAccounts,
2379
2374
  bitmapWords,
2380
2375
  accountsOff: engineOff + accountsOffRel,
2381
2376
  engineInsuranceOff: 16,
2382
- engineParamsOff: 32,
2383
- // V12_1_ENGINE_PARAMS_OFF_SBF
2384
- paramsSize: 184,
2385
- // V12_1_PARAMS_SIZE_SBF
2386
- // Engine offsets identical to V12_1 SBF
2387
- engineCurrentSlotOff: V12_1_SBF_OFF_CURRENT_SLOT,
2377
+ engineParamsOff: V12_15_ENGINE_PARAMS_OFF,
2378
+ // 32
2379
+ paramsSize: V12_15_PARAMS_SIZE,
2380
+ // 192
2381
+ engineCurrentSlotOff: V12_15_ENGINE_CURRENT_SLOT_OFF,
2382
+ // 224
2388
2383
  engineFundingIndexOff: -1,
2384
+ // not present in v12.15 engine struct
2389
2385
  engineLastFundingSlotOff: -1,
2390
- engineFundingRateBpsOff: V12_1_SBF_OFF_FUNDING_RATE,
2391
- engineMarkPriceOff: V12_1_SBF_OFF_MARK_PRICE_E6,
2392
- engineLastCrankSlotOff: V12_1_SBF_OFF_LAST_CRANK_SLOT,
2393
- engineMaxCrankStalenessOff: V12_1_SBF_OFF_MAX_CRANK_STALENESS,
2394
- engineTotalOiOff: V12_1_SBF_OFF_TOTAL_OI,
2395
- engineLongOiOff: V12_1_SBF_OFF_LONG_OI,
2396
- engineShortOiOff: V12_1_SBF_OFF_SHORT_OI,
2397
- engineCTotOff: V12_1_SBF_OFF_C_TOT,
2398
- enginePnlPosTotOff: V12_1_SBF_OFF_PNL_POS_TOT,
2399
- engineLiqCursorOff: V12_1_SBF_OFF_LIQ_CURSOR,
2400
- engineGcCursorOff: V12_1_SBF_OFF_GC_CURSOR,
2401
- engineLastSweepStartOff: V12_1_SBF_OFF_LAST_SWEEP_START,
2402
- engineLastSweepCompleteOff: V12_1_SBF_OFF_LAST_SWEEP_COMPLETE,
2403
- engineCrankCursorOff: V12_1_SBF_OFF_CRANK_CURSOR,
2404
- engineSweepStartIdxOff: V12_1_SBF_OFF_SWEEP_START_IDX,
2405
- engineLifetimeLiquidationsOff: V12_1_SBF_OFF_LIFETIME_LIQUIDATIONS,
2386
+ // not present in v12.15 engine struct
2387
+ // funding_rate_e9 is i128 — stored in engineFundingRateBpsOff for EngineState.fundingRateBpsPerSlotLast
2388
+ // callers should treat this as the i128 funding rate in e9 units
2389
+ engineFundingRateBpsOff: V12_15_ENGINE_FUNDING_RATE_E9_OFF,
2390
+ // 240
2391
+ engineMarkPriceOff: -1,
2392
+ // not present in v12.15 (removed with oracle refactor)
2393
+ engineLastCrankSlotOff: -1,
2394
+ // not yet mapped; set -1 until offset verified on-chain
2395
+ engineMaxCrankStalenessOff: -1,
2396
+ // not yet mapped
2397
+ engineTotalOiOff: -1,
2398
+ // not present in v12.15 engine
2399
+ engineLongOiOff: -1,
2400
+ // not present in v12.15 engine
2401
+ engineShortOiOff: -1,
2402
+ // not present in v12.15 engine
2403
+ engineCTotOff: V12_15_ENGINE_C_TOT_OFF,
2404
+ // 344
2405
+ enginePnlPosTotOff: V12_15_ENGINE_PNL_POS_TOT_OFF,
2406
+ // 368
2407
+ engineLiqCursorOff: -1,
2408
+ // not yet mapped
2409
+ engineGcCursorOff: -1,
2410
+ // not yet mapped
2411
+ engineLastSweepStartOff: -1,
2412
+ // not yet mapped
2413
+ engineLastSweepCompleteOff: -1,
2414
+ // not yet mapped
2415
+ engineCrankCursorOff: -1,
2416
+ // not yet mapped
2417
+ engineSweepStartIdxOff: -1,
2418
+ // not yet mapped
2419
+ engineLifetimeLiquidationsOff: -1,
2420
+ // not yet mapped
2406
2421
  engineLifetimeForceClosesOff: -1,
2422
+ // not present in v12.15
2407
2423
  engineNetLpPosOff: -1,
2424
+ // not present in v12.15
2408
2425
  engineLpSumAbsOff: -1,
2426
+ // not present in v12.15
2409
2427
  engineLpMaxAbsOff: -1,
2428
+ // not present in v12.15
2410
2429
  engineLpMaxAbsSweepOff: -1,
2430
+ // not present in v12.15
2411
2431
  engineEmergencyOiModeOff: -1,
2432
+ // not present in v12.15
2412
2433
  engineEmergencyStartSlotOff: -1,
2434
+ // not present in v12.15
2413
2435
  engineLastBreakerSlotOff: -1,
2436
+ // not present in v12.15
2414
2437
  engineBitmapOff: bitmapOff,
2415
- postBitmap: 18,
2416
- // Account offsets — shifted +8 from V12_1 due to entry_price insertion
2417
- acctOwnerOff: V12_1_EP_ACCT_OWNER_OFF,
2418
- // 216 (was 208)
2438
+ postBitmap,
2439
+ acctOwnerOff: V12_15_ACCT_OWNER_OFF,
2440
+ // 192
2419
2441
  hasInsuranceIsolation: false,
2442
+ // InsuranceFund = {balance: u128} = 16 bytes in v12.15
2420
2443
  engineInsuranceIsolatedOff: -1,
2421
2444
  engineInsuranceIsolationBpsOff: -1
2422
2445
  };
2423
2446
  }
2424
2447
  function detectSlabLayout(dataLen, data) {
2425
- const v121epn = V12_1_EP_SIZES.get(dataLen);
2426
- if (v121epn !== void 0) return buildLayoutV12_1EP(v121epn);
2448
+ const v1215n = V12_15_SIZES.get(dataLen);
2449
+ if (v1215n !== void 0) return buildLayoutV12_15(v1215n);
2427
2450
  const v121n = V12_1_SIZES.get(dataLen);
2428
2451
  if (v121n !== void 0) return buildLayoutV12_1(v121n, dataLen);
2429
2452
  const vsdpn = V_SETDEXPOOL_SIZES.get(dataLen);
@@ -2470,15 +2493,6 @@ var PARAMS_LIQUIDATION_FEE_BPS_OFF = 96;
2470
2493
  var PARAMS_LIQUIDATION_FEE_CAP_OFF = 104;
2471
2494
  var PARAMS_LIQUIDATION_BUFFER_OFF = 120;
2472
2495
  var PARAMS_MIN_LIQUIDATION_OFF = 128;
2473
- var V12_1_PARAMS_MAINT_FEE_OFF = 56;
2474
- var V12_1_PARAMS_MAX_CRANK_OFF = 72;
2475
- var V12_1_PARAMS_LIQ_FEE_BPS_OFF = 80;
2476
- var V12_1_PARAMS_LIQ_FEE_CAP_OFF = 88;
2477
- var V12_1_PARAMS_MIN_LIQ_OFF = 104;
2478
- var V12_1_PARAMS_MIN_INITIAL_DEP_OFF = 120;
2479
- var V12_1_PARAMS_MIN_NZ_MM_OFF = 136;
2480
- var V12_1_PARAMS_MIN_NZ_IM_OFF = 152;
2481
- var V12_1_PARAMS_INS_FLOOR_OFF = 168;
2482
2496
  var ACCT_ACCOUNT_ID_OFF = 0;
2483
2497
  var ACCT_CAPITAL_OFF = 8;
2484
2498
  var ACCT_KIND_OFF = 24;
@@ -2728,15 +2742,15 @@ function parseParams(data, layoutHint) {
2728
2742
  if (data.length < base + Math.min(paramsSize, 56)) {
2729
2743
  throw new Error("Slab data too short for RiskParams");
2730
2744
  }
2731
- const isV12_1Sbf = layout !== null && layout !== void 0 && layout.engineOff === V12_1_SBF_ENGINE_OFF && paramsSize === 184;
2745
+ const isV12_15Params = paramsSize === V12_15_PARAMS_SIZE;
2732
2746
  const result = {
2733
- warmupPeriodSlots: readU64LE(data, base + PARAMS_WARMUP_PERIOD_OFF),
2747
+ warmupPeriodSlots: isV12_15Params ? readU64LE(data, base + V12_15_PARAMS_H_MIN_OFF) : readU64LE(data, base + PARAMS_WARMUP_PERIOD_OFF),
2734
2748
  maintenanceMarginBps: readU64LE(data, base + PARAMS_MAINTENANCE_MARGIN_OFF),
2735
2749
  initialMarginBps: readU64LE(data, base + PARAMS_INITIAL_MARGIN_OFF),
2736
2750
  tradingFeeBps: readU64LE(data, base + PARAMS_TRADING_FEE_OFF),
2737
- maxAccounts: readU64LE(data, base + PARAMS_MAX_ACCOUNTS_OFF),
2751
+ maxAccounts: isV12_15Params ? readU64LE(data, base + V12_15_PARAMS_MAX_ACCOUNTS_OFF) : readU64LE(data, base + PARAMS_MAX_ACCOUNTS_OFF),
2738
2752
  newAccountFee: readU128LE(data, base + PARAMS_NEW_ACCOUNT_FEE_OFF),
2739
- // Extended params: defaults; overwritten below if layout supports them
2753
+ // Extended params: only read if V1 (paramsSize >= 144)
2740
2754
  riskReductionThreshold: 0n,
2741
2755
  maintenanceFeePerSlot: 0n,
2742
2756
  maxCrankStalenessSlots: 0n,
@@ -2744,21 +2758,19 @@ function parseParams(data, layoutHint) {
2744
2758
  liquidationFeeCap: 0n,
2745
2759
  liquidationBufferBps: 0n,
2746
2760
  minLiquidationAbs: 0n,
2747
- minInitialDeposit: 0n,
2748
- minNonzeroMmReq: 0n,
2749
- minNonzeroImReq: 0n,
2750
- insuranceFloor: 0n
2761
+ hMin: 0n,
2762
+ hMax: 0n
2751
2763
  };
2752
- if (isV12_1Sbf) {
2753
- result.maintenanceFeePerSlot = readU128LE(data, base + V12_1_PARAMS_MAINT_FEE_OFF);
2754
- result.maxCrankStalenessSlots = readU64LE(data, base + V12_1_PARAMS_MAX_CRANK_OFF);
2755
- result.liquidationFeeBps = readU64LE(data, base + V12_1_PARAMS_LIQ_FEE_BPS_OFF);
2756
- result.liquidationFeeCap = readU128LE(data, base + V12_1_PARAMS_LIQ_FEE_CAP_OFF);
2757
- result.minLiquidationAbs = readU128LE(data, base + V12_1_PARAMS_MIN_LIQ_OFF);
2758
- result.minInitialDeposit = readU128LE(data, base + V12_1_PARAMS_MIN_INITIAL_DEP_OFF);
2759
- result.minNonzeroMmReq = readU128LE(data, base + V12_1_PARAMS_MIN_NZ_MM_OFF);
2760
- result.minNonzeroImReq = readU128LE(data, base + V12_1_PARAMS_MIN_NZ_IM_OFF);
2761
- result.insuranceFloor = readU128LE(data, base + V12_1_PARAMS_INS_FLOOR_OFF);
2764
+ if (isV12_15Params) {
2765
+ result.hMin = readU64LE(data, base + V12_15_PARAMS_H_MIN_OFF);
2766
+ result.hMax = readU64LE(data, base + V12_15_PARAMS_H_MAX_OFF);
2767
+ result.riskReductionThreshold = readU128LE(data, base + PARAMS_RISK_THRESHOLD_OFF);
2768
+ result.maintenanceFeePerSlot = readU128LE(data, base + PARAMS_MAINTENANCE_FEE_OFF);
2769
+ result.maxCrankStalenessSlots = readU64LE(data, base + PARAMS_MAX_CRANK_STALENESS_OFF);
2770
+ result.liquidationFeeBps = readU64LE(data, base + PARAMS_LIQUIDATION_FEE_BPS_OFF);
2771
+ result.liquidationFeeCap = readU128LE(data, base + PARAMS_LIQUIDATION_FEE_CAP_OFF);
2772
+ result.liquidationBufferBps = readU64LE(data, base + PARAMS_LIQUIDATION_BUFFER_OFF);
2773
+ result.minLiquidationAbs = readU128LE(data, base + PARAMS_MIN_LIQUIDATION_OFF);
2762
2774
  } else if (paramsSize >= 144) {
2763
2775
  result.riskReductionThreshold = readU128LE(data, base + PARAMS_RISK_THRESHOLD_OFF);
2764
2776
  result.maintenanceFeePerSlot = readU128LE(data, base + PARAMS_MAINTENANCE_FEE_OFF);
@@ -2767,6 +2779,8 @@ function parseParams(data, layoutHint) {
2767
2779
  result.liquidationFeeCap = readU128LE(data, base + PARAMS_LIQUIDATION_FEE_CAP_OFF);
2768
2780
  result.liquidationBufferBps = readU64LE(data, base + PARAMS_LIQUIDATION_BUFFER_OFF);
2769
2781
  result.minLiquidationAbs = readU128LE(data, base + PARAMS_MIN_LIQUIDATION_OFF);
2782
+ result.hMin = result.warmupPeriodSlots;
2783
+ result.hMax = result.warmupPeriodSlots;
2770
2784
  }
2771
2785
  return result;
2772
2786
  }
@@ -2776,6 +2790,8 @@ function parseEngine(data) {
2776
2790
  throw new Error(`Unrecognized slab data length: ${data.length}. Cannot determine layout version.`);
2777
2791
  }
2778
2792
  const base = layout.engineOff;
2793
+ const isV12_15 = (layout.accountSize === V12_15_ACCOUNT_SIZE || layout.accountSize === V12_15_ACCOUNT_SIZE_SMALL) && layout.engineOff === V12_15_ENGINE_OFF;
2794
+ const fundingRateBpsPerSlotLast = isV12_15 ? readI128LE(data, base + V12_15_ENGINE_FUNDING_RATE_E9_OFF) : readI64LE(data, base + layout.engineFundingRateBpsOff);
2779
2795
  return {
2780
2796
  vault: readU128LE(data, base),
2781
2797
  insuranceFund: {
@@ -2788,21 +2804,24 @@ function parseEngine(data) {
2788
2804
  currentSlot: readU64LE(data, base + layout.engineCurrentSlotOff),
2789
2805
  fundingIndexQpbE6: layout.engineFundingIndexOff >= 0 ? readI128LE(data, base + layout.engineFundingIndexOff) : 0n,
2790
2806
  lastFundingSlot: layout.engineLastFundingSlotOff >= 0 ? readU64LE(data, base + layout.engineLastFundingSlotOff) : 0n,
2791
- fundingRateBpsPerSlotLast: readI64LE(data, base + layout.engineFundingRateBpsOff),
2792
- lastCrankSlot: readU64LE(data, base + layout.engineLastCrankSlotOff),
2793
- maxCrankStalenessSlots: readU64LE(data, base + layout.engineMaxCrankStalenessOff),
2807
+ fundingRateBpsPerSlotLast,
2808
+ fundingRateE9: isV12_15 ? readI128LE(data, base + V12_15_ENGINE_FUNDING_RATE_E9_OFF) : 0n,
2809
+ marketMode: isV12_15 ? readU8(data, base + V12_15_ENGINE_MARKET_MODE_OFF) === 1 ? 1 : 0 : null,
2810
+ lastCrankSlot: layout.engineLastCrankSlotOff >= 0 ? readU64LE(data, base + layout.engineLastCrankSlotOff) : 0n,
2811
+ maxCrankStalenessSlots: layout.engineMaxCrankStalenessOff >= 0 ? readU64LE(data, base + layout.engineMaxCrankStalenessOff) : 0n,
2794
2812
  totalOpenInterest: layout.engineTotalOiOff >= 0 ? readU128LE(data, base + layout.engineTotalOiOff) : 0n,
2795
2813
  longOi: layout.engineLongOiOff >= 0 ? readU128LE(data, base + layout.engineLongOiOff) : 0n,
2796
2814
  shortOi: layout.engineShortOiOff >= 0 ? readU128LE(data, base + layout.engineShortOiOff) : 0n,
2797
2815
  cTot: readU128LE(data, base + layout.engineCTotOff),
2798
2816
  pnlPosTot: readU128LE(data, base + layout.enginePnlPosTotOff),
2799
- liqCursor: readU16LE(data, base + layout.engineLiqCursorOff),
2800
- gcCursor: readU16LE(data, base + layout.engineGcCursorOff),
2801
- lastSweepStartSlot: readU64LE(data, base + layout.engineLastSweepStartOff),
2802
- lastSweepCompleteSlot: readU64LE(data, base + layout.engineLastSweepCompleteOff),
2803
- crankCursor: readU16LE(data, base + layout.engineCrankCursorOff),
2804
- sweepStartIdx: readU16LE(data, base + layout.engineSweepStartIdxOff),
2805
- lifetimeLiquidations: readU64LE(data, base + layout.engineLifetimeLiquidationsOff),
2817
+ pnlMaturedPosTot: isV12_15 ? readU128LE(data, base + V12_15_ENGINE_PNL_MATURED_POS_TOT_OFF) : 0n,
2818
+ liqCursor: layout.engineLiqCursorOff >= 0 ? readU16LE(data, base + layout.engineLiqCursorOff) : 0,
2819
+ gcCursor: layout.engineGcCursorOff >= 0 ? readU16LE(data, base + layout.engineGcCursorOff) : 0,
2820
+ lastSweepStartSlot: layout.engineLastSweepStartOff >= 0 ? readU64LE(data, base + layout.engineLastSweepStartOff) : 0n,
2821
+ lastSweepCompleteSlot: layout.engineLastSweepCompleteOff >= 0 ? readU64LE(data, base + layout.engineLastSweepCompleteOff) : 0n,
2822
+ crankCursor: layout.engineCrankCursorOff >= 0 ? readU16LE(data, base + layout.engineCrankCursorOff) : 0,
2823
+ sweepStartIdx: layout.engineSweepStartIdxOff >= 0 ? readU16LE(data, base + layout.engineSweepStartIdxOff) : 0,
2824
+ lifetimeLiquidations: layout.engineLifetimeLiquidationsOff >= 0 ? readU64LE(data, base + layout.engineLifetimeLiquidationsOff) : 0n,
2806
2825
  lifetimeForceCloses: layout.engineLifetimeForceClosesOff >= 0 ? readU64LE(data, base + layout.engineLifetimeForceClosesOff) : 0n,
2807
2826
  netLpPos: layout.engineNetLpPosOff >= 0 ? readI128LE(data, base + layout.engineNetLpPosOff) : 0n,
2808
2827
  lpSumAbs: layout.engineLpSumAbsOff >= 0 ? readU128LE(data, base + layout.engineLpSumAbsOff) : 0n,
@@ -2872,18 +2891,58 @@ function parseAccount(data, idx) {
2872
2891
  if (data.length < base + layout.accountSize) {
2873
2892
  throw new Error("Slab data too short for account");
2874
2893
  }
2875
- const isV12_1EP = layout.accountSize === V12_1_EP_SBF_ACCOUNT_SIZE && layout.engineOff === V12_1_SBF_ENGINE_OFF;
2876
- const isV12_1 = !isV12_1EP && (layout.engineOff === V12_1_ENGINE_OFF || layout.engineOff === V12_1_SBF_ENGINE_OFF) && (layout.accountSize === V12_1_ACCOUNT_SIZE || layout.accountSize === V12_1_ACCOUNT_SIZE_SBF);
2877
- const isAdl = layout.accountSize >= 312 || isV12_1 || isV12_1EP;
2894
+ const isV12_15 = layout.accountSize === V12_15_ACCOUNT_SIZE || layout.accountSize === V12_15_ACCOUNT_SIZE_SMALL;
2895
+ const isV12_1 = !isV12_15 && (layout.engineOff === V12_1_ENGINE_OFF || layout.engineOff === V12_1_SBF_ENGINE_OFF) && (layout.accountSize === V12_1_ACCOUNT_SIZE || layout.accountSize === V12_1_ACCOUNT_SIZE_SBF);
2896
+ const isAdl = !isV12_15 && (layout.accountSize >= 312 || isV12_1);
2897
+ if (isV12_15) {
2898
+ const kindByte2 = readU8(data, base + V12_15_ACCT_KIND_OFF);
2899
+ const kind2 = kindByte2 === 1 ? 1 /* LP */ : 0 /* User */;
2900
+ const cohortCount = readU8(data, base + V12_15_ACCT_EXACT_COHORT_COUNT_OFF);
2901
+ const exactReserveCohorts = [];
2902
+ for (let i = 0; i < 62; i++) {
2903
+ const cohortOff = base + V12_15_ACCT_EXACT_RESERVE_COHORTS_OFF + i * 64;
2904
+ exactReserveCohorts.push(data.slice(cohortOff, cohortOff + 64));
2905
+ }
2906
+ const overflowOlderPresent = readU8(data, base + V12_15_ACCT_OVERFLOW_OLDER_PRESENT_OFF) !== 0;
2907
+ const overflowNewestPresent = readU8(data, base + V12_15_ACCT_OVERFLOW_NEWEST_PRESENT_OFF) !== 0;
2908
+ return {
2909
+ kind: kind2,
2910
+ accountId: readU64LE(data, base + V12_15_ACCT_ACCOUNT_ID_OFF),
2911
+ capital: readU128LE(data, base + V12_15_ACCT_CAPITAL_OFF),
2912
+ pnl: readI128LE(data, base + V12_15_ACCT_PNL_OFF),
2913
+ reservedPnl: readU128LE(data, base + V12_15_ACCT_RESERVED_PNL_OFF),
2914
+ warmupStartedAtSlot: 0n,
2915
+ // removed in v12.15
2916
+ warmupSlopePerStep: 0n,
2917
+ // removed in v12.15
2918
+ positionSize: readI128LE(data, base + V12_15_ACCT_POSITION_BASIS_Q_OFF),
2919
+ entryPrice: readU64LE(data, base + V12_15_ACCT_ENTRY_PRICE_OFF),
2920
+ fundingIndex: 0n,
2921
+ // not present in v12.15 account struct
2922
+ matcherProgram: new PublicKey3(data.subarray(base + V12_15_ACCT_MATCHER_PROGRAM_OFF, base + V12_15_ACCT_MATCHER_PROGRAM_OFF + 32)),
2923
+ matcherContext: new PublicKey3(data.subarray(base + V12_15_ACCT_MATCHER_CONTEXT_OFF, base + V12_15_ACCT_MATCHER_CONTEXT_OFF + 32)),
2924
+ owner: new PublicKey3(data.subarray(base + V12_15_ACCT_OWNER_OFF, base + V12_15_ACCT_OWNER_OFF + 32)),
2925
+ feeCredits: readI128LE(data, base + V12_15_ACCT_FEE_CREDITS_OFF),
2926
+ lastFeeSlot: 0n,
2927
+ // removed in v12.15
2928
+ feesEarnedTotal: readU128LE(data, base + V12_15_ACCT_FEES_EARNED_TOTAL_OFF),
2929
+ exactReserveCohorts,
2930
+ exactCohortCount: cohortCount,
2931
+ overflowOlder: data.slice(base + V12_15_ACCT_OVERFLOW_OLDER_OFF, base + V12_15_ACCT_OVERFLOW_OLDER_OFF + 64),
2932
+ overflowOlderPresent,
2933
+ overflowNewest: data.slice(base + V12_15_ACCT_OVERFLOW_NEWEST_OFF, base + V12_15_ACCT_OVERFLOW_NEWEST_OFF + 64),
2934
+ overflowNewestPresent
2935
+ };
2936
+ }
2878
2937
  const warmupStartedOff = isAdl ? V_ADL_ACCT_WARMUP_STARTED_OFF : ACCT_WARMUP_STARTED_OFF;
2879
2938
  const warmupSlopeOff = isAdl ? V_ADL_ACCT_WARMUP_SLOPE_OFF : ACCT_WARMUP_SLOPE_OFF;
2880
- const positionSizeOff = isV12_1 || isV12_1EP ? V12_1_ACCT_POSITION_SIZE_OFF : isAdl ? V_ADL_ACCT_POSITION_SIZE_OFF : ACCT_POSITION_SIZE_OFF;
2881
- const entryPriceOff = isV12_1EP ? V12_1_EP_ACCT_ENTRY_PRICE_OFF : isV12_1 ? V12_1_ACCT_ENTRY_PRICE_OFF : isAdl ? V_ADL_ACCT_ENTRY_PRICE_OFF : ACCT_ENTRY_PRICE_OFF;
2882
- const fundingIndexOff = isV12_1 || isV12_1EP ? -1 : isAdl ? V_ADL_ACCT_FUNDING_INDEX_OFF : ACCT_FUNDING_INDEX_OFF;
2883
- const matcherProgOff = isV12_1EP ? V12_1_EP_ACCT_MATCHER_PROGRAM_OFF : isV12_1 ? V12_1_ACCT_MATCHER_PROGRAM_OFF : isAdl ? V_ADL_ACCT_MATCHER_PROGRAM_OFF : ACCT_MATCHER_PROGRAM_OFF;
2884
- const matcherCtxOff = isV12_1EP ? V12_1_EP_ACCT_MATCHER_CONTEXT_OFF : isV12_1 ? V12_1_ACCT_MATCHER_CONTEXT_OFF : isAdl ? V_ADL_ACCT_MATCHER_CONTEXT_OFF : ACCT_MATCHER_CONTEXT_OFF;
2885
- const feeCreditsOff = isV12_1EP ? V12_1_EP_ACCT_FEE_CREDITS_OFF : isV12_1 ? V12_1_ACCT_FEE_CREDITS_OFF : isAdl ? V_ADL_ACCT_FEE_CREDITS_OFF : ACCT_FEE_CREDITS_OFF;
2886
- const lastFeeSlotOff = isV12_1EP ? V12_1_EP_ACCT_LAST_FEE_SLOT_OFF : isV12_1 ? V12_1_ACCT_LAST_FEE_SLOT_OFF : isAdl ? V_ADL_ACCT_LAST_FEE_SLOT_OFF : ACCT_LAST_FEE_SLOT_OFF;
2939
+ const positionSizeOff = isV12_1 ? V12_1_ACCT_POSITION_SIZE_OFF : isAdl ? V_ADL_ACCT_POSITION_SIZE_OFF : ACCT_POSITION_SIZE_OFF;
2940
+ const entryPriceOff = isV12_1 ? V12_1_ACCT_ENTRY_PRICE_OFF : isAdl ? V_ADL_ACCT_ENTRY_PRICE_OFF : ACCT_ENTRY_PRICE_OFF;
2941
+ const fundingIndexOff = isV12_1 ? V12_1_ACCT_FUNDING_INDEX_OFF : isAdl ? V_ADL_ACCT_FUNDING_INDEX_OFF : ACCT_FUNDING_INDEX_OFF;
2942
+ const matcherProgOff = isV12_1 ? V12_1_ACCT_MATCHER_PROGRAM_OFF : isAdl ? V_ADL_ACCT_MATCHER_PROGRAM_OFF : ACCT_MATCHER_PROGRAM_OFF;
2943
+ const matcherCtxOff = isV12_1 ? V12_1_ACCT_MATCHER_CONTEXT_OFF : isAdl ? V_ADL_ACCT_MATCHER_CONTEXT_OFF : ACCT_MATCHER_CONTEXT_OFF;
2944
+ const feeCreditsOff = isV12_1 ? V12_1_ACCT_FEE_CREDITS_OFF : isAdl ? V_ADL_ACCT_FEE_CREDITS_OFF : ACCT_FEE_CREDITS_OFF;
2945
+ const lastFeeSlotOff = isV12_1 ? V12_1_ACCT_LAST_FEE_SLOT_OFF : isAdl ? V_ADL_ACCT_LAST_FEE_SLOT_OFF : ACCT_LAST_FEE_SLOT_OFF;
2887
2946
  const kindByte = readU8(data, base + ACCT_KIND_OFF);
2888
2947
  const kind = kindByte === 1 ? 1 /* LP */ : 0 /* User */;
2889
2948
  return {
@@ -2896,13 +2955,23 @@ function parseAccount(data, idx) {
2896
2955
  warmupSlopePerStep: readU128LE(data, base + warmupSlopeOff),
2897
2956
  positionSize: readI128LE(data, base + positionSizeOff),
2898
2957
  entryPrice: entryPriceOff >= 0 ? readU64LE(data, base + entryPriceOff) : 0n,
2899
- // V12_1/V12_1_EP: funding_index not present in SBF layout
2900
- fundingIndex: isV12_1 || isV12_1EP ? fundingIndexOff >= 0 ? BigInt(readI64LE(data, base + fundingIndexOff)) : 0n : readI128LE(data, base + fundingIndexOff),
2958
+ // V12_1: entry_price removed
2959
+ // V12_1 changed funding_index from i128 to i64 (legacy field moved to end of account)
2960
+ fundingIndex: isV12_1 ? BigInt(readI64LE(data, base + fundingIndexOff)) : readI128LE(data, base + fundingIndexOff),
2901
2961
  matcherProgram: new PublicKey3(data.subarray(base + matcherProgOff, base + matcherProgOff + 32)),
2902
2962
  matcherContext: new PublicKey3(data.subarray(base + matcherCtxOff, base + matcherCtxOff + 32)),
2903
2963
  owner: new PublicKey3(data.subarray(base + layout.acctOwnerOff, base + layout.acctOwnerOff + 32)),
2904
2964
  feeCredits: readI128LE(data, base + feeCreditsOff),
2905
- lastFeeSlot: readU64LE(data, base + lastFeeSlotOff)
2965
+ lastFeeSlot: readU64LE(data, base + lastFeeSlotOff),
2966
+ feesEarnedTotal: 0n,
2967
+ // not present in pre-v12.15 layouts
2968
+ exactReserveCohorts: null,
2969
+ // not present in pre-v12.15 layouts
2970
+ exactCohortCount: null,
2971
+ overflowOlder: null,
2972
+ overflowOlderPresent: null,
2973
+ overflowNewest: null,
2974
+ overflowNewestPresent: null
2906
2975
  };
2907
2976
  }
2908
2977
  function parseAllAccounts(data) {
@@ -2930,12 +2999,6 @@ function deriveVaultAuthority(programId, slab) {
2930
2999
  programId
2931
3000
  );
2932
3001
  }
2933
- function deriveInsuranceLpMint(programId, slab) {
2934
- return PublicKey4.findProgramAddressSync(
2935
- [textEncoder.encode("ins_lp"), slab.toBytes()],
2936
- programId
2937
- );
2938
- }
2939
3002
  var LP_INDEX_U16_MAX = 65535;
2940
3003
  function deriveLpPda(programId, slab, lpIdx) {
2941
3004
  if (typeof lpIdx !== "number" || !Number.isInteger(lpIdx) || lpIdx < 0 || lpIdx > LP_INDEX_U16_MAX) {
@@ -2950,12 +3013,6 @@ function deriveLpPda(programId, slab, lpIdx) {
2950
3013
  programId
2951
3014
  );
2952
3015
  }
2953
- function deriveKeeperFund(programId, slab) {
2954
- return PublicKey4.findProgramAddressSync(
2955
- [textEncoder.encode("keeper_fund"), slab.toBytes()],
2956
- programId
2957
- );
2958
- }
2959
3016
  var PUMPSWAP_PROGRAM_ID = new PublicKey4(
2960
3017
  "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"
2961
3018
  );
@@ -2992,7 +3049,14 @@ function derivePythPushOraclePDA(feedIdHex) {
2992
3049
  }
2993
3050
  const feedId = new Uint8Array(32);
2994
3051
  for (let i = 0; i < 32; i++) {
2995
- feedId[i] = parseInt(normalized.substring(i * 2, i * 2 + 2), 16);
3052
+ const hexPair = normalized.substring(i * 2, i * 2 + 2);
3053
+ const byte = parseInt(hexPair, 16);
3054
+ if (Number.isNaN(byte)) {
3055
+ throw new Error(
3056
+ `derivePythPushOraclePDA: failed to parse hex byte at position ${i}: "${hexPair}"`
3057
+ );
3058
+ }
3059
+ feedId[i] = byte;
2996
3060
  }
2997
3061
  const shardBuf = new Uint8Array(2);
2998
3062
  return PublicKey4.findProgramAddressSync(
@@ -3024,7 +3088,9 @@ import { PublicKey as PublicKey6 } from "@solana/web3.js";
3024
3088
  // src/solana/static-markets.ts
3025
3089
  import { PublicKey as PublicKey5 } from "@solana/web3.js";
3026
3090
  var MAINNET_MARKETS = [
3027
- { slabAddress: "7psyeWRts4pRX2cyAWD1NH87bR9ugXP7pe6ARgfG79Do", symbol: "SOL-PERP", name: "SOL/USDC Perpetual" }
3091
+ // Populated at mainnet launch currently empty.
3092
+ // To add entries:
3093
+ // { slabAddress: "ABC123...", symbol: "SOL-PERP", name: "SOL Perpetual" },
3028
3094
  ];
3029
3095
  var DEVNET_MARKETS = [
3030
3096
  // Populated from prior discoverMarkets() runs on devnet.
@@ -3192,6 +3258,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3192
3258
  fundingIndexQpbE6: readI128LE2(data, base + 112),
3193
3259
  lastFundingSlot: readU64LE2(data, base + 128),
3194
3260
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 136),
3261
+ fundingRateE9: 0n,
3262
+ marketMode: null,
3195
3263
  lastCrankSlot: readU64LE2(data, base + 144),
3196
3264
  maxCrankStalenessSlots: readU64LE2(data, base + 152),
3197
3265
  totalOpenInterest: readU128LE2(data, base + 160),
@@ -3199,6 +3267,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3199
3267
  shortOi: 0n,
3200
3268
  cTot: readU128LE2(data, base + 176),
3201
3269
  pnlPosTot: readU128LE2(data, base + 192),
3270
+ pnlMaturedPosTot: 0n,
3202
3271
  liqCursor: readU16LE2(data, base + 208),
3203
3272
  gcCursor: readU16LE2(data, base + 210),
3204
3273
  lastSweepStartSlot: readU64LE2(data, base + 216),
@@ -3234,6 +3303,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3234
3303
  fundingIndexQpbE6: readI128LE2(data, base + 360),
3235
3304
  lastFundingSlot: readU64LE2(data, base + 376),
3236
3305
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 384),
3306
+ fundingRateE9: 0n,
3307
+ marketMode: null,
3237
3308
  lastCrankSlot: readU64LE2(data, base + 392),
3238
3309
  maxCrankStalenessSlots: readU64LE2(data, base + 400),
3239
3310
  totalOpenInterest: readU128LE2(data, base + 408),
@@ -3243,6 +3314,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3243
3314
  // V2 has no short_oi
3244
3315
  cTot: readU128LE2(data, base + 424),
3245
3316
  pnlPosTot: readU128LE2(data, base + 440),
3317
+ pnlMaturedPosTot: 0n,
3246
3318
  liqCursor: readU16LE2(data, base + 456),
3247
3319
  gcCursor: readU16LE2(data, base + 458),
3248
3320
  lastSweepStartSlot: readU64LE2(data, base + 464),
@@ -3280,6 +3352,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3280
3352
  fundingIndexQpbE6: readI128LE2(data, base + l.engineFundingIndexOff),
3281
3353
  lastFundingSlot: readU64LE2(data, base + l.engineLastFundingSlotOff),
3282
3354
  fundingRateBpsPerSlotLast: readI64LE2(data, base + l.engineFundingRateBpsOff),
3355
+ fundingRateE9: 0n,
3356
+ marketMode: null,
3283
3357
  lastCrankSlot: readU64LE2(data, base + l.engineLastCrankSlotOff),
3284
3358
  maxCrankStalenessSlots: readU64LE2(data, base + l.engineMaxCrankStalenessOff),
3285
3359
  totalOpenInterest: readU128LE2(data, base + l.engineTotalOiOff),
@@ -3287,6 +3361,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3287
3361
  shortOi: l.engineShortOiOff >= 0 ? readU128LE2(data, base + l.engineShortOiOff) : 0n,
3288
3362
  cTot: readU128LE2(data, base + l.engineCTotOff),
3289
3363
  pnlPosTot: readU128LE2(data, base + l.enginePnlPosTotOff),
3364
+ pnlMaturedPosTot: 0n,
3290
3365
  liqCursor: readU16LE2(data, base + l.engineLiqCursorOff),
3291
3366
  gcCursor: readU16LE2(data, base + l.engineGcCursorOff),
3292
3367
  lastSweepStartSlot: readU64LE2(data, base + l.engineLastSweepStartOff),
@@ -3320,6 +3395,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3320
3395
  fundingIndexQpbE6: readI128LE2(data, base + 368),
3321
3396
  lastFundingSlot: readU64LE2(data, base + 384),
3322
3397
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 392),
3398
+ fundingRateE9: 0n,
3399
+ marketMode: null,
3323
3400
  lastCrankSlot: readU64LE2(data, base + 424),
3324
3401
  maxCrankStalenessSlots: readU64LE2(data, base + 408),
3325
3402
  totalOpenInterest: readU128LE2(data, base + 416),
@@ -3327,6 +3404,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3327
3404
  shortOi: readU128LE2(data, base + 448),
3328
3405
  cTot: readU128LE2(data, base + 464),
3329
3406
  pnlPosTot: readU128LE2(data, base + 480),
3407
+ pnlMaturedPosTot: 0n,
3330
3408
  liqCursor: readU16LE2(data, base + 496),
3331
3409
  gcCursor: readU16LE2(data, base + 498),
3332
3410
  lastSweepStartSlot: readU64LE2(data, base + 504),
@@ -3792,9 +3870,7 @@ function computeRaydiumClmmPriceE6(data) {
3792
3870
  }
3793
3871
  const sqrtPriceX64 = readU128LE3(dv3, 253);
3794
3872
  if (sqrtPriceX64 === 0n) return 0n;
3795
- const scaledSqrt = sqrtPriceX64 * 1000000n;
3796
- const term = scaledSqrt >> 64n;
3797
- const priceE6Raw = term * sqrtPriceX64 >> 64n;
3873
+ const priceE6Raw = sqrtPriceX64 * sqrtPriceX64 * 1000000n >> 128n;
3798
3874
  const decimalDiff = 6 + decimals0 - decimals1;
3799
3875
  const adjustedDiff = decimalDiff - 6;
3800
3876
  if (adjustedDiff >= 0) {
@@ -3835,13 +3911,26 @@ function computeMeteoraDlmmPriceE6(data) {
3835
3911
  `Meteora DLMM: |activeId| ${Math.abs(activeId)} exceeds max ${MAX_ACTIVE_ID_ABS}`
3836
3912
  );
3837
3913
  }
3914
+ const MAX_ABS_BIN_ID = 5e5;
3915
+ if (activeId > MAX_ABS_BIN_ID || activeId < -MAX_ABS_BIN_ID) {
3916
+ throw new Error(
3917
+ `Meteora DLMM: activeId ${activeId} exceeds safe range (\xB1${MAX_ABS_BIN_ID})`
3918
+ );
3919
+ }
3838
3920
  const SCALE = 1000000000000000000n;
3839
3921
  const base = SCALE + BigInt(binStep) * SCALE / 10000n;
3840
3922
  const isNeg = activeId < 0;
3841
3923
  let exp = isNeg ? BigInt(-activeId) : BigInt(activeId);
3842
3924
  let result = SCALE;
3843
3925
  let b = base;
3926
+ let iterations = 0;
3927
+ const MAX_ITERATIONS = 25;
3844
3928
  while (exp > 0n) {
3929
+ if (iterations++ >= MAX_ITERATIONS) {
3930
+ throw new Error(
3931
+ `Meteora DLMM: exponentiation loop exceeded ${MAX_ITERATIONS} iterations (activeId=${activeId})`
3932
+ );
3933
+ }
3845
3934
  if (exp & 1n) {
3846
3935
  result = result * b / SCALE;
3847
3936
  }
@@ -3872,6 +3961,7 @@ function readU128LE3(dv3, offset) {
3872
3961
  var CHAINLINK_MIN_SIZE = 224;
3873
3962
  var MAX_DECIMALS = 18;
3874
3963
  var CHAINLINK_DECIMALS_OFFSET = 138;
3964
+ var CHAINLINK_TIMESTAMP_OFFSET = 168;
3875
3965
  var CHAINLINK_ANSWER_OFFSET = 216;
3876
3966
  function readU82(data, off) {
3877
3967
  return data[off];
@@ -3879,7 +3969,7 @@ function readU82(data, off) {
3879
3969
  function readBigInt64LE(data, off) {
3880
3970
  return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigInt64(off, true);
3881
3971
  }
3882
- function parseChainlinkPrice(data) {
3972
+ function parseChainlinkPrice(data, options) {
3883
3973
  if (data.length < CHAINLINK_MIN_SIZE) {
3884
3974
  throw new Error(
3885
3975
  `Oracle account data too small: ${data.length} bytes (need at least ${CHAINLINK_MIN_SIZE})`
@@ -3897,7 +3987,18 @@ function parseChainlinkPrice(data) {
3897
3987
  `Oracle price is non-positive: ${price}`
3898
3988
  );
3899
3989
  }
3900
- return { price, decimals };
3990
+ const updatedAtBig = readBigInt64LE(data, CHAINLINK_TIMESTAMP_OFFSET);
3991
+ const updatedAt = Number(updatedAtBig);
3992
+ if (options?.maxStalenessSeconds !== void 0 && updatedAt > 0) {
3993
+ const now = Math.floor(Date.now() / 1e3);
3994
+ const age = now - updatedAt;
3995
+ if (age > options.maxStalenessSeconds) {
3996
+ throw new Error(
3997
+ `Oracle price is stale: last updated ${age}s ago (max ${options.maxStalenessSeconds}s)`
3998
+ );
3999
+ }
4000
+ }
4001
+ return { price, decimals, updatedAt: updatedAt > 0 ? updatedAt : void 0 };
3901
4002
  }
3902
4003
  function isValidChainlinkOracle(data) {
3903
4004
  try {
@@ -3917,7 +4018,11 @@ var TOKEN_2022_PROGRAM_ID = new PublicKey8(
3917
4018
  async function detectTokenProgram(connection, mint) {
3918
4019
  const info = await connection.getAccountInfo(mint);
3919
4020
  if (!info) throw new Error(`Mint account not found: ${mint.toBase58()}`);
3920
- return info.owner;
4021
+ if (info.owner.equals(TOKEN_PROGRAM_ID3)) return TOKEN_PROGRAM_ID3;
4022
+ if (info.owner.equals(TOKEN_2022_PROGRAM_ID)) return TOKEN_2022_PROGRAM_ID;
4023
+ throw new Error(
4024
+ `Mint ${mint.toBase58()} is owned by ${info.owner.toBase58()}, which is neither TOKEN_PROGRAM_ID nor TOKEN_2022_PROGRAM_ID`
4025
+ );
3921
4026
  }
3922
4027
  function isToken2022(tokenProgramId) {
3923
4028
  return tokenProgramId.equals(TOKEN_2022_PROGRAM_ID);
@@ -3950,12 +4055,14 @@ var PROGRAM_IDS = {
3950
4055
  }
3951
4056
  };
3952
4057
  function getProgramId(network) {
3953
- const override = safeEnv("PROGRAM_ID");
3954
- if (override) {
3955
- console.warn(
3956
- `[percolator-sdk] PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3957
- );
3958
- return new PublicKey9(override);
4058
+ if (!network) {
4059
+ const override = safeEnv("PROGRAM_ID");
4060
+ if (override) {
4061
+ console.warn(
4062
+ `[percolator-sdk] PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
4063
+ );
4064
+ return new PublicKey9(override);
4065
+ }
3959
4066
  }
3960
4067
  const detectedNetwork = getCurrentNetwork();
3961
4068
  const targetNetwork = network ?? detectedNetwork;
@@ -3963,12 +4070,14 @@ function getProgramId(network) {
3963
4070
  return new PublicKey9(programId);
3964
4071
  }
3965
4072
  function getMatcherProgramId(network) {
3966
- const override = safeEnv("MATCHER_PROGRAM_ID");
3967
- if (override) {
3968
- console.warn(
3969
- `[percolator-sdk] MATCHER_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
3970
- );
3971
- return new PublicKey9(override);
4073
+ if (!network) {
4074
+ const override = safeEnv("MATCHER_PROGRAM_ID");
4075
+ if (override) {
4076
+ console.warn(
4077
+ `[percolator-sdk] MATCHER_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
4078
+ );
4079
+ return new PublicKey9(override);
4080
+ }
3972
4081
  }
3973
4082
  const detectedNetwork = getCurrentNetwork();
3974
4083
  const targetNetwork = network ?? detectedNetwork;
@@ -4001,10 +4110,7 @@ function getStakeProgramId(network) {
4001
4110
  }
4002
4111
  const detectedNetwork = network ?? (() => {
4003
4112
  const n = safeEnv("NEXT_PUBLIC_DEFAULT_NETWORK")?.toLowerCase() ?? safeEnv("NETWORK")?.toLowerCase() ?? "";
4004
- if (n === "mainnet" || n === "mainnet-beta") return "mainnet";
4005
- if (n === "devnet") return "devnet";
4006
- if (typeof window !== "undefined") return "mainnet";
4007
- return "devnet";
4113
+ return n === "mainnet" || n === "mainnet-beta" ? "mainnet" : "devnet";
4008
4114
  })();
4009
4115
  const id = STAKE_PROGRAM_IDS[detectedNetwork];
4010
4116
  if (!id) {
@@ -4014,7 +4120,7 @@ function getStakeProgramId(network) {
4014
4120
  }
4015
4121
  return new PublicKey10(id);
4016
4122
  }
4017
- var STAKE_PROGRAM_ID = new PublicKey10(STAKE_PROGRAM_IDS.devnet);
4123
+ var STAKE_PROGRAM_ID = new PublicKey10(STAKE_PROGRAM_IDS.mainnet);
4018
4124
  var STAKE_IX = {
4019
4125
  InitPool: 0,
4020
4126
  Deposit: 1,
@@ -4075,6 +4181,9 @@ function readU16LE3(data, off) {
4075
4181
  );
4076
4182
  }
4077
4183
  function u64Le(v) {
4184
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
4185
+ throw new Error(`u64Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
4186
+ }
4078
4187
  const big = BigInt(v);
4079
4188
  if (big < 0n) throw new Error(`u64Le: value must be non-negative, got ${big}`);
4080
4189
  if (big > 0xFFFFFFFFFFFFFFFFn) throw new Error(`u64Le: value exceeds u64 max`);
@@ -4083,6 +4192,9 @@ function u64Le(v) {
4083
4192
  return arr;
4084
4193
  }
4085
4194
  function u128Le(v) {
4195
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
4196
+ throw new Error(`u128Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
4197
+ }
4086
4198
  const big = BigInt(v);
4087
4199
  if (big < 0n) throw new Error(`u128Le: value must be non-negative, got ${big}`);
4088
4200
  if (big > (1n << 128n) - 1n) throw new Error(`u128Le: value exceeds u128 max`);
@@ -4093,7 +4205,7 @@ function u128Le(v) {
4093
4205
  return arr;
4094
4206
  }
4095
4207
  function u16Le(v) {
4096
- if (v < 0 || v > 65535) throw new Error(`u16Le: value out of u16 range (0..65535), got ${v}`);
4208
+ if (!Number.isInteger(v) || v < 0 || v > 65535) throw new Error(`u16Le: value must be integer in range 0..65535, got ${v}`);
4097
4209
  const arr = new Uint8Array(2);
4098
4210
  new DataView(arr.buffer).setUint16(0, v, true);
4099
4211
  return arr;
@@ -4348,7 +4460,9 @@ function computePnlPct(pnl, capital) {
4348
4460
  }
4349
4461
  function isAdlTriggered(slabData) {
4350
4462
  const layout = detectSlabLayout(slabData.length);
4351
- if (!layout) return false;
4463
+ if (!layout) {
4464
+ return false;
4465
+ }
4352
4466
  try {
4353
4467
  const engine = parseEngine(slabData);
4354
4468
  if (engine.pnlPosTot === 0n) return false;
@@ -4391,6 +4505,14 @@ function rankAdlPositions(slabData) {
4391
4505
  if (account.kind !== 0 /* User */) continue;
4392
4506
  if (account.positionSize === 0n) continue;
4393
4507
  const side = account.positionSize > 0n ? "long" : "short";
4508
+ if (side === "long" && account.positionSize <= 0n) {
4509
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=long but positionSize=${account.positionSize}`);
4510
+ continue;
4511
+ }
4512
+ if (side === "short" && account.positionSize >= 0n) {
4513
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=short but positionSize=${account.positionSize}`);
4514
+ continue;
4515
+ }
4394
4516
  const pnlPct = computePnlPct(account.pnl, account.capital);
4395
4517
  positions.push({
4396
4518
  idx,
@@ -4423,7 +4545,8 @@ function buildAdlInstruction(caller, slab, oracle, programId, targetIdx, backupO
4423
4545
  `buildAdlInstruction: targetIdx must be a non-negative integer, got ${targetIdx}`
4424
4546
  );
4425
4547
  }
4426
- const data = Buffer.from(encodeExecuteAdl({ targetIdx }));
4548
+ const dataBytes = encodeExecuteAdl({ targetIdx });
4549
+ const data = Buffer.from(dataBytes);
4427
4550
  const keys = [
4428
4551
  { pubkey: caller, isSigner: true, isWritable: false },
4429
4552
  { pubkey: slab, isSigner: false, isWritable: true },
@@ -4463,7 +4586,11 @@ function parseAdlEvent(logs) {
4463
4586
  }
4464
4587
  if (tag !== ADL_EVENT_TAG) continue;
4465
4588
  try {
4466
- const targetIdx = Number(BigInt(match[2]));
4589
+ const targetIdxBig = BigInt(match[2]);
4590
+ if (targetIdxBig < 0n || targetIdxBig > 65535n) {
4591
+ continue;
4592
+ }
4593
+ const targetIdx = Number(targetIdxBig);
4467
4594
  const price = BigInt(match[3]);
4468
4595
  const closedLo = BigInt(match[4]);
4469
4596
  const closedHi = BigInt(match[5]);
@@ -4491,9 +4618,387 @@ async function fetchAdlRankings(apiBase, slab, fetchFn = fetch) {
4491
4618
  );
4492
4619
  }
4493
4620
  const json = await res.json();
4621
+ if (typeof json !== "object" || json === null) {
4622
+ throw new Error("fetchAdlRankings: API returned non-object response");
4623
+ }
4624
+ const obj = json;
4625
+ if (!Array.isArray(obj.rankings)) {
4626
+ throw new Error("fetchAdlRankings: API response missing rankings array");
4627
+ }
4628
+ for (const entry of obj.rankings) {
4629
+ if (typeof entry !== "object" || entry === null) {
4630
+ throw new Error("fetchAdlRankings: invalid ranking entry (not an object)");
4631
+ }
4632
+ const r = entry;
4633
+ if (typeof r.idx !== "number" || !Number.isInteger(r.idx) || r.idx < 0) {
4634
+ throw new Error(`fetchAdlRankings: invalid ranking idx: ${r.idx}`);
4635
+ }
4636
+ }
4494
4637
  return json;
4495
4638
  }
4496
4639
 
4640
+ // src/solana/rpc-pool.ts
4641
+ import {
4642
+ Connection as Connection5
4643
+ } from "@solana/web3.js";
4644
+ async function checkRpcHealth(endpoint, timeoutMs = 5e3) {
4645
+ const conn = new Connection5(endpoint, { commitment: "processed" });
4646
+ const start = performance.now();
4647
+ const timeout = rejectAfter(timeoutMs, `Health probe timed out after ${timeoutMs}ms`);
4648
+ try {
4649
+ const slot = await Promise.race([
4650
+ conn.getSlot("processed"),
4651
+ timeout.promise
4652
+ ]);
4653
+ const latencyMs = Math.round(performance.now() - start);
4654
+ return { endpoint, healthy: true, latencyMs, slot };
4655
+ } catch (err) {
4656
+ const latencyMs = Math.round(performance.now() - start);
4657
+ return {
4658
+ endpoint,
4659
+ healthy: false,
4660
+ latencyMs,
4661
+ slot: 0,
4662
+ error: err instanceof Error ? err.message : String(err)
4663
+ };
4664
+ } finally {
4665
+ timeout.cancel();
4666
+ }
4667
+ }
4668
+ function resolveRetryConfig(cfg) {
4669
+ if (cfg === false) return null;
4670
+ const c = cfg ?? {};
4671
+ return {
4672
+ maxRetries: c.maxRetries ?? 3,
4673
+ baseDelayMs: c.baseDelayMs ?? 500,
4674
+ maxDelayMs: c.maxDelayMs ?? 1e4,
4675
+ jitterFactor: Math.max(0, Math.min(1, c.jitterFactor ?? 0.25)),
4676
+ retryableStatusCodes: c.retryableStatusCodes ?? [429, 502, 503, 504]
4677
+ };
4678
+ }
4679
+ function normalizeEndpoint(ep) {
4680
+ if (typeof ep === "string") return { url: ep };
4681
+ return ep;
4682
+ }
4683
+ function endpointLabel(ep) {
4684
+ if (ep.label) return ep.label;
4685
+ try {
4686
+ return new URL(ep.url).hostname;
4687
+ } catch {
4688
+ return ep.url.slice(0, 40);
4689
+ }
4690
+ }
4691
+ function isRetryable(err, codes) {
4692
+ if (!err) return false;
4693
+ const msg = err instanceof Error ? err.message : String(err);
4694
+ for (const code of codes) {
4695
+ if (msg.includes(String(code))) return true;
4696
+ }
4697
+ 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")) {
4698
+ return true;
4699
+ }
4700
+ return false;
4701
+ }
4702
+ function computeDelay(attempt, config) {
4703
+ const raw = Math.min(
4704
+ config.baseDelayMs * Math.pow(2, attempt),
4705
+ config.maxDelayMs
4706
+ );
4707
+ const jitter = Math.floor(Math.random() * raw * config.jitterFactor);
4708
+ return raw + jitter;
4709
+ }
4710
+ function rejectAfter(ms, message) {
4711
+ let timer;
4712
+ const promise = new Promise((_, reject) => {
4713
+ timer = setTimeout(() => reject(new Error(message)), ms);
4714
+ });
4715
+ return { promise, cancel: () => clearTimeout(timer) };
4716
+ }
4717
+ function sleep(ms) {
4718
+ return new Promise((resolve) => setTimeout(resolve, ms));
4719
+ }
4720
+ function redactUrl(raw) {
4721
+ try {
4722
+ const u = new URL(raw);
4723
+ const sensitive = /^(api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|key|password|bearer|credential|jwt)$/i;
4724
+ for (const k of [...u.searchParams.keys()]) {
4725
+ if (sensitive.test(k)) {
4726
+ u.searchParams.set(k, "***");
4727
+ }
4728
+ }
4729
+ return u.toString();
4730
+ } catch {
4731
+ return raw;
4732
+ }
4733
+ }
4734
+ var RpcPool = class _RpcPool {
4735
+ endpoints;
4736
+ strategy;
4737
+ retryConfig;
4738
+ requestTimeoutMs;
4739
+ verbose;
4740
+ /** Round-robin index tracker. */
4741
+ rrIndex = 0;
4742
+ /** Consecutive failure threshold before marking an endpoint unhealthy. */
4743
+ static UNHEALTHY_THRESHOLD = 3;
4744
+ /** Minimum endpoints before auto-recovery is attempted. */
4745
+ static MIN_HEALTHY = 1;
4746
+ constructor(config) {
4747
+ if (!config.endpoints || config.endpoints.length === 0) {
4748
+ throw new Error("RpcPool: at least one endpoint is required");
4749
+ }
4750
+ this.strategy = config.strategy ?? "failover";
4751
+ this.retryConfig = resolveRetryConfig(config.retry);
4752
+ this.requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
4753
+ this.verbose = config.verbose ?? true;
4754
+ const commitment = config.commitment ?? "confirmed";
4755
+ this.endpoints = config.endpoints.map((raw) => {
4756
+ const ep = normalizeEndpoint(raw);
4757
+ const connConfig = {
4758
+ commitment,
4759
+ ...ep.connectionConfig
4760
+ };
4761
+ return {
4762
+ config: ep,
4763
+ connection: new Connection5(ep.url, connConfig),
4764
+ label: endpointLabel(ep),
4765
+ weight: Math.max(1, ep.weight ?? 1),
4766
+ failures: 0,
4767
+ healthy: true,
4768
+ lastLatencyMs: -1
4769
+ };
4770
+ });
4771
+ }
4772
+ // -----------------------------------------------------------------------
4773
+ // Public API
4774
+ // -----------------------------------------------------------------------
4775
+ /**
4776
+ * Execute a function against a pooled connection with automatic retry
4777
+ * and failover.
4778
+ *
4779
+ * @param fn - Async function that receives a `Connection` and returns a result.
4780
+ * @returns The result of `fn`.
4781
+ * @throws The last error if all retries and failovers are exhausted.
4782
+ *
4783
+ * @example
4784
+ * ```ts
4785
+ * const balance = await pool.call(c => c.getBalance(pubkey));
4786
+ * const markets = await pool.call(c => discoverMarkets(c, programId, opts));
4787
+ * ```
4788
+ */
4789
+ async call(fn) {
4790
+ const maxAttempts = this.retryConfig ? this.retryConfig.maxRetries + 1 : 1;
4791
+ let lastError;
4792
+ const triedEndpoints = /* @__PURE__ */ new Set();
4793
+ const maxTotalIterations = maxAttempts + this.endpoints.length;
4794
+ let totalIterations = 0;
4795
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4796
+ if (++totalIterations > maxTotalIterations) break;
4797
+ const epIdx = this.selectEndpoint(triedEndpoints);
4798
+ if (epIdx === -1) {
4799
+ break;
4800
+ }
4801
+ const ep = this.endpoints[epIdx];
4802
+ const timeout = rejectAfter(this.requestTimeoutMs, `RPC request timed out after ${this.requestTimeoutMs}ms (${ep.label})`);
4803
+ try {
4804
+ const result = await Promise.race([
4805
+ fn(ep.connection),
4806
+ timeout.promise
4807
+ ]);
4808
+ timeout.cancel();
4809
+ ep.failures = 0;
4810
+ ep.healthy = true;
4811
+ return result;
4812
+ } catch (err) {
4813
+ timeout.cancel();
4814
+ lastError = err;
4815
+ ep.failures++;
4816
+ if (ep.failures >= _RpcPool.UNHEALTHY_THRESHOLD) {
4817
+ ep.healthy = false;
4818
+ if (this.verbose) {
4819
+ console.warn(
4820
+ `[RpcPool] Endpoint ${ep.label} marked unhealthy after ${ep.failures} consecutive failures`
4821
+ );
4822
+ }
4823
+ }
4824
+ const retryable = this.retryConfig ? isRetryable(err, this.retryConfig.retryableStatusCodes) : false;
4825
+ if (!retryable) {
4826
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4827
+ triedEndpoints.add(epIdx);
4828
+ attempt--;
4829
+ if (triedEndpoints.size >= this.endpoints.length) break;
4830
+ continue;
4831
+ }
4832
+ throw err;
4833
+ }
4834
+ if (this.verbose) {
4835
+ console.warn(
4836
+ `[RpcPool] Retryable error on ${ep.label} (attempt ${attempt + 1}/${maxAttempts}):`,
4837
+ err instanceof Error ? err.message : err
4838
+ );
4839
+ }
4840
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4841
+ triedEndpoints.add(epIdx);
4842
+ }
4843
+ if (attempt < maxAttempts - 1 && this.retryConfig) {
4844
+ const delay = computeDelay(attempt, this.retryConfig);
4845
+ await sleep(delay);
4846
+ }
4847
+ }
4848
+ }
4849
+ this.maybeRecoverEndpoints();
4850
+ throw lastError ?? new Error("RpcPool: all endpoints exhausted");
4851
+ }
4852
+ /**
4853
+ * Get a raw `Connection` from the current preferred endpoint.
4854
+ * Useful when you need to pass a Connection to external code.
4855
+ *
4856
+ * NOTE: This bypasses retry and failover logic. Prefer `call()`.
4857
+ *
4858
+ * @returns Solana Connection from the current preferred endpoint.
4859
+ *
4860
+ * @example
4861
+ * ```ts
4862
+ * const conn = pool.getConnection();
4863
+ * const balance = await conn.getBalance(pubkey);
4864
+ * ```
4865
+ */
4866
+ getConnection() {
4867
+ const idx = this.selectEndpoint();
4868
+ if (idx === -1) {
4869
+ this.maybeRecoverEndpoints();
4870
+ return this.endpoints[0].connection;
4871
+ }
4872
+ return this.endpoints[idx].connection;
4873
+ }
4874
+ /**
4875
+ * Run a health check against all endpoints in the pool.
4876
+ *
4877
+ * @param timeoutMs - Per-endpoint probe timeout (default: 5000)
4878
+ * @returns Array of health results, one per endpoint.
4879
+ *
4880
+ * @example
4881
+ * ```ts
4882
+ * const results = await pool.healthCheck();
4883
+ * for (const r of results) {
4884
+ * console.log(`${r.endpoint}: ${r.healthy ? 'UP' : 'DOWN'} (${r.latencyMs}ms, slot ${r.slot})`);
4885
+ * }
4886
+ * ```
4887
+ */
4888
+ async healthCheck(timeoutMs = 5e3) {
4889
+ const results = await Promise.all(
4890
+ this.endpoints.map(async (ep) => {
4891
+ const result = await checkRpcHealth(ep.config.url, timeoutMs);
4892
+ ep.lastLatencyMs = result.latencyMs;
4893
+ ep.healthy = result.healthy;
4894
+ if (result.healthy) ep.failures = 0;
4895
+ result.endpoint = redactUrl(result.endpoint);
4896
+ return result;
4897
+ })
4898
+ );
4899
+ return results;
4900
+ }
4901
+ /**
4902
+ * Get the number of endpoints in the pool.
4903
+ */
4904
+ get size() {
4905
+ return this.endpoints.length;
4906
+ }
4907
+ /**
4908
+ * Get the number of currently healthy endpoints.
4909
+ */
4910
+ get healthyCount() {
4911
+ return this.endpoints.filter((ep) => ep.healthy).length;
4912
+ }
4913
+ /**
4914
+ * Get endpoint labels and their current status.
4915
+ *
4916
+ * @returns Array of `{ label, url, healthy, failures, lastLatencyMs }`.
4917
+ */
4918
+ status() {
4919
+ return this.endpoints.map((ep) => ({
4920
+ label: ep.label,
4921
+ url: redactUrl(ep.config.url),
4922
+ healthy: ep.healthy,
4923
+ failures: ep.failures,
4924
+ lastLatencyMs: ep.lastLatencyMs
4925
+ }));
4926
+ }
4927
+ // -----------------------------------------------------------------------
4928
+ // Internals
4929
+ // -----------------------------------------------------------------------
4930
+ /**
4931
+ * Select the next endpoint based on strategy.
4932
+ * Returns -1 if no endpoint is available.
4933
+ */
4934
+ selectEndpoint(exclude) {
4935
+ const healthy = this.endpoints.map((ep, i) => ({ ep, i })).filter(({ ep, i }) => ep.healthy && !exclude?.has(i));
4936
+ if (healthy.length === 0) {
4937
+ const remaining = this.endpoints.map((_, i) => i).filter((i) => !exclude?.has(i));
4938
+ return remaining.length > 0 ? remaining[0] : -1;
4939
+ }
4940
+ if (this.strategy === "failover") {
4941
+ return healthy[0].i;
4942
+ }
4943
+ const totalWeight = healthy.reduce((sum, { ep }) => sum + ep.weight, 0);
4944
+ this.rrIndex = (this.rrIndex + 1) % totalWeight;
4945
+ let cumulative = 0;
4946
+ for (const { ep, i } of healthy) {
4947
+ cumulative += ep.weight;
4948
+ if (this.rrIndex < cumulative) return i;
4949
+ }
4950
+ return healthy[healthy.length - 1].i;
4951
+ }
4952
+ /**
4953
+ * If all endpoints are unhealthy, reset them so we at least try again.
4954
+ */
4955
+ maybeRecoverEndpoints() {
4956
+ const healthyCount = this.endpoints.filter((ep) => ep.healthy).length;
4957
+ if (healthyCount < _RpcPool.MIN_HEALTHY) {
4958
+ if (this.verbose) {
4959
+ console.warn("[RpcPool] All endpoints unhealthy \u2014 resetting for recovery");
4960
+ }
4961
+ for (const ep of this.endpoints) {
4962
+ ep.healthy = true;
4963
+ ep.failures = 0;
4964
+ }
4965
+ }
4966
+ }
4967
+ };
4968
+ async function withRetry(fn, config) {
4969
+ const resolved = resolveRetryConfig(config) ?? {
4970
+ maxRetries: 3,
4971
+ baseDelayMs: 500,
4972
+ maxDelayMs: 1e4,
4973
+ jitterFactor: 0.25,
4974
+ retryableStatusCodes: [429, 502, 503, 504]
4975
+ };
4976
+ let lastError;
4977
+ const maxAttempts = resolved.maxRetries + 1;
4978
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4979
+ try {
4980
+ return await fn();
4981
+ } catch (err) {
4982
+ lastError = err;
4983
+ if (!isRetryable(err, resolved.retryableStatusCodes)) {
4984
+ throw err;
4985
+ }
4986
+ if (attempt < maxAttempts - 1) {
4987
+ const delay = computeDelay(attempt, resolved);
4988
+ await sleep(delay);
4989
+ }
4990
+ }
4991
+ }
4992
+ throw lastError ?? new Error("withRetry: all attempts exhausted");
4993
+ }
4994
+ var _internal = {
4995
+ isRetryable,
4996
+ computeDelay,
4997
+ resolveRetryConfig,
4998
+ normalizeEndpoint,
4999
+ endpointLabel
5000
+ };
5001
+
4497
5002
  // src/runtime/tx.ts
4498
5003
  import {
4499
5004
  TransactionInstruction as TransactionInstruction2,
@@ -4645,6 +5150,139 @@ function formatResult(result, jsonMode) {
4645
5150
  return lines.join("\n");
4646
5151
  }
4647
5152
 
5153
+ // src/runtime/lighthouse.ts
5154
+ import { PublicKey as PublicKey13, Transaction as Transaction2 } from "@solana/web3.js";
5155
+ var LIGHTHOUSE_PROGRAM_ID = new PublicKey13(
5156
+ "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95"
5157
+ );
5158
+ var LIGHTHOUSE_PROGRAM_ID_STR2 = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
5159
+ var LIGHTHOUSE_CONSTRAINT_ADDRESS = 6400;
5160
+ var LIGHTHOUSE_ERROR_CODES = /* @__PURE__ */ new Set([
5161
+ 6e3,
5162
+ // InstructionMissing
5163
+ 6001,
5164
+ // InstructionFallbackNotFound
5165
+ 6002,
5166
+ // InstructionDidNotDeserialize
5167
+ 6003,
5168
+ // InstructionDidNotSerialize
5169
+ 6016,
5170
+ // IdlInstructionStub
5171
+ 6032,
5172
+ // ConstraintMut
5173
+ 6033,
5174
+ // ConstraintHasOne
5175
+ 6034,
5176
+ // ConstraintSigner
5177
+ 6035,
5178
+ // ConstraintRaw
5179
+ 6036,
5180
+ // ConstraintOwner
5181
+ 6037,
5182
+ // ConstraintRentExempt
5183
+ 6038,
5184
+ // ConstraintSeeds
5185
+ 6039,
5186
+ // ConstraintExecutable
5187
+ 6040,
5188
+ // ConstraintState
5189
+ 6041,
5190
+ // ConstraintAssociated
5191
+ 6042,
5192
+ // ConstraintAssociatedInit
5193
+ 6043,
5194
+ // ConstraintClose
5195
+ 6400
5196
+ // ConstraintAddress (the one we hit most often)
5197
+ ]);
5198
+ function isLighthouseInstruction(ix) {
5199
+ return ix.programId.equals(LIGHTHOUSE_PROGRAM_ID);
5200
+ }
5201
+ function isLighthouseError(error) {
5202
+ const msg = extractErrorMessage(error);
5203
+ if (!msg) return false;
5204
+ if (msg.includes(LIGHTHOUSE_PROGRAM_ID_STR2)) return true;
5205
+ if (/custom\s+program\s+error:\s*0x1900\b/i.test(msg)) return true;
5206
+ if (/"Custom"\s*:\s*6400\b/.test(msg) && /InstructionError/i.test(msg)) return true;
5207
+ return false;
5208
+ }
5209
+ function isLighthouseFailureInLogs(logs) {
5210
+ if (!Array.isArray(logs)) return false;
5211
+ let insideLighthouse = false;
5212
+ for (const line of logs) {
5213
+ if (typeof line !== "string") continue;
5214
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} invoke`)) {
5215
+ insideLighthouse = true;
5216
+ continue;
5217
+ }
5218
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} success`)) {
5219
+ insideLighthouse = false;
5220
+ continue;
5221
+ }
5222
+ if (insideLighthouse && /failed/i.test(line)) {
5223
+ return true;
5224
+ }
5225
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} failed`)) {
5226
+ return true;
5227
+ }
5228
+ }
5229
+ return false;
5230
+ }
5231
+ function stripLighthouseInstructions(instructions, percolatorProgramId) {
5232
+ if (percolatorProgramId) {
5233
+ const hasPercolatorIx = instructions.some(
5234
+ (ix) => ix.programId.equals(percolatorProgramId)
5235
+ );
5236
+ if (!hasPercolatorIx) {
5237
+ return instructions;
5238
+ }
5239
+ }
5240
+ return instructions.filter((ix) => !isLighthouseInstruction(ix));
5241
+ }
5242
+ function stripLighthouseFromTransaction(transaction, percolatorProgramId) {
5243
+ if (percolatorProgramId) {
5244
+ const hasPercolatorIx = transaction.instructions.some(
5245
+ (ix) => ix.programId.equals(percolatorProgramId)
5246
+ );
5247
+ if (!hasPercolatorIx) return transaction;
5248
+ }
5249
+ const hasLighthouse = transaction.instructions.some(isLighthouseInstruction);
5250
+ if (!hasLighthouse) return transaction;
5251
+ const clean = new Transaction2();
5252
+ clean.recentBlockhash = transaction.recentBlockhash;
5253
+ clean.feePayer = transaction.feePayer;
5254
+ for (const ix of transaction.instructions) {
5255
+ if (!isLighthouseInstruction(ix)) {
5256
+ clean.add(ix);
5257
+ }
5258
+ }
5259
+ return clean;
5260
+ }
5261
+ function countLighthouseInstructions(ixsOrTx) {
5262
+ const instructions = Array.isArray(ixsOrTx) ? ixsOrTx : ixsOrTx.instructions;
5263
+ return instructions.filter(isLighthouseInstruction).length;
5264
+ }
5265
+ 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";
5266
+ function classifyLighthouseError(error) {
5267
+ if (isLighthouseError(error)) {
5268
+ return LIGHTHOUSE_USER_MESSAGE;
5269
+ }
5270
+ return null;
5271
+ }
5272
+ function extractErrorMessage(error) {
5273
+ if (!error) return null;
5274
+ if (typeof error === "string") return error;
5275
+ if (error instanceof Error) return error.message;
5276
+ if (typeof error === "object" && "message" in error) {
5277
+ return String(error.message);
5278
+ }
5279
+ try {
5280
+ return JSON.stringify(error);
5281
+ } catch {
5282
+ return null;
5283
+ }
5284
+ }
5285
+
4648
5286
  // src/math/trading.ts
4649
5287
  function computeMarkPnl(positionSize, entryPrice, oraclePrice) {
4650
5288
  if (positionSize === 0n || oraclePrice === 0n) return 0n;
@@ -4669,16 +5307,10 @@ function computeLiqPrice(entryPrice, capital, positionSize, maintenanceMarginBps
4669
5307
  function computePreTradeLiqPrice(oracleE6, margin, posSize, maintBps, feeBps, direction) {
4670
5308
  if (oracleE6 === 0n || margin === 0n || posSize === 0n) return 0n;
4671
5309
  const absPos = posSize < 0n ? -posSize : posSize;
5310
+ const fee = absPos * feeBps / 10000n;
5311
+ const effectiveCapital = margin > fee ? margin - fee : 0n;
4672
5312
  const signedPos = direction === "long" ? absPos : -absPos;
4673
- const feeAdjust = oracleE6 * feeBps / 10000n;
4674
- let adjustedEntry;
4675
- if (direction === "long") {
4676
- adjustedEntry = oracleE6 + feeAdjust;
4677
- } else {
4678
- const shortEntry = oracleE6 - feeAdjust;
4679
- adjustedEntry = shortEntry > 0n ? shortEntry : 1n;
4680
- }
4681
- return computeLiqPrice(adjustedEntry, margin, signedPos, maintBps);
5313
+ return computeLiqPrice(oracleE6, effectiveCapital, signedPos, maintBps);
4682
5314
  }
4683
5315
  function computeTradingFee(notional, tradingFeeBps) {
4684
5316
  return notional * tradingFeeBps / 10000n;
@@ -4698,9 +5330,20 @@ function computeFeeSplit(totalFee, config) {
4698
5330
  if (config.lpBps === 0n && config.protocolBps === 0n && config.creatorBps === 0n) {
4699
5331
  return [totalFee, 0n, 0n];
4700
5332
  }
5333
+ const totalBps = config.lpBps + config.protocolBps + config.creatorBps;
5334
+ if (totalBps !== 10000n) {
5335
+ throw new Error(
5336
+ `Fee split must equal exactly 10000 bps (100%): lpBps=${config.lpBps} + protocolBps=${config.protocolBps} + creatorBps=${config.creatorBps} = ${totalBps}`
5337
+ );
5338
+ }
4701
5339
  const lp = totalFee * config.lpBps / 10000n;
4702
5340
  const protocol = totalFee * config.protocolBps / 10000n;
4703
5341
  const creator = totalFee - lp - protocol;
5342
+ if (creator < 0n) {
5343
+ throw new Error(
5344
+ `Internal error: creator fee is negative (${creator}). This should not happen if lpBps + protocolBps + creatorBps === 10000.`
5345
+ );
5346
+ }
4704
5347
  return [lp, protocol, creator];
4705
5348
  }
4706
5349
  function computePnlPercent(pnlTokens, capital) {
@@ -4715,10 +5358,17 @@ function computePnlPercent(pnlTokens, capital) {
4715
5358
  }
4716
5359
  function computeEstimatedEntryPrice(oracleE6, tradingFeeBps, direction) {
4717
5360
  if (oracleE6 === 0n) return 0n;
5361
+ if (tradingFeeBps < 0n) {
5362
+ throw new Error(`computeEstimatedEntryPrice: tradingFeeBps must be non-negative, got ${tradingFeeBps}`);
5363
+ }
4718
5364
  const feeImpact = oracleE6 * tradingFeeBps / 10000n;
4719
- if (direction === "long") return oracleE6 + feeImpact;
4720
- const shortEntry = oracleE6 - feeImpact;
4721
- return shortEntry > 0n ? shortEntry : 1n;
5365
+ const result = direction === "long" ? oracleE6 + feeImpact : oracleE6 - feeImpact;
5366
+ if (result <= 0n) {
5367
+ throw new Error(
5368
+ `computeEstimatedEntryPrice: result ${result} is non-positive (tradingFeeBps=${tradingFeeBps} too high for oracle=${oracleE6})`
5369
+ );
5370
+ }
5371
+ return result;
4722
5372
  }
4723
5373
  var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
4724
5374
  var MIN_SAFE_BIGINT = BigInt(-Number.MAX_SAFE_INTEGER);
@@ -4739,7 +5389,12 @@ function computeMaxLeverage(initialMarginBps) {
4739
5389
  if (initialMarginBps <= 0n) {
4740
5390
  throw new Error("computeMaxLeverage: initialMarginBps must be positive");
4741
5391
  }
4742
- return Number(10000n / initialMarginBps);
5392
+ const scaledResult = 10000n * 1000000n / initialMarginBps;
5393
+ return Number(scaledResult) / 1e6;
5394
+ }
5395
+ function computeMaxWithdrawable(capital, pnl, reservedPnl) {
5396
+ const maturedPnl = pnl - reservedPnl;
5397
+ return capital + (maturedPnl > 0n ? maturedPnl : 0n);
4743
5398
  }
4744
5399
 
4745
5400
  // src/math/warmup.ts
@@ -4751,6 +5406,9 @@ function computeWarmupUnlockedCapital(totalCapital, currentSlot, warmupStartSlot
4751
5406
  return totalCapital * elapsed / warmupPeriodSlots;
4752
5407
  }
4753
5408
  function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
5409
+ if (initialMarginBps <= 0n) {
5410
+ throw new Error("computeWarmupLeverageCap: initialMarginBps must be positive");
5411
+ }
4754
5412
  const maxLev = computeMaxLeverage(initialMarginBps);
4755
5413
  if (warmupPeriodSlots === 0n || warmupStartSlot === 0n) return maxLev;
4756
5414
  if (totalCapital <= 0n) return 1;
@@ -4761,7 +5419,14 @@ function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, w
4761
5419
  warmupPeriodSlots
4762
5420
  );
4763
5421
  if (unlocked <= 0n) return 1;
4764
- const effectiveLev = Number(BigInt(maxLev) * unlocked / totalCapital);
5422
+ const scaledResult = BigInt(maxLev) * unlocked / totalCapital;
5423
+ if (scaledResult > BigInt(Number.MAX_SAFE_INTEGER)) {
5424
+ console.warn(
5425
+ `[computeWarmupLeverageCap] Warning: effective leverage ${scaledResult} exceeds MAX_SAFE_INTEGER, returning MAX_SAFE_INTEGER as a safety bound`
5426
+ );
5427
+ return Number.MAX_SAFE_INTEGER;
5428
+ }
5429
+ const effectiveLev = Number(scaledResult);
4765
5430
  return Math.max(1, effectiveLev);
4766
5431
  }
4767
5432
  function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
@@ -4774,10 +5439,41 @@ function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlo
4774
5439
  );
4775
5440
  return unlocked * BigInt(maxLev);
4776
5441
  }
5442
+ function computeWarmupProgress(currentSlot, warmupStartedAtSlot, warmupPeriodSlots, pnl, reservedPnl) {
5443
+ if (warmupPeriodSlots === 0n || warmupStartedAtSlot === 0n) {
5444
+ return {
5445
+ maturedPnl: pnl > 0n ? pnl : 0n,
5446
+ reservedPnl: 0n,
5447
+ progressBps: 10000n,
5448
+ // 100%
5449
+ slotsRemaining: 0n
5450
+ };
5451
+ }
5452
+ const elapsed = currentSlot >= warmupStartedAtSlot ? currentSlot - warmupStartedAtSlot : 0n;
5453
+ if (elapsed >= warmupPeriodSlots) {
5454
+ return {
5455
+ maturedPnl: pnl > 0n ? pnl : 0n,
5456
+ reservedPnl: 0n,
5457
+ progressBps: 10000n,
5458
+ // 100%
5459
+ slotsRemaining: 0n
5460
+ };
5461
+ }
5462
+ const progressBps = elapsed * 10000n / warmupPeriodSlots;
5463
+ const slotsRemaining = warmupPeriodSlots - elapsed;
5464
+ const maturedPnl = pnl > 0n ? pnl * progressBps / 10000n : 0n;
5465
+ const locked = reservedPnl > 0n ? reservedPnl : 0n;
5466
+ return {
5467
+ maturedPnl,
5468
+ reservedPnl: locked,
5469
+ progressBps,
5470
+ slotsRemaining
5471
+ };
5472
+ }
4777
5473
 
4778
5474
  // src/validation.ts
4779
- import { PublicKey as PublicKey13 } from "@solana/web3.js";
4780
- var U16_MAX2 = 65535;
5475
+ import { PublicKey as PublicKey14 } from "@solana/web3.js";
5476
+ var U16_MAX = 65535;
4781
5477
  var U64_MAX = BigInt("18446744073709551615");
4782
5478
  var I64_MIN = BigInt("-9223372036854775808");
4783
5479
  var I64_MAX = BigInt("9223372036854775807");
@@ -4806,7 +5502,7 @@ var ValidationError = class extends Error {
4806
5502
  };
4807
5503
  function validatePublicKey(value, field) {
4808
5504
  try {
4809
- return new PublicKey13(value);
5505
+ return new PublicKey14(value);
4810
5506
  } catch {
4811
5507
  throw new ValidationError(
4812
5508
  field,
@@ -4817,24 +5513,26 @@ function validatePublicKey(value, field) {
4817
5513
  function validateIndex(value, field) {
4818
5514
  const t = requireDecimalUIntString(value, field);
4819
5515
  const bi = BigInt(t);
4820
- if (bi > BigInt(U16_MAX2)) {
5516
+ if (bi > BigInt(U16_MAX)) {
4821
5517
  throw new ValidationError(
4822
5518
  field,
4823
- `must be <= ${U16_MAX2} (u16 max), got ${t}`
5519
+ `must be <= ${U16_MAX} (u16 max), got ${t}`
4824
5520
  );
4825
5521
  }
5522
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5523
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5524
+ }
4826
5525
  return Number(bi);
4827
5526
  }
4828
5527
  function validateAmount(value, field) {
4829
- let num;
4830
- try {
4831
- num = BigInt(value);
4832
- } catch {
5528
+ const t = value.trim();
5529
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4833
5530
  throw new ValidationError(
4834
5531
  field,
4835
- `"${value}" is not a valid number. Use decimal digits only.`
5532
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4836
5533
  );
4837
5534
  }
5535
+ const num = BigInt(t);
4838
5536
  if (num < 0n) {
4839
5537
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4840
5538
  }
@@ -4847,15 +5545,14 @@ function validateAmount(value, field) {
4847
5545
  return num;
4848
5546
  }
4849
5547
  function validateU128(value, field) {
4850
- let num;
4851
- try {
4852
- num = BigInt(value);
4853
- } catch {
5548
+ const t = value.trim();
5549
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4854
5550
  throw new ValidationError(
4855
5551
  field,
4856
- `"${value}" is not a valid number. Use decimal digits only.`
5552
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4857
5553
  );
4858
5554
  }
5555
+ const num = BigInt(t);
4859
5556
  if (num < 0n) {
4860
5557
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4861
5558
  }
@@ -4868,15 +5565,14 @@ function validateU128(value, field) {
4868
5565
  return num;
4869
5566
  }
4870
5567
  function validateI64(value, field) {
4871
- let num;
4872
- try {
4873
- num = BigInt(value);
4874
- } catch {
5568
+ const t = value.trim();
5569
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4875
5570
  throw new ValidationError(
4876
5571
  field,
4877
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5572
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4878
5573
  );
4879
5574
  }
5575
+ const num = BigInt(t);
4880
5576
  if (num < I64_MIN) {
4881
5577
  throw new ValidationError(
4882
5578
  field,
@@ -4892,15 +5588,14 @@ function validateI64(value, field) {
4892
5588
  return num;
4893
5589
  }
4894
5590
  function validateI128(value, field) {
4895
- let num;
4896
- try {
4897
- num = BigInt(value);
4898
- } catch {
5591
+ const t = value.trim();
5592
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4899
5593
  throw new ValidationError(
4900
5594
  field,
4901
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5595
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4902
5596
  );
4903
5597
  }
5598
+ const num = BigInt(t);
4904
5599
  if (num < I128_MIN) {
4905
5600
  throw new ValidationError(
4906
5601
  field,
@@ -4924,6 +5619,9 @@ function validateBps(value, field) {
4924
5619
  `must be <= 10000 (100%), got ${t}`
4925
5620
  );
4926
5621
  }
5622
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5623
+ throw new ValidationError(field, `internal error: bps value exceeds MAX_SAFE_INTEGER`);
5624
+ }
4927
5625
  return Number(bi);
4928
5626
  }
4929
5627
  function validateU64(value, field) {
@@ -4932,12 +5630,15 @@ function validateU64(value, field) {
4932
5630
  function validateU16(value, field) {
4933
5631
  const t = requireDecimalUIntString(value, field);
4934
5632
  const bi = BigInt(t);
4935
- if (bi > BigInt(U16_MAX2)) {
5633
+ if (bi > BigInt(U16_MAX)) {
4936
5634
  throw new ValidationError(
4937
5635
  field,
4938
- `must be <= ${U16_MAX2} (u16 max), got ${t}`
5636
+ `must be <= ${U16_MAX} (u16 max), got ${t}`
4939
5637
  );
4940
5638
  }
5639
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5640
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5641
+ }
4941
5642
  return Number(bi);
4942
5643
  }
4943
5644
 
@@ -4988,7 +5689,9 @@ function parseDexScreenerPairs(json) {
4988
5689
  else if (liquidity > 1e4) confidence = 60;
4989
5690
  else if (liquidity > 1e3) confidence = 45;
4990
5691
  const priceUsd = pair.priceUsd;
4991
- const price = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) || 0 : 0;
5692
+ const rawPrice = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) : NaN;
5693
+ if (!Number.isFinite(rawPrice) || rawPrice <= 0) continue;
5694
+ const price = rawPrice;
4992
5695
  let baseSym = "?";
4993
5696
  let quoteSym = "?";
4994
5697
  if (isRecord(pair.baseToken) && typeof pair.baseToken.symbol === "string") {
@@ -5019,8 +5722,8 @@ function parseJupiterMintEntry(json, mint) {
5019
5722
  if (!isRecord(row)) return null;
5020
5723
  const rawPrice = row.price;
5021
5724
  if (rawPrice === void 0 || rawPrice === null) return null;
5022
- const price = parseFloat(String(rawPrice)) || 0;
5023
- if (price <= 0) return null;
5725
+ const price = parseFloat(String(rawPrice));
5726
+ if (!Number.isFinite(price) || price <= 0) return null;
5024
5727
  let mintSymbol = "?";
5025
5728
  if (typeof row.mintSymbol === "string") mintSymbol = row.mintSymbol;
5026
5729
  return { price, mintSymbol };
@@ -5086,10 +5789,17 @@ async function fetchDexSources(mint, signal) {
5086
5789
  headers: { "User-Agent": "percolator/1.0" }
5087
5790
  }
5088
5791
  );
5089
- if (!resp.ok) return [];
5792
+ if (!resp.ok) {
5793
+ console.debug(`[fetchDexSources] HTTP ${resp.status} for mint ${mint}`);
5794
+ return [];
5795
+ }
5090
5796
  const json = await resp.json();
5091
5797
  return parseDexScreenerPairs(json);
5092
- } catch {
5798
+ } catch (err) {
5799
+ console.warn(
5800
+ `[fetchDexSources] Error fetching DexScreener data for mint ${mint}:`,
5801
+ err instanceof Error ? err.message : String(err)
5802
+ );
5093
5803
  return [];
5094
5804
  }
5095
5805
  }
@@ -5100,7 +5810,7 @@ function lookupPythSource(mint) {
5100
5810
  type: "pyth",
5101
5811
  address: entry.feedId,
5102
5812
  pairLabel: `${entry.symbol} / USD (Pyth)`,
5103
- liquidity: Infinity,
5813
+ liquidity: Number.MAX_SAFE_INTEGER,
5104
5814
  // Pyth is considered deep liquidity
5105
5815
  price: 0,
5106
5816
  // We don't fetch live price here; caller can enrich
@@ -5117,10 +5827,16 @@ async function fetchJupiterSource(mint, signal) {
5117
5827
  headers: { "User-Agent": "percolator/1.0" }
5118
5828
  }
5119
5829
  );
5120
- if (!resp.ok) return null;
5830
+ if (!resp.ok) {
5831
+ console.debug(`[fetchJupiterSource] HTTP ${resp.status} for mint ${mint}`);
5832
+ return null;
5833
+ }
5121
5834
  const json = await resp.json();
5122
5835
  const row = parseJupiterMintEntry(json, mint);
5123
- if (!row) return null;
5836
+ if (!row) {
5837
+ console.debug(`[fetchJupiterSource] No price data from Jupiter for mint ${mint}`);
5838
+ return null;
5839
+ }
5124
5840
  return {
5125
5841
  type: "jupiter",
5126
5842
  address: mint,
@@ -5131,23 +5847,39 @@ async function fetchJupiterSource(mint, signal) {
5131
5847
  confidence: 40
5132
5848
  // Fallback — lower confidence
5133
5849
  };
5134
- } catch {
5850
+ } catch (err) {
5851
+ console.warn(
5852
+ `[fetchJupiterSource] Error fetching Jupiter data for mint ${mint}:`,
5853
+ err instanceof Error ? err.message : String(err)
5854
+ );
5135
5855
  return null;
5136
5856
  }
5137
5857
  }
5138
5858
  async function resolvePrice(mint, signal, options) {
5139
5859
  const timeoutMs = options?.timeoutMs ?? DEFAULT_RESOLVE_TIMEOUT_MS;
5140
5860
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
5141
- const combinedSignal = signal ? combineAbortSignals([signal, timeoutSignal]) : timeoutSignal;
5861
+ const effectiveSignal2 = signal ? combineAbortSignals([signal, timeoutSignal]) : timeoutSignal;
5142
5862
  const [dexSources, jupiterSource] = await Promise.all([
5143
- fetchDexSources(mint, combinedSignal),
5144
- fetchJupiterSource(mint, combinedSignal)
5863
+ fetchDexSources(mint, effectiveSignal2),
5864
+ fetchJupiterSource(mint, effectiveSignal2)
5145
5865
  ]);
5146
5866
  const pythSource = lookupPythSource(mint);
5147
5867
  const allSources = [];
5148
5868
  if (pythSource) {
5149
- const refPrice = dexSources[0]?.price || jupiterSource?.price || 0;
5150
- pythSource.price = refPrice;
5869
+ const dexPrice = dexSources[0]?.price ?? 0;
5870
+ const jupPrice = jupiterSource?.price ?? 0;
5871
+ if (dexPrice > 0 && jupPrice > 0) {
5872
+ const mid = (dexPrice + jupPrice) / 2;
5873
+ const deviation = Math.abs(dexPrice - jupPrice) / mid;
5874
+ if (deviation > 0.5) {
5875
+ pythSource.price = 0;
5876
+ pythSource.confidence = 20;
5877
+ } else {
5878
+ pythSource.price = mid;
5879
+ }
5880
+ } else {
5881
+ pythSource.price = dexPrice || jupPrice || 0;
5882
+ }
5151
5883
  allSources.push(pythSource);
5152
5884
  }
5153
5885
  allSources.push(...dexSources);
@@ -5172,9 +5904,7 @@ export {
5172
5904
  ACCOUNTS_CLOSE_ACCOUNT,
5173
5905
  ACCOUNTS_CLOSE_SLAB,
5174
5906
  ACCOUNTS_CLOSE_STALE_SLABS,
5175
- ACCOUNTS_CREATE_INSURANCE_MINT,
5176
5907
  ACCOUNTS_DEPOSIT_COLLATERAL,
5177
- ACCOUNTS_DEPOSIT_INSURANCE_LP,
5178
5908
  ACCOUNTS_EXECUTE_ADL,
5179
5909
  ACCOUNTS_FUND_MARKET_INSURANCE,
5180
5910
  ACCOUNTS_INIT_LP,
@@ -5190,7 +5920,6 @@ export {
5190
5920
  ACCOUNTS_QUEUE_WITHDRAWAL,
5191
5921
  ACCOUNTS_RECLAIM_SLAB_RENT,
5192
5922
  ACCOUNTS_RESOLVE_MARKET,
5193
- ACCOUNTS_SET_DEX_POOL,
5194
5923
  ACCOUNTS_SET_INSURANCE_ISOLATION,
5195
5924
  ACCOUNTS_SET_MAINTENANCE_FEE,
5196
5925
  ACCOUNTS_SET_OI_IMBALANCE_HARD_BLOCK,
@@ -5200,7 +5929,6 @@ export {
5200
5929
  ACCOUNTS_SET_RISK_THRESHOLD,
5201
5930
  ACCOUNTS_SET_WALLET_CAP,
5202
5931
  ACCOUNTS_TOPUP_INSURANCE,
5203
- ACCOUNTS_TOPUP_KEEPER_FUND,
5204
5932
  ACCOUNTS_TRADE_CPI,
5205
5933
  ACCOUNTS_TRADE_NOCPI,
5206
5934
  ACCOUNTS_TRANSFER_POSITION_OWNERSHIP,
@@ -5209,17 +5937,22 @@ export {
5209
5937
  ACCOUNTS_UPDATE_CONFIG,
5210
5938
  ACCOUNTS_WITHDRAW_COLLATERAL,
5211
5939
  ACCOUNTS_WITHDRAW_INSURANCE,
5212
- ACCOUNTS_WITHDRAW_INSURANCE_LP,
5213
5940
  AccountKind,
5214
5941
  CHAINLINK_ANSWER_OFFSET,
5215
5942
  CHAINLINK_DECIMALS_OFFSET,
5216
5943
  CHAINLINK_MIN_SIZE,
5944
+ CHAINLINK_TIMESTAMP_OFFSET,
5217
5945
  CREATOR_LOCK_SEED,
5218
5946
  CTX_VAMM_OFFSET,
5219
5947
  DEFAULT_OI_RAMP_SLOTS,
5220
5948
  ENGINE_MARK_PRICE_OFF,
5221
5949
  ENGINE_OFF,
5222
5950
  IX_TAG,
5951
+ LIGHTHOUSE_CONSTRAINT_ADDRESS,
5952
+ LIGHTHOUSE_ERROR_CODES,
5953
+ LIGHTHOUSE_PROGRAM_ID,
5954
+ LIGHTHOUSE_PROGRAM_ID_STR2 as LIGHTHOUSE_PROGRAM_ID_STR,
5955
+ LIGHTHOUSE_USER_MESSAGE,
5223
5956
  MARK_PRICE_EMA_ALPHA_E6,
5224
5957
  MARK_PRICE_EMA_WINDOW_SLOTS,
5225
5958
  MAX_DECIMALS,
@@ -5241,10 +5974,12 @@ export {
5241
5974
  RAMP_START_BPS,
5242
5975
  RAYDIUM_CLMM_PROGRAM_ID,
5243
5976
  RENOUNCE_ADMIN_CONFIRMATION,
5977
+ RpcPool,
5244
5978
  SLAB_TIERS,
5245
5979
  SLAB_TIERS_V0,
5246
5980
  SLAB_TIERS_V1,
5247
5981
  SLAB_TIERS_V12_1,
5982
+ SLAB_TIERS_V12_15,
5248
5983
  SLAB_TIERS_V1D,
5249
5984
  SLAB_TIERS_V1D_LEGACY,
5250
5985
  SLAB_TIERS_V1M,
@@ -5262,11 +5997,14 @@ export {
5262
5997
  VAMM_MAGIC,
5263
5998
  ValidationError,
5264
5999
  WELL_KNOWN,
6000
+ _internal,
5265
6001
  buildAccountMetas,
5266
6002
  buildAdlInstruction,
5267
6003
  buildAdlTransaction,
5268
6004
  buildIx,
5269
6005
  checkPhaseTransition,
6006
+ checkRpcHealth,
6007
+ classifyLighthouseError,
5270
6008
  clearStaticMarkets,
5271
6009
  computeDexSpotPriceE6,
5272
6010
  computeDynamicFeeBps,
@@ -5279,6 +6017,7 @@ export {
5279
6017
  computeLiqPrice,
5280
6018
  computeMarkPnl,
5281
6019
  computeMaxLeverage,
6020
+ computeMaxWithdrawable,
5282
6021
  computePnlPercent,
5283
6022
  computePreTradeLiqPrice,
5284
6023
  computeRequiredMargin,
@@ -5286,15 +6025,15 @@ export {
5286
6025
  computeVammQuote,
5287
6026
  computeWarmupLeverageCap,
5288
6027
  computeWarmupMaxPositionSize,
6028
+ computeWarmupProgress,
5289
6029
  computeWarmupUnlockedCapital,
5290
6030
  concatBytes,
6031
+ countLighthouseInstructions,
5291
6032
  decodeError,
5292
6033
  decodeStakePool,
5293
6034
  depositAccounts,
5294
6035
  deriveCreatorLockPda,
5295
6036
  deriveDepositPda,
5296
- deriveInsuranceLpMint,
5297
- deriveKeeperFund,
5298
6037
  deriveLpPda,
5299
6038
  derivePythPriceUpdateAccount,
5300
6039
  derivePythPushOraclePDA,
@@ -5330,14 +6069,11 @@ export {
5330
6069
  encodeClaimQueuedWithdrawal,
5331
6070
  encodeClearPendingSettlement,
5332
6071
  encodeCloseAccount,
5333
- encodeCloseKeeperFund,
5334
6072
  encodeCloseOrphanSlab,
5335
6073
  encodeCloseSlab,
5336
6074
  encodeCloseStaleSlabs,
5337
- encodeCreateInsuranceMint,
5338
6075
  encodeCreateLpVault,
5339
6076
  encodeDepositCollateral,
5340
- encodeDepositInsuranceLP,
5341
6077
  encodeDepositLpCollateral,
5342
6078
  encodeExecuteAdl,
5343
6079
  encodeForceCloseResolved,
@@ -5394,7 +6130,6 @@ export {
5394
6130
  encodeStakeUpdateConfig,
5395
6131
  encodeStakeWithdraw,
5396
6132
  encodeTopUpInsurance,
5397
- encodeTopUpKeeperFund,
5398
6133
  encodeTradeCpi,
5399
6134
  encodeTradeCpiV2,
5400
6135
  encodeTradeNoCpi,
@@ -5408,7 +6143,6 @@ export {
5408
6143
  encodeUpdateRiskParams,
5409
6144
  encodeWithdrawCollateral,
5410
6145
  encodeWithdrawInsurance,
5411
- encodeWithdrawInsuranceLP,
5412
6146
  encodeWithdrawInsuranceLimited,
5413
6147
  encodeWithdrawLpCollateral,
5414
6148
  fetchAdlRankedPositions,
@@ -5430,6 +6164,10 @@ export {
5430
6164
  initPoolAccounts,
5431
6165
  isAccountUsed,
5432
6166
  isAdlTriggered,
6167
+ isAnchorErrorCode,
6168
+ isLighthouseError,
6169
+ isLighthouseFailureInLogs,
6170
+ isLighthouseInstruction,
5433
6171
  isStandardToken,
5434
6172
  isToken2022,
5435
6173
  isValidChainlinkOracle,
@@ -5454,6 +6192,8 @@ export {
5454
6192
  simulateOrSend,
5455
6193
  slabDataSize,
5456
6194
  slabDataSizeV1,
6195
+ stripLighthouseFromTransaction,
6196
+ stripLighthouseInstructions,
5457
6197
  validateAmount,
5458
6198
  validateBps,
5459
6199
  validateI128,
@@ -5464,6 +6204,7 @@ export {
5464
6204
  validateU128,
5465
6205
  validateU16,
5466
6206
  validateU64,
6207
+ withRetry,
5467
6208
  withdrawAccounts
5468
6209
  };
5469
6210
  //# sourceMappingURL=index.js.map