@vercora-protocol/sdk 0.0.1 → 0.0.5

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/client.js CHANGED
@@ -6,6 +6,69 @@ const web3_js_1 = require("@solana/web3.js");
6
6
  const spl_token_1 = require("@solana/spl-token");
7
7
  const pda_1 = require("./pda");
8
8
  const types_1 = require("./types");
9
+ /**
10
+ * Some RPC plans (e.g. QuickNode Discover) reject `getMultipleAccounts` with more than 5 keys.
11
+ * Chunk so batched reads stay compatible.
12
+ */
13
+ const GET_MULTIPLE_ACCOUNTS_MAX_KEYS = 5;
14
+ /**
15
+ * Anchor `BorshAccountsCoder.decode` often returns IDL snake_case keys (`active_stake`);
16
+ * typed clients expect camelCase (`activeStake`). Reading the wrong shape yields `undefined`
17
+ * and `.toString()` throws — batched code then silently returned 0 for stakes.
18
+ */
19
+ function pickDecodedField(obj, camel, snake) {
20
+ const v = obj[camel] ?? obj[snake];
21
+ return v;
22
+ }
23
+ function decodedMarketOutcomeLabel(acc) {
24
+ const v = acc.label ?? acc['label'];
25
+ if (typeof v === 'string')
26
+ return v.trim();
27
+ if (v != null)
28
+ return String(v).trim();
29
+ return '';
30
+ }
31
+ /**
32
+ * Read `MarketOutcome` fields from raw account bytes (8-byte disc + Borsh body).
33
+ * Matches `programs/vercora/src/state/market_outcome.rs`: u8 vote, u8 bump, Borsh `String` label (u32 len + UTF-8).
34
+ * Used when IDL/account coders omit or mis-decode `label` so **all** clients see the same text as on-chain.
35
+ */
36
+ function parseMarketOutcomeAccountLayout(data) {
37
+ if (data.length < 8 + 2 + 4)
38
+ return null;
39
+ const vote = data.readUInt8(8);
40
+ const strLen = data.readUInt32LE(10);
41
+ if (strLen > 64 || 14 + strLen > data.length)
42
+ return null;
43
+ const label = data.subarray(14, 14 + strLen).toString('utf8').trim();
44
+ return { resolutionVoteCount: vote, label };
45
+ }
46
+ function decodedParimutuelActiveStakeBn(obj) {
47
+ const v = pickDecodedField(obj, 'activeStake', 'active_stake');
48
+ if (v == null)
49
+ return new anchor_1.BN(0);
50
+ try {
51
+ if (typeof v === 'bigint')
52
+ return new anchor_1.BN(v.toString());
53
+ if (typeof v === 'number' && Number.isFinite(v))
54
+ return new anchor_1.BN(Math.trunc(v));
55
+ return new anchor_1.BN(v.toString());
56
+ }
57
+ catch {
58
+ return new anchor_1.BN(0);
59
+ }
60
+ }
61
+ /**
62
+ * Read `active_stake` (u64 LE) from raw account data.
63
+ * Layout: 8-byte Anchor discriminator, then Borsh: market(32) + user(32) + outcome_index(1) + active_stake(8).
64
+ * Prefer this over IDL decode for stake-only reads — avoids camelCase/snake_case mismatches in coders.
65
+ */
66
+ const PARIMUTUEL_POSITION_ACTIVE_STAKE_OFFSET = 8 + 32 + 32 + 1;
67
+ function readParimutuelActiveStakeU64Le(data) {
68
+ if (data.length < PARIMUTUEL_POSITION_ACTIVE_STAKE_OFFSET + 8)
69
+ return new anchor_1.BN(0);
70
+ return new anchor_1.BN(data.readBigUInt64LE(PARIMUTUEL_POSITION_ACTIVE_STAKE_OFFSET).toString());
71
+ }
9
72
  class PredictionMarketClient {
10
73
  constructor(program) {
11
74
  this.program = program;
@@ -15,6 +78,50 @@ class PredictionMarketClient {
15
78
  get walletKey() {
16
79
  return this.program.provider.wallet.publicKey;
17
80
  }
81
+ /**
82
+ * Active stake for a pari position: read u64 at fixed Borsh offset (authoritative), then fall back to IDL decode.
83
+ */
84
+ decodeParimutuelPositionActiveStake(data) {
85
+ const buf = Buffer.from(data);
86
+ if (buf.length >= PARIMUTUEL_POSITION_ACTIVE_STAKE_OFFSET + 8) {
87
+ return readParimutuelActiveStakeU64Le(buf);
88
+ }
89
+ try {
90
+ const coder = this.program.account.parimutuelPosition?.coder;
91
+ if (coder?.decode) {
92
+ const dec = coder.decode(buf);
93
+ if (dec && typeof dec === 'object') {
94
+ return decodedParimutuelActiveStakeBn(dec);
95
+ }
96
+ }
97
+ }
98
+ catch {
99
+ /* fall through */
100
+ }
101
+ try {
102
+ const raw = this.program.coder.accounts.decode('ParimutuelPosition', buf);
103
+ return decodedParimutuelActiveStakeBn(raw);
104
+ }
105
+ catch {
106
+ return new anchor_1.BN(0);
107
+ }
108
+ }
109
+ /**
110
+ * Same as `Connection.getMultipleAccountsInfo`, but splits into chunks of
111
+ * {@link GET_MULTIPLE_ACCOUNTS_MAX_KEYS}.
112
+ * Chunks run **sequentially** so we do not burst the RPC (reduces 429s on free tiers).
113
+ */
114
+ async getMultipleAccountsInfoBatched(keys) {
115
+ if (keys.length === 0)
116
+ return [];
117
+ const out = [];
118
+ for (let i = 0; i < keys.length; i += GET_MULTIPLE_ACCOUNTS_MAX_KEYS) {
119
+ const chunk = keys.slice(i, i + GET_MULTIPLE_ACCOUNTS_MAX_KEYS);
120
+ const infos = await this.connection.getMultipleAccountsInfo(chunk);
121
+ out.push(...infos);
122
+ }
123
+ return out;
124
+ }
18
125
  async collateralTokenProgramForMint(mint) {
19
126
  const info = await this.connection.getAccountInfo(mint);
20
127
  if (!info)
@@ -48,7 +155,7 @@ class PredictionMarketClient {
48
155
  */
49
156
  async initializeConfig(params, opts) {
50
157
  return this.program.methods
51
- .initializeConfig(params.secondaryAuthority, params.depositPlatformFeeBps, params.platformTreasuryWallet, params.platformFeeLamports, params.parimutuelPenaltyProtocolShareBps, params.parimutuelWithdrawPlatformFeeBps)
158
+ .initializeConfig(params.secondaryAuthority, params.depositPlatformFeeBps, params.platformTreasuryWallet, params.platformFeeLamports, params.parimutuelWithdrawPlatformFeeBps)
52
159
  .accounts({
53
160
  globalConfig: this.globalConfig,
54
161
  authority: this.walletKey,
@@ -63,7 +170,7 @@ class PredictionMarketClient {
63
170
  */
64
171
  async updateConfig(params, opts) {
65
172
  return this.program.methods
66
- .updateConfig(params.secondaryAuthority, params.depositPlatformFeeBps, params.platformTreasuryWallet, params.platformFeeLamports, params.parimutuelPenaltyProtocolShareBps, params.parimutuelWithdrawPlatformFeeBps)
173
+ .updateConfig(params.secondaryAuthority, params.depositPlatformFeeBps, params.platformTreasuryWallet, params.platformFeeLamports, params.parimutuelWithdrawPlatformFeeBps)
67
174
  .accounts({
68
175
  globalConfig: this.globalConfig,
69
176
  authority: this.walletKey,
@@ -99,6 +206,33 @@ class PredictionMarketClient {
99
206
  })
100
207
  .rpc(opts ?? { skipPreflight: true });
101
208
  }
209
+ // ─── Market categories ───────────────────────────────────────────────────────
210
+ /**
211
+ * Create a new `MarketCategory` PDA.
212
+ * `params.categoryId` must equal `globalConfig.nextCategoryId` at call time.
213
+ */
214
+ async createMarketCategory(params, opts) {
215
+ return this.program.methods
216
+ .createMarketCategory(params.categoryId, params.name)
217
+ .accounts({
218
+ globalConfig: this.globalConfig,
219
+ marketCategory: (0, pda_1.deriveMarketCategory)(this.program.programId, params.categoryId),
220
+ authority: this.walletKey,
221
+ systemProgram: web3_js_1.SystemProgram.programId,
222
+ })
223
+ .rpc(opts ?? { skipPreflight: true });
224
+ }
225
+ /** Update an existing market category's name and active state. */
226
+ async updateMarketCategory(params, opts) {
227
+ return this.program.methods
228
+ .updateMarketCategory(params.name, params.active)
229
+ .accounts({
230
+ globalConfig: this.globalConfig,
231
+ marketCategory: (0, pda_1.deriveMarketCategory)(this.program.programId, params.categoryId),
232
+ authority: this.walletKey,
233
+ })
234
+ .rpc(opts ?? { skipPreflight: true });
235
+ }
102
236
  // ─── Market creation (3 steps) ──────────────────────────────────────────────
103
237
  /**
104
238
  * Step 1 — Create Market + Vault.
@@ -126,6 +260,7 @@ class PredictionMarketClient {
126
260
  creatorFeeBps: params.creatorFeeBps,
127
261
  depositPlatformFeeBps: params.depositPlatformFeeBps,
128
262
  numResolvers: params.numResolvers,
263
+ maxOutcomeInvestment: params.maxOutcomeInvestment ?? new anchor_1.BN(0),
129
264
  title: params.title,
130
265
  marketType: (0, types_1.toMarketTypeIx)(params.marketType),
131
266
  })
@@ -190,7 +325,6 @@ class PredictionMarketClient {
190
325
  marketId: parimutuelStateParams.marketId,
191
326
  earlyWithdrawPenaltyBps: parimutuelStateParams.earlyWithdrawPenaltyBps,
192
327
  penaltyKeptInPoolBps: parimutuelStateParams.penaltyKeptInPoolBps,
193
- penaltySurplusCreatorShareBps: parimutuelStateParams.penaltySurplusCreatorShareBps,
194
328
  })
195
329
  .accounts({
196
330
  payer: this.walletKey,
@@ -204,6 +338,31 @@ class PredictionMarketClient {
204
338
  }
205
339
  return await provider.sendAndConfirm(tx, undefined, opts ?? { skipPreflight: true });
206
340
  }
341
+ /**
342
+ * Initialize `MarketOutcome` PDAs for indices `0..labels.length-1` (creator as payer).
343
+ */
344
+ async initializeMarketOutcomeSlots(marketPda, params, opts) {
345
+ const provider = this.program.provider;
346
+ const tx = new web3_js_1.Transaction();
347
+ for (let i = 0; i < params.labels.length; i++) {
348
+ const label = params.labels[i];
349
+ const ix = await this.program.methods
350
+ .initializeMarketOutcome({
351
+ marketId: params.marketId,
352
+ outcomeIndex: i,
353
+ label,
354
+ })
355
+ .accounts({
356
+ payer: this.walletKey,
357
+ market: marketPda,
358
+ marketOutcome: (0, pda_1.deriveMarketOutcome)(this.program.programId, marketPda, i),
359
+ systemProgram: web3_js_1.SystemProgram.programId,
360
+ })
361
+ .instruction();
362
+ tx.add(ix);
363
+ }
364
+ return await provider.sendAndConfirm(tx, undefined, opts ?? { skipPreflight: true });
365
+ }
207
366
  /**
208
367
  * Step 3 — Initialize 8 Outcome Mints.
209
368
  * Decimals are inherited from the collateral mint stored on the market account.
@@ -236,11 +395,14 @@ class PredictionMarketClient {
236
395
  /** Length must equal `params.numResolvers` (typically the first N of an 8-slot UI). */
237
396
  resolverPubkeys, params, opts) {
238
397
  const { marketPda } = await this.createMarket(creator, collateralMint, creatorFeeAccount, params, opts);
398
+ const labels = params.outcomeLabels ??
399
+ Array.from({ length: params.outcomeCount }, (_, i) => `Outcome ${i}`);
400
+ if (labels.length !== params.outcomeCount) {
401
+ throw new Error('outcomeLabels length must equal outcomeCount');
402
+ }
403
+ await this.initializeMarketOutcomeSlots(marketPda, { marketId: params.marketId, labels }, opts);
239
404
  if (params.marketType === 'parimutuel') {
240
- const gc = await this.fetchGlobalConfig();
241
405
  const pi = params.parimutuelInit ?? {};
242
- const penaltySurplusCreatorShareBps = pi.penaltySurplusCreatorShareBps ??
243
- 10000 - gc.parimutuelPenaltyProtocolShareBps;
244
406
  await this.initializeMarketResolverSlots(marketPda, {
245
407
  marketId: params.marketId,
246
408
  resolverPubkeys: resolverPubkeys.slice(0, params.numResolvers),
@@ -248,7 +410,6 @@ class PredictionMarketClient {
248
410
  marketId: params.marketId,
249
411
  earlyWithdrawPenaltyBps: pi.earlyWithdrawPenaltyBps ?? 500,
250
412
  penaltyKeptInPoolBps: pi.penaltyKeptInPoolBps ?? 8000,
251
- penaltySurplusCreatorShareBps,
252
413
  });
253
414
  }
254
415
  else {
@@ -271,7 +432,6 @@ class PredictionMarketClient {
271
432
  marketId: params.marketId,
272
433
  earlyWithdrawPenaltyBps: params.earlyWithdrawPenaltyBps,
273
434
  penaltyKeptInPoolBps: params.penaltyKeptInPoolBps,
274
- penaltySurplusCreatorShareBps: params.penaltySurplusCreatorShareBps,
275
435
  })
276
436
  .accounts({
277
437
  payer: this.walletKey,
@@ -287,15 +447,34 @@ class PredictionMarketClient {
287
447
  tx.add(pariIx);
288
448
  return await provider.sendAndConfirm(tx, [], opts ?? { commitment: 'confirmed', skipPreflight: true });
289
449
  }
450
+ /**
451
+ * Update early-exit penalty parameters on an open pari-mutuel pool.
452
+ * Only callable by the market creator.
453
+ */
454
+ async updateParimutuelState(marketPda, params, opts) {
455
+ return this.program.methods
456
+ .updateParimutuelState({
457
+ marketId: params.marketId,
458
+ earlyWithdrawPenaltyBps: params.earlyWithdrawPenaltyBps,
459
+ penaltyKeptInPoolBps: params.penaltyKeptInPoolBps,
460
+ })
461
+ .accounts({
462
+ creator: this.walletKey,
463
+ market: marketPda,
464
+ parimutuelState: (0, pda_1.deriveParimutuelState)(this.program.programId, marketPda),
465
+ })
466
+ .rpc(opts ?? { skipPreflight: true });
467
+ }
290
468
  async parimutuelStake(marketPda, params, opts) {
291
469
  const parimutuelState = (0, pda_1.deriveParimutuelState)(this.program.programId, marketPda);
292
470
  const market = await this.fetchMarket(marketPda);
293
471
  const globalConfig = await this.fetchGlobalConfig();
472
+ const collateralTokenProgram = await this.collateralTokenProgramForMint(market.collateralMint);
294
473
  const position = (0, pda_1.deriveParimutuelPosition)(this.program.programId, marketPda, this.walletKey, params.outcomeIndex);
295
474
  const vaultPda = (0, pda_1.deriveVault)(this.program.programId, marketPda);
296
475
  const allowedMint = (0, pda_1.deriveAllowedMint)(this.program.programId, market.collateralMint);
297
- const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
298
- const platformTreasuryAta = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, globalConfig.platformTreasury, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
476
+ const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, collateralTokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
477
+ const platformTreasuryAta = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, globalConfig.platformTreasury, false, collateralTokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
299
478
  return this.program.methods
300
479
  .parimutuelStake({
301
480
  marketId: params.marketId,
@@ -315,7 +494,7 @@ class PredictionMarketClient {
315
494
  platformTreasuryWallet: globalConfig.platformTreasury,
316
495
  platformTreasuryAta,
317
496
  allowedMint,
318
- collateralTokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
497
+ collateralTokenProgram,
319
498
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
320
499
  systemProgram: web3_js_1.SystemProgram.programId,
321
500
  })
@@ -327,8 +506,9 @@ class PredictionMarketClient {
327
506
  const position = (0, pda_1.deriveParimutuelPosition)(this.program.programId, marketPda, this.walletKey, params.outcomeIndex);
328
507
  const vaultPda = (0, pda_1.deriveVault)(this.program.programId, marketPda);
329
508
  const globalConfig = await this.fetchGlobalConfig();
330
- const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
331
- const platformTreasuryAta = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, globalConfig.platformTreasury, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
509
+ const collateralTokenProgram = await this.collateralTokenProgramForMint(market.collateralMint);
510
+ const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, collateralTokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
511
+ const platformTreasuryAta = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, globalConfig.platformTreasury, false, collateralTokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
332
512
  return this.program.methods
333
513
  .parimutuelWithdraw({
334
514
  marketId: params.marketId,
@@ -347,7 +527,7 @@ class PredictionMarketClient {
347
527
  globalConfig: this.globalConfig,
348
528
  platformTreasuryWallet: globalConfig.platformTreasury,
349
529
  platformTreasuryAta,
350
- collateralTokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
530
+ collateralTokenProgram,
351
531
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
352
532
  systemProgram: web3_js_1.SystemProgram.programId,
353
533
  })
@@ -356,9 +536,10 @@ class PredictionMarketClient {
356
536
  async parimutuelClaim(marketPda, params, opts) {
357
537
  const parimutuelState = (0, pda_1.deriveParimutuelState)(this.program.programId, marketPda);
358
538
  const market = await this.fetchMarket(marketPda);
539
+ const collateralTokenProgram = await this.collateralTokenProgramForMint(market.collateralMint);
359
540
  const position = (0, pda_1.deriveParimutuelPosition)(this.program.programId, marketPda, this.walletKey, params.outcomeIndex);
360
541
  const vaultPda = (0, pda_1.deriveVault)(this.program.programId, marketPda);
361
- const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
542
+ const userCollateral = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, this.walletKey, false, collateralTokenProgram, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
362
543
  return this.program.methods
363
544
  .parimutuelClaim({
364
545
  marketId: params.marketId,
@@ -372,7 +553,7 @@ class PredictionMarketClient {
372
553
  vault: vaultPda,
373
554
  collateralMint: market.collateralMint,
374
555
  userCollateralAccount: userCollateral,
375
- collateralTokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
556
+ collateralTokenProgram,
376
557
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
377
558
  })
378
559
  .rpc(opts ?? { skipPreflight: true });
@@ -462,7 +643,7 @@ class PredictionMarketClient {
462
643
  */
463
644
  async voteResolution(marketPda, params, opts) {
464
645
  const votePda = (0, pda_1.deriveResolutionVote)(this.program.programId, marketPda, params.resolverIndex);
465
- const tallyPda = (0, pda_1.deriveOutcomeTally)(this.program.programId, marketPda, params.outcomeIndex);
646
+ const marketOutcomePda = (0, pda_1.deriveMarketOutcome)(this.program.programId, marketPda, params.outcomeIndex);
466
647
  return this.program.methods
467
648
  .voteResolution({
468
649
  marketId: params.marketId,
@@ -474,7 +655,7 @@ class PredictionMarketClient {
474
655
  market: marketPda,
475
656
  resolver: (0, pda_1.deriveResolver)(this.program.programId, marketPda, params.resolverIndex),
476
657
  resolutionVote: votePda,
477
- outcomeTally: tallyPda,
658
+ marketOutcome: marketOutcomePda,
478
659
  systemProgram: web3_js_1.SystemProgram.programId,
479
660
  })
480
661
  .rpc(opts ?? { skipPreflight: true });
@@ -482,7 +663,7 @@ class PredictionMarketClient {
482
663
  /** Clears the resolver’s active vote and decrements that outcome’s on-chain tally. */
483
664
  async revokeResolutionVote(marketPda, params, opts) {
484
665
  const votePda = (0, pda_1.deriveResolutionVote)(this.program.programId, marketPda, params.resolverIndex);
485
- const tallyPda = (0, pda_1.deriveOutcomeTally)(this.program.programId, marketPda, params.outcomeIndex);
666
+ const marketOutcomePda = (0, pda_1.deriveMarketOutcome)(this.program.programId, marketPda, params.outcomeIndex);
486
667
  return this.program.methods
487
668
  .revokeResolutionVote({
488
669
  marketId: params.marketId,
@@ -494,7 +675,7 @@ class PredictionMarketClient {
494
675
  market: marketPda,
495
676
  resolver: (0, pda_1.deriveResolver)(this.program.programId, marketPda, params.resolverIndex),
496
677
  resolutionVote: votePda,
497
- outcomeTally: tallyPda,
678
+ marketOutcome: marketOutcomePda,
498
679
  })
499
680
  .rpc(opts ?? { skipPreflight: true });
500
681
  }
@@ -504,20 +685,20 @@ class PredictionMarketClient {
504
685
  * Passes optional per-outcome tally accounts (null if that tally PDA was never created).
505
686
  */
506
687
  async finalizeResolution(marketPda, params, opts) {
507
- const tallies = (0, pda_1.deriveAllOutcomeTallies)(this.program.programId, marketPda);
508
- const infos = await Promise.all(tallies.map((p) => this.connection.getAccountInfo(p)));
688
+ const outcomes = (0, pda_1.deriveAllMarketOutcomes)(this.program.programId, marketPda);
689
+ const infos = await Promise.all(outcomes.map((p) => this.connection.getAccountInfo(p)));
509
690
  return this.program.methods
510
691
  .finalizeResolution({ marketId: params.marketId })
511
692
  .accounts({
512
693
  market: marketPda,
513
- outcomeTally0: infos[0] ? tallies[0] : null,
514
- outcomeTally1: infos[1] ? tallies[1] : null,
515
- outcomeTally2: infos[2] ? tallies[2] : null,
516
- outcomeTally3: infos[3] ? tallies[3] : null,
517
- outcomeTally4: infos[4] ? tallies[4] : null,
518
- outcomeTally5: infos[5] ? tallies[5] : null,
519
- outcomeTally6: infos[6] ? tallies[6] : null,
520
- outcomeTally7: infos[7] ? tallies[7] : null,
694
+ marketOutcome0: infos[0] ? outcomes[0] : null,
695
+ marketOutcome1: infos[1] ? outcomes[1] : null,
696
+ marketOutcome2: infos[2] ? outcomes[2] : null,
697
+ marketOutcome3: infos[3] ? outcomes[3] : null,
698
+ marketOutcome4: infos[4] ? outcomes[4] : null,
699
+ marketOutcome5: infos[5] ? outcomes[5] : null,
700
+ marketOutcome6: infos[6] ? outcomes[6] : null,
701
+ marketOutcome7: infos[7] ? outcomes[7] : null,
521
702
  })
522
703
  .rpc(opts ?? { skipPreflight: true });
523
704
  }
@@ -584,6 +765,29 @@ class PredictionMarketClient {
584
765
  })
585
766
  .rpc(opts ?? { skipPreflight: true });
586
767
  }
768
+ /**
769
+ * Abandon an incomplete market and recover all rent (market account + vault).
770
+ *
771
+ * Only callable by the market creator while the vault holds zero collateral.
772
+ * Guards on the program side additionally reject resolved and voided markets.
773
+ * On success the market and vault accounts are closed and lamports returned to creator.
774
+ */
775
+ async abandonMarket(marketPda, params, opts) {
776
+ const market = await this.fetchMarket(marketPda);
777
+ const collateralMint = market.collateralMint;
778
+ const collateralTokenProgram = await this.collateralTokenProgramForMint(collateralMint);
779
+ return this.program.methods
780
+ .abandonMarket({ marketId: params.marketId })
781
+ .accounts({
782
+ creator: this.walletKey,
783
+ market: marketPda,
784
+ vault: (0, pda_1.deriveVault)(this.program.programId, marketPda),
785
+ collateralMint,
786
+ collateralTokenProgram,
787
+ systemProgram: web3_js_1.SystemProgram.programId,
788
+ })
789
+ .rpc(opts ?? { skipPreflight: true });
790
+ }
587
791
  // ─── State readers ───────────────────────────────────────────────────────────
588
792
  async fetchGlobalConfig() {
589
793
  return this.program.account.globalConfig.fetch(this.globalConfig);
@@ -591,6 +795,23 @@ class PredictionMarketClient {
591
795
  async fetchMarket(market) {
592
796
  return this.program.account.market.fetch(market);
593
797
  }
798
+ /**
799
+ * Read `MarketOutcome.label` for each active outcome index (parallel fetches).
800
+ * Missing accounts yield `Outcome {i+1}` placeholders.
801
+ */
802
+ async fetchMarketOutcomeLabels(marketPda, outcomeCount) {
803
+ return Promise.all(Array.from({ length: outcomeCount }, async (_, i) => {
804
+ const pda = (0, pda_1.deriveMarketOutcome)(this.program.programId, marketPda, i);
805
+ try {
806
+ const acc = (await this.program.account.marketOutcome.fetch(pda));
807
+ const t = (acc.label ?? '').trim();
808
+ return t || `Outcome ${i + 1}`;
809
+ }
810
+ catch {
811
+ return `Outcome ${i + 1}`;
812
+ }
813
+ }));
814
+ }
594
815
  /** Returns the collateral balance (base units) held in the vault. */
595
816
  async fetchVaultBalance(market) {
596
817
  const vault = (0, pda_1.deriveVault)(this.program.programId, market);
@@ -604,6 +825,109 @@ class PredictionMarketClient {
604
825
  const acc = await (0, spl_token_1.getAccount)(this.connection, ata, undefined, spl_token_1.TOKEN_PROGRAM_ID);
605
826
  return acc.amount;
606
827
  }
828
+ /** Fetch a single `MarketCategory` account by its PDA. */
829
+ async fetchMarketCategory(categoryPda) {
830
+ return this.program.account.marketCategory.fetch(categoryPda);
831
+ }
832
+ /**
833
+ * Fetch all on-chain `MarketCategory` accounts.
834
+ * Returns an array sorted by `id` ascending.
835
+ */
836
+ async fetchAllMarketCategories() {
837
+ const rows = await this.program.account.marketCategory.all();
838
+ return rows
839
+ .map((r) => ({ pubkey: r.publicKey, account: r.account }))
840
+ .sort((a, b) => a.account.id.cmp(b.account.id));
841
+ }
842
+ /**
843
+ * Fetch the `ResolutionVote` PDA for a specific resolver slot.
844
+ * Returns `null` if the account does not exist (resolver has not voted).
845
+ */
846
+ async fetchResolutionVote(marketPda, resolverIndex) {
847
+ const pda = (0, pda_1.deriveResolutionVote)(this.program.programId, marketPda, resolverIndex);
848
+ const info = await this.connection.getAccountInfo(pda);
849
+ if (!info)
850
+ return null;
851
+ return this.program.coder.accounts.decode('ResolutionVote', Buffer.from(info.data));
852
+ }
853
+ /**
854
+ * Decode all 8 `MarketOutcome` PDAs via chunked `getMultipleAccounts` (QuickNode Discover: max 5 keys/call).
855
+ * `labels` has length `outcomeCount` (for UI); `resolutionVoteCounts` always has length 8.
856
+ */
857
+ async fetchMarketOutcomesSnapshot(marketPda, outcomeCount) {
858
+ const n = Math.min(8, Math.max(0, outcomeCount));
859
+ const allPdas = (0, pda_1.deriveAllMarketOutcomes)(this.program.programId, marketPda);
860
+ const infos = await this.getMultipleAccountsInfoBatched(allPdas);
861
+ const labels = [];
862
+ const resolutionVoteCounts = [];
863
+ for (let i = 0; i < 8; i++) {
864
+ const info = infos[i];
865
+ let voteCount = 0;
866
+ let label = `Outcome ${i + 1}`;
867
+ if (info) {
868
+ const buf = Buffer.from(info.data);
869
+ const layout = parseMarketOutcomeAccountLayout(buf);
870
+ try {
871
+ let acc;
872
+ try {
873
+ const coder = this.program.account.marketOutcome?.coder;
874
+ acc = coder?.decode
875
+ ? (coder.decode(buf) ?? {})
876
+ : this.program.coder.accounts.decode('MarketOutcome', buf);
877
+ }
878
+ catch {
879
+ acc = this.program.coder.accounts.decode('MarketOutcome', buf);
880
+ }
881
+ const voteRaw = pickDecodedField(acc, 'resolutionVoteCount', 'resolution_vote_count');
882
+ if (voteRaw == null) {
883
+ voteCount = layout?.resolutionVoteCount ?? 0;
884
+ }
885
+ else if (typeof voteRaw === 'number') {
886
+ voteCount = Math.floor(voteRaw);
887
+ }
888
+ else {
889
+ voteCount = Number(voteRaw.toString(10));
890
+ }
891
+ const t = decodedMarketOutcomeLabel(acc);
892
+ if (t) {
893
+ label = t;
894
+ }
895
+ else if (layout?.label) {
896
+ label = layout.label;
897
+ }
898
+ }
899
+ catch {
900
+ if (layout) {
901
+ voteCount = layout.resolutionVoteCount;
902
+ if (layout.label)
903
+ label = layout.label;
904
+ }
905
+ }
906
+ }
907
+ resolutionVoteCounts.push(voteCount);
908
+ if (i < n)
909
+ labels.push(label);
910
+ }
911
+ return { labels, resolutionVoteCounts };
912
+ }
913
+ /**
914
+ * Fetch the resolver vote tally (resolutionVoteCount) for all 8 outcome slots.
915
+ * Missing PDAs return 0.
916
+ */
917
+ async fetchOutcomeTallyCounts(marketPda) {
918
+ const { resolutionVoteCounts } = await this.fetchMarketOutcomesSnapshot(marketPda, 8);
919
+ return resolutionVoteCounts;
920
+ }
921
+ /**
922
+ * Fetch all allowed collateral mints from the platform allowlist.
923
+ * Returns an array of mint public keys sorted lexicographically.
924
+ */
925
+ async fetchAllowedCollateralMints() {
926
+ const rows = await this.program.account.allowedMint.all();
927
+ const mints = rows.map((r) => r.account.mint);
928
+ mints.sort((a, b) => a.toBase58().localeCompare(b.toBase58()));
929
+ return mints;
930
+ }
607
931
  // ─── User profiles ───────────────────────────────────────────────────────────
608
932
  /**
609
933
  * Create or update the caller's on-chain user profile.
@@ -676,7 +1000,40 @@ class PredictionMarketClient {
676
1000
  const info = await this.connection.getAccountInfo(pda);
677
1001
  if (!info)
678
1002
  return null;
679
- return this.program.account.parimutuelPosition.fetch(pda);
1003
+ const buf = Buffer.from(info.data);
1004
+ const activeStakeFromLayout = readParimutuelActiveStakeU64Le(buf);
1005
+ let raw;
1006
+ try {
1007
+ const coder = this.program.account.parimutuelPosition?.coder;
1008
+ raw = coder?.decode
1009
+ ? (coder.decode(buf) ?? {})
1010
+ : this.program.coder.accounts.decode('ParimutuelPosition', buf);
1011
+ }
1012
+ catch {
1013
+ return null;
1014
+ }
1015
+ const activeStake = buf.length >= PARIMUTUEL_POSITION_ACTIVE_STAKE_OFFSET + 8
1016
+ ? activeStakeFromLayout
1017
+ : decodedParimutuelActiveStakeBn(raw);
1018
+ return { ...raw, activeStake };
1019
+ }
1020
+ /**
1021
+ * All parimutuel position PDAs for this user via chunked `getMultipleAccounts` (see plan limits).
1022
+ */
1023
+ async fetchParimutuelActiveStakesBatch(market, user, outcomeCount) {
1024
+ const n = Math.max(0, outcomeCount);
1025
+ const pdas = Array.from({ length: n }, (_, i) => (0, pda_1.deriveParimutuelPosition)(this.program.programId, market, user, i));
1026
+ const infos = await this.getMultipleAccountsInfoBatched(pdas);
1027
+ return infos.map((info) => {
1028
+ if (!info)
1029
+ return new anchor_1.BN(0);
1030
+ try {
1031
+ return this.decodeParimutuelPositionActiveStake(info.data);
1032
+ }
1033
+ catch {
1034
+ return new anchor_1.BN(0);
1035
+ }
1036
+ });
680
1037
  }
681
1038
  /**
682
1039
  * Fetch outcome token balances (base units) for all active outcomes for a user in a
@@ -722,13 +1079,28 @@ class PredictionMarketClient {
722
1079
  */
723
1080
  async fetchAllResolvers(market, numResolvers) {
724
1081
  const pdas = Array.from({ length: numResolvers }, (_, i) => (0, pda_1.deriveResolver)(this.program.programId, market, i));
725
- const infos = await Promise.all(pdas.map((p) => this.connection.getAccountInfo(p)));
726
- const results = await Promise.all(pdas.map(async (pda, i) => {
727
- if (!infos[i])
728
- return null;
729
- const acc = (await this.program.account.resolver.fetch(pda));
730
- return { index: i, resolverPubkey: acc.resolverPubkey };
731
- }));
1082
+ const infos = await this.getMultipleAccountsInfoBatched(pdas);
1083
+ const results = [];
1084
+ for (let i = 0; i < numResolvers; i++) {
1085
+ const info = infos[i];
1086
+ if (!info) {
1087
+ results.push(null);
1088
+ continue;
1089
+ }
1090
+ try {
1091
+ const acc = this.program.coder.accounts.decode('Resolver', Buffer.from(info.data));
1092
+ const resolverPubkey = pickDecodedField(acc, 'resolverPubkey', 'resolver_pubkey');
1093
+ if (!resolverPubkey) {
1094
+ results.push(null);
1095
+ }
1096
+ else {
1097
+ results.push({ index: i, resolverPubkey });
1098
+ }
1099
+ }
1100
+ catch {
1101
+ results.push(null);
1102
+ }
1103
+ }
732
1104
  return results.filter((r) => r !== null);
733
1105
  }
734
1106
  // ─── Internal ────────────────────────────────────────────────────────────────