@percolatorct/sdk 0.5.0 → 1.0.0-beta.1

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.
@@ -1,5 +1,7 @@
1
1
  import { Connection, PublicKey } from "@solana/web3.js";
2
2
  import { type SlabHeader, type MarketConfig, type EngineState, type RiskParams } from "./slab.js";
3
+ import { type StaticMarketEntry } from "./static-markets.js";
4
+ import { type Network } from "../config/program-ids.js";
3
5
  /**
4
6
  * A discovered Percolator market from on-chain program accounts.
5
7
  */
@@ -33,24 +35,34 @@ export interface DiscoveredMarket {
33
35
  * History: Small was V0 (62_808) until 2026-03-13 program upgrade. V0 values preserved
34
36
  * in SLAB_TIERS_V0 for discovery of legacy on-chain accounts.
35
37
  */
38
+ /**
39
+ * Default slab tiers for the current mainnet program (v12.1).
40
+ * These are used by useCreateMarket to allocate slab accounts of the correct size.
41
+ */
36
42
  export declare const SLAB_TIERS: {
43
+ readonly micro: {
44
+ maxAccounts: number;
45
+ dataSize: number;
46
+ label: string;
47
+ description: string;
48
+ };
37
49
  readonly small: {
38
- readonly maxAccounts: 256;
39
- readonly dataSize: 65352;
40
- readonly label: "Small";
41
- readonly description: "256 slots · ~0.45 SOL";
50
+ maxAccounts: number;
51
+ dataSize: number;
52
+ label: string;
53
+ description: string;
42
54
  };
43
55
  readonly medium: {
44
- readonly maxAccounts: 1024;
45
- readonly dataSize: 257448;
46
- readonly label: "Medium";
47
- readonly description: "1,024 slots · ~1.79 SOL";
56
+ maxAccounts: number;
57
+ dataSize: number;
58
+ label: string;
59
+ description: string;
48
60
  };
49
61
  readonly large: {
50
- readonly maxAccounts: 4096;
51
- readonly dataSize: 1025832;
52
- readonly label: "Large";
53
- readonly description: "4,096 slots · ~7.14 SOL";
62
+ maxAccounts: number;
63
+ dataSize: number;
64
+ label: string;
65
+ description: string;
54
66
  };
55
67
  };
56
68
  /** @deprecated V0 slab sizes — kept for backward compatibility with old on-chain slabs */
@@ -154,23 +166,29 @@ export declare const SLAB_TIERS_V1D_LEGACY: {
154
166
  };
155
167
  /** @deprecated Alias — use SLAB_TIERS (already V1) */
156
168
  export declare const SLAB_TIERS_V1: {
169
+ readonly micro: {
170
+ maxAccounts: number;
171
+ dataSize: number;
172
+ label: string;
173
+ description: string;
174
+ };
157
175
  readonly small: {
158
- readonly maxAccounts: 256;
159
- readonly dataSize: 65352;
160
- readonly label: "Small";
161
- readonly description: "256 slots · ~0.45 SOL";
176
+ maxAccounts: number;
177
+ dataSize: number;
178
+ label: string;
179
+ description: string;
162
180
  };
163
181
  readonly medium: {
164
- readonly maxAccounts: 1024;
165
- readonly dataSize: 257448;
166
- readonly label: "Medium";
167
- readonly description: "1,024 slots · ~1.79 SOL";
182
+ maxAccounts: number;
183
+ dataSize: number;
184
+ label: string;
185
+ description: string;
168
186
  };
169
187
  readonly large: {
170
- readonly maxAccounts: 4096;
171
- readonly dataSize: 1025832;
172
- readonly label: "Large";
173
- readonly description: "4,096 slots · ~7.14 SOL";
188
+ maxAccounts: number;
189
+ dataSize: number;
190
+ label: string;
191
+ description: string;
174
192
  };
175
193
  };
176
194
  /**
@@ -253,6 +271,56 @@ export interface DiscoverMarketsOptions {
253
271
  * Default: all known tiers.
254
272
  */
255
273
  maxTierQueries?: number;
274
+ /**
275
+ * Base URL of the Percolator REST API (e.g. `"https://percolatorlaunch.com/api"`).
276
+ *
277
+ * When set, `discoverMarkets` will fall back to the REST API's `GET /markets`
278
+ * endpoint if `getProgramAccounts` fails or returns 0 results (common on public
279
+ * mainnet RPCs that reject `getProgramAccounts`).
280
+ *
281
+ * The API returns slab addresses which are then fetched on-chain via
282
+ * `getMarketsByAddress` (uses `getMultipleAccounts`, works on all RPCs).
283
+ *
284
+ * GH#59 / PERC-8424: Unblocks mainnet users without a Helius API key.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * const markets = await discoverMarkets(connection, programId, {
289
+ * apiBaseUrl: "https://percolatorlaunch.com/api",
290
+ * });
291
+ * ```
292
+ */
293
+ apiBaseUrl?: string;
294
+ /**
295
+ * Timeout in ms for the API fallback HTTP request.
296
+ * Only used when `apiBaseUrl` is set.
297
+ * Default: 10_000 (10 seconds).
298
+ */
299
+ apiTimeoutMs?: number;
300
+ /**
301
+ * Network hint for tier-3 static bundle fallback (`"mainnet"` or `"devnet"`).
302
+ *
303
+ * When both `getProgramAccounts` (tier 1) and the REST API (tier 2) fail,
304
+ * `discoverMarkets` will fall back to a bundled static list of known slab
305
+ * addresses for the specified network. The addresses are fetched on-chain
306
+ * via `getMarketsByAddress` (`getMultipleAccounts` — works on all RPCs).
307
+ *
308
+ * If not set, tier-3 fallback is disabled.
309
+ *
310
+ * The static list can be extended at runtime via `registerStaticMarkets()`.
311
+ *
312
+ * @see {@link registerStaticMarkets} to add addresses at runtime
313
+ * @see {@link getStaticMarkets} to inspect the current static list
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * const markets = await discoverMarkets(connection, programId, {
318
+ * apiBaseUrl: "https://percolatorlaunch.com/api",
319
+ * network: "mainnet", // enables tier-3 static fallback
320
+ * });
321
+ * ```
322
+ */
323
+ network?: Network;
256
324
  }
257
325
  /**
258
326
  * Discover all Percolator markets owned by the given program.
@@ -261,3 +329,164 @@ export interface DiscoverMarketsOptions {
261
329
  * @param options.sequential - Run tier queries sequentially with 429 retry (PERC-1650).
262
330
  */
263
331
  export declare function discoverMarkets(connection: Connection, programId: PublicKey, options?: DiscoverMarketsOptions): Promise<DiscoveredMarket[]>;
332
+ /**
333
+ * Options for `getMarketsByAddress`.
334
+ */
335
+ export interface GetMarketsByAddressOptions {
336
+ /**
337
+ * Maximum number of addresses per `getMultipleAccounts` RPC call.
338
+ * Solana limits a single call to 100 accounts; callers may lower this
339
+ * to reduce per-request payload size or avoid 429s.
340
+ *
341
+ * Default: 100 (Solana maximum).
342
+ */
343
+ batchSize?: number;
344
+ /**
345
+ * Delay in ms between batches when the address list exceeds `batchSize`.
346
+ * Helps avoid rate-limiting on public RPCs.
347
+ *
348
+ * Default: 0 (no delay).
349
+ */
350
+ interBatchDelayMs?: number;
351
+ }
352
+ /**
353
+ * Fetch and parse Percolator markets by their known slab addresses.
354
+ *
355
+ * Unlike `discoverMarkets()` — which uses `getProgramAccounts` and is blocked
356
+ * on public mainnet RPCs — this function uses `getMultipleAccounts`, which works
357
+ * on any RPC endpoint (including `api.mainnet-beta.solana.com`).
358
+ *
359
+ * Callers must already know the market slab addresses (e.g. from an indexer,
360
+ * a hardcoded registry, or a previous `discoverMarkets` call on a permissive RPC).
361
+ *
362
+ * @param connection - Solana RPC connection
363
+ * @param programId - The Percolator program that owns these slabs
364
+ * @param addresses - Array of slab account public keys to fetch
365
+ * @param options - Optional batching/delay configuration
366
+ * @returns Parsed markets for all valid slab accounts; invalid/missing accounts are silently skipped.
367
+ *
368
+ * @example
369
+ * ```ts
370
+ * import { getMarketsByAddress, getProgramId } from "@percolator/sdk";
371
+ * import { Connection, PublicKey } from "@solana/web3.js";
372
+ *
373
+ * const connection = new Connection("https://api.mainnet-beta.solana.com");
374
+ * const programId = getProgramId("mainnet");
375
+ * const slabs = [
376
+ * new PublicKey("So11111111111111111111111111111111111111112"),
377
+ * // ... more known slab addresses
378
+ * ];
379
+ *
380
+ * const markets = await getMarketsByAddress(connection, programId, slabs);
381
+ * console.log(`Found ${markets.length} markets`);
382
+ * ```
383
+ */
384
+ export declare function getMarketsByAddress(connection: Connection, programId: PublicKey, addresses: PublicKey[], options?: GetMarketsByAddressOptions): Promise<DiscoveredMarket[]>;
385
+ /**
386
+ * Shape of a single market entry returned by the Percolator REST API
387
+ * (`GET /markets`). Only the fields needed for discovery are typed here;
388
+ * the full API response may contain additional statistics fields.
389
+ */
390
+ export interface ApiMarketEntry {
391
+ slab_address: string;
392
+ symbol?: string;
393
+ name?: string;
394
+ decimals?: number;
395
+ status?: string;
396
+ [key: string]: unknown;
397
+ }
398
+ /** Options for {@link discoverMarketsViaApi}. */
399
+ export interface DiscoverMarketsViaApiOptions {
400
+ /**
401
+ * Timeout in ms for the HTTP request to the REST API.
402
+ * Default: 10_000 (10 seconds).
403
+ */
404
+ timeoutMs?: number;
405
+ /**
406
+ * Options forwarded to {@link getMarketsByAddress} for the on-chain fetch
407
+ * step (batch size, inter-batch delay).
408
+ */
409
+ onChainOptions?: GetMarketsByAddressOptions;
410
+ }
411
+ /**
412
+ * Discover Percolator markets by first querying the REST API for slab addresses,
413
+ * then fetching full on-chain data via `getMarketsByAddress` (which uses
414
+ * `getMultipleAccounts` — works on all RPCs including public mainnet nodes).
415
+ *
416
+ * This is the recommended discovery path for mainnet users who do not have a
417
+ * Helius API key, since `getProgramAccounts` is rejected by public RPCs.
418
+ *
419
+ * The REST API acts as an address directory only — all market data is verified
420
+ * on-chain via `getMarketsByAddress`, so the caller gets the same
421
+ * `DiscoveredMarket[]` result as `discoverMarkets()`.
422
+ *
423
+ * @param connection - Solana RPC connection (any endpoint, including public)
424
+ * @param programId - The Percolator program that owns the slabs
425
+ * @param apiBaseUrl - Base URL of the Percolator REST API
426
+ * (e.g. `"https://percolatorlaunch.com/api"`)
427
+ * @param options - Optional timeout and on-chain fetch configuration
428
+ * @returns Parsed markets for all valid slab accounts discovered via the API
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * import { discoverMarketsViaApi, getProgramId } from "@percolator/sdk";
433
+ * import { Connection } from "@solana/web3.js";
434
+ *
435
+ * const connection = new Connection("https://api.mainnet-beta.solana.com");
436
+ * const programId = getProgramId("mainnet");
437
+ * const markets = await discoverMarketsViaApi(
438
+ * connection,
439
+ * programId,
440
+ * "https://percolatorlaunch.com/api",
441
+ * );
442
+ * console.log(`Discovered ${markets.length} markets via API fallback`);
443
+ * ```
444
+ */
445
+ export declare function discoverMarketsViaApi(connection: Connection, programId: PublicKey, apiBaseUrl: string, options?: DiscoverMarketsViaApiOptions): Promise<DiscoveredMarket[]>;
446
+ /** Options for {@link discoverMarketsViaStaticBundle}. */
447
+ export interface DiscoverMarketsViaStaticBundleOptions {
448
+ /**
449
+ * Options forwarded to {@link getMarketsByAddress} for the on-chain fetch
450
+ * step (batch size, inter-batch delay).
451
+ */
452
+ onChainOptions?: GetMarketsByAddressOptions;
453
+ }
454
+ /**
455
+ * Discover Percolator markets from a static list of known slab addresses.
456
+ *
457
+ * This is the tier-3 (last-resort) fallback for `discoverMarkets()`. It uses
458
+ * a bundled list of known slab addresses and fetches their full account data
459
+ * on-chain via `getMarketsByAddress` (`getMultipleAccounts` — works on all RPCs).
460
+ *
461
+ * The static list acts as an address directory only — all market data is verified
462
+ * on-chain, so stale entries are silently skipped (the account won't have valid
463
+ * magic bytes or will have been closed).
464
+ *
465
+ * @param connection - Solana RPC connection (any endpoint)
466
+ * @param programId - The Percolator program that owns the slabs
467
+ * @param entries - Static market entries (typically from {@link getStaticMarkets})
468
+ * @param options - Optional on-chain fetch configuration
469
+ * @returns Parsed markets for all valid slab accounts; stale/missing entries are skipped.
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * import {
474
+ * discoverMarketsViaStaticBundle,
475
+ * getStaticMarkets,
476
+ * getProgramId,
477
+ * } from "@percolator/sdk";
478
+ * import { Connection } from "@solana/web3.js";
479
+ *
480
+ * const connection = new Connection("https://api.mainnet-beta.solana.com");
481
+ * const programId = getProgramId("mainnet");
482
+ * const entries = getStaticMarkets("mainnet");
483
+ *
484
+ * const markets = await discoverMarketsViaStaticBundle(
485
+ * connection,
486
+ * programId,
487
+ * entries,
488
+ * );
489
+ * console.log(`Recovered ${markets.length} markets from static bundle`);
490
+ * ```
491
+ */
492
+ export declare function discoverMarketsViaStaticBundle(connection: Connection, programId: PublicKey, entries: StaticMarketEntry[], options?: DiscoverMarketsViaStaticBundleOptions): Promise<DiscoveredMarket[]>;
@@ -2,8 +2,10 @@ export * from "./slab.js";
2
2
  export * from "./pda.js";
3
3
  export * from "./ata.js";
4
4
  export * from "./discovery.js";
5
+ export * from "./static-markets.js";
5
6
  export * from "./dex-oracle.js";
6
7
  export * from "./oracle.js";
7
8
  export * from "./token-program.js";
8
9
  export * from "./stake.js";
9
10
  export * from "./adl.js";
11
+ export * from "./rpc-pool.js";
@@ -16,11 +16,19 @@ declare const CHAINLINK_MIN_SIZE = 224;
16
16
  declare const MAX_DECIMALS = 18;
17
17
  /** Offset of decimals field in Chainlink aggregator account */
18
18
  declare const CHAINLINK_DECIMALS_OFFSET = 138;
19
+ /** Offset of updated_at timestamp (i64 LE, Unix seconds) in Chainlink aggregator */
20
+ declare const CHAINLINK_TIMESTAMP_OFFSET = 168;
19
21
  /** Offset of latest answer in Chainlink aggregator account */
20
22
  declare const CHAINLINK_ANSWER_OFFSET = 216;
21
23
  export interface OraclePrice {
22
24
  price: bigint;
23
25
  decimals: number;
26
+ /** Unix timestamp (seconds) of the last oracle update, if available. */
27
+ updatedAt?: number;
28
+ }
29
+ export interface ParseChainlinkOptions {
30
+ /** Maximum allowed staleness in seconds. If the oracle update is older, an error is thrown. */
31
+ maxStalenessSeconds?: number;
24
32
  }
25
33
  /**
26
34
  * Parse price data from a Chainlink aggregator account buffer.
@@ -34,11 +42,11 @@ export interface OraclePrice {
34
42
  * @returns Parsed oracle price with decimals
35
43
  * @throws if the buffer is invalid or contains unreasonable data
36
44
  */
37
- export declare function parseChainlinkPrice(data: Uint8Array): OraclePrice;
45
+ export declare function parseChainlinkPrice(data: Uint8Array, options?: ParseChainlinkOptions): OraclePrice;
38
46
  /**
39
47
  * Validate that a buffer looks like a valid Chainlink aggregator account.
40
48
  * Returns true if the buffer passes all validation checks, false otherwise.
41
49
  * Use this for non-throwing validation.
42
50
  */
43
51
  export declare function isValidChainlinkOracle(data: Uint8Array): boolean;
44
- export { CHAINLINK_MIN_SIZE, CHAINLINK_DECIMALS_OFFSET, CHAINLINK_ANSWER_OFFSET, MAX_DECIMALS };
52
+ export { CHAINLINK_MIN_SIZE, CHAINLINK_DECIMALS_OFFSET, CHAINLINK_TIMESTAMP_OFFSET, CHAINLINK_ANSWER_OFFSET, MAX_DECIMALS };