@percolatorct/sdk 1.0.0-beta.23 → 1.0.0-beta.25

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript SDK for building clients, bots, and UIs on top of the [Percolator](https://github.com/dcccrypto/percolator) perpetual futures protocol on Solana.
4
4
 
5
- > **⚠️ BETA** — `1.0.0-beta.1`. Security audit (0x-SquidSol) complete. All 709 tests passing. Pending Helius API key + mainnet market deployment before `1.0.0` stable.
5
+ > **EXPERIMENTALNOT AUDITED.** `1.0.0-beta.25`. 742 tests passing. Do NOT use with real funds.
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@percolator/sdk?color=14F195)](https://www.npmjs.com/package/@percolator/sdk)
8
8
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
@@ -434,8 +434,10 @@ const markets = await discoverMarkets(connection);
434
434
 
435
435
  | Program | Network | Address |
436
436
  |---------|---------|---------|
437
- | Percolator | Mainnet | `GM8zjJ8LTBMv9xEsverh6H6wLyevgMHEJXcEzyY3rY24` |
437
+ | Percolator | Mainnet | `ESa89R5Es3rJ5mnwGybVRG1GrNt9etP11Z5V2QWD4edv` |
438
438
  | Matcher | Mainnet | `DHP6DtwXP1yJsz8YzfoeigRFPB979gzmumkmCxDLSkUX` |
439
+ | Stake | Mainnet | `DC5fovFQD5SZYsetwvEqd4Wi4PFY1Yfnc669VMe6oa7F` |
440
+ | NFT | Mainnet | `FqhKJT9gtScjrmfUuRMjeg7cXNpif1fqsy5Jh65tJmTS` |
439
441
  | Percolator | Devnet | `FxfD37s1AZTeWfFQps9Zpebi2dNQ9QSSDtfMKdbsfKrD` |
440
442
  | Matcher | Devnet | `GTRgyTDfrMvBubALAqtHuQwT8tbGyXid7svXZKtWfC9k` |
441
443
 
@@ -689,19 +691,26 @@ const markets = await getMarketsByAddress(
689
691
  ```bash
690
692
  pnpm install # Install dependencies
691
693
  pnpm build # Build with tsup (outputs to dist/)
692
- pnpm test # Run test suite (vitest)
694
+ pnpm test # Run all 742 tests (vitest)
693
695
  pnpm lint # Type-check (tsc --noEmit)
696
+ pnpm verify-layout # Verify ABI byte offsets against on-chain layout
694
697
  ```
695
698
 
696
699
  ### Testing
697
700
 
698
- Tests cover ABI encoding roundtrips, PDA derivation, slab parsing, validation, and trading math:
701
+ Tests cover ABI encoding roundtrips, PDA derivation, slab parsing, validation, and trading math. 742 tests, 0 failures.
699
702
 
700
703
  ```bash
701
704
  pnpm test # Run all tests
702
705
  pnpm test -- --watch # Watch mode
703
706
  ```
704
707
 
708
+ ### v12.17 Layout Support
709
+
710
+ The SDK supports the v12.17 slab layout natively via `detectSlabLayout()`. The layout detection function inspects account size to select the correct field offsets for header, config, and per-account data.
711
+
712
+ A key fix in this version corrects the SBF byte offsets for `d1`/`d2` delta fields that were misaligned in earlier SDK versions. The `parseAllAccounts()` function applies the correct offsets for both devnet (legacy layout) and mainnet (v12.17 layout) slabs automatically.
713
+
705
714
  ### Publishing
706
715
 
707
716
  ```bash
@@ -2,3 +2,4 @@ export * from "./encode.js";
2
2
  export * from "./instructions.js";
3
3
  export * from "./accounts.js";
4
4
  export * from "./errors.js";
5
+ export * from "./nft.js";
@@ -204,12 +204,41 @@ export interface WithdrawCollateralArgs {
204
204
  }
205
205
  export declare function encodeWithdrawCollateral(args: WithdrawCollateralArgs): Uint8Array;
206
206
  /**
207
- * KeeperCrank instruction data (4 bytes)
208
- * Funding rate is computed on-chain from LP inventory.
207
+ * Liquidation policy for KeeperCrank candidates (v12.17 two-phase crank).
208
+ *
209
+ * On-chain wire tags:
210
+ * 0x00 = FullClose — liquidate the entire position
211
+ * 0x01 = ExactPartial(u128) — reduce position by exactly `quantity` units
212
+ * 0xFF = TouchOnly — accrue fees / sweep dust, do NOT liquidate
213
+ */
214
+ export declare const LiquidationPolicyTag: {
215
+ readonly FullClose: 0;
216
+ readonly ExactPartial: 1;
217
+ readonly TouchOnly: 255;
218
+ };
219
+ export type KeeperCrankCandidate = {
220
+ policy: typeof LiquidationPolicyTag.FullClose;
221
+ idx: number;
222
+ } | {
223
+ policy: typeof LiquidationPolicyTag.ExactPartial;
224
+ idx: number;
225
+ quantity: bigint | string;
226
+ } | {
227
+ policy: typeof LiquidationPolicyTag.TouchOnly;
228
+ idx: number;
229
+ };
230
+ /**
231
+ * KeeperCrank instruction data (v12.17 two-phase crank).
232
+ *
233
+ * Wire format: tag(1) + caller_idx(u16) + format_version=1(u8) +
234
+ * candidates: [ idx(u16) + policy_tag(u8) [+ quantity(u128) if ExactPartial] ]*
235
+ *
236
+ * Empty candidates list = simple crank (accrue funding, sweep dust).
237
+ * With candidates = targeted liquidation/touch pass.
209
238
  */
210
239
  export interface KeeperCrankArgs {
211
240
  callerIdx: number;
212
- allowPanic: boolean;
241
+ candidates?: KeeperCrankCandidate[];
213
242
  }
214
243
  export declare function encodeKeeperCrank(args: KeeperCrankArgs): Uint8Array;
215
244
  /**
@@ -243,21 +272,24 @@ export interface TopUpInsuranceArgs {
243
272
  }
244
273
  export declare function encodeTopUpInsurance(args: TopUpInsuranceArgs): Uint8Array;
245
274
  /**
246
- * TradeCpi instruction data (21 bytes)
275
+ * TradeCpi instruction data (29 bytes)
276
+ *
277
+ * v12.17: limit_price_e6 is now REQUIRED (slippage protection).
278
+ * Set to 0 to accept any price (no slippage protection).
279
+ * For buys: tx reverts if execution price > limitPriceE6.
280
+ * For sells: tx reverts if execution price < limitPriceE6.
247
281
  */
248
282
  export interface TradeCpiArgs {
249
283
  lpIdx: number;
250
284
  userIdx: number;
251
285
  size: bigint | string;
286
+ /** Limit price in e6 units. 0 = no limit (accept any price). */
287
+ limitPriceE6: bigint | string;
252
288
  }
253
289
  export declare function encodeTradeCpi(args: TradeCpiArgs): Uint8Array;
254
290
  /**
255
- * TradeCpiV2 instruction data (22 bytes) PERC-154 optimized trade CPI.
256
- *
257
- * Same as TradeCpi but includes a caller-provided PDA bump byte.
258
- * Uses create_program_address instead of find_program_address,
259
- * saving ~1500 CU per trade. The bump should be obtained once via
260
- * deriveLpPda() and cached for the lifetime of the market.
291
+ * @deprecated Tag 35 removed in v12.17. Use TradeCpi (tag 10) with limitPriceE6 instead.
292
+ * TradeCpi now handles PDA bump internally. Sending tag 35 will fail with InvalidInstructionData.
261
293
  */
262
294
  export interface TradeCpiV2Args {
263
295
  lpIdx: number;
@@ -265,13 +297,24 @@ export interface TradeCpiV2Args {
265
297
  size: bigint | string;
266
298
  bump: number;
267
299
  }
300
+ /** @deprecated Tag 35 removed in v12.17. Use encodeTradeCpi with limitPriceE6 instead. */
268
301
  export declare function encodeTradeCpiV2(args: TradeCpiV2Args): Uint8Array;
269
302
  /**
270
- * SetRiskThreshold instruction data (17 bytes)
303
+ * @deprecated Tag 36 removed in v12.17. Will fail on-chain with InvalidInstructionData.
304
+ */
305
+ export interface UnresolveMarketArgs {
306
+ confirmation: bigint | string;
307
+ }
308
+ /** @deprecated Tag 36 removed in v12.17. Will fail on-chain. */
309
+ export declare function encodeUnresolveMarket(args: UnresolveMarketArgs): Uint8Array;
310
+ /**
311
+ * @deprecated Tag 11 removed in v12.17. Insurance floor is now set at InitMarket.
312
+ * Sending this instruction will fail with InvalidInstructionData.
271
313
  */
272
314
  export interface SetRiskThresholdArgs {
273
315
  newThreshold: bigint | string;
274
316
  }
317
+ /** @deprecated Tag 11 removed in v12.17. Will fail on-chain. */
275
318
  export declare function encodeSetRiskThreshold(args: SetRiskThresholdArgs): Uint8Array;
276
319
  /**
277
320
  * UpdateAdmin instruction data (33 bytes)
@@ -285,31 +328,27 @@ export declare function encodeUpdateAdmin(args: UpdateAdminArgs): Uint8Array;
285
328
  */
286
329
  export declare function encodeCloseSlab(): Uint8Array;
287
330
  /**
288
- * UpdateConfig instruction data
289
- * Updates funding and threshold parameters at runtime (admin only)
331
+ * UpdateConfig instruction data (33 bytes)
332
+ *
333
+ * v12.17: Only 4 funding parameters. Threshold/insurance parameters are set
334
+ * at InitMarket and updated via dedicated instructions (SetRiskThreshold removed).
335
+ * fundingInvScaleNotionalE6 removed (now computed on-chain from LP state).
290
336
  */
291
337
  export interface UpdateConfigArgs {
292
338
  fundingHorizonSlots: bigint | string;
293
339
  fundingKBps: bigint | string;
294
- fundingInvScaleNotionalE6: bigint | string;
295
340
  fundingMaxPremiumBps: bigint | string;
296
341
  fundingMaxBpsPerSlot: bigint | string;
297
- threshFloor: bigint | string;
298
- threshRiskBps: bigint | string;
299
- threshUpdateIntervalSlots: bigint | string;
300
- threshStepBps: bigint | string;
301
- threshAlphaBps: bigint | string;
302
- threshMin: bigint | string;
303
- threshMax: bigint | string;
304
- threshMinStep: bigint | string;
305
342
  }
306
343
  export declare function encodeUpdateConfig(args: UpdateConfigArgs): Uint8Array;
307
344
  /**
308
- * SetMaintenanceFee instruction data (17 bytes)
345
+ * @deprecated Tag 15 removed in v12.17. Maintenance fee is set at InitMarket only.
346
+ * Sending this instruction will fail with InvalidInstructionData.
309
347
  */
310
348
  export interface SetMaintenanceFeeArgs {
311
349
  newFee: bigint | string;
312
350
  }
351
+ /** @deprecated Tag 15 removed in v12.17. Will fail on-chain. */
313
352
  export declare function encodeSetMaintenanceFee(args: SetMaintenanceFeeArgs): Uint8Array;
314
353
  /**
315
354
  * SetOracleAuthority instruction data (33 bytes)
@@ -378,18 +417,16 @@ export interface AdminForceCloseArgs {
378
417
  }
379
418
  export declare function encodeAdminForceClose(args: AdminForceCloseArgs): Uint8Array;
380
419
  /**
381
- * UpdateRiskParams instruction data (17 or 25 bytes)
382
- * Update initial and maintenance margin BPS (admin only).
383
- *
384
- * R2-S13: The Rust program uses `data.len() >= 25` to detect the optional
385
- * tradingFeeBps field, so variable-length encoding is safe. When tradingFeeBps
386
- * is omitted, the data is 17 bytes (tag + 2×u64). When included, 25 bytes.
420
+ * @deprecated Tag 22 is now SetInsuranceWithdrawPolicy in v12.17.
421
+ * This encoder sends the WRONG wire format (u64+u64 instead of pubkey+u64+u16+u64).
422
+ * Use encodeSetInsuranceWithdrawPolicy instead.
387
423
  */
388
424
  export interface UpdateRiskParamsArgs {
389
425
  initialMarginBps: bigint | string;
390
426
  maintenanceMarginBps: bigint | string;
391
427
  tradingFeeBps?: bigint | string;
392
428
  }
429
+ /** @deprecated Use encodeSetInsuranceWithdrawPolicy (tag 22). This sends wrong wire format. */
393
430
  export declare function encodeUpdateRiskParams(args: UpdateRiskParamsArgs): Uint8Array;
394
431
  /**
395
432
  * On-chain confirmation code for RenounceAdmin (must match program constant).
@@ -401,11 +438,9 @@ export declare const RENOUNCE_ADMIN_CONFIRMATION = 5928230587143701317n;
401
438
  */
402
439
  export declare const UNRESOLVE_CONFIRMATION = 16045690984503054900n;
403
440
  /**
404
- * RenounceAdmin instruction data (9 bytes)
405
- * Irreversibly set admin to all zeros. After this, all admin-only instructions fail.
406
- *
407
- * Requires the confirmation code 0x52454E4F554E4345 ("RENOUNCE" as u64 LE)
408
- * to prevent accidental invocation.
441
+ * @deprecated Tag 23 is now WithdrawInsuranceLimited in v12.17.
442
+ * This encoder sends the confirmation code as a withdrawal amount DANGEROUS.
443
+ * Use encodeWithdrawInsuranceLimited instead.
409
444
  */
410
445
  export declare function encodeRenounceAdmin(): Uint8Array;
411
446
  /**
@@ -464,29 +499,15 @@ export declare function encodePauseMarket(): Uint8Array;
464
499
  */
465
500
  export declare function encodeUnpauseMarket(): Uint8Array;
466
501
  /**
467
- * SetPythOracle (Tag 32) switch a market to Pyth-pinned mode.
468
- *
469
- * After this instruction:
470
- * - oracle_authority is cleared → PushOraclePrice is disabled
471
- * - index_feed_id is set to feed_id → validated on every price read
472
- * - max_staleness_secs and conf_filter_bps are updated
473
- * - All price reads go directly to read_pyth_price_e6() with on-chain
474
- * staleness + confidence + feed-ID validation (no silent fallback)
475
- *
476
- * Instruction data: tag(1) + feed_id(32) + max_staleness_secs(8) + conf_filter_bps(2) = 43 bytes
477
- *
478
- * Accounts:
479
- * 0. [signer, writable] Admin
480
- * 1. [writable] Slab
502
+ * @deprecated Tag 32 removed in v12.17. Pyth oracle is configured at InitMarket via indexFeedId.
503
+ * Sending this instruction will fail with InvalidInstructionData.
481
504
  */
482
505
  export interface SetPythOracleArgs {
483
- /** 32-byte Pyth feed ID. All zeros is invalid (reserved for Hyperp mode). */
484
506
  feedId: Uint8Array;
485
- /** Maximum age of Pyth price in seconds before OracleStale is returned. Must be > 0. */
486
507
  maxStalenessSecs: bigint;
487
- /** Max confidence/price ratio in bps (0 = no confidence check). */
488
508
  confFilterBps: number;
489
509
  }
510
+ /** @deprecated Tag 32 removed in v12.17. Pyth is configured at InitMarket. */
490
511
  export declare function encodeSetPythOracle(args: SetPythOracleArgs): Uint8Array;
491
512
  /**
492
513
  * Derive the expected Pyth PriceUpdateV2 account address for a given feed ID.
@@ -498,18 +519,8 @@ export declare function encodeSetPythOracle(args: SetPythOracleArgs): Uint8Array
498
519
  export declare const PYTH_RECEIVER_PROGRAM_ID = "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ";
499
520
  export declare function derivePythPriceUpdateAccount(feedId: Uint8Array, shardId?: number): Promise<string>;
500
521
  /**
501
- * UpdateMarkPrice (Tag 33) permissionless EMA mark price crank.
502
- *
503
- * Reads the current oracle price on-chain, applies 8-hour EMA smoothing
504
- * with circuit breaker, and writes result to authority_price_e6.
505
- *
506
- * Instruction data: 1 byte (tag only — all params read from on-chain state)
507
- *
508
- * Accounts:
509
- * 0. [writable] Slab
510
- * 1. [] Oracle account (Pyth PriceUpdateV2 / Chainlink / DEX AMM)
511
- * 2. [] Clock sysvar (SysvarC1ock11111111111111111111111111111111)
512
- * 3..N [] Remaining accounts (PumpSwap vaults, etc. if needed)
522
+ * @deprecated Tag 33 removed in v12.17. Use UpdateHyperpMark (tag 34) for DEX-oracle markets.
523
+ * Sending this instruction will fail with InvalidInstructionData.
513
524
  */
514
525
  export declare function encodeUpdateMarkPrice(): Uint8Array;
515
526
  /**
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Standalone percolator-nft program SDK module.
3
+ *
4
+ * This covers the NFT program at `PERCOLATOR_NFT_PROGRAM_ID` which is
5
+ * separate from the main Percolator program. It handles:
6
+ * - MintPositionNft (tag 0)
7
+ * - BurnPositionNft (tag 1)
8
+ * - SettleFunding (tag 2)
9
+ * - GetPositionValue (tag 3)
10
+ * - ExecuteTransferHook (tag 4, SPL interface — not called directly)
11
+ * - EmergencyBurn (tag 5)
12
+ *
13
+ * PDA seeds (matches percolator-nft/src/state.rs):
14
+ * PositionNft state : ["position_nft", slab, user_idx_u16_LE]
15
+ * PositionNft mint : ["position_nft_mint", slab, user_idx_u16_LE]
16
+ * Mint authority : ["mint_authority"]
17
+ */
18
+ import { PublicKey } from "@solana/web3.js";
19
+ /** The standalone percolator-nft program (TransferHook + mint authority). */
20
+ export declare const NFT_PROGRAM_ID: PublicKey;
21
+ export declare function getNftProgramId(): PublicKey;
22
+ export declare const NFT_IX_TAG: {
23
+ readonly MintPositionNft: 0;
24
+ readonly BurnPositionNft: 1;
25
+ readonly SettleFunding: 2;
26
+ readonly GetPositionValue: 3;
27
+ readonly ExecuteTransferHook: 4;
28
+ readonly EmergencyBurn: 5;
29
+ };
30
+ /** Encode MintPositionNft (tag 0). Data: tag(1) + user_idx(2). */
31
+ export declare function encodeNftMint(userIdx: number): Uint8Array;
32
+ /** Encode BurnPositionNft (tag 1). Data: tag(1). */
33
+ export declare function encodeNftBurn(): Uint8Array;
34
+ /** Encode SettleFunding (tag 2). Data: tag(1). */
35
+ export declare function encodeNftSettleFunding(): Uint8Array;
36
+ /** Encode EmergencyBurn (tag 5). Data: tag(1). */
37
+ export declare function encodeNftEmergencyBurn(): Uint8Array;
38
+ type AccountMeta = "s" | "w" | "sw" | "r";
39
+ /**
40
+ * Account metas for MintPositionNft (tag 0).
41
+ *
42
+ * 0. [signer, writable] payer / position owner
43
+ * 1. [writable] PositionNft PDA (created)
44
+ * 2. [writable, signer] NFT mint (Token-2022, fresh keypair)
45
+ * 3. [writable] Owner's NFT ATA (created)
46
+ * 4. [] Slab account
47
+ * 5. [] Mint authority PDA
48
+ * 6. [] Token-2022 program
49
+ * 7. [] Associated token account program
50
+ * 8. [] System program
51
+ * 9. [writable] ExtraAccountMetaList PDA
52
+ */
53
+ export declare const ACCOUNTS_NFT_MINT: AccountMeta[];
54
+ /**
55
+ * Account metas for BurnPositionNft (tag 1).
56
+ *
57
+ * 0. [signer] NFT holder
58
+ * 1. [writable] PositionNft PDA (closed)
59
+ * 2. [writable] NFT mint (supply → 0)
60
+ * 3. [writable] Holder's NFT ATA (closed)
61
+ * 4. [] Slab account
62
+ * 5. [] Mint authority PDA
63
+ * 6. [] Token-2022 program
64
+ */
65
+ export declare const ACCOUNTS_NFT_BURN: AccountMeta[];
66
+ /**
67
+ * Account metas for EmergencyBurn (tag 5).
68
+ *
69
+ * 0. [signer] NFT holder
70
+ * 1. [writable] PositionNft PDA (closed)
71
+ * 2. [writable] NFT mint
72
+ * 3. [writable] Holder's NFT ATA
73
+ * 4. [] Slab account
74
+ * 5. [] Mint authority PDA
75
+ * 6. [] Token-2022 program
76
+ */
77
+ export declare const ACCOUNTS_NFT_EMERGENCY_BURN: AccountMeta[];
78
+ /**
79
+ * Derive the PositionNft state PDA.
80
+ * Seeds: ["position_nft", slab, user_idx_u16_LE]
81
+ */
82
+ export declare function deriveNftPda(slab: PublicKey, userIdx: number, programId?: PublicKey): [PublicKey, number];
83
+ /**
84
+ * Derive the PositionNft mint PDA.
85
+ * Seeds: ["position_nft_mint", slab, user_idx_u16_LE]
86
+ */
87
+ export declare function deriveNftMint(slab: PublicKey, userIdx: number, programId?: PublicKey): [PublicKey, number];
88
+ /**
89
+ * Derive the program-wide mint authority PDA.
90
+ * Seeds: ["mint_authority"]
91
+ */
92
+ export declare function deriveMintAuthority(programId?: PublicKey): [PublicKey, number];
93
+ /**
94
+ * On-chain PositionNft state (208 bytes, matches percolator-nft/src/state.rs).
95
+ *
96
+ * [0..8] magic u64
97
+ * [8] version u8
98
+ * [9] bump u8
99
+ * [10..16] _pad0
100
+ * [16..48] slab [u8; 32]
101
+ * [48..50] user_idx u16 LE
102
+ * [50..56] _pad1
103
+ * [56..88] nft_mint [u8; 32]
104
+ * [88..96] entry_price_e6 u64
105
+ * [96..104] position_size u64
106
+ * [104] is_long u8
107
+ * [105..112] _pad2
108
+ * [112..128] position_basis_q i128
109
+ * [128..144] last_funding_index_e18 i128
110
+ * [144..152] minted_at i64
111
+ * [152..160] account_id u64
112
+ * [160..208] _reserved
113
+ */
114
+ export declare const POSITION_NFT_STATE_LEN = 208;
115
+ export interface PositionNftState {
116
+ version: number;
117
+ bump: number;
118
+ slab: PublicKey;
119
+ userIdx: number;
120
+ nftMint: PublicKey;
121
+ entryPriceE6: bigint;
122
+ positionSize: bigint;
123
+ isLong: boolean;
124
+ positionBasisQ: bigint;
125
+ lastFundingIndexE18: bigint;
126
+ mintedAt: bigint;
127
+ accountId: bigint;
128
+ }
129
+ /**
130
+ * Parse a PositionNft account from raw bytes.
131
+ * @throws if data is shorter than POSITION_NFT_STATE_LEN (208 bytes).
132
+ */
133
+ export declare function parsePositionNftAccount(data: Uint8Array): PositionNftState;
134
+ export {};
@@ -18,7 +18,7 @@ export declare const PROGRAM_IDS: {
18
18
  };
19
19
  readonly mainnet: {
20
20
  readonly percolator: "ESa89R5Es3rJ5mnwGybVRG1GrNt9etP11Z5V2QWD4edv";
21
- readonly matcher: "DHP6DtwXP1yJsz8YzfoeigRFPB979gzmumkmCxDLSkUX";
21
+ readonly matcher: "GDK8wx38kpiSVSfGTVNiSdptX3Z5R4kQyqh6Q3QX6wmi";
22
22
  };
23
23
  };
24
24
  export type Network = "devnet" | "mainnet";