@percolatorct/sdk 1.0.0-beta.14 → 1.0.0-beta.16

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,40 @@ 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_ACCT_ACCOUNT_ID_OFF = 0;
1624
+ var V12_15_ACCT_CAPITAL_OFF = 8;
1625
+ var V12_15_ACCT_KIND_OFF = 24;
1626
+ var V12_15_ACCT_PNL_OFF = 32;
1627
+ var V12_15_ACCT_RESERVED_PNL_OFF = 48;
1628
+ var V12_15_ACCT_POSITION_BASIS_Q_OFF = 64;
1629
+ var V12_15_ACCT_ENTRY_PRICE_OFF = 120;
1630
+ var V12_15_ACCT_MATCHER_PROGRAM_OFF = 128;
1631
+ var V12_15_ACCT_MATCHER_CONTEXT_OFF = 160;
1632
+ var V12_15_ACCT_OWNER_OFF = 192;
1633
+ var V12_15_ACCT_FEE_CREDITS_OFF = 224;
1634
+ var V12_15_ACCT_FEES_EARNED_TOTAL_OFF = 240;
1635
+ var V12_15_ACCT_EXACT_RESERVE_COHORTS_OFF = 256;
1636
+ var V12_15_ACCT_EXACT_COHORT_COUNT_OFF = 4224;
1637
+ var V12_15_ACCT_OVERFLOW_OLDER_OFF = 4240;
1638
+ var V12_15_ACCT_OVERFLOW_OLDER_PRESENT_OFF = 4304;
1639
+ var V12_15_ACCT_OVERFLOW_NEWEST_OFF = 4320;
1640
+ var V12_15_ACCT_OVERFLOW_NEWEST_PRESENT_OFF = 4384;
1641
+ var V12_15_PARAMS_SIZE = 192;
1642
+ var V12_15_PARAMS_MAX_ACCOUNTS_OFF = 24;
1643
+ var V12_15_PARAMS_H_MIN_OFF = 160;
1644
+ var V12_15_PARAMS_H_MAX_OFF = 168;
1645
+ var V12_15_ENGINE_PARAMS_OFF = 32;
1646
+ var V12_15_ENGINE_CURRENT_SLOT_OFF = 224;
1647
+ var V12_15_ENGINE_FUNDING_RATE_E9_OFF = 240;
1648
+ var V12_15_ENGINE_MARKET_MODE_OFF = 256;
1649
+ var V12_15_ENGINE_C_TOT_OFF = 344;
1650
+ var V12_15_ENGINE_PNL_POS_TOT_OFF = 368;
1651
+ var V12_15_ENGINE_PNL_MATURED_POS_TOT_OFF = 384;
1652
+ var V12_15_ENGINE_BITMAP_OFF = 862;
1653
+ var V12_15_SIZES = /* @__PURE__ */ new Map();
1667
1654
  var V1M_ENGINE_OFF = 640;
1668
1655
  var V1M_CONFIG_LEN = 536;
1669
1656
  var V1M_ACCOUNT_SIZE = 248;
@@ -1736,7 +1723,9 @@ for (const n of TIERS) {
1736
1723
  V1M2_SIZES.set(computeSlabSize(V1M2_ENGINE_OFF, V1M2_ENGINE_BITMAP_OFF, V1M2_ACCOUNT_SIZE, n, 18), n);
1737
1724
  V_SETDEXPOOL_SIZES.set(computeSlabSize(V_SETDEXPOOL_ENGINE_OFF, V_ADL_ENGINE_BITMAP_OFF, V_ADL_ACCOUNT_SIZE, n, 18), n);
1738
1725
  V12_1_SIZES.set(computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18), n);
1726
+ V12_15_SIZES.set(computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, n, 18), n);
1739
1727
  }
1728
+ V12_15_SIZES.set(computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, 2048, 18), 2048);
1740
1729
  var V12_1_SBF_ACCOUNT_SIZE = 280;
1741
1730
  var V12_1_SBF_ENGINE_OFF = 616;
1742
1731
  var V12_1_SBF_BITMAP_OFF = 584;
@@ -1747,14 +1736,6 @@ for (const [, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Large",
1747
1736
  const total = V12_1_SBF_ENGINE_OFF + accountsOff + n * V12_1_SBF_ACCOUNT_SIZE;
1748
1737
  V12_1_SIZES.set(total, n);
1749
1738
  }
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
1739
  var SLAB_TIERS_V2 = {
1759
1740
  small: { maxAccounts: 256, dataSize: 65088, label: "Small", description: "256 slots (V2 BPF intermediate)" },
1760
1741
  large: { maxAccounts: 4096, dataSize: 1025568, label: "Large", description: "4,096 slots (V2 BPF intermediate)" }
@@ -2219,6 +2200,11 @@ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Lar
2219
2200
  const size = computeSlabSize(V12_1_ENGINE_OFF, V12_1_ENGINE_BITMAP_OFF, V12_1_ACCOUNT_SIZE, n, 18);
2220
2201
  SLAB_TIERS_V12_1[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (v12.1)` };
2221
2202
  }
2203
+ var SLAB_TIERS_V12_15 = {};
2204
+ for (const [label, n] of [["Micro", 64], ["Small", 256], ["Medium", 1024], ["Medium2048", 2048], ["Large", 4096]]) {
2205
+ const size = computeSlabSize(V12_15_ENGINE_OFF, V12_15_ENGINE_BITMAP_OFF, V12_15_ACCOUNT_SIZE, n, 18);
2206
+ SLAB_TIERS_V12_15[label.toLowerCase()] = { maxAccounts: n, dataSize: size, label, description: `${n} slots (v12.15)` };
2207
+ }
2222
2208
  function buildLayoutVSetDexPool(maxAccounts) {
2223
2209
  const engineOff = V_SETDEXPOOL_ENGINE_OFF;
2224
2210
  const bitmapOff = V_ADL_ENGINE_BITMAP_OFF;
@@ -2315,12 +2301,16 @@ function buildLayoutV12_1(maxAccounts, dataLen) {
2315
2301
  engineLastFundingSlotOff: isSbf ? -1 : V12_1_ENGINE_LAST_FUNDING_SLOT_OFF,
2316
2302
  // not in deployed struct
2317
2303
  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,
2304
+ engineMarkPriceOff: isSbf ? -1 : V12_1_ENGINE_MARK_PRICE_OFF,
2305
+ // not in deployed struct
2319
2306
  engineLastCrankSlotOff: isSbf ? V12_1_SBF_OFF_LAST_CRANK_SLOT : V12_1_ENGINE_LAST_CRANK_SLOT_OFF,
2320
2307
  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,
2308
+ engineTotalOiOff: isSbf ? -1 : V12_1_ENGINE_TOTAL_OI_OFF,
2309
+ // not in deployed struct
2310
+ engineLongOiOff: isSbf ? -1 : V12_1_ENGINE_LONG_OI_OFF,
2311
+ // not in deployed struct
2312
+ engineShortOiOff: isSbf ? -1 : V12_1_ENGINE_SHORT_OI_OFF,
2313
+ // not in deployed struct
2324
2314
  engineCTotOff: isSbf ? V12_1_SBF_OFF_C_TOT : V12_1_ENGINE_C_TOT_OFF,
2325
2315
  enginePnlPosTotOff: isSbf ? V12_1_SBF_OFF_PNL_POS_TOT : V12_1_ENGINE_PNL_POS_TOT_OFF,
2326
2316
  engineLiqCursorOff: isSbf ? V12_1_SBF_OFF_LIQ_CURSOR : V12_1_ENGINE_LIQ_CURSOR_OFF,
@@ -2356,10 +2346,10 @@ function buildLayoutV12_1(maxAccounts, dataLen) {
2356
2346
  engineInsuranceIsolationBpsOff: isSbf ? -1 : 64
2357
2347
  };
2358
2348
  }
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;
2349
+ function buildLayoutV12_15(maxAccounts) {
2350
+ const engineOff = V12_15_ENGINE_OFF;
2351
+ const bitmapOff = V12_15_ENGINE_BITMAP_OFF;
2352
+ const accountSize = V12_15_ACCOUNT_SIZE;
2363
2353
  const bitmapWords = Math.ceil(maxAccounts / 64);
2364
2354
  const bitmapBytes = bitmapWords * 8;
2365
2355
  const postBitmap = 18;
@@ -2367,63 +2357,94 @@ function buildLayoutV12_1EP(maxAccounts) {
2367
2357
  const preAccountsLen = bitmapOff + bitmapBytes + postBitmap + nextFreeBytes;
2368
2358
  const accountsOffRel = Math.ceil(preAccountsLen / 8) * 8;
2369
2359
  return {
2370
- version: 1,
2371
- headerLen: 72,
2372
- configOffset: 72,
2373
- configLen: 544,
2374
- reservedOff: 80,
2375
- // V1_RESERVED_OFF
2360
+ version: 2,
2361
+ headerLen: V0_HEADER_LEN,
2362
+ // 72 (same as V12_1)
2363
+ configOffset: V0_HEADER_LEN,
2364
+ // 72
2365
+ configLen: 552,
2366
+ // SBF CONFIG_LEN for v12.15
2367
+ reservedOff: V1_RESERVED_OFF,
2368
+ // 80
2376
2369
  engineOff,
2377
2370
  accountSize,
2378
2371
  maxAccounts,
2379
2372
  bitmapWords,
2380
2373
  accountsOff: engineOff + accountsOffRel,
2381
2374
  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,
2375
+ engineParamsOff: V12_15_ENGINE_PARAMS_OFF,
2376
+ // 32
2377
+ paramsSize: V12_15_PARAMS_SIZE,
2378
+ // 192
2379
+ engineCurrentSlotOff: V12_15_ENGINE_CURRENT_SLOT_OFF,
2380
+ // 224
2388
2381
  engineFundingIndexOff: -1,
2382
+ // not present in v12.15 engine struct
2389
2383
  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,
2384
+ // not present in v12.15 engine struct
2385
+ // funding_rate_e9 is i128 — stored in engineFundingRateBpsOff for EngineState.fundingRateBpsPerSlotLast
2386
+ // callers should treat this as the i128 funding rate in e9 units
2387
+ engineFundingRateBpsOff: V12_15_ENGINE_FUNDING_RATE_E9_OFF,
2388
+ // 240
2389
+ engineMarkPriceOff: -1,
2390
+ // not present in v12.15 (removed with oracle refactor)
2391
+ engineLastCrankSlotOff: -1,
2392
+ // not yet mapped; set -1 until offset verified on-chain
2393
+ engineMaxCrankStalenessOff: -1,
2394
+ // not yet mapped
2395
+ engineTotalOiOff: -1,
2396
+ // not present in v12.15 engine
2397
+ engineLongOiOff: -1,
2398
+ // not present in v12.15 engine
2399
+ engineShortOiOff: -1,
2400
+ // not present in v12.15 engine
2401
+ engineCTotOff: V12_15_ENGINE_C_TOT_OFF,
2402
+ // 344
2403
+ enginePnlPosTotOff: V12_15_ENGINE_PNL_POS_TOT_OFF,
2404
+ // 368
2405
+ engineLiqCursorOff: -1,
2406
+ // not yet mapped
2407
+ engineGcCursorOff: -1,
2408
+ // not yet mapped
2409
+ engineLastSweepStartOff: -1,
2410
+ // not yet mapped
2411
+ engineLastSweepCompleteOff: -1,
2412
+ // not yet mapped
2413
+ engineCrankCursorOff: -1,
2414
+ // not yet mapped
2415
+ engineSweepStartIdxOff: -1,
2416
+ // not yet mapped
2417
+ engineLifetimeLiquidationsOff: -1,
2418
+ // not yet mapped
2406
2419
  engineLifetimeForceClosesOff: -1,
2420
+ // not present in v12.15
2407
2421
  engineNetLpPosOff: -1,
2422
+ // not present in v12.15
2408
2423
  engineLpSumAbsOff: -1,
2424
+ // not present in v12.15
2409
2425
  engineLpMaxAbsOff: -1,
2426
+ // not present in v12.15
2410
2427
  engineLpMaxAbsSweepOff: -1,
2428
+ // not present in v12.15
2411
2429
  engineEmergencyOiModeOff: -1,
2430
+ // not present in v12.15
2412
2431
  engineEmergencyStartSlotOff: -1,
2432
+ // not present in v12.15
2413
2433
  engineLastBreakerSlotOff: -1,
2434
+ // not present in v12.15
2414
2435
  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)
2436
+ postBitmap,
2437
+ acctOwnerOff: V12_15_ACCT_OWNER_OFF,
2438
+ // 192
2419
2439
  hasInsuranceIsolation: false,
2440
+ // InsuranceFund = {balance: u128} = 16 bytes in v12.15
2420
2441
  engineInsuranceIsolatedOff: -1,
2421
2442
  engineInsuranceIsolationBpsOff: -1
2422
2443
  };
2423
2444
  }
2424
2445
  function detectSlabLayout(dataLen, data) {
2425
- const v121epn = V12_1_EP_SIZES.get(dataLen);
2426
- if (v121epn !== void 0) return buildLayoutV12_1EP(v121epn);
2446
+ const v1215n = V12_15_SIZES.get(dataLen);
2447
+ if (v1215n !== void 0) return buildLayoutV12_15(v1215n);
2427
2448
  const v121n = V12_1_SIZES.get(dataLen);
2428
2449
  if (v121n !== void 0) return buildLayoutV12_1(v121n, dataLen);
2429
2450
  const vsdpn = V_SETDEXPOOL_SIZES.get(dataLen);
@@ -2470,15 +2491,6 @@ var PARAMS_LIQUIDATION_FEE_BPS_OFF = 96;
2470
2491
  var PARAMS_LIQUIDATION_FEE_CAP_OFF = 104;
2471
2492
  var PARAMS_LIQUIDATION_BUFFER_OFF = 120;
2472
2493
  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
2494
  var ACCT_ACCOUNT_ID_OFF = 0;
2483
2495
  var ACCT_CAPITAL_OFF = 8;
2484
2496
  var ACCT_KIND_OFF = 24;
@@ -2728,15 +2740,15 @@ function parseParams(data, layoutHint) {
2728
2740
  if (data.length < base + Math.min(paramsSize, 56)) {
2729
2741
  throw new Error("Slab data too short for RiskParams");
2730
2742
  }
2731
- const isV12_1Sbf = layout !== null && layout !== void 0 && layout.engineOff === V12_1_SBF_ENGINE_OFF && paramsSize === 184;
2743
+ const isV12_15Params = paramsSize === V12_15_PARAMS_SIZE;
2732
2744
  const result = {
2733
- warmupPeriodSlots: readU64LE(data, base + PARAMS_WARMUP_PERIOD_OFF),
2745
+ warmupPeriodSlots: isV12_15Params ? readU64LE(data, base + V12_15_PARAMS_H_MIN_OFF) : readU64LE(data, base + PARAMS_WARMUP_PERIOD_OFF),
2734
2746
  maintenanceMarginBps: readU64LE(data, base + PARAMS_MAINTENANCE_MARGIN_OFF),
2735
2747
  initialMarginBps: readU64LE(data, base + PARAMS_INITIAL_MARGIN_OFF),
2736
2748
  tradingFeeBps: readU64LE(data, base + PARAMS_TRADING_FEE_OFF),
2737
- maxAccounts: readU64LE(data, base + PARAMS_MAX_ACCOUNTS_OFF),
2749
+ maxAccounts: isV12_15Params ? readU64LE(data, base + V12_15_PARAMS_MAX_ACCOUNTS_OFF) : readU64LE(data, base + PARAMS_MAX_ACCOUNTS_OFF),
2738
2750
  newAccountFee: readU128LE(data, base + PARAMS_NEW_ACCOUNT_FEE_OFF),
2739
- // Extended params: defaults; overwritten below if layout supports them
2751
+ // Extended params: only read if V1 (paramsSize >= 144)
2740
2752
  riskReductionThreshold: 0n,
2741
2753
  maintenanceFeePerSlot: 0n,
2742
2754
  maxCrankStalenessSlots: 0n,
@@ -2744,21 +2756,19 @@ function parseParams(data, layoutHint) {
2744
2756
  liquidationFeeCap: 0n,
2745
2757
  liquidationBufferBps: 0n,
2746
2758
  minLiquidationAbs: 0n,
2747
- minInitialDeposit: 0n,
2748
- minNonzeroMmReq: 0n,
2749
- minNonzeroImReq: 0n,
2750
- insuranceFloor: 0n
2759
+ hMin: 0n,
2760
+ hMax: 0n
2751
2761
  };
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);
2762
+ if (isV12_15Params) {
2763
+ result.hMin = readU64LE(data, base + V12_15_PARAMS_H_MIN_OFF);
2764
+ result.hMax = readU64LE(data, base + V12_15_PARAMS_H_MAX_OFF);
2765
+ result.riskReductionThreshold = readU128LE(data, base + PARAMS_RISK_THRESHOLD_OFF);
2766
+ result.maintenanceFeePerSlot = readU128LE(data, base + PARAMS_MAINTENANCE_FEE_OFF);
2767
+ result.maxCrankStalenessSlots = readU64LE(data, base + PARAMS_MAX_CRANK_STALENESS_OFF);
2768
+ result.liquidationFeeBps = readU64LE(data, base + PARAMS_LIQUIDATION_FEE_BPS_OFF);
2769
+ result.liquidationFeeCap = readU128LE(data, base + PARAMS_LIQUIDATION_FEE_CAP_OFF);
2770
+ result.liquidationBufferBps = readU64LE(data, base + PARAMS_LIQUIDATION_BUFFER_OFF);
2771
+ result.minLiquidationAbs = readU128LE(data, base + PARAMS_MIN_LIQUIDATION_OFF);
2762
2772
  } else if (paramsSize >= 144) {
2763
2773
  result.riskReductionThreshold = readU128LE(data, base + PARAMS_RISK_THRESHOLD_OFF);
2764
2774
  result.maintenanceFeePerSlot = readU128LE(data, base + PARAMS_MAINTENANCE_FEE_OFF);
@@ -2767,6 +2777,8 @@ function parseParams(data, layoutHint) {
2767
2777
  result.liquidationFeeCap = readU128LE(data, base + PARAMS_LIQUIDATION_FEE_CAP_OFF);
2768
2778
  result.liquidationBufferBps = readU64LE(data, base + PARAMS_LIQUIDATION_BUFFER_OFF);
2769
2779
  result.minLiquidationAbs = readU128LE(data, base + PARAMS_MIN_LIQUIDATION_OFF);
2780
+ result.hMin = result.warmupPeriodSlots;
2781
+ result.hMax = result.warmupPeriodSlots;
2770
2782
  }
2771
2783
  return result;
2772
2784
  }
@@ -2776,6 +2788,8 @@ function parseEngine(data) {
2776
2788
  throw new Error(`Unrecognized slab data length: ${data.length}. Cannot determine layout version.`);
2777
2789
  }
2778
2790
  const base = layout.engineOff;
2791
+ const isV12_15 = layout.accountSize === V12_15_ACCOUNT_SIZE && layout.engineOff === V12_15_ENGINE_OFF;
2792
+ const fundingRateBpsPerSlotLast = isV12_15 ? readI128LE(data, base + V12_15_ENGINE_FUNDING_RATE_E9_OFF) : readI64LE(data, base + layout.engineFundingRateBpsOff);
2779
2793
  return {
2780
2794
  vault: readU128LE(data, base),
2781
2795
  insuranceFund: {
@@ -2788,21 +2802,24 @@ function parseEngine(data) {
2788
2802
  currentSlot: readU64LE(data, base + layout.engineCurrentSlotOff),
2789
2803
  fundingIndexQpbE6: layout.engineFundingIndexOff >= 0 ? readI128LE(data, base + layout.engineFundingIndexOff) : 0n,
2790
2804
  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),
2805
+ fundingRateBpsPerSlotLast,
2806
+ fundingRateE9: isV12_15 ? readI128LE(data, base + V12_15_ENGINE_FUNDING_RATE_E9_OFF) : 0n,
2807
+ marketMode: isV12_15 ? readU8(data, base + V12_15_ENGINE_MARKET_MODE_OFF) === 1 ? 1 : 0 : null,
2808
+ lastCrankSlot: layout.engineLastCrankSlotOff >= 0 ? readU64LE(data, base + layout.engineLastCrankSlotOff) : 0n,
2809
+ maxCrankStalenessSlots: layout.engineMaxCrankStalenessOff >= 0 ? readU64LE(data, base + layout.engineMaxCrankStalenessOff) : 0n,
2794
2810
  totalOpenInterest: layout.engineTotalOiOff >= 0 ? readU128LE(data, base + layout.engineTotalOiOff) : 0n,
2795
2811
  longOi: layout.engineLongOiOff >= 0 ? readU128LE(data, base + layout.engineLongOiOff) : 0n,
2796
2812
  shortOi: layout.engineShortOiOff >= 0 ? readU128LE(data, base + layout.engineShortOiOff) : 0n,
2797
2813
  cTot: readU128LE(data, base + layout.engineCTotOff),
2798
2814
  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),
2815
+ pnlMaturedPosTot: isV12_15 ? readU128LE(data, base + V12_15_ENGINE_PNL_MATURED_POS_TOT_OFF) : 0n,
2816
+ liqCursor: layout.engineLiqCursorOff >= 0 ? readU16LE(data, base + layout.engineLiqCursorOff) : 0,
2817
+ gcCursor: layout.engineGcCursorOff >= 0 ? readU16LE(data, base + layout.engineGcCursorOff) : 0,
2818
+ lastSweepStartSlot: layout.engineLastSweepStartOff >= 0 ? readU64LE(data, base + layout.engineLastSweepStartOff) : 0n,
2819
+ lastSweepCompleteSlot: layout.engineLastSweepCompleteOff >= 0 ? readU64LE(data, base + layout.engineLastSweepCompleteOff) : 0n,
2820
+ crankCursor: layout.engineCrankCursorOff >= 0 ? readU16LE(data, base + layout.engineCrankCursorOff) : 0,
2821
+ sweepStartIdx: layout.engineSweepStartIdxOff >= 0 ? readU16LE(data, base + layout.engineSweepStartIdxOff) : 0,
2822
+ lifetimeLiquidations: layout.engineLifetimeLiquidationsOff >= 0 ? readU64LE(data, base + layout.engineLifetimeLiquidationsOff) : 0n,
2806
2823
  lifetimeForceCloses: layout.engineLifetimeForceClosesOff >= 0 ? readU64LE(data, base + layout.engineLifetimeForceClosesOff) : 0n,
2807
2824
  netLpPos: layout.engineNetLpPosOff >= 0 ? readI128LE(data, base + layout.engineNetLpPosOff) : 0n,
2808
2825
  lpSumAbs: layout.engineLpSumAbsOff >= 0 ? readU128LE(data, base + layout.engineLpSumAbsOff) : 0n,
@@ -2872,18 +2889,58 @@ function parseAccount(data, idx) {
2872
2889
  if (data.length < base + layout.accountSize) {
2873
2890
  throw new Error("Slab data too short for account");
2874
2891
  }
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;
2892
+ const isV12_15 = layout.accountSize === V12_15_ACCOUNT_SIZE;
2893
+ 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);
2894
+ const isAdl = !isV12_15 && (layout.accountSize >= 312 || isV12_1);
2895
+ if (isV12_15) {
2896
+ const kindByte2 = readU8(data, base + V12_15_ACCT_KIND_OFF);
2897
+ const kind2 = kindByte2 === 1 ? 1 /* LP */ : 0 /* User */;
2898
+ const cohortCount = readU8(data, base + V12_15_ACCT_EXACT_COHORT_COUNT_OFF);
2899
+ const exactReserveCohorts = [];
2900
+ for (let i = 0; i < 62; i++) {
2901
+ const cohortOff = base + V12_15_ACCT_EXACT_RESERVE_COHORTS_OFF + i * 64;
2902
+ exactReserveCohorts.push(data.slice(cohortOff, cohortOff + 64));
2903
+ }
2904
+ const overflowOlderPresent = readU8(data, base + V12_15_ACCT_OVERFLOW_OLDER_PRESENT_OFF) !== 0;
2905
+ const overflowNewestPresent = readU8(data, base + V12_15_ACCT_OVERFLOW_NEWEST_PRESENT_OFF) !== 0;
2906
+ return {
2907
+ kind: kind2,
2908
+ accountId: readU64LE(data, base + V12_15_ACCT_ACCOUNT_ID_OFF),
2909
+ capital: readU128LE(data, base + V12_15_ACCT_CAPITAL_OFF),
2910
+ pnl: readI128LE(data, base + V12_15_ACCT_PNL_OFF),
2911
+ reservedPnl: readU128LE(data, base + V12_15_ACCT_RESERVED_PNL_OFF),
2912
+ warmupStartedAtSlot: 0n,
2913
+ // removed in v12.15
2914
+ warmupSlopePerStep: 0n,
2915
+ // removed in v12.15
2916
+ positionSize: readI128LE(data, base + V12_15_ACCT_POSITION_BASIS_Q_OFF),
2917
+ entryPrice: readU64LE(data, base + V12_15_ACCT_ENTRY_PRICE_OFF),
2918
+ fundingIndex: 0n,
2919
+ // not present in v12.15 account struct
2920
+ matcherProgram: new PublicKey3(data.subarray(base + V12_15_ACCT_MATCHER_PROGRAM_OFF, base + V12_15_ACCT_MATCHER_PROGRAM_OFF + 32)),
2921
+ matcherContext: new PublicKey3(data.subarray(base + V12_15_ACCT_MATCHER_CONTEXT_OFF, base + V12_15_ACCT_MATCHER_CONTEXT_OFF + 32)),
2922
+ owner: new PublicKey3(data.subarray(base + V12_15_ACCT_OWNER_OFF, base + V12_15_ACCT_OWNER_OFF + 32)),
2923
+ feeCredits: readI128LE(data, base + V12_15_ACCT_FEE_CREDITS_OFF),
2924
+ lastFeeSlot: 0n,
2925
+ // removed in v12.15
2926
+ feesEarnedTotal: readU128LE(data, base + V12_15_ACCT_FEES_EARNED_TOTAL_OFF),
2927
+ exactReserveCohorts,
2928
+ exactCohortCount: cohortCount,
2929
+ overflowOlder: data.slice(base + V12_15_ACCT_OVERFLOW_OLDER_OFF, base + V12_15_ACCT_OVERFLOW_OLDER_OFF + 64),
2930
+ overflowOlderPresent,
2931
+ overflowNewest: data.slice(base + V12_15_ACCT_OVERFLOW_NEWEST_OFF, base + V12_15_ACCT_OVERFLOW_NEWEST_OFF + 64),
2932
+ overflowNewestPresent
2933
+ };
2934
+ }
2878
2935
  const warmupStartedOff = isAdl ? V_ADL_ACCT_WARMUP_STARTED_OFF : ACCT_WARMUP_STARTED_OFF;
2879
2936
  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;
2937
+ const positionSizeOff = isV12_1 ? V12_1_ACCT_POSITION_SIZE_OFF : isAdl ? V_ADL_ACCT_POSITION_SIZE_OFF : ACCT_POSITION_SIZE_OFF;
2938
+ const entryPriceOff = isV12_1 ? V12_1_ACCT_ENTRY_PRICE_OFF : isAdl ? V_ADL_ACCT_ENTRY_PRICE_OFF : ACCT_ENTRY_PRICE_OFF;
2939
+ const fundingIndexOff = isV12_1 ? V12_1_ACCT_FUNDING_INDEX_OFF : isAdl ? V_ADL_ACCT_FUNDING_INDEX_OFF : ACCT_FUNDING_INDEX_OFF;
2940
+ const matcherProgOff = isV12_1 ? V12_1_ACCT_MATCHER_PROGRAM_OFF : isAdl ? V_ADL_ACCT_MATCHER_PROGRAM_OFF : ACCT_MATCHER_PROGRAM_OFF;
2941
+ const matcherCtxOff = isV12_1 ? V12_1_ACCT_MATCHER_CONTEXT_OFF : isAdl ? V_ADL_ACCT_MATCHER_CONTEXT_OFF : ACCT_MATCHER_CONTEXT_OFF;
2942
+ const feeCreditsOff = isV12_1 ? V12_1_ACCT_FEE_CREDITS_OFF : isAdl ? V_ADL_ACCT_FEE_CREDITS_OFF : ACCT_FEE_CREDITS_OFF;
2943
+ 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
2944
  const kindByte = readU8(data, base + ACCT_KIND_OFF);
2888
2945
  const kind = kindByte === 1 ? 1 /* LP */ : 0 /* User */;
2889
2946
  return {
@@ -2896,13 +2953,23 @@ function parseAccount(data, idx) {
2896
2953
  warmupSlopePerStep: readU128LE(data, base + warmupSlopeOff),
2897
2954
  positionSize: readI128LE(data, base + positionSizeOff),
2898
2955
  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),
2956
+ // V12_1: entry_price removed
2957
+ // V12_1 changed funding_index from i128 to i64 (legacy field moved to end of account)
2958
+ fundingIndex: isV12_1 ? BigInt(readI64LE(data, base + fundingIndexOff)) : readI128LE(data, base + fundingIndexOff),
2901
2959
  matcherProgram: new PublicKey3(data.subarray(base + matcherProgOff, base + matcherProgOff + 32)),
2902
2960
  matcherContext: new PublicKey3(data.subarray(base + matcherCtxOff, base + matcherCtxOff + 32)),
2903
2961
  owner: new PublicKey3(data.subarray(base + layout.acctOwnerOff, base + layout.acctOwnerOff + 32)),
2904
2962
  feeCredits: readI128LE(data, base + feeCreditsOff),
2905
- lastFeeSlot: readU64LE(data, base + lastFeeSlotOff)
2963
+ lastFeeSlot: readU64LE(data, base + lastFeeSlotOff),
2964
+ feesEarnedTotal: 0n,
2965
+ // not present in pre-v12.15 layouts
2966
+ exactReserveCohorts: null,
2967
+ // not present in pre-v12.15 layouts
2968
+ exactCohortCount: null,
2969
+ overflowOlder: null,
2970
+ overflowOlderPresent: null,
2971
+ overflowNewest: null,
2972
+ overflowNewestPresent: null
2906
2973
  };
2907
2974
  }
2908
2975
  function parseAllAccounts(data) {
@@ -2930,12 +2997,6 @@ function deriveVaultAuthority(programId, slab) {
2930
2997
  programId
2931
2998
  );
2932
2999
  }
2933
- function deriveInsuranceLpMint(programId, slab) {
2934
- return PublicKey4.findProgramAddressSync(
2935
- [textEncoder.encode("ins_lp"), slab.toBytes()],
2936
- programId
2937
- );
2938
- }
2939
3000
  var LP_INDEX_U16_MAX = 65535;
2940
3001
  function deriveLpPda(programId, slab, lpIdx) {
2941
3002
  if (typeof lpIdx !== "number" || !Number.isInteger(lpIdx) || lpIdx < 0 || lpIdx > LP_INDEX_U16_MAX) {
@@ -2950,12 +3011,6 @@ function deriveLpPda(programId, slab, lpIdx) {
2950
3011
  programId
2951
3012
  );
2952
3013
  }
2953
- function deriveKeeperFund(programId, slab) {
2954
- return PublicKey4.findProgramAddressSync(
2955
- [textEncoder.encode("keeper_fund"), slab.toBytes()],
2956
- programId
2957
- );
2958
- }
2959
3014
  var PUMPSWAP_PROGRAM_ID = new PublicKey4(
2960
3015
  "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"
2961
3016
  );
@@ -2992,7 +3047,14 @@ function derivePythPushOraclePDA(feedIdHex) {
2992
3047
  }
2993
3048
  const feedId = new Uint8Array(32);
2994
3049
  for (let i = 0; i < 32; i++) {
2995
- feedId[i] = parseInt(normalized.substring(i * 2, i * 2 + 2), 16);
3050
+ const hexPair = normalized.substring(i * 2, i * 2 + 2);
3051
+ const byte = parseInt(hexPair, 16);
3052
+ if (Number.isNaN(byte)) {
3053
+ throw new Error(
3054
+ `derivePythPushOraclePDA: failed to parse hex byte at position ${i}: "${hexPair}"`
3055
+ );
3056
+ }
3057
+ feedId[i] = byte;
2996
3058
  }
2997
3059
  const shardBuf = new Uint8Array(2);
2998
3060
  return PublicKey4.findProgramAddressSync(
@@ -3024,7 +3086,9 @@ import { PublicKey as PublicKey6 } from "@solana/web3.js";
3024
3086
  // src/solana/static-markets.ts
3025
3087
  import { PublicKey as PublicKey5 } from "@solana/web3.js";
3026
3088
  var MAINNET_MARKETS = [
3027
- { slabAddress: "9TGSmPLTLMii4UqstL629twGeVJ9Ndr8VD3pexnvQTsV", symbol: "SOL-PERP", name: "SOL/USDC Perpetual" }
3089
+ // Populated at mainnet launch currently empty.
3090
+ // To add entries:
3091
+ // { slabAddress: "ABC123...", symbol: "SOL-PERP", name: "SOL Perpetual" },
3028
3092
  ];
3029
3093
  var DEVNET_MARKETS = [
3030
3094
  // Populated from prior discoverMarkets() runs on devnet.
@@ -3192,6 +3256,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3192
3256
  fundingIndexQpbE6: readI128LE2(data, base + 112),
3193
3257
  lastFundingSlot: readU64LE2(data, base + 128),
3194
3258
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 136),
3259
+ fundingRateE9: 0n,
3260
+ marketMode: null,
3195
3261
  lastCrankSlot: readU64LE2(data, base + 144),
3196
3262
  maxCrankStalenessSlots: readU64LE2(data, base + 152),
3197
3263
  totalOpenInterest: readU128LE2(data, base + 160),
@@ -3199,6 +3265,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3199
3265
  shortOi: 0n,
3200
3266
  cTot: readU128LE2(data, base + 176),
3201
3267
  pnlPosTot: readU128LE2(data, base + 192),
3268
+ pnlMaturedPosTot: 0n,
3202
3269
  liqCursor: readU16LE2(data, base + 208),
3203
3270
  gcCursor: readU16LE2(data, base + 210),
3204
3271
  lastSweepStartSlot: readU64LE2(data, base + 216),
@@ -3234,6 +3301,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3234
3301
  fundingIndexQpbE6: readI128LE2(data, base + 360),
3235
3302
  lastFundingSlot: readU64LE2(data, base + 376),
3236
3303
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 384),
3304
+ fundingRateE9: 0n,
3305
+ marketMode: null,
3237
3306
  lastCrankSlot: readU64LE2(data, base + 392),
3238
3307
  maxCrankStalenessSlots: readU64LE2(data, base + 400),
3239
3308
  totalOpenInterest: readU128LE2(data, base + 408),
@@ -3243,6 +3312,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3243
3312
  // V2 has no short_oi
3244
3313
  cTot: readU128LE2(data, base + 424),
3245
3314
  pnlPosTot: readU128LE2(data, base + 440),
3315
+ pnlMaturedPosTot: 0n,
3246
3316
  liqCursor: readU16LE2(data, base + 456),
3247
3317
  gcCursor: readU16LE2(data, base + 458),
3248
3318
  lastSweepStartSlot: readU64LE2(data, base + 464),
@@ -3280,6 +3350,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3280
3350
  fundingIndexQpbE6: readI128LE2(data, base + l.engineFundingIndexOff),
3281
3351
  lastFundingSlot: readU64LE2(data, base + l.engineLastFundingSlotOff),
3282
3352
  fundingRateBpsPerSlotLast: readI64LE2(data, base + l.engineFundingRateBpsOff),
3353
+ fundingRateE9: 0n,
3354
+ marketMode: null,
3283
3355
  lastCrankSlot: readU64LE2(data, base + l.engineLastCrankSlotOff),
3284
3356
  maxCrankStalenessSlots: readU64LE2(data, base + l.engineMaxCrankStalenessOff),
3285
3357
  totalOpenInterest: readU128LE2(data, base + l.engineTotalOiOff),
@@ -3287,6 +3359,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3287
3359
  shortOi: l.engineShortOiOff >= 0 ? readU128LE2(data, base + l.engineShortOiOff) : 0n,
3288
3360
  cTot: readU128LE2(data, base + l.engineCTotOff),
3289
3361
  pnlPosTot: readU128LE2(data, base + l.enginePnlPosTotOff),
3362
+ pnlMaturedPosTot: 0n,
3290
3363
  liqCursor: readU16LE2(data, base + l.engineLiqCursorOff),
3291
3364
  gcCursor: readU16LE2(data, base + l.engineGcCursorOff),
3292
3365
  lastSweepStartSlot: readU64LE2(data, base + l.engineLastSweepStartOff),
@@ -3320,6 +3393,8 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3320
3393
  fundingIndexQpbE6: readI128LE2(data, base + 368),
3321
3394
  lastFundingSlot: readU64LE2(data, base + 384),
3322
3395
  fundingRateBpsPerSlotLast: readI64LE2(data, base + 392),
3396
+ fundingRateE9: 0n,
3397
+ marketMode: null,
3323
3398
  lastCrankSlot: readU64LE2(data, base + 424),
3324
3399
  maxCrankStalenessSlots: readU64LE2(data, base + 408),
3325
3400
  totalOpenInterest: readU128LE2(data, base + 416),
@@ -3327,6 +3402,7 @@ function parseEngineLight(data, layout, maxAccounts = 4096) {
3327
3402
  shortOi: readU128LE2(data, base + 448),
3328
3403
  cTot: readU128LE2(data, base + 464),
3329
3404
  pnlPosTot: readU128LE2(data, base + 480),
3405
+ pnlMaturedPosTot: 0n,
3330
3406
  liqCursor: readU16LE2(data, base + 496),
3331
3407
  gcCursor: readU16LE2(data, base + 498),
3332
3408
  lastSweepStartSlot: readU64LE2(data, base + 504),
@@ -3792,9 +3868,7 @@ function computeRaydiumClmmPriceE6(data) {
3792
3868
  }
3793
3869
  const sqrtPriceX64 = readU128LE3(dv3, 253);
3794
3870
  if (sqrtPriceX64 === 0n) return 0n;
3795
- const scaledSqrt = sqrtPriceX64 * 1000000n;
3796
- const term = scaledSqrt >> 64n;
3797
- const priceE6Raw = term * sqrtPriceX64 >> 64n;
3871
+ const priceE6Raw = sqrtPriceX64 * sqrtPriceX64 * 1000000n >> 128n;
3798
3872
  const decimalDiff = 6 + decimals0 - decimals1;
3799
3873
  const adjustedDiff = decimalDiff - 6;
3800
3874
  if (adjustedDiff >= 0) {
@@ -3835,13 +3909,26 @@ function computeMeteoraDlmmPriceE6(data) {
3835
3909
  `Meteora DLMM: |activeId| ${Math.abs(activeId)} exceeds max ${MAX_ACTIVE_ID_ABS}`
3836
3910
  );
3837
3911
  }
3912
+ const MAX_ABS_BIN_ID = 5e5;
3913
+ if (activeId > MAX_ABS_BIN_ID || activeId < -MAX_ABS_BIN_ID) {
3914
+ throw new Error(
3915
+ `Meteora DLMM: activeId ${activeId} exceeds safe range (\xB1${MAX_ABS_BIN_ID})`
3916
+ );
3917
+ }
3838
3918
  const SCALE = 1000000000000000000n;
3839
3919
  const base = SCALE + BigInt(binStep) * SCALE / 10000n;
3840
3920
  const isNeg = activeId < 0;
3841
3921
  let exp = isNeg ? BigInt(-activeId) : BigInt(activeId);
3842
3922
  let result = SCALE;
3843
3923
  let b = base;
3924
+ let iterations = 0;
3925
+ const MAX_ITERATIONS = 25;
3844
3926
  while (exp > 0n) {
3927
+ if (iterations++ >= MAX_ITERATIONS) {
3928
+ throw new Error(
3929
+ `Meteora DLMM: exponentiation loop exceeded ${MAX_ITERATIONS} iterations (activeId=${activeId})`
3930
+ );
3931
+ }
3845
3932
  if (exp & 1n) {
3846
3933
  result = result * b / SCALE;
3847
3934
  }
@@ -3872,6 +3959,7 @@ function readU128LE3(dv3, offset) {
3872
3959
  var CHAINLINK_MIN_SIZE = 224;
3873
3960
  var MAX_DECIMALS = 18;
3874
3961
  var CHAINLINK_DECIMALS_OFFSET = 138;
3962
+ var CHAINLINK_TIMESTAMP_OFFSET = 168;
3875
3963
  var CHAINLINK_ANSWER_OFFSET = 216;
3876
3964
  function readU82(data, off) {
3877
3965
  return data[off];
@@ -3879,7 +3967,7 @@ function readU82(data, off) {
3879
3967
  function readBigInt64LE(data, off) {
3880
3968
  return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigInt64(off, true);
3881
3969
  }
3882
- function parseChainlinkPrice(data) {
3970
+ function parseChainlinkPrice(data, options) {
3883
3971
  if (data.length < CHAINLINK_MIN_SIZE) {
3884
3972
  throw new Error(
3885
3973
  `Oracle account data too small: ${data.length} bytes (need at least ${CHAINLINK_MIN_SIZE})`
@@ -3897,7 +3985,18 @@ function parseChainlinkPrice(data) {
3897
3985
  `Oracle price is non-positive: ${price}`
3898
3986
  );
3899
3987
  }
3900
- return { price, decimals };
3988
+ const updatedAtBig = readBigInt64LE(data, CHAINLINK_TIMESTAMP_OFFSET);
3989
+ const updatedAt = Number(updatedAtBig);
3990
+ if (options?.maxStalenessSeconds !== void 0 && updatedAt > 0) {
3991
+ const now = Math.floor(Date.now() / 1e3);
3992
+ const age = now - updatedAt;
3993
+ if (age > options.maxStalenessSeconds) {
3994
+ throw new Error(
3995
+ `Oracle price is stale: last updated ${age}s ago (max ${options.maxStalenessSeconds}s)`
3996
+ );
3997
+ }
3998
+ }
3999
+ return { price, decimals, updatedAt: updatedAt > 0 ? updatedAt : void 0 };
3901
4000
  }
3902
4001
  function isValidChainlinkOracle(data) {
3903
4002
  try {
@@ -3917,7 +4016,11 @@ var TOKEN_2022_PROGRAM_ID = new PublicKey8(
3917
4016
  async function detectTokenProgram(connection, mint) {
3918
4017
  const info = await connection.getAccountInfo(mint);
3919
4018
  if (!info) throw new Error(`Mint account not found: ${mint.toBase58()}`);
3920
- return info.owner;
4019
+ if (info.owner.equals(TOKEN_PROGRAM_ID3)) return TOKEN_PROGRAM_ID3;
4020
+ if (info.owner.equals(TOKEN_2022_PROGRAM_ID)) return TOKEN_2022_PROGRAM_ID;
4021
+ throw new Error(
4022
+ `Mint ${mint.toBase58()} is owned by ${info.owner.toBase58()}, which is neither TOKEN_PROGRAM_ID nor TOKEN_2022_PROGRAM_ID`
4023
+ );
3921
4024
  }
3922
4025
  function isToken2022(tokenProgramId) {
3923
4026
  return tokenProgramId.equals(TOKEN_2022_PROGRAM_ID);
@@ -3950,12 +4053,14 @@ var PROGRAM_IDS = {
3950
4053
  }
3951
4054
  };
3952
4055
  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);
4056
+ if (!network) {
4057
+ const override = safeEnv("PROGRAM_ID");
4058
+ if (override) {
4059
+ console.warn(
4060
+ `[percolator-sdk] PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
4061
+ );
4062
+ return new PublicKey9(override);
4063
+ }
3959
4064
  }
3960
4065
  const detectedNetwork = getCurrentNetwork();
3961
4066
  const targetNetwork = network ?? detectedNetwork;
@@ -3963,12 +4068,14 @@ function getProgramId(network) {
3963
4068
  return new PublicKey9(programId);
3964
4069
  }
3965
4070
  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);
4071
+ if (!network) {
4072
+ const override = safeEnv("MATCHER_PROGRAM_ID");
4073
+ if (override) {
4074
+ console.warn(
4075
+ `[percolator-sdk] MATCHER_PROGRAM_ID env override active: ${override} \u2014 ensure this points to a trusted program`
4076
+ );
4077
+ return new PublicKey9(override);
4078
+ }
3972
4079
  }
3973
4080
  const detectedNetwork = getCurrentNetwork();
3974
4081
  const targetNetwork = network ?? detectedNetwork;
@@ -4001,10 +4108,7 @@ function getStakeProgramId(network) {
4001
4108
  }
4002
4109
  const detectedNetwork = network ?? (() => {
4003
4110
  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";
4111
+ return n === "mainnet" || n === "mainnet-beta" ? "mainnet" : "devnet";
4008
4112
  })();
4009
4113
  const id = STAKE_PROGRAM_IDS[detectedNetwork];
4010
4114
  if (!id) {
@@ -4014,7 +4118,7 @@ function getStakeProgramId(network) {
4014
4118
  }
4015
4119
  return new PublicKey10(id);
4016
4120
  }
4017
- var STAKE_PROGRAM_ID = new PublicKey10(STAKE_PROGRAM_IDS.devnet);
4121
+ var STAKE_PROGRAM_ID = new PublicKey10(STAKE_PROGRAM_IDS.mainnet);
4018
4122
  var STAKE_IX = {
4019
4123
  InitPool: 0,
4020
4124
  Deposit: 1,
@@ -4075,6 +4179,9 @@ function readU16LE3(data, off) {
4075
4179
  );
4076
4180
  }
4077
4181
  function u64Le(v) {
4182
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
4183
+ throw new Error(`u64Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
4184
+ }
4078
4185
  const big = BigInt(v);
4079
4186
  if (big < 0n) throw new Error(`u64Le: value must be non-negative, got ${big}`);
4080
4187
  if (big > 0xFFFFFFFFFFFFFFFFn) throw new Error(`u64Le: value exceeds u64 max`);
@@ -4083,6 +4190,9 @@ function u64Le(v) {
4083
4190
  return arr;
4084
4191
  }
4085
4192
  function u128Le(v) {
4193
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
4194
+ throw new Error(`u128Le: number ${v} exceeds Number.MAX_SAFE_INTEGER \u2014 use BigInt`);
4195
+ }
4086
4196
  const big = BigInt(v);
4087
4197
  if (big < 0n) throw new Error(`u128Le: value must be non-negative, got ${big}`);
4088
4198
  if (big > (1n << 128n) - 1n) throw new Error(`u128Le: value exceeds u128 max`);
@@ -4093,7 +4203,7 @@ function u128Le(v) {
4093
4203
  return arr;
4094
4204
  }
4095
4205
  function u16Le(v) {
4096
- if (v < 0 || v > 65535) throw new Error(`u16Le: value out of u16 range (0..65535), got ${v}`);
4206
+ if (!Number.isInteger(v) || v < 0 || v > 65535) throw new Error(`u16Le: value must be integer in range 0..65535, got ${v}`);
4097
4207
  const arr = new Uint8Array(2);
4098
4208
  new DataView(arr.buffer).setUint16(0, v, true);
4099
4209
  return arr;
@@ -4348,7 +4458,9 @@ function computePnlPct(pnl, capital) {
4348
4458
  }
4349
4459
  function isAdlTriggered(slabData) {
4350
4460
  const layout = detectSlabLayout(slabData.length);
4351
- if (!layout) return false;
4461
+ if (!layout) {
4462
+ return false;
4463
+ }
4352
4464
  try {
4353
4465
  const engine = parseEngine(slabData);
4354
4466
  if (engine.pnlPosTot === 0n) return false;
@@ -4391,6 +4503,14 @@ function rankAdlPositions(slabData) {
4391
4503
  if (account.kind !== 0 /* User */) continue;
4392
4504
  if (account.positionSize === 0n) continue;
4393
4505
  const side = account.positionSize > 0n ? "long" : "short";
4506
+ if (side === "long" && account.positionSize <= 0n) {
4507
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=long but positionSize=${account.positionSize}`);
4508
+ continue;
4509
+ }
4510
+ if (side === "short" && account.positionSize >= 0n) {
4511
+ console.warn(`[fetchAdlRankedPositions] account idx=${idx}: side=short but positionSize=${account.positionSize}`);
4512
+ continue;
4513
+ }
4394
4514
  const pnlPct = computePnlPct(account.pnl, account.capital);
4395
4515
  positions.push({
4396
4516
  idx,
@@ -4423,7 +4543,8 @@ function buildAdlInstruction(caller, slab, oracle, programId, targetIdx, backupO
4423
4543
  `buildAdlInstruction: targetIdx must be a non-negative integer, got ${targetIdx}`
4424
4544
  );
4425
4545
  }
4426
- const data = Buffer.from(encodeExecuteAdl({ targetIdx }));
4546
+ const dataBytes = encodeExecuteAdl({ targetIdx });
4547
+ const data = Buffer.from(dataBytes);
4427
4548
  const keys = [
4428
4549
  { pubkey: caller, isSigner: true, isWritable: false },
4429
4550
  { pubkey: slab, isSigner: false, isWritable: true },
@@ -4463,7 +4584,11 @@ function parseAdlEvent(logs) {
4463
4584
  }
4464
4585
  if (tag !== ADL_EVENT_TAG) continue;
4465
4586
  try {
4466
- const targetIdx = Number(BigInt(match[2]));
4587
+ const targetIdxBig = BigInt(match[2]);
4588
+ if (targetIdxBig < 0n || targetIdxBig > 65535n) {
4589
+ continue;
4590
+ }
4591
+ const targetIdx = Number(targetIdxBig);
4467
4592
  const price = BigInt(match[3]);
4468
4593
  const closedLo = BigInt(match[4]);
4469
4594
  const closedHi = BigInt(match[5]);
@@ -4491,9 +4616,387 @@ async function fetchAdlRankings(apiBase, slab, fetchFn = fetch) {
4491
4616
  );
4492
4617
  }
4493
4618
  const json = await res.json();
4619
+ if (typeof json !== "object" || json === null) {
4620
+ throw new Error("fetchAdlRankings: API returned non-object response");
4621
+ }
4622
+ const obj = json;
4623
+ if (!Array.isArray(obj.rankings)) {
4624
+ throw new Error("fetchAdlRankings: API response missing rankings array");
4625
+ }
4626
+ for (const entry of obj.rankings) {
4627
+ if (typeof entry !== "object" || entry === null) {
4628
+ throw new Error("fetchAdlRankings: invalid ranking entry (not an object)");
4629
+ }
4630
+ const r = entry;
4631
+ if (typeof r.idx !== "number" || !Number.isInteger(r.idx) || r.idx < 0) {
4632
+ throw new Error(`fetchAdlRankings: invalid ranking idx: ${r.idx}`);
4633
+ }
4634
+ }
4494
4635
  return json;
4495
4636
  }
4496
4637
 
4638
+ // src/solana/rpc-pool.ts
4639
+ import {
4640
+ Connection as Connection5
4641
+ } from "@solana/web3.js";
4642
+ async function checkRpcHealth(endpoint, timeoutMs = 5e3) {
4643
+ const conn = new Connection5(endpoint, { commitment: "processed" });
4644
+ const start = performance.now();
4645
+ const timeout = rejectAfter(timeoutMs, `Health probe timed out after ${timeoutMs}ms`);
4646
+ try {
4647
+ const slot = await Promise.race([
4648
+ conn.getSlot("processed"),
4649
+ timeout.promise
4650
+ ]);
4651
+ const latencyMs = Math.round(performance.now() - start);
4652
+ return { endpoint, healthy: true, latencyMs, slot };
4653
+ } catch (err) {
4654
+ const latencyMs = Math.round(performance.now() - start);
4655
+ return {
4656
+ endpoint,
4657
+ healthy: false,
4658
+ latencyMs,
4659
+ slot: 0,
4660
+ error: err instanceof Error ? err.message : String(err)
4661
+ };
4662
+ } finally {
4663
+ timeout.cancel();
4664
+ }
4665
+ }
4666
+ function resolveRetryConfig(cfg) {
4667
+ if (cfg === false) return null;
4668
+ const c = cfg ?? {};
4669
+ return {
4670
+ maxRetries: c.maxRetries ?? 3,
4671
+ baseDelayMs: c.baseDelayMs ?? 500,
4672
+ maxDelayMs: c.maxDelayMs ?? 1e4,
4673
+ jitterFactor: Math.max(0, Math.min(1, c.jitterFactor ?? 0.25)),
4674
+ retryableStatusCodes: c.retryableStatusCodes ?? [429, 502, 503, 504]
4675
+ };
4676
+ }
4677
+ function normalizeEndpoint(ep) {
4678
+ if (typeof ep === "string") return { url: ep };
4679
+ return ep;
4680
+ }
4681
+ function endpointLabel(ep) {
4682
+ if (ep.label) return ep.label;
4683
+ try {
4684
+ return new URL(ep.url).hostname;
4685
+ } catch {
4686
+ return ep.url.slice(0, 40);
4687
+ }
4688
+ }
4689
+ function isRetryable(err, codes) {
4690
+ if (!err) return false;
4691
+ const msg = err instanceof Error ? err.message : String(err);
4692
+ for (const code of codes) {
4693
+ if (msg.includes(String(code))) return true;
4694
+ }
4695
+ 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")) {
4696
+ return true;
4697
+ }
4698
+ return false;
4699
+ }
4700
+ function computeDelay(attempt, config) {
4701
+ const raw = Math.min(
4702
+ config.baseDelayMs * Math.pow(2, attempt),
4703
+ config.maxDelayMs
4704
+ );
4705
+ const jitter = Math.floor(Math.random() * raw * config.jitterFactor);
4706
+ return raw + jitter;
4707
+ }
4708
+ function rejectAfter(ms, message) {
4709
+ let timer;
4710
+ const promise = new Promise((_, reject) => {
4711
+ timer = setTimeout(() => reject(new Error(message)), ms);
4712
+ });
4713
+ return { promise, cancel: () => clearTimeout(timer) };
4714
+ }
4715
+ function sleep(ms) {
4716
+ return new Promise((resolve) => setTimeout(resolve, ms));
4717
+ }
4718
+ function redactUrl(raw) {
4719
+ try {
4720
+ const u = new URL(raw);
4721
+ const sensitive = /^(api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|key|password|bearer|credential|jwt)$/i;
4722
+ for (const k of [...u.searchParams.keys()]) {
4723
+ if (sensitive.test(k)) {
4724
+ u.searchParams.set(k, "***");
4725
+ }
4726
+ }
4727
+ return u.toString();
4728
+ } catch {
4729
+ return raw;
4730
+ }
4731
+ }
4732
+ var RpcPool = class _RpcPool {
4733
+ endpoints;
4734
+ strategy;
4735
+ retryConfig;
4736
+ requestTimeoutMs;
4737
+ verbose;
4738
+ /** Round-robin index tracker. */
4739
+ rrIndex = 0;
4740
+ /** Consecutive failure threshold before marking an endpoint unhealthy. */
4741
+ static UNHEALTHY_THRESHOLD = 3;
4742
+ /** Minimum endpoints before auto-recovery is attempted. */
4743
+ static MIN_HEALTHY = 1;
4744
+ constructor(config) {
4745
+ if (!config.endpoints || config.endpoints.length === 0) {
4746
+ throw new Error("RpcPool: at least one endpoint is required");
4747
+ }
4748
+ this.strategy = config.strategy ?? "failover";
4749
+ this.retryConfig = resolveRetryConfig(config.retry);
4750
+ this.requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
4751
+ this.verbose = config.verbose ?? true;
4752
+ const commitment = config.commitment ?? "confirmed";
4753
+ this.endpoints = config.endpoints.map((raw) => {
4754
+ const ep = normalizeEndpoint(raw);
4755
+ const connConfig = {
4756
+ commitment,
4757
+ ...ep.connectionConfig
4758
+ };
4759
+ return {
4760
+ config: ep,
4761
+ connection: new Connection5(ep.url, connConfig),
4762
+ label: endpointLabel(ep),
4763
+ weight: Math.max(1, ep.weight ?? 1),
4764
+ failures: 0,
4765
+ healthy: true,
4766
+ lastLatencyMs: -1
4767
+ };
4768
+ });
4769
+ }
4770
+ // -----------------------------------------------------------------------
4771
+ // Public API
4772
+ // -----------------------------------------------------------------------
4773
+ /**
4774
+ * Execute a function against a pooled connection with automatic retry
4775
+ * and failover.
4776
+ *
4777
+ * @param fn - Async function that receives a `Connection` and returns a result.
4778
+ * @returns The result of `fn`.
4779
+ * @throws The last error if all retries and failovers are exhausted.
4780
+ *
4781
+ * @example
4782
+ * ```ts
4783
+ * const balance = await pool.call(c => c.getBalance(pubkey));
4784
+ * const markets = await pool.call(c => discoverMarkets(c, programId, opts));
4785
+ * ```
4786
+ */
4787
+ async call(fn) {
4788
+ const maxAttempts = this.retryConfig ? this.retryConfig.maxRetries + 1 : 1;
4789
+ let lastError;
4790
+ const triedEndpoints = /* @__PURE__ */ new Set();
4791
+ const maxTotalIterations = maxAttempts + this.endpoints.length;
4792
+ let totalIterations = 0;
4793
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4794
+ if (++totalIterations > maxTotalIterations) break;
4795
+ const epIdx = this.selectEndpoint(triedEndpoints);
4796
+ if (epIdx === -1) {
4797
+ break;
4798
+ }
4799
+ const ep = this.endpoints[epIdx];
4800
+ const timeout = rejectAfter(this.requestTimeoutMs, `RPC request timed out after ${this.requestTimeoutMs}ms (${ep.label})`);
4801
+ try {
4802
+ const result = await Promise.race([
4803
+ fn(ep.connection),
4804
+ timeout.promise
4805
+ ]);
4806
+ timeout.cancel();
4807
+ ep.failures = 0;
4808
+ ep.healthy = true;
4809
+ return result;
4810
+ } catch (err) {
4811
+ timeout.cancel();
4812
+ lastError = err;
4813
+ ep.failures++;
4814
+ if (ep.failures >= _RpcPool.UNHEALTHY_THRESHOLD) {
4815
+ ep.healthy = false;
4816
+ if (this.verbose) {
4817
+ console.warn(
4818
+ `[RpcPool] Endpoint ${ep.label} marked unhealthy after ${ep.failures} consecutive failures`
4819
+ );
4820
+ }
4821
+ }
4822
+ const retryable = this.retryConfig ? isRetryable(err, this.retryConfig.retryableStatusCodes) : false;
4823
+ if (!retryable) {
4824
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4825
+ triedEndpoints.add(epIdx);
4826
+ attempt--;
4827
+ if (triedEndpoints.size >= this.endpoints.length) break;
4828
+ continue;
4829
+ }
4830
+ throw err;
4831
+ }
4832
+ if (this.verbose) {
4833
+ console.warn(
4834
+ `[RpcPool] Retryable error on ${ep.label} (attempt ${attempt + 1}/${maxAttempts}):`,
4835
+ err instanceof Error ? err.message : err
4836
+ );
4837
+ }
4838
+ if (this.strategy === "failover" && this.endpoints.length > 1) {
4839
+ triedEndpoints.add(epIdx);
4840
+ }
4841
+ if (attempt < maxAttempts - 1 && this.retryConfig) {
4842
+ const delay = computeDelay(attempt, this.retryConfig);
4843
+ await sleep(delay);
4844
+ }
4845
+ }
4846
+ }
4847
+ this.maybeRecoverEndpoints();
4848
+ throw lastError ?? new Error("RpcPool: all endpoints exhausted");
4849
+ }
4850
+ /**
4851
+ * Get a raw `Connection` from the current preferred endpoint.
4852
+ * Useful when you need to pass a Connection to external code.
4853
+ *
4854
+ * NOTE: This bypasses retry and failover logic. Prefer `call()`.
4855
+ *
4856
+ * @returns Solana Connection from the current preferred endpoint.
4857
+ *
4858
+ * @example
4859
+ * ```ts
4860
+ * const conn = pool.getConnection();
4861
+ * const balance = await conn.getBalance(pubkey);
4862
+ * ```
4863
+ */
4864
+ getConnection() {
4865
+ const idx = this.selectEndpoint();
4866
+ if (idx === -1) {
4867
+ this.maybeRecoverEndpoints();
4868
+ return this.endpoints[0].connection;
4869
+ }
4870
+ return this.endpoints[idx].connection;
4871
+ }
4872
+ /**
4873
+ * Run a health check against all endpoints in the pool.
4874
+ *
4875
+ * @param timeoutMs - Per-endpoint probe timeout (default: 5000)
4876
+ * @returns Array of health results, one per endpoint.
4877
+ *
4878
+ * @example
4879
+ * ```ts
4880
+ * const results = await pool.healthCheck();
4881
+ * for (const r of results) {
4882
+ * console.log(`${r.endpoint}: ${r.healthy ? 'UP' : 'DOWN'} (${r.latencyMs}ms, slot ${r.slot})`);
4883
+ * }
4884
+ * ```
4885
+ */
4886
+ async healthCheck(timeoutMs = 5e3) {
4887
+ const results = await Promise.all(
4888
+ this.endpoints.map(async (ep) => {
4889
+ const result = await checkRpcHealth(ep.config.url, timeoutMs);
4890
+ ep.lastLatencyMs = result.latencyMs;
4891
+ ep.healthy = result.healthy;
4892
+ if (result.healthy) ep.failures = 0;
4893
+ result.endpoint = redactUrl(result.endpoint);
4894
+ return result;
4895
+ })
4896
+ );
4897
+ return results;
4898
+ }
4899
+ /**
4900
+ * Get the number of endpoints in the pool.
4901
+ */
4902
+ get size() {
4903
+ return this.endpoints.length;
4904
+ }
4905
+ /**
4906
+ * Get the number of currently healthy endpoints.
4907
+ */
4908
+ get healthyCount() {
4909
+ return this.endpoints.filter((ep) => ep.healthy).length;
4910
+ }
4911
+ /**
4912
+ * Get endpoint labels and their current status.
4913
+ *
4914
+ * @returns Array of `{ label, url, healthy, failures, lastLatencyMs }`.
4915
+ */
4916
+ status() {
4917
+ return this.endpoints.map((ep) => ({
4918
+ label: ep.label,
4919
+ url: redactUrl(ep.config.url),
4920
+ healthy: ep.healthy,
4921
+ failures: ep.failures,
4922
+ lastLatencyMs: ep.lastLatencyMs
4923
+ }));
4924
+ }
4925
+ // -----------------------------------------------------------------------
4926
+ // Internals
4927
+ // -----------------------------------------------------------------------
4928
+ /**
4929
+ * Select the next endpoint based on strategy.
4930
+ * Returns -1 if no endpoint is available.
4931
+ */
4932
+ selectEndpoint(exclude) {
4933
+ const healthy = this.endpoints.map((ep, i) => ({ ep, i })).filter(({ ep, i }) => ep.healthy && !exclude?.has(i));
4934
+ if (healthy.length === 0) {
4935
+ const remaining = this.endpoints.map((_, i) => i).filter((i) => !exclude?.has(i));
4936
+ return remaining.length > 0 ? remaining[0] : -1;
4937
+ }
4938
+ if (this.strategy === "failover") {
4939
+ return healthy[0].i;
4940
+ }
4941
+ const totalWeight = healthy.reduce((sum, { ep }) => sum + ep.weight, 0);
4942
+ this.rrIndex = (this.rrIndex + 1) % totalWeight;
4943
+ let cumulative = 0;
4944
+ for (const { ep, i } of healthy) {
4945
+ cumulative += ep.weight;
4946
+ if (this.rrIndex < cumulative) return i;
4947
+ }
4948
+ return healthy[healthy.length - 1].i;
4949
+ }
4950
+ /**
4951
+ * If all endpoints are unhealthy, reset them so we at least try again.
4952
+ */
4953
+ maybeRecoverEndpoints() {
4954
+ const healthyCount = this.endpoints.filter((ep) => ep.healthy).length;
4955
+ if (healthyCount < _RpcPool.MIN_HEALTHY) {
4956
+ if (this.verbose) {
4957
+ console.warn("[RpcPool] All endpoints unhealthy \u2014 resetting for recovery");
4958
+ }
4959
+ for (const ep of this.endpoints) {
4960
+ ep.healthy = true;
4961
+ ep.failures = 0;
4962
+ }
4963
+ }
4964
+ }
4965
+ };
4966
+ async function withRetry(fn, config) {
4967
+ const resolved = resolveRetryConfig(config) ?? {
4968
+ maxRetries: 3,
4969
+ baseDelayMs: 500,
4970
+ maxDelayMs: 1e4,
4971
+ jitterFactor: 0.25,
4972
+ retryableStatusCodes: [429, 502, 503, 504]
4973
+ };
4974
+ let lastError;
4975
+ const maxAttempts = resolved.maxRetries + 1;
4976
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4977
+ try {
4978
+ return await fn();
4979
+ } catch (err) {
4980
+ lastError = err;
4981
+ if (!isRetryable(err, resolved.retryableStatusCodes)) {
4982
+ throw err;
4983
+ }
4984
+ if (attempt < maxAttempts - 1) {
4985
+ const delay = computeDelay(attempt, resolved);
4986
+ await sleep(delay);
4987
+ }
4988
+ }
4989
+ }
4990
+ throw lastError ?? new Error("withRetry: all attempts exhausted");
4991
+ }
4992
+ var _internal = {
4993
+ isRetryable,
4994
+ computeDelay,
4995
+ resolveRetryConfig,
4996
+ normalizeEndpoint,
4997
+ endpointLabel
4998
+ };
4999
+
4497
5000
  // src/runtime/tx.ts
4498
5001
  import {
4499
5002
  TransactionInstruction as TransactionInstruction2,
@@ -4645,6 +5148,139 @@ function formatResult(result, jsonMode) {
4645
5148
  return lines.join("\n");
4646
5149
  }
4647
5150
 
5151
+ // src/runtime/lighthouse.ts
5152
+ import { PublicKey as PublicKey13, Transaction as Transaction2 } from "@solana/web3.js";
5153
+ var LIGHTHOUSE_PROGRAM_ID = new PublicKey13(
5154
+ "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95"
5155
+ );
5156
+ var LIGHTHOUSE_PROGRAM_ID_STR2 = "L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95";
5157
+ var LIGHTHOUSE_CONSTRAINT_ADDRESS = 6400;
5158
+ var LIGHTHOUSE_ERROR_CODES = /* @__PURE__ */ new Set([
5159
+ 6e3,
5160
+ // InstructionMissing
5161
+ 6001,
5162
+ // InstructionFallbackNotFound
5163
+ 6002,
5164
+ // InstructionDidNotDeserialize
5165
+ 6003,
5166
+ // InstructionDidNotSerialize
5167
+ 6016,
5168
+ // IdlInstructionStub
5169
+ 6032,
5170
+ // ConstraintMut
5171
+ 6033,
5172
+ // ConstraintHasOne
5173
+ 6034,
5174
+ // ConstraintSigner
5175
+ 6035,
5176
+ // ConstraintRaw
5177
+ 6036,
5178
+ // ConstraintOwner
5179
+ 6037,
5180
+ // ConstraintRentExempt
5181
+ 6038,
5182
+ // ConstraintSeeds
5183
+ 6039,
5184
+ // ConstraintExecutable
5185
+ 6040,
5186
+ // ConstraintState
5187
+ 6041,
5188
+ // ConstraintAssociated
5189
+ 6042,
5190
+ // ConstraintAssociatedInit
5191
+ 6043,
5192
+ // ConstraintClose
5193
+ 6400
5194
+ // ConstraintAddress (the one we hit most often)
5195
+ ]);
5196
+ function isLighthouseInstruction(ix) {
5197
+ return ix.programId.equals(LIGHTHOUSE_PROGRAM_ID);
5198
+ }
5199
+ function isLighthouseError(error) {
5200
+ const msg = extractErrorMessage(error);
5201
+ if (!msg) return false;
5202
+ if (msg.includes(LIGHTHOUSE_PROGRAM_ID_STR2)) return true;
5203
+ if (/custom\s+program\s+error:\s*0x1900\b/i.test(msg)) return true;
5204
+ if (/"Custom"\s*:\s*6400\b/.test(msg) && /InstructionError/i.test(msg)) return true;
5205
+ return false;
5206
+ }
5207
+ function isLighthouseFailureInLogs(logs) {
5208
+ if (!Array.isArray(logs)) return false;
5209
+ let insideLighthouse = false;
5210
+ for (const line of logs) {
5211
+ if (typeof line !== "string") continue;
5212
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} invoke`)) {
5213
+ insideLighthouse = true;
5214
+ continue;
5215
+ }
5216
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} success`)) {
5217
+ insideLighthouse = false;
5218
+ continue;
5219
+ }
5220
+ if (insideLighthouse && /failed/i.test(line)) {
5221
+ return true;
5222
+ }
5223
+ if (line.includes(`Program ${LIGHTHOUSE_PROGRAM_ID_STR2} failed`)) {
5224
+ return true;
5225
+ }
5226
+ }
5227
+ return false;
5228
+ }
5229
+ function stripLighthouseInstructions(instructions, percolatorProgramId) {
5230
+ if (percolatorProgramId) {
5231
+ const hasPercolatorIx = instructions.some(
5232
+ (ix) => ix.programId.equals(percolatorProgramId)
5233
+ );
5234
+ if (!hasPercolatorIx) {
5235
+ return instructions;
5236
+ }
5237
+ }
5238
+ return instructions.filter((ix) => !isLighthouseInstruction(ix));
5239
+ }
5240
+ function stripLighthouseFromTransaction(transaction, percolatorProgramId) {
5241
+ if (percolatorProgramId) {
5242
+ const hasPercolatorIx = transaction.instructions.some(
5243
+ (ix) => ix.programId.equals(percolatorProgramId)
5244
+ );
5245
+ if (!hasPercolatorIx) return transaction;
5246
+ }
5247
+ const hasLighthouse = transaction.instructions.some(isLighthouseInstruction);
5248
+ if (!hasLighthouse) return transaction;
5249
+ const clean = new Transaction2();
5250
+ clean.recentBlockhash = transaction.recentBlockhash;
5251
+ clean.feePayer = transaction.feePayer;
5252
+ for (const ix of transaction.instructions) {
5253
+ if (!isLighthouseInstruction(ix)) {
5254
+ clean.add(ix);
5255
+ }
5256
+ }
5257
+ return clean;
5258
+ }
5259
+ function countLighthouseInstructions(ixsOrTx) {
5260
+ const instructions = Array.isArray(ixsOrTx) ? ixsOrTx : ixsOrTx.instructions;
5261
+ return instructions.filter(isLighthouseInstruction).length;
5262
+ }
5263
+ 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";
5264
+ function classifyLighthouseError(error) {
5265
+ if (isLighthouseError(error)) {
5266
+ return LIGHTHOUSE_USER_MESSAGE;
5267
+ }
5268
+ return null;
5269
+ }
5270
+ function extractErrorMessage(error) {
5271
+ if (!error) return null;
5272
+ if (typeof error === "string") return error;
5273
+ if (error instanceof Error) return error.message;
5274
+ if (typeof error === "object" && "message" in error) {
5275
+ return String(error.message);
5276
+ }
5277
+ try {
5278
+ return JSON.stringify(error);
5279
+ } catch {
5280
+ return null;
5281
+ }
5282
+ }
5283
+
4648
5284
  // src/math/trading.ts
4649
5285
  function computeMarkPnl(positionSize, entryPrice, oraclePrice) {
4650
5286
  if (positionSize === 0n || oraclePrice === 0n) return 0n;
@@ -4669,16 +5305,10 @@ function computeLiqPrice(entryPrice, capital, positionSize, maintenanceMarginBps
4669
5305
  function computePreTradeLiqPrice(oracleE6, margin, posSize, maintBps, feeBps, direction) {
4670
5306
  if (oracleE6 === 0n || margin === 0n || posSize === 0n) return 0n;
4671
5307
  const absPos = posSize < 0n ? -posSize : posSize;
5308
+ const fee = absPos * feeBps / 10000n;
5309
+ const effectiveCapital = margin > fee ? margin - fee : 0n;
4672
5310
  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);
5311
+ return computeLiqPrice(oracleE6, effectiveCapital, signedPos, maintBps);
4682
5312
  }
4683
5313
  function computeTradingFee(notional, tradingFeeBps) {
4684
5314
  return notional * tradingFeeBps / 10000n;
@@ -4698,9 +5328,20 @@ function computeFeeSplit(totalFee, config) {
4698
5328
  if (config.lpBps === 0n && config.protocolBps === 0n && config.creatorBps === 0n) {
4699
5329
  return [totalFee, 0n, 0n];
4700
5330
  }
5331
+ const totalBps = config.lpBps + config.protocolBps + config.creatorBps;
5332
+ if (totalBps !== 10000n) {
5333
+ throw new Error(
5334
+ `Fee split must equal exactly 10000 bps (100%): lpBps=${config.lpBps} + protocolBps=${config.protocolBps} + creatorBps=${config.creatorBps} = ${totalBps}`
5335
+ );
5336
+ }
4701
5337
  const lp = totalFee * config.lpBps / 10000n;
4702
5338
  const protocol = totalFee * config.protocolBps / 10000n;
4703
5339
  const creator = totalFee - lp - protocol;
5340
+ if (creator < 0n) {
5341
+ throw new Error(
5342
+ `Internal error: creator fee is negative (${creator}). This should not happen if lpBps + protocolBps + creatorBps === 10000.`
5343
+ );
5344
+ }
4704
5345
  return [lp, protocol, creator];
4705
5346
  }
4706
5347
  function computePnlPercent(pnlTokens, capital) {
@@ -4715,10 +5356,17 @@ function computePnlPercent(pnlTokens, capital) {
4715
5356
  }
4716
5357
  function computeEstimatedEntryPrice(oracleE6, tradingFeeBps, direction) {
4717
5358
  if (oracleE6 === 0n) return 0n;
5359
+ if (tradingFeeBps < 0n) {
5360
+ throw new Error(`computeEstimatedEntryPrice: tradingFeeBps must be non-negative, got ${tradingFeeBps}`);
5361
+ }
4718
5362
  const feeImpact = oracleE6 * tradingFeeBps / 10000n;
4719
- if (direction === "long") return oracleE6 + feeImpact;
4720
- const shortEntry = oracleE6 - feeImpact;
4721
- return shortEntry > 0n ? shortEntry : 1n;
5363
+ const result = direction === "long" ? oracleE6 + feeImpact : oracleE6 - feeImpact;
5364
+ if (result <= 0n) {
5365
+ throw new Error(
5366
+ `computeEstimatedEntryPrice: result ${result} is non-positive (tradingFeeBps=${tradingFeeBps} too high for oracle=${oracleE6})`
5367
+ );
5368
+ }
5369
+ return result;
4722
5370
  }
4723
5371
  var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
4724
5372
  var MIN_SAFE_BIGINT = BigInt(-Number.MAX_SAFE_INTEGER);
@@ -4739,7 +5387,12 @@ function computeMaxLeverage(initialMarginBps) {
4739
5387
  if (initialMarginBps <= 0n) {
4740
5388
  throw new Error("computeMaxLeverage: initialMarginBps must be positive");
4741
5389
  }
4742
- return Number(10000n / initialMarginBps);
5390
+ const scaledResult = 10000n * 1000000n / initialMarginBps;
5391
+ return Number(scaledResult) / 1e6;
5392
+ }
5393
+ function computeMaxWithdrawable(capital, pnl, reservedPnl) {
5394
+ const maturedPnl = pnl - reservedPnl;
5395
+ return capital + (maturedPnl > 0n ? maturedPnl : 0n);
4743
5396
  }
4744
5397
 
4745
5398
  // src/math/warmup.ts
@@ -4751,6 +5404,9 @@ function computeWarmupUnlockedCapital(totalCapital, currentSlot, warmupStartSlot
4751
5404
  return totalCapital * elapsed / warmupPeriodSlots;
4752
5405
  }
4753
5406
  function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
5407
+ if (initialMarginBps <= 0n) {
5408
+ throw new Error("computeWarmupLeverageCap: initialMarginBps must be positive");
5409
+ }
4754
5410
  const maxLev = computeMaxLeverage(initialMarginBps);
4755
5411
  if (warmupPeriodSlots === 0n || warmupStartSlot === 0n) return maxLev;
4756
5412
  if (totalCapital <= 0n) return 1;
@@ -4761,7 +5417,14 @@ function computeWarmupLeverageCap(initialMarginBps, totalCapital, currentSlot, w
4761
5417
  warmupPeriodSlots
4762
5418
  );
4763
5419
  if (unlocked <= 0n) return 1;
4764
- const effectiveLev = Number(BigInt(maxLev) * unlocked / totalCapital);
5420
+ const scaledResult = BigInt(maxLev) * unlocked / totalCapital;
5421
+ if (scaledResult > BigInt(Number.MAX_SAFE_INTEGER)) {
5422
+ console.warn(
5423
+ `[computeWarmupLeverageCap] Warning: effective leverage ${scaledResult} exceeds MAX_SAFE_INTEGER, returning MAX_SAFE_INTEGER as a safety bound`
5424
+ );
5425
+ return Number.MAX_SAFE_INTEGER;
5426
+ }
5427
+ const effectiveLev = Number(scaledResult);
4765
5428
  return Math.max(1, effectiveLev);
4766
5429
  }
4767
5430
  function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlot, warmupStartSlot, warmupPeriodSlots) {
@@ -4774,10 +5437,41 @@ function computeWarmupMaxPositionSize(initialMarginBps, totalCapital, currentSlo
4774
5437
  );
4775
5438
  return unlocked * BigInt(maxLev);
4776
5439
  }
5440
+ function computeWarmupProgress(currentSlot, warmupStartedAtSlot, warmupPeriodSlots, pnl, reservedPnl) {
5441
+ if (warmupPeriodSlots === 0n || warmupStartedAtSlot === 0n) {
5442
+ return {
5443
+ maturedPnl: pnl > 0n ? pnl : 0n,
5444
+ reservedPnl: 0n,
5445
+ progressBps: 10000n,
5446
+ // 100%
5447
+ slotsRemaining: 0n
5448
+ };
5449
+ }
5450
+ const elapsed = currentSlot >= warmupStartedAtSlot ? currentSlot - warmupStartedAtSlot : 0n;
5451
+ if (elapsed >= warmupPeriodSlots) {
5452
+ return {
5453
+ maturedPnl: pnl > 0n ? pnl : 0n,
5454
+ reservedPnl: 0n,
5455
+ progressBps: 10000n,
5456
+ // 100%
5457
+ slotsRemaining: 0n
5458
+ };
5459
+ }
5460
+ const progressBps = elapsed * 10000n / warmupPeriodSlots;
5461
+ const slotsRemaining = warmupPeriodSlots - elapsed;
5462
+ const maturedPnl = pnl > 0n ? pnl * progressBps / 10000n : 0n;
5463
+ const locked = reservedPnl > 0n ? reservedPnl : 0n;
5464
+ return {
5465
+ maturedPnl,
5466
+ reservedPnl: locked,
5467
+ progressBps,
5468
+ slotsRemaining
5469
+ };
5470
+ }
4777
5471
 
4778
5472
  // src/validation.ts
4779
- import { PublicKey as PublicKey13 } from "@solana/web3.js";
4780
- var U16_MAX2 = 65535;
5473
+ import { PublicKey as PublicKey14 } from "@solana/web3.js";
5474
+ var U16_MAX = 65535;
4781
5475
  var U64_MAX = BigInt("18446744073709551615");
4782
5476
  var I64_MIN = BigInt("-9223372036854775808");
4783
5477
  var I64_MAX = BigInt("9223372036854775807");
@@ -4806,7 +5500,7 @@ var ValidationError = class extends Error {
4806
5500
  };
4807
5501
  function validatePublicKey(value, field) {
4808
5502
  try {
4809
- return new PublicKey13(value);
5503
+ return new PublicKey14(value);
4810
5504
  } catch {
4811
5505
  throw new ValidationError(
4812
5506
  field,
@@ -4817,24 +5511,26 @@ function validatePublicKey(value, field) {
4817
5511
  function validateIndex(value, field) {
4818
5512
  const t = requireDecimalUIntString(value, field);
4819
5513
  const bi = BigInt(t);
4820
- if (bi > BigInt(U16_MAX2)) {
5514
+ if (bi > BigInt(U16_MAX)) {
4821
5515
  throw new ValidationError(
4822
5516
  field,
4823
- `must be <= ${U16_MAX2} (u16 max), got ${t}`
5517
+ `must be <= ${U16_MAX} (u16 max), got ${t}`
4824
5518
  );
4825
5519
  }
5520
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5521
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5522
+ }
4826
5523
  return Number(bi);
4827
5524
  }
4828
5525
  function validateAmount(value, field) {
4829
- let num;
4830
- try {
4831
- num = BigInt(value);
4832
- } catch {
5526
+ const t = value.trim();
5527
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4833
5528
  throw new ValidationError(
4834
5529
  field,
4835
- `"${value}" is not a valid number. Use decimal digits only.`
5530
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4836
5531
  );
4837
5532
  }
5533
+ const num = BigInt(t);
4838
5534
  if (num < 0n) {
4839
5535
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4840
5536
  }
@@ -4847,15 +5543,14 @@ function validateAmount(value, field) {
4847
5543
  return num;
4848
5544
  }
4849
5545
  function validateU128(value, field) {
4850
- let num;
4851
- try {
4852
- num = BigInt(value);
4853
- } catch {
5546
+ const t = value.trim();
5547
+ if (!/^(0|[1-9]\d*)$/.test(t)) {
4854
5548
  throw new ValidationError(
4855
5549
  field,
4856
- `"${value}" is not a valid number. Use decimal digits only.`
5550
+ `"${value}" is not a valid non-negative integer. Use decimal digits only.`
4857
5551
  );
4858
5552
  }
5553
+ const num = BigInt(t);
4859
5554
  if (num < 0n) {
4860
5555
  throw new ValidationError(field, `must be non-negative, got ${num}`);
4861
5556
  }
@@ -4868,15 +5563,14 @@ function validateU128(value, field) {
4868
5563
  return num;
4869
5564
  }
4870
5565
  function validateI64(value, field) {
4871
- let num;
4872
- try {
4873
- num = BigInt(value);
4874
- } catch {
5566
+ const t = value.trim();
5567
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4875
5568
  throw new ValidationError(
4876
5569
  field,
4877
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5570
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4878
5571
  );
4879
5572
  }
5573
+ const num = BigInt(t);
4880
5574
  if (num < I64_MIN) {
4881
5575
  throw new ValidationError(
4882
5576
  field,
@@ -4892,15 +5586,14 @@ function validateI64(value, field) {
4892
5586
  return num;
4893
5587
  }
4894
5588
  function validateI128(value, field) {
4895
- let num;
4896
- try {
4897
- num = BigInt(value);
4898
- } catch {
5589
+ const t = value.trim();
5590
+ if (!/^-?(0|[1-9]\d*)$/.test(t)) {
4899
5591
  throw new ValidationError(
4900
5592
  field,
4901
- `"${value}" is not a valid number. Use decimal digits only, with optional leading minus.`
5593
+ `"${value}" is not a valid integer. Use decimal digits only, with optional leading minus.`
4902
5594
  );
4903
5595
  }
5596
+ const num = BigInt(t);
4904
5597
  if (num < I128_MIN) {
4905
5598
  throw new ValidationError(
4906
5599
  field,
@@ -4924,6 +5617,9 @@ function validateBps(value, field) {
4924
5617
  `must be <= 10000 (100%), got ${t}`
4925
5618
  );
4926
5619
  }
5620
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5621
+ throw new ValidationError(field, `internal error: bps value exceeds MAX_SAFE_INTEGER`);
5622
+ }
4927
5623
  return Number(bi);
4928
5624
  }
4929
5625
  function validateU64(value, field) {
@@ -4932,12 +5628,15 @@ function validateU64(value, field) {
4932
5628
  function validateU16(value, field) {
4933
5629
  const t = requireDecimalUIntString(value, field);
4934
5630
  const bi = BigInt(t);
4935
- if (bi > BigInt(U16_MAX2)) {
5631
+ if (bi > BigInt(U16_MAX)) {
4936
5632
  throw new ValidationError(
4937
5633
  field,
4938
- `must be <= ${U16_MAX2} (u16 max), got ${t}`
5634
+ `must be <= ${U16_MAX} (u16 max), got ${t}`
4939
5635
  );
4940
5636
  }
5637
+ if (bi > BigInt(Number.MAX_SAFE_INTEGER)) {
5638
+ throw new ValidationError(field, `internal error: u16 value exceeds MAX_SAFE_INTEGER`);
5639
+ }
4941
5640
  return Number(bi);
4942
5641
  }
4943
5642
 
@@ -4988,7 +5687,9 @@ function parseDexScreenerPairs(json) {
4988
5687
  else if (liquidity > 1e4) confidence = 60;
4989
5688
  else if (liquidity > 1e3) confidence = 45;
4990
5689
  const priceUsd = pair.priceUsd;
4991
- const price = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) || 0 : 0;
5690
+ const rawPrice = typeof priceUsd === "string" || typeof priceUsd === "number" ? parseFloat(String(priceUsd)) : NaN;
5691
+ if (!Number.isFinite(rawPrice) || rawPrice <= 0) continue;
5692
+ const price = rawPrice;
4992
5693
  let baseSym = "?";
4993
5694
  let quoteSym = "?";
4994
5695
  if (isRecord(pair.baseToken) && typeof pair.baseToken.symbol === "string") {
@@ -5019,8 +5720,8 @@ function parseJupiterMintEntry(json, mint) {
5019
5720
  if (!isRecord(row)) return null;
5020
5721
  const rawPrice = row.price;
5021
5722
  if (rawPrice === void 0 || rawPrice === null) return null;
5022
- const price = parseFloat(String(rawPrice)) || 0;
5023
- if (price <= 0) return null;
5723
+ const price = parseFloat(String(rawPrice));
5724
+ if (!Number.isFinite(price) || price <= 0) return null;
5024
5725
  let mintSymbol = "?";
5025
5726
  if (typeof row.mintSymbol === "string") mintSymbol = row.mintSymbol;
5026
5727
  return { price, mintSymbol };
@@ -5086,10 +5787,17 @@ async function fetchDexSources(mint, signal) {
5086
5787
  headers: { "User-Agent": "percolator/1.0" }
5087
5788
  }
5088
5789
  );
5089
- if (!resp.ok) return [];
5790
+ if (!resp.ok) {
5791
+ console.debug(`[fetchDexSources] HTTP ${resp.status} for mint ${mint}`);
5792
+ return [];
5793
+ }
5090
5794
  const json = await resp.json();
5091
5795
  return parseDexScreenerPairs(json);
5092
- } catch {
5796
+ } catch (err) {
5797
+ console.warn(
5798
+ `[fetchDexSources] Error fetching DexScreener data for mint ${mint}:`,
5799
+ err instanceof Error ? err.message : String(err)
5800
+ );
5093
5801
  return [];
5094
5802
  }
5095
5803
  }
@@ -5100,7 +5808,7 @@ function lookupPythSource(mint) {
5100
5808
  type: "pyth",
5101
5809
  address: entry.feedId,
5102
5810
  pairLabel: `${entry.symbol} / USD (Pyth)`,
5103
- liquidity: Infinity,
5811
+ liquidity: Number.MAX_SAFE_INTEGER,
5104
5812
  // Pyth is considered deep liquidity
5105
5813
  price: 0,
5106
5814
  // We don't fetch live price here; caller can enrich
@@ -5117,10 +5825,16 @@ async function fetchJupiterSource(mint, signal) {
5117
5825
  headers: { "User-Agent": "percolator/1.0" }
5118
5826
  }
5119
5827
  );
5120
- if (!resp.ok) return null;
5828
+ if (!resp.ok) {
5829
+ console.debug(`[fetchJupiterSource] HTTP ${resp.status} for mint ${mint}`);
5830
+ return null;
5831
+ }
5121
5832
  const json = await resp.json();
5122
5833
  const row = parseJupiterMintEntry(json, mint);
5123
- if (!row) return null;
5834
+ if (!row) {
5835
+ console.debug(`[fetchJupiterSource] No price data from Jupiter for mint ${mint}`);
5836
+ return null;
5837
+ }
5124
5838
  return {
5125
5839
  type: "jupiter",
5126
5840
  address: mint,
@@ -5131,23 +5845,39 @@ async function fetchJupiterSource(mint, signal) {
5131
5845
  confidence: 40
5132
5846
  // Fallback — lower confidence
5133
5847
  };
5134
- } catch {
5848
+ } catch (err) {
5849
+ console.warn(
5850
+ `[fetchJupiterSource] Error fetching Jupiter data for mint ${mint}:`,
5851
+ err instanceof Error ? err.message : String(err)
5852
+ );
5135
5853
  return null;
5136
5854
  }
5137
5855
  }
5138
5856
  async function resolvePrice(mint, signal, options) {
5139
5857
  const timeoutMs = options?.timeoutMs ?? DEFAULT_RESOLVE_TIMEOUT_MS;
5140
5858
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
5141
- const combinedSignal = signal ? combineAbortSignals([signal, timeoutSignal]) : timeoutSignal;
5859
+ const effectiveSignal2 = signal ? combineAbortSignals([signal, timeoutSignal]) : timeoutSignal;
5142
5860
  const [dexSources, jupiterSource] = await Promise.all([
5143
- fetchDexSources(mint, combinedSignal),
5144
- fetchJupiterSource(mint, combinedSignal)
5861
+ fetchDexSources(mint, effectiveSignal2),
5862
+ fetchJupiterSource(mint, effectiveSignal2)
5145
5863
  ]);
5146
5864
  const pythSource = lookupPythSource(mint);
5147
5865
  const allSources = [];
5148
5866
  if (pythSource) {
5149
- const refPrice = dexSources[0]?.price || jupiterSource?.price || 0;
5150
- pythSource.price = refPrice;
5867
+ const dexPrice = dexSources[0]?.price ?? 0;
5868
+ const jupPrice = jupiterSource?.price ?? 0;
5869
+ if (dexPrice > 0 && jupPrice > 0) {
5870
+ const mid = (dexPrice + jupPrice) / 2;
5871
+ const deviation = Math.abs(dexPrice - jupPrice) / mid;
5872
+ if (deviation > 0.5) {
5873
+ pythSource.price = 0;
5874
+ pythSource.confidence = 20;
5875
+ } else {
5876
+ pythSource.price = mid;
5877
+ }
5878
+ } else {
5879
+ pythSource.price = dexPrice || jupPrice || 0;
5880
+ }
5151
5881
  allSources.push(pythSource);
5152
5882
  }
5153
5883
  allSources.push(...dexSources);
@@ -5172,9 +5902,7 @@ export {
5172
5902
  ACCOUNTS_CLOSE_ACCOUNT,
5173
5903
  ACCOUNTS_CLOSE_SLAB,
5174
5904
  ACCOUNTS_CLOSE_STALE_SLABS,
5175
- ACCOUNTS_CREATE_INSURANCE_MINT,
5176
5905
  ACCOUNTS_DEPOSIT_COLLATERAL,
5177
- ACCOUNTS_DEPOSIT_INSURANCE_LP,
5178
5906
  ACCOUNTS_EXECUTE_ADL,
5179
5907
  ACCOUNTS_FUND_MARKET_INSURANCE,
5180
5908
  ACCOUNTS_INIT_LP,
@@ -5190,7 +5918,6 @@ export {
5190
5918
  ACCOUNTS_QUEUE_WITHDRAWAL,
5191
5919
  ACCOUNTS_RECLAIM_SLAB_RENT,
5192
5920
  ACCOUNTS_RESOLVE_MARKET,
5193
- ACCOUNTS_SET_DEX_POOL,
5194
5921
  ACCOUNTS_SET_INSURANCE_ISOLATION,
5195
5922
  ACCOUNTS_SET_MAINTENANCE_FEE,
5196
5923
  ACCOUNTS_SET_OI_IMBALANCE_HARD_BLOCK,
@@ -5200,7 +5927,6 @@ export {
5200
5927
  ACCOUNTS_SET_RISK_THRESHOLD,
5201
5928
  ACCOUNTS_SET_WALLET_CAP,
5202
5929
  ACCOUNTS_TOPUP_INSURANCE,
5203
- ACCOUNTS_TOPUP_KEEPER_FUND,
5204
5930
  ACCOUNTS_TRADE_CPI,
5205
5931
  ACCOUNTS_TRADE_NOCPI,
5206
5932
  ACCOUNTS_TRANSFER_POSITION_OWNERSHIP,
@@ -5209,17 +5935,22 @@ export {
5209
5935
  ACCOUNTS_UPDATE_CONFIG,
5210
5936
  ACCOUNTS_WITHDRAW_COLLATERAL,
5211
5937
  ACCOUNTS_WITHDRAW_INSURANCE,
5212
- ACCOUNTS_WITHDRAW_INSURANCE_LP,
5213
5938
  AccountKind,
5214
5939
  CHAINLINK_ANSWER_OFFSET,
5215
5940
  CHAINLINK_DECIMALS_OFFSET,
5216
5941
  CHAINLINK_MIN_SIZE,
5942
+ CHAINLINK_TIMESTAMP_OFFSET,
5217
5943
  CREATOR_LOCK_SEED,
5218
5944
  CTX_VAMM_OFFSET,
5219
5945
  DEFAULT_OI_RAMP_SLOTS,
5220
5946
  ENGINE_MARK_PRICE_OFF,
5221
5947
  ENGINE_OFF,
5222
5948
  IX_TAG,
5949
+ LIGHTHOUSE_CONSTRAINT_ADDRESS,
5950
+ LIGHTHOUSE_ERROR_CODES,
5951
+ LIGHTHOUSE_PROGRAM_ID,
5952
+ LIGHTHOUSE_PROGRAM_ID_STR2 as LIGHTHOUSE_PROGRAM_ID_STR,
5953
+ LIGHTHOUSE_USER_MESSAGE,
5223
5954
  MARK_PRICE_EMA_ALPHA_E6,
5224
5955
  MARK_PRICE_EMA_WINDOW_SLOTS,
5225
5956
  MAX_DECIMALS,
@@ -5241,10 +5972,12 @@ export {
5241
5972
  RAMP_START_BPS,
5242
5973
  RAYDIUM_CLMM_PROGRAM_ID,
5243
5974
  RENOUNCE_ADMIN_CONFIRMATION,
5975
+ RpcPool,
5244
5976
  SLAB_TIERS,
5245
5977
  SLAB_TIERS_V0,
5246
5978
  SLAB_TIERS_V1,
5247
5979
  SLAB_TIERS_V12_1,
5980
+ SLAB_TIERS_V12_15,
5248
5981
  SLAB_TIERS_V1D,
5249
5982
  SLAB_TIERS_V1D_LEGACY,
5250
5983
  SLAB_TIERS_V1M,
@@ -5262,11 +5995,14 @@ export {
5262
5995
  VAMM_MAGIC,
5263
5996
  ValidationError,
5264
5997
  WELL_KNOWN,
5998
+ _internal,
5265
5999
  buildAccountMetas,
5266
6000
  buildAdlInstruction,
5267
6001
  buildAdlTransaction,
5268
6002
  buildIx,
5269
6003
  checkPhaseTransition,
6004
+ checkRpcHealth,
6005
+ classifyLighthouseError,
5270
6006
  clearStaticMarkets,
5271
6007
  computeDexSpotPriceE6,
5272
6008
  computeDynamicFeeBps,
@@ -5279,6 +6015,7 @@ export {
5279
6015
  computeLiqPrice,
5280
6016
  computeMarkPnl,
5281
6017
  computeMaxLeverage,
6018
+ computeMaxWithdrawable,
5282
6019
  computePnlPercent,
5283
6020
  computePreTradeLiqPrice,
5284
6021
  computeRequiredMargin,
@@ -5286,15 +6023,15 @@ export {
5286
6023
  computeVammQuote,
5287
6024
  computeWarmupLeverageCap,
5288
6025
  computeWarmupMaxPositionSize,
6026
+ computeWarmupProgress,
5289
6027
  computeWarmupUnlockedCapital,
5290
6028
  concatBytes,
6029
+ countLighthouseInstructions,
5291
6030
  decodeError,
5292
6031
  decodeStakePool,
5293
6032
  depositAccounts,
5294
6033
  deriveCreatorLockPda,
5295
6034
  deriveDepositPda,
5296
- deriveInsuranceLpMint,
5297
- deriveKeeperFund,
5298
6035
  deriveLpPda,
5299
6036
  derivePythPriceUpdateAccount,
5300
6037
  derivePythPushOraclePDA,
@@ -5330,14 +6067,11 @@ export {
5330
6067
  encodeClaimQueuedWithdrawal,
5331
6068
  encodeClearPendingSettlement,
5332
6069
  encodeCloseAccount,
5333
- encodeCloseKeeperFund,
5334
6070
  encodeCloseOrphanSlab,
5335
6071
  encodeCloseSlab,
5336
6072
  encodeCloseStaleSlabs,
5337
- encodeCreateInsuranceMint,
5338
6073
  encodeCreateLpVault,
5339
6074
  encodeDepositCollateral,
5340
- encodeDepositInsuranceLP,
5341
6075
  encodeDepositLpCollateral,
5342
6076
  encodeExecuteAdl,
5343
6077
  encodeForceCloseResolved,
@@ -5394,7 +6128,6 @@ export {
5394
6128
  encodeStakeUpdateConfig,
5395
6129
  encodeStakeWithdraw,
5396
6130
  encodeTopUpInsurance,
5397
- encodeTopUpKeeperFund,
5398
6131
  encodeTradeCpi,
5399
6132
  encodeTradeCpiV2,
5400
6133
  encodeTradeNoCpi,
@@ -5408,7 +6141,6 @@ export {
5408
6141
  encodeUpdateRiskParams,
5409
6142
  encodeWithdrawCollateral,
5410
6143
  encodeWithdrawInsurance,
5411
- encodeWithdrawInsuranceLP,
5412
6144
  encodeWithdrawInsuranceLimited,
5413
6145
  encodeWithdrawLpCollateral,
5414
6146
  fetchAdlRankedPositions,
@@ -5430,6 +6162,10 @@ export {
5430
6162
  initPoolAccounts,
5431
6163
  isAccountUsed,
5432
6164
  isAdlTriggered,
6165
+ isAnchorErrorCode,
6166
+ isLighthouseError,
6167
+ isLighthouseFailureInLogs,
6168
+ isLighthouseInstruction,
5433
6169
  isStandardToken,
5434
6170
  isToken2022,
5435
6171
  isValidChainlinkOracle,
@@ -5454,6 +6190,8 @@ export {
5454
6190
  simulateOrSend,
5455
6191
  slabDataSize,
5456
6192
  slabDataSizeV1,
6193
+ stripLighthouseFromTransaction,
6194
+ stripLighthouseInstructions,
5457
6195
  validateAmount,
5458
6196
  validateBps,
5459
6197
  validateI128,
@@ -5464,6 +6202,7 @@ export {
5464
6202
  validateU128,
5465
6203
  validateU16,
5466
6204
  validateU64,
6205
+ withRetry,
5467
6206
  withdrawAccounts
5468
6207
  };
5469
6208
  //# sourceMappingURL=index.js.map