@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.d.ts +77 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +410 -38
- package/dist/client.js.map +1 -1
- package/dist/generated/vercora.d.ts +469 -109
- package/dist/generated/vercora.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/pda.d.ts +9 -2
- package/dist/pda.d.ts.map +1 -1
- package/dist/pda.js +14 -7
- package/dist/pda.js.map +1 -1
- package/dist/types.d.ts +75 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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,
|
|
298
|
-
const platformTreasuryAta = (0, spl_token_1.getAssociatedTokenAddressSync)(market.collateralMint, globalConfig.platformTreasury, false,
|
|
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
|
|
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
|
|
331
|
-
const
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
508
|
-
const infos = await Promise.all(
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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
|
|
726
|
-
const results =
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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 ────────────────────────────────────────────────────────────────
|