@theliem/xmarket-sdk 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1860 @@
1
+ 'use strict';
2
+
3
+ var anchor4 = require('@coral-xyz/anchor');
4
+ var web3_js = require('@solana/web3.js');
5
+ var crypto = require('crypto');
6
+ var splToken = require('@solana/spl-token');
7
+ var oracleIdl = require('./oracle-FZJJIJGI.json');
8
+ var hookIdl = require('./hook-THBRGUM6.json');
9
+ var questionMarketIdl = require('./question_market-CB6ZUZ5E.json');
10
+ var conditionalTokensIdl = require('./conditional_tokens-3O5V46N5.json');
11
+ var clobExchangeIdl = require('./clob_exchange-ATSH42KC.json');
12
+ var BN4 = require('bn.js');
13
+ var nacl = require('tweetnacl');
14
+
15
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
+
17
+ function _interopNamespace(e) {
18
+ if (e && e.__esModule) return e;
19
+ var n = Object.create(null);
20
+ if (e) {
21
+ Object.keys(e).forEach(function (k) {
22
+ if (k !== 'default') {
23
+ var d = Object.getOwnPropertyDescriptor(e, k);
24
+ Object.defineProperty(n, k, d.get ? d : {
25
+ enumerable: true,
26
+ get: function () { return e[k]; }
27
+ });
28
+ }
29
+ });
30
+ }
31
+ n.default = e;
32
+ return Object.freeze(n);
33
+ }
34
+
35
+ var anchor4__namespace = /*#__PURE__*/_interopNamespace(anchor4);
36
+ var oracleIdl__default = /*#__PURE__*/_interopDefault(oracleIdl);
37
+ var hookIdl__default = /*#__PURE__*/_interopDefault(hookIdl);
38
+ var questionMarketIdl__default = /*#__PURE__*/_interopDefault(questionMarketIdl);
39
+ var conditionalTokensIdl__default = /*#__PURE__*/_interopDefault(conditionalTokensIdl);
40
+ var clobExchangeIdl__default = /*#__PURE__*/_interopDefault(clobExchangeIdl);
41
+ var BN4__default = /*#__PURE__*/_interopDefault(BN4);
42
+ var nacl__namespace = /*#__PURE__*/_interopNamespace(nacl);
43
+
44
+ // src/sdk.ts
45
+ var DEVNET_CONFIG = {
46
+ name: "devnet",
47
+ rpcUrl: "https://api.devnet.solana.com",
48
+ programIds: {
49
+ oracle: new web3_js.PublicKey("FzLnnbKtQCTKviEbrDYMLZYXNDHQdjSSa4Ybj4jAbrUL"),
50
+ conditionalTokens: new web3_js.PublicKey("A6N1F8MRsdgcojAx8p6FaECvw8mo8w6qJcWsbKQBANK4"),
51
+ questionMarket: new web3_js.PublicKey("FCvCbWoLpzNYKzQnXYXCo1yz9DTpgUs11QqHVqnqtWhA"),
52
+ hook: new web3_js.PublicKey("F7y5MfW8d5kqR25QHDhUQ9LrKCiZUZVF6Pdnw15q5zZW"),
53
+ clobExchange: new web3_js.PublicKey("Cs4GY1yZVxjxhhyUum3AupMuS3bo4yKRXifgLTTAh1sf")
54
+ },
55
+ defaultCollateral: {
56
+ mint: new web3_js.PublicKey("53osACZSom79AHf6nK4eq8DL6viMZmyVERYPDB6S6Eck"),
57
+ decimals: 9
58
+ }
59
+ };
60
+ var LOCALNET_CONFIG = {
61
+ name: "localnet",
62
+ rpcUrl: "http://localhost:8899",
63
+ programIds: { ...DEVNET_CONFIG.programIds },
64
+ defaultCollateral: {
65
+ mint: new web3_js.PublicKey("6Gk4qsruSjunenwzP4L5DQukawfVNvdr9rPX2qG77jmH"),
66
+ decimals: 6
67
+ }
68
+ };
69
+ var MAINNET_CONFIG = {
70
+ name: "mainnet",
71
+ rpcUrl: "https://api.mainnet-beta.solana.com",
72
+ programIds: {
73
+ oracle: new web3_js.PublicKey("E667trvbHmZEnptjnjXp9x8e1Fpji3viHo1J1DGPKijh"),
74
+ conditionalTokens: new web3_js.PublicKey("3WFSX7zPLrJSU811h7awgqXTZuKhUR1Fi1VBDayHhk4s"),
75
+ questionMarket: new web3_js.PublicKey("GiQh2xBmEiM4gYq4U9PMSaUJNq1T9TfEJL5cXvNYhtGm"),
76
+ hook: new web3_js.PublicKey("11111111111111111111111111111111"),
77
+ // not yet deployed
78
+ clobExchange: new web3_js.PublicKey("11111111111111111111111111111111")
79
+ // not yet deployed
80
+ },
81
+ defaultCollateral: {
82
+ mint: new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
83
+ // USDC mainnet
84
+ decimals: 6
85
+ }
86
+ };
87
+ var NETWORK_CONFIGS = {
88
+ devnet: DEVNET_CONFIG,
89
+ localnet: LOCALNET_CONFIG,
90
+ mainnet: MAINNET_CONFIG
91
+ };
92
+ var SEEDS = {
93
+ config: Buffer.from("config"),
94
+ question: Buffer.from("question"),
95
+ condition: Buffer.from("condition"),
96
+ collateralVault: Buffer.from("collateral_vault"),
97
+ vaultToken: Buffer.from("vault_token"),
98
+ position: Buffer.from("position"),
99
+ yesMint: Buffer.from("yes_mint"),
100
+ noMint: Buffer.from("no_mint"),
101
+ mintAuthority: Buffer.from("mint_authority"),
102
+ reporter: Buffer.from("reporter"),
103
+ result: Buffer.from("result"),
104
+ ctfConfig: Buffer.from("ctf_config"),
105
+ hookConfig: Buffer.from("hook_config"),
106
+ extraAccountMetas: Buffer.from("extra-account-metas"),
107
+ clobConfig: Buffer.from("clob_config"),
108
+ order: Buffer.from("order")
109
+ };
110
+ var PDA = class {
111
+ // ─── Question Market ────────────────────────────────────────────────────────
112
+ static questionMarketConfig(owner, programIds) {
113
+ return web3_js.PublicKey.findProgramAddressSync(
114
+ [SEEDS.config, owner.toBuffer()],
115
+ programIds.questionMarket
116
+ );
117
+ }
118
+ static question(config, questionId, programIds) {
119
+ return web3_js.PublicKey.findProgramAddressSync(
120
+ [SEEDS.question, config.toBuffer(), Buffer.from(questionId)],
121
+ programIds.questionMarket
122
+ );
123
+ }
124
+ // ─── Conditional Tokens ─────────────────────────────────────────────────────
125
+ static ctfConfig(programIds) {
126
+ return web3_js.PublicKey.findProgramAddressSync(
127
+ [SEEDS.ctfConfig],
128
+ programIds.conditionalTokens
129
+ );
130
+ }
131
+ static condition(oracle, questionId, programIds) {
132
+ return web3_js.PublicKey.findProgramAddressSync(
133
+ [SEEDS.condition, oracle.toBuffer(), Buffer.from(questionId)],
134
+ programIds.conditionalTokens
135
+ );
136
+ }
137
+ static collateralVault(collateralMint, programIds) {
138
+ return web3_js.PublicKey.findProgramAddressSync(
139
+ [SEEDS.collateralVault, collateralMint.toBuffer()],
140
+ programIds.conditionalTokens
141
+ );
142
+ }
143
+ static vaultToken(collateralMint, programIds) {
144
+ return web3_js.PublicKey.findProgramAddressSync(
145
+ [SEEDS.vaultToken, collateralMint.toBuffer()],
146
+ programIds.conditionalTokens
147
+ );
148
+ }
149
+ static yesMint(condition, programIds) {
150
+ return web3_js.PublicKey.findProgramAddressSync(
151
+ [SEEDS.yesMint, condition.toBuffer()],
152
+ programIds.conditionalTokens
153
+ );
154
+ }
155
+ static noMint(condition, programIds) {
156
+ return web3_js.PublicKey.findProgramAddressSync(
157
+ [SEEDS.noMint, condition.toBuffer()],
158
+ programIds.conditionalTokens
159
+ );
160
+ }
161
+ static mintAuthority(condition, programIds) {
162
+ return web3_js.PublicKey.findProgramAddressSync(
163
+ [SEEDS.mintAuthority, condition.toBuffer()],
164
+ programIds.conditionalTokens
165
+ );
166
+ }
167
+ static position(condition, outcomeIndex, owner, programIds) {
168
+ return web3_js.PublicKey.findProgramAddressSync(
169
+ [SEEDS.position, condition.toBuffer(), Buffer.from([outcomeIndex]), owner.toBuffer()],
170
+ programIds.conditionalTokens
171
+ );
172
+ }
173
+ // ─── Oracle ─────────────────────────────────────────────────────────────────
174
+ static oracleConfig(owner, programIds) {
175
+ return web3_js.PublicKey.findProgramAddressSync(
176
+ [SEEDS.config, owner.toBuffer()],
177
+ programIds.oracle
178
+ );
179
+ }
180
+ static reporter(oracleConfig, reporterAddress, programIds) {
181
+ return web3_js.PublicKey.findProgramAddressSync(
182
+ [SEEDS.reporter, oracleConfig.toBuffer(), reporterAddress.toBuffer()],
183
+ programIds.oracle
184
+ );
185
+ }
186
+ static questionResult(oracleConfig, questionId, programIds) {
187
+ return web3_js.PublicKey.findProgramAddressSync(
188
+ [SEEDS.result, oracleConfig.toBuffer(), Buffer.from(questionId)],
189
+ programIds.oracle
190
+ );
191
+ }
192
+ // ─── Hook ───────────────────────────────────────────────────────────────────
193
+ static hookConfig(programIds) {
194
+ return web3_js.PublicKey.findProgramAddressSync(
195
+ [SEEDS.hookConfig],
196
+ programIds.hook
197
+ );
198
+ }
199
+ static extraAccountMetaList(mint, programIds) {
200
+ return web3_js.PublicKey.findProgramAddressSync(
201
+ [SEEDS.extraAccountMetas, mint.toBuffer()],
202
+ programIds.hook
203
+ );
204
+ }
205
+ // ─── CLOB ───────────────────────────────────────────────────────────────────
206
+ static clobConfig(programIds) {
207
+ return web3_js.PublicKey.findProgramAddressSync(
208
+ [SEEDS.clobConfig],
209
+ programIds.clobExchange
210
+ );
211
+ }
212
+ static orderStatus(maker, nonce, programIds) {
213
+ const nonceBuf = Buffer.alloc(8);
214
+ nonceBuf.writeBigUInt64LE(BigInt(nonce.toString()));
215
+ return web3_js.PublicKey.findProgramAddressSync(
216
+ [SEEDS.order, maker.toBuffer(), nonceBuf],
217
+ programIds.clobExchange
218
+ );
219
+ }
220
+ };
221
+ function generateQuestionId(content, salt) {
222
+ const input = content + (salt ?? Date.now());
223
+ return new Uint8Array(crypto.createHash("sha256").update(input).digest());
224
+ }
225
+ function generateContentHash(content) {
226
+ return new Uint8Array(crypto.createHash("sha256").update(content).digest());
227
+ }
228
+
229
+ // src/programs/oracle.ts
230
+ var OracleClient = class {
231
+ constructor(program, provider, programIds) {
232
+ this.program = program;
233
+ this.provider = provider;
234
+ this.programIds = programIds;
235
+ }
236
+ get walletPubkey() {
237
+ return this.provider.wallet.publicKey;
238
+ }
239
+ configPda(owner = this.walletPubkey) {
240
+ return PDA.oracleConfig(owner, this.programIds)[0];
241
+ }
242
+ // ─── Instructions ────────────────────────────────────────────────────────────
243
+ /** One-time setup. Caller becomes owner. */
244
+ async initialize(admin) {
245
+ const sig = await this.program.methods.initialize(admin).accounts({
246
+ owner: this.walletPubkey,
247
+ oracleConfig: this.configPda(),
248
+ systemProgram: web3_js.SystemProgram.programId
249
+ }).rpc();
250
+ return { signature: sig };
251
+ }
252
+ /** Admin or owner adds an address to the oracle whitelist. */
253
+ async addToWhitelist(address, ownerPubkey) {
254
+ const sig = await this.program.methods.addToWhitelist(address).accounts({
255
+ authority: this.walletPubkey,
256
+ payer: this.walletPubkey,
257
+ oracleConfig: this.configPda(ownerPubkey)
258
+ }).rpc();
259
+ return { signature: sig };
260
+ }
261
+ /** Admin or owner removes an address from the oracle whitelist. */
262
+ async removeFromWhitelist(address, ownerPubkey) {
263
+ const sig = await this.program.methods.removeFromWhitelist(address).accounts({
264
+ authority: this.walletPubkey,
265
+ payer: this.walletPubkey,
266
+ oracleConfig: this.configPda(ownerPubkey)
267
+ }).rpc();
268
+ return { signature: sig };
269
+ }
270
+ /**
271
+ * Whitelisted reporter resolves a question.
272
+ * CPIs directly into CTF.set_payout — condition is resolved in one tx.
273
+ * @param conditionPda - CTF condition account (oracle_config = oraclePda, question_id must match)
274
+ * @param ownerPubkey - oracle config owner (defaults to wallet)
275
+ * @param payer - fee payer (defaults to wallet)
276
+ */
277
+ async resolveQuestion(questionId, outcomeCount, payoutNumerators, conditionPda, ownerPubkey, payer, signers = []) {
278
+ const oracleConfig = this.configPda(ownerPubkey);
279
+ const [questionResultPda] = PDA.questionResult(oracleConfig, questionId, this.programIds);
280
+ const sig = await this.program.methods.resolveQuestion(
281
+ Array.from(questionId),
282
+ outcomeCount,
283
+ payoutNumerators.map((n) => new anchor4__namespace.BN(n))
284
+ ).accounts({
285
+ reporter: this.walletPubkey,
286
+ oracleConfig,
287
+ questionResult: questionResultPda,
288
+ condition: conditionPda,
289
+ conditionalTokensProgram: this.programIds.conditionalTokens,
290
+ payer: payer ?? this.walletPubkey,
291
+ systemProgram: web3_js.SystemProgram.programId
292
+ }).signers(signers).rpc();
293
+ return { signature: sig };
294
+ }
295
+ /** Owner updates the admin. */
296
+ async updateAdmin(newAdmin, ownerPubkey) {
297
+ const sig = await this.program.methods.updateAdmin(newAdmin).accounts({
298
+ owner: this.walletPubkey,
299
+ oracleConfig: this.configPda(ownerPubkey)
300
+ }).rpc();
301
+ return { signature: sig };
302
+ }
303
+ /** Owner transfers ownership to a new keypair. */
304
+ async transferOwnership(newOwner, ownerPubkey) {
305
+ const sig = await this.program.methods.transferOwnership(newOwner).accounts({
306
+ owner: this.walletPubkey,
307
+ oracleConfig: this.configPda(ownerPubkey)
308
+ }).rpc();
309
+ return { signature: sig };
310
+ }
311
+ /** Admin or owner pause/unpause the oracle. */
312
+ async pause(paused, ownerPubkey) {
313
+ const sig = await this.program.methods.pause(paused).accounts({
314
+ authority: this.walletPubkey,
315
+ oracleConfig: this.configPda(ownerPubkey)
316
+ }).rpc();
317
+ return { signature: sig };
318
+ }
319
+ // ─── Queries ─────────────────────────────────────────────────────────────────
320
+ async fetchConfig(owner) {
321
+ try {
322
+ const acc = await this.program.account.oracleConfig.fetch(
323
+ this.configPda(owner ?? this.walletPubkey)
324
+ );
325
+ return {
326
+ owner: acc.owner,
327
+ admin: acc.admin,
328
+ questionCount: acc.questionCount.toNumber(),
329
+ whitelist: acc.whitelist,
330
+ whitelistLen: acc.whitelistLen,
331
+ isPaused: acc.isPaused,
332
+ bump: acc.bump
333
+ };
334
+ } catch {
335
+ return null;
336
+ }
337
+ }
338
+ async fetchQuestionResult(questionId, ownerPubkey) {
339
+ try {
340
+ const oracleConfig = this.configPda(ownerPubkey);
341
+ const [pda] = PDA.questionResult(oracleConfig, questionId, this.programIds);
342
+ const acc = await this.program.account.questionResult.fetch(pda);
343
+ return {
344
+ oracleConfig: acc.oracleConfig,
345
+ questionId: new Uint8Array(acc.questionId),
346
+ outcomeIndex: acc.outcomeIndex,
347
+ outcomeCount: acc.outcomeCount,
348
+ payoutNumerators: acc.payoutNumerators,
349
+ isResolved: acc.isResolved,
350
+ resolvedAt: acc.resolvedAt.toNumber(),
351
+ reporter: acc.reporter,
352
+ condition: acc.condition ?? null,
353
+ bump: acc.bump
354
+ };
355
+ } catch {
356
+ return null;
357
+ }
358
+ }
359
+ };
360
+ var HookClient = class {
361
+ constructor(program, provider, programIds) {
362
+ this.program = program;
363
+ this.provider = provider;
364
+ this.programIds = programIds;
365
+ }
366
+ get walletPubkey() {
367
+ return this.provider.wallet.publicKey;
368
+ }
369
+ configPda() {
370
+ return PDA.hookConfig(this.programIds)[0];
371
+ }
372
+ // ─── Instructions ────────────────────────────────────────────────────────────
373
+ /** One-time setup. Caller becomes owner. */
374
+ async initialize(initialWhitelist) {
375
+ const sig = await this.program.methods.initializeHookConfig(initialWhitelist).accounts({
376
+ owner: this.walletPubkey,
377
+ hookConfig: this.configPda(),
378
+ systemProgram: web3_js.SystemProgram.programId
379
+ }).rpc();
380
+ return { signature: sig };
381
+ }
382
+ /**
383
+ * Register extra account metas for a Token-2022 YES/NO mint.
384
+ * Must be called once per mint after CTF creates it.
385
+ */
386
+ async initializeExtraAccountMetaList(mint) {
387
+ const [extraAccountMetaList] = PDA.extraAccountMetaList(mint, this.programIds);
388
+ const sig = await this.program.methods.initializeExtraAccountMetaList().accounts({
389
+ payer: this.walletPubkey,
390
+ extraAccountMetaList,
391
+ mint,
392
+ hookConfig: this.configPda(),
393
+ tokenProgram: splToken.TOKEN_2022_PROGRAM_ID,
394
+ systemProgram: web3_js.SystemProgram.programId
395
+ }).rpc();
396
+ return { signature: sig };
397
+ }
398
+ /** Owner adds a program to the transfer whitelist. */
399
+ async addToWhitelist(program) {
400
+ const sig = await this.program.methods.addToWhitelist(program).accounts({
401
+ owner: this.walletPubkey,
402
+ hookConfig: this.configPda()
403
+ }).rpc();
404
+ return { signature: sig };
405
+ }
406
+ /** Owner removes a program from the transfer whitelist. */
407
+ async removeFromWhitelist(program) {
408
+ const sig = await this.program.methods.removeFromWhitelist(program).accounts({
409
+ owner: this.walletPubkey,
410
+ hookConfig: this.configPda()
411
+ }).rpc();
412
+ return { signature: sig };
413
+ }
414
+ /** Permanently freeze the whitelist — no further changes allowed. */
415
+ async freezeWhitelist() {
416
+ const sig = await this.program.methods.freezeWhitelist().accounts({
417
+ owner: this.walletPubkey,
418
+ hookConfig: this.configPda()
419
+ }).rpc();
420
+ return { signature: sig };
421
+ }
422
+ /**
423
+ * SPL Transfer-Hook `execute` — invoked automatically by Token-2022 on every
424
+ * YES/NO token transfer. Validates the destination against the whitelist.
425
+ *
426
+ * Calling this directly is useful for:
427
+ * - Off-chain simulation ("would this transfer be allowed?")
428
+ * - Integration tests that verify whitelist enforcement
429
+ *
430
+ * Token-2022 calls this automatically; you normally don't call it manually.
431
+ *
432
+ * @param sourceToken - Source token account (tokens leaving)
433
+ * @param mint - The YES/NO Token-2022 mint
434
+ * @param destinationToken - Destination token account (tokens arriving)
435
+ * @param owner - Authority that authorized the transfer
436
+ * @param amount - Token amount being transferred
437
+ */
438
+ async execute(sourceToken, mint, destinationToken, owner, amount) {
439
+ const [extraAccountMetaList] = PDA.extraAccountMetaList(mint, this.programIds);
440
+ const sig = await this.program.methods.execute(amount).accounts({
441
+ sourceToken,
442
+ mint,
443
+ destinationToken,
444
+ owner,
445
+ extraAccountMetaList,
446
+ hookConfig: this.configPda()
447
+ }).rpc();
448
+ return { signature: sig };
449
+ }
450
+ // ─── Queries ─────────────────────────────────────────────────────────────────
451
+ async fetchConfig() {
452
+ try {
453
+ const acc = await this.program.account.hookConfig.fetch(this.configPda());
454
+ return {
455
+ owner: acc.owner,
456
+ whitelist: acc.whitelist,
457
+ whitelistLen: acc.whitelistLen,
458
+ isFrozen: acc.isFrozen,
459
+ bump: acc.bump
460
+ };
461
+ } catch {
462
+ return null;
463
+ }
464
+ }
465
+ async isWhitelisted(program) {
466
+ const config = await this.fetchConfig();
467
+ if (!config) return false;
468
+ return config.whitelist.some((p) => p.equals(program));
469
+ }
470
+ };
471
+
472
+ // src/types.ts
473
+ var QuestionStatus = /* @__PURE__ */ ((QuestionStatus2) => {
474
+ QuestionStatus2["Pending"] = "pending";
475
+ QuestionStatus2["Approved"] = "approved";
476
+ QuestionStatus2["Rejected"] = "rejected";
477
+ QuestionStatus2["Resolved"] = "resolved";
478
+ return QuestionStatus2;
479
+ })(QuestionStatus || {});
480
+ var XMarketError = class extends Error {
481
+ constructor(message, code) {
482
+ super(message);
483
+ this.code = code;
484
+ this.name = "XMarketError";
485
+ }
486
+ };
487
+ var UnauthorizedError = class extends XMarketError {
488
+ constructor(msg = "Unauthorized") {
489
+ super(msg, "UNAUTHORIZED");
490
+ this.name = "UnauthorizedError";
491
+ }
492
+ };
493
+ var AccountNotFoundError = class extends XMarketError {
494
+ constructor(account, address) {
495
+ super(`${account} not found${address ? `: ${address}` : ""}`, "NOT_FOUND");
496
+ this.name = "AccountNotFoundError";
497
+ }
498
+ };
499
+ var InvalidParamError = class extends XMarketError {
500
+ constructor(msg = "Invalid parameter") {
501
+ super(msg, "INVALID_PARAM");
502
+ this.name = "InvalidParamError";
503
+ }
504
+ };
505
+
506
+ // src/programs/market.ts
507
+ var MarketClient = class {
508
+ constructor(program, provider, programIds, ownerPubkey) {
509
+ this.program = program;
510
+ this.provider = provider;
511
+ this.programIds = programIds;
512
+ this.configPda = PDA.questionMarketConfig(ownerPubkey, programIds)[0];
513
+ }
514
+ get walletPubkey() {
515
+ return this.provider.wallet.publicKey;
516
+ }
517
+ // ─── Instructions (return Transaction — caller signs + sends) ───────────────
518
+ async initialize(admin, oracle, owner = this.walletPubkey) {
519
+ return this.program.methods.initialize({
520
+ admin,
521
+ conditionalTokensProgram: this.programIds.conditionalTokens,
522
+ oracle
523
+ }).accounts({
524
+ owner,
525
+ config: this.configPda,
526
+ systemProgram: web3_js.SystemProgram.programId
527
+ }).transaction();
528
+ }
529
+ /**
530
+ * Build createQuestion transaction.
531
+ * @param payer - Pays rent for all new accounts (can differ from creator)
532
+ * @param creator - Identity of the question creator (signs but does not pay)
533
+ */
534
+ async createQuestion(params, oracle, creator = this.walletPubkey, payer = creator) {
535
+ const questionId = params.questionId ?? generateQuestionId(params.content);
536
+ const contentHash = params.contentHash ?? generateContentHash(params.content);
537
+ const [questionPda] = PDA.question(this.configPda, questionId, this.programIds);
538
+ const [conditionPda] = PDA.condition(oracle, questionId, this.programIds);
539
+ const [yesMint] = PDA.yesMint(conditionPda, this.programIds);
540
+ const [noMint] = PDA.noMint(conditionPda, this.programIds);
541
+ const [mintAuthority] = PDA.mintAuthority(conditionPda, this.programIds);
542
+ const [collateralVault] = PDA.collateralVault(params.collateralMint, this.programIds);
543
+ const tx = await this.program.methods.createQuestionWithCondition({
544
+ questionId: Array.from(questionId),
545
+ contentHash: Array.from(contentHash),
546
+ hookProgram: params.hookProgram,
547
+ authorizedClob: params.authorizedClob,
548
+ expirationTime: new anchor4__namespace.BN(params.expirationTime)
549
+ }).accounts({
550
+ payer,
551
+ creator,
552
+ config: this.configPda,
553
+ question: questionPda,
554
+ currencyMint: params.collateralMint,
555
+ oracle,
556
+ condition: conditionPda,
557
+ yesMint,
558
+ noMint,
559
+ mintAuthority,
560
+ collateralVault,
561
+ conditionalTokensProgram: this.programIds.conditionalTokens,
562
+ tokenProgram: splToken.TOKEN_2022_PROGRAM_ID,
563
+ systemProgram: web3_js.SystemProgram.programId,
564
+ rent: web3_js.SYSVAR_RENT_PUBKEY
565
+ }).transaction();
566
+ return { tx, questionPda, conditionPda, questionId };
567
+ }
568
+ /**
569
+ * Build createQuestionAdmin transaction (whitelist/admin path — status = Approved immediately).
570
+ * @param creator - Whitelisted creator (must be in whitelist or be admin/owner)
571
+ * @param payer - Fee payer (pays rent; can differ from creator)
572
+ */
573
+ async createQuestionAdmin(params, oracle, creator = this.walletPubkey, payer = creator) {
574
+ const questionId = params.questionId ?? generateQuestionId(params.content);
575
+ const contentHash = params.contentHash ?? generateContentHash(params.content);
576
+ const [questionPda] = PDA.question(this.configPda, questionId, this.programIds);
577
+ const [conditionPda] = PDA.condition(oracle, questionId, this.programIds);
578
+ const [yesMint] = PDA.yesMint(conditionPda, this.programIds);
579
+ const [noMint] = PDA.noMint(conditionPda, this.programIds);
580
+ const [mintAuthority] = PDA.mintAuthority(conditionPda, this.programIds);
581
+ const [collateralVault] = PDA.collateralVault(params.collateralMint, this.programIds);
582
+ const tx = await this.program.methods.createQuestionAdmin({
583
+ questionId: Array.from(questionId),
584
+ contentHash: Array.from(contentHash),
585
+ hookProgram: params.hookProgram,
586
+ authorizedClob: params.authorizedClob,
587
+ expirationTime: new anchor4__namespace.BN(params.expirationTime)
588
+ }).accounts({
589
+ creator,
590
+ payer,
591
+ config: this.configPda,
592
+ question: questionPda,
593
+ currencyMint: params.collateralMint,
594
+ oracle,
595
+ condition: conditionPda,
596
+ yesMint,
597
+ noMint,
598
+ mintAuthority,
599
+ collateralVault,
600
+ conditionalTokensProgram: this.programIds.conditionalTokens,
601
+ tokenProgram: splToken.TOKEN_2022_PROGRAM_ID,
602
+ systemProgram: web3_js.SystemProgram.programId,
603
+ rent: web3_js.SYSVAR_RENT_PUBKEY
604
+ }).transaction();
605
+ return { tx, questionPda, conditionPda, questionId };
606
+ }
607
+ async approveQuestion(questionPda, admin = this.walletPubkey, payer = admin) {
608
+ return this.program.methods.approveQuestion().accounts({
609
+ admin,
610
+ payer,
611
+ config: this.configPda,
612
+ question: questionPda
613
+ }).transaction();
614
+ }
615
+ async updateConfig(params, authority = this.walletPubkey, payer = authority) {
616
+ return this.program.methods.updateConfig({
617
+ newAdmin: params.newAdmin ?? null,
618
+ newOracle: params.newOracle ?? null,
619
+ isPaused: params.isPaused ?? null,
620
+ newConditionalTokensProgram: params.newConditionalTokensProgram ?? null
621
+ }).accounts({
622
+ authority,
623
+ payer,
624
+ config: this.configPda
625
+ }).transaction();
626
+ }
627
+ /** Set the admin (owner only). Replaces any existing admin. */
628
+ async addAdmin(newAdmin, owner = this.walletPubkey) {
629
+ return this.program.methods.addAdmin(newAdmin).accounts({
630
+ owner,
631
+ config: this.configPda
632
+ }).transaction();
633
+ }
634
+ /** Clear the admin (owner only). Sets admin to default pubkey. */
635
+ async removeAdmin(owner = this.walletPubkey) {
636
+ return this.program.methods.removeAdmin().accounts({
637
+ owner,
638
+ config: this.configPda
639
+ }).transaction();
640
+ }
641
+ async addToWhitelist(address, authority = this.walletPubkey, payer = authority) {
642
+ return this.program.methods.addToWhitelist(address).accounts({
643
+ authority,
644
+ payer,
645
+ config: this.configPda
646
+ }).transaction();
647
+ }
648
+ async removeFromWhitelist(address, authority = this.walletPubkey, payer = authority) {
649
+ return this.program.methods.removeFromWhitelist(address).accounts({
650
+ authority,
651
+ payer,
652
+ config: this.configPda
653
+ }).transaction();
654
+ }
655
+ // ─── Queries ─────────────────────────────────────────────────────────────────
656
+ async fetchConfig() {
657
+ try {
658
+ const acc = await this.program.account.questionMarketConfig.fetch(this.configPda);
659
+ return {
660
+ owner: acc.owner,
661
+ admin: acc.admin,
662
+ oracle: acc.oracle,
663
+ conditionalTokensProgram: acc.conditionalTokensProgram,
664
+ questionCount: acc.questionCount.toNumber(),
665
+ approvedCount: acc.approvedCount.toNumber(),
666
+ rejectedCount: acc.rejectedCount.toNumber(),
667
+ whitelist: acc.whitelist,
668
+ whitelistLen: acc.whitelistLen,
669
+ isPaused: acc.isPaused,
670
+ bump: acc.bump
671
+ };
672
+ } catch {
673
+ return null;
674
+ }
675
+ }
676
+ async fetchQuestion(questionPda) {
677
+ try {
678
+ const acc = await this.program.account.question.fetch(questionPda);
679
+ return {
680
+ config: acc.config,
681
+ questionId: new Uint8Array(acc.questionId),
682
+ contentHash: new Uint8Array(acc.contentHash),
683
+ expirationTime: acc.expirationTime.toNumber(),
684
+ currencyMint: acc.currencyMint,
685
+ creator: acc.creator,
686
+ condition: acc.condition ?? null,
687
+ status: this._parseStatus(acc.status),
688
+ createdAt: acc.createdAt.toNumber(),
689
+ approvedAt: acc.approvedAt.toNumber(),
690
+ resolvedAt: acc.resolvedAt.toNumber(),
691
+ bump: acc.bump
692
+ };
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ questionPda(questionId) {
698
+ return PDA.question(this.configPda, questionId, this.programIds)[0];
699
+ }
700
+ _parseStatus(raw) {
701
+ if (raw.pending !== void 0) return "pending" /* Pending */;
702
+ if (raw.approved !== void 0) return "approved" /* Approved */;
703
+ if (raw.rejected !== void 0) return "rejected" /* Rejected */;
704
+ if (raw.resolved !== void 0) return "resolved" /* Resolved */;
705
+ return "pending" /* Pending */;
706
+ }
707
+ /**
708
+ * Convenience: fetch YES and NO token balances for a question.
709
+ * Returns null for each if question not approved or position not initialized.
710
+ * Requires ctfClient to be injected (done automatically by XMarketSDK).
711
+ */
712
+ async fetchQuestionBalances(questionPda, owner) {
713
+ if (!this.ctfClient) return { yes: null, no: null };
714
+ const q = await this.fetchQuestion(questionPda);
715
+ if (!q || !q.condition) return { yes: null, no: null };
716
+ return this.ctfClient.fetchBothPositions(q.condition, owner);
717
+ }
718
+ };
719
+ var CtfClient = class {
720
+ constructor(program, provider, programIds) {
721
+ this.program = program;
722
+ this.provider = provider;
723
+ this.programIds = programIds;
724
+ }
725
+ get walletPubkey() {
726
+ return this.provider.wallet.publicKey;
727
+ }
728
+ // ─── Instructions ────────────────────────────────────────────────────────────
729
+ /**
730
+ * Create a Condition directly (bypasses QuestionMarket).
731
+ * oracle is UncheckedAccount — set it to any pubkey (e.g. user wallet)
732
+ * so that wallet can later sign reportPayouts.
733
+ * payer covers rent for condition + mints.
734
+ */
735
+ async prepareCondition(questionId, oracle, collateralMint, hookProgram, authorizedClob, payer = this.walletPubkey, signers = []) {
736
+ const [conditionPda] = PDA.condition(oracle, questionId, this.programIds);
737
+ const [yesMint] = PDA.yesMint(conditionPda, this.programIds);
738
+ const [noMint] = PDA.noMint(conditionPda, this.programIds);
739
+ const [mintAuthority] = PDA.mintAuthority(conditionPda, this.programIds);
740
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
741
+ const sig = await this.program.methods.prepareCondition(
742
+ Array.from(questionId),
743
+ hookProgram,
744
+ authorizedClob
745
+ ).accounts({
746
+ oracle,
747
+ condition: conditionPda,
748
+ yesMint,
749
+ noMint,
750
+ mintAuthority,
751
+ collateralMint,
752
+ collateralVault,
753
+ payer,
754
+ tokenProgram: splToken.TOKEN_2022_PROGRAM_ID,
755
+ systemProgram: web3_js.SystemProgram.programId,
756
+ rent: web3_js.SYSVAR_RENT_PUBKEY
757
+ }).signers(signers).rpc({ skipPreflight: true });
758
+ return { signature: sig, conditionPda, yesMint, noMint };
759
+ }
760
+ /** One-time setup. Caller becomes the CTF owner. Can only be called once. */
761
+ async initializeCtfConfig() {
762
+ const [ctfConfig] = PDA.ctfConfig(this.programIds);
763
+ const sig = await this.program.methods.initializeCtfConfig().accounts({
764
+ payer: this.walletPubkey,
765
+ ctfConfig,
766
+ systemProgram: web3_js.SystemProgram.programId
767
+ }).rpc();
768
+ return { signature: sig };
769
+ }
770
+ /**
771
+ * Create the shared collateral vault for a given collateral mint (e.g. USDC).
772
+ * Only callable by the CTF owner (as stored in ctf_config).
773
+ * authority defaults to the wallet — pass a different signer if needed.
774
+ */
775
+ async initializeVault(collateralMint) {
776
+ const [ctfConfig] = PDA.ctfConfig(this.programIds);
777
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
778
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
779
+ const sig = await this.program.methods.initializeVault().accounts({
780
+ authority: this.walletPubkey,
781
+ payer: this.walletPubkey,
782
+ ctfConfig,
783
+ collateralMint,
784
+ collateralVault,
785
+ vaultTokenAccount,
786
+ systemProgram: web3_js.SystemProgram.programId,
787
+ tokenProgram: splToken.TOKEN_PROGRAM_ID,
788
+ rent: web3_js.SYSVAR_RENT_PUBKEY
789
+ }).rpc();
790
+ return { signature: sig };
791
+ }
792
+ /**
793
+ * Initialize an empty Position account (balance = 0) for a user.
794
+ * Needed before redeemPositions when the user only holds the opposite outcome
795
+ * (e.g. buyer received YES via CLOB match but never had a NO position).
796
+ *
797
+ * Idempotent — call `fetchPosition` first to skip if already initialized.
798
+ */
799
+ async initPosition(condition, outcomeIndex, user = this.walletPubkey) {
800
+ const [position] = PDA.position(condition, outcomeIndex, user, this.programIds);
801
+ const sig = await this.program.methods.initPosition(outcomeIndex).accounts({
802
+ user,
803
+ condition,
804
+ position,
805
+ systemProgram: web3_js.SystemProgram.programId
806
+ }).rpc();
807
+ return { signature: sig };
808
+ }
809
+ /**
810
+ * Split `amount` collateral into equal YES + NO tokens.
811
+ * ATAs created automatically via `init_if_needed`.
812
+ */
813
+ async splitPosition(condition, collateralMint, amount, user = this.walletPubkey, payer = this.walletPubkey, signers = []) {
814
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
815
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
816
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
817
+ const [noMint] = PDA.noMint(condition, this.programIds);
818
+ const [mintAuthority] = PDA.mintAuthority(condition, this.programIds);
819
+ const [yesPosition] = PDA.position(condition, 1, user, this.programIds);
820
+ const [noPosition] = PDA.position(condition, 0, user, this.programIds);
821
+ const userYesAta = splToken.getAssociatedTokenAddressSync(yesMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
822
+ const userNoAta = splToken.getAssociatedTokenAddressSync(noMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
823
+ const userCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, user);
824
+ const sig = await this.program.methods.splitPosition(amount).accounts({
825
+ user,
826
+ payer,
827
+ condition,
828
+ collateralVault,
829
+ vaultTokenAccount,
830
+ userCollateral,
831
+ yesMint,
832
+ userYesAta,
833
+ noMint,
834
+ userNoAta,
835
+ yesPosition,
836
+ noPosition,
837
+ mintAuthority,
838
+ tokenProgram: splToken.TOKEN_PROGRAM_ID,
839
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
840
+ associatedTokenProgram: splToken.ASSOCIATED_TOKEN_PROGRAM_ID,
841
+ systemProgram: web3_js.SystemProgram.programId
842
+ }).signers(signers).rpc({ skipPreflight: true });
843
+ return { signature: sig };
844
+ }
845
+ /**
846
+ * Merge `amount` YES + NO tokens back into collateral.
847
+ * Both token balances must be ≥ amount.
848
+ */
849
+ async mergePosition(condition, collateralMint, amount, user = this.walletPubkey, payer = this.walletPubkey, signers = []) {
850
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
851
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
852
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
853
+ const [noMint] = PDA.noMint(condition, this.programIds);
854
+ const [yesPosition] = PDA.position(condition, 1, user, this.programIds);
855
+ const [noPosition] = PDA.position(condition, 0, user, this.programIds);
856
+ const userYesAta = splToken.getAssociatedTokenAddressSync(yesMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
857
+ const userNoAta = splToken.getAssociatedTokenAddressSync(noMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
858
+ const userCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, user);
859
+ const sig = await this.program.methods.mergePosition(amount).accounts({
860
+ user,
861
+ payer,
862
+ condition,
863
+ collateralVault,
864
+ vaultTokenAccount,
865
+ userCollateral,
866
+ yesMint,
867
+ userYesAta,
868
+ noMint,
869
+ userNoAta,
870
+ yesPosition,
871
+ noPosition,
872
+ tokenProgram: splToken.TOKEN_PROGRAM_ID,
873
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
874
+ systemProgram: web3_js.SystemProgram.programId
875
+ }).signers(signers).rpc({ skipPreflight: true });
876
+ return { signature: sig };
877
+ }
878
+ /**
879
+ * After condition resolves: burn outcome tokens proportional to payout
880
+ * and receive USDC. Works for winning, losing, or both positions.
881
+ */
882
+ async redeemPositions(condition, collateralMint, user = this.walletPubkey, payer = this.walletPubkey, signers = []) {
883
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
884
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
885
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
886
+ const [noMint] = PDA.noMint(condition, this.programIds);
887
+ const [yesPosition] = PDA.position(condition, 1, user, this.programIds);
888
+ const [noPosition] = PDA.position(condition, 0, user, this.programIds);
889
+ const userYesAta = splToken.getAssociatedTokenAddressSync(yesMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
890
+ const userNoAta = splToken.getAssociatedTokenAddressSync(noMint, user, false, splToken.TOKEN_2022_PROGRAM_ID);
891
+ const userCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, user);
892
+ const sig = await this.program.methods.redeemPositions().accounts({
893
+ user,
894
+ payer,
895
+ condition,
896
+ collateralVault,
897
+ vaultTokenAccount,
898
+ userCollateral,
899
+ yesMint,
900
+ userYesAta,
901
+ noMint,
902
+ userNoAta,
903
+ yesPosition,
904
+ noPosition,
905
+ tokenProgram: splToken.TOKEN_PROGRAM_ID,
906
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
907
+ systemProgram: web3_js.SystemProgram.programId
908
+ }).signers(signers).rpc({ skipPreflight: true });
909
+ return { signature: sig };
910
+ }
911
+ /**
912
+ * Oracle directly resolves a condition with payout numerators.
913
+ * Bypasses QuestionMarket — only for oracle-owned conditions.
914
+ */
915
+ async reportPayouts(condition, payoutNumerators) {
916
+ const sig = await this.program.methods.reportPayouts(payoutNumerators.map((n) => new anchor4__namespace.BN(n))).accounts({
917
+ oracle: this.walletPubkey,
918
+ condition
919
+ }).rpc();
920
+ return { signature: sig };
921
+ }
922
+ /**
923
+ * Build the `transfer_position` instruction for use in a CLOB CPI.
924
+ *
925
+ * This instruction requires `clob_authority` (the CLOB's PDA) to sign.
926
+ * A PDA can only sign from within its own program via CPI — this method
927
+ * CANNOT be called directly from a wallet transaction.
928
+ *
929
+ * Use-cases:
930
+ * - Custom CLOB implementations that call CTF.transfer_position via CPI
931
+ * - Anchor CPI builders in other Rust programs
932
+ *
933
+ * @param condition - Condition PDA
934
+ * @param outcomeMint - YES or NO mint
935
+ * @param fromUser - Source wallet
936
+ * @param fromTokenAccount - Source ATA
937
+ * @param fromPosition - Source Position PDA
938
+ * @param toUser - Destination wallet
939
+ * @param toTokenAccount - Destination ATA (created if needed — payer covers rent)
940
+ * @param toPosition - Destination Position PDA
941
+ * @param payer - Pays rent for toPosition + toTokenAccount creation
942
+ * @param clobAuthority - CLOB config PDA (must sign via CPI)
943
+ * @param outcomeIndex - 0 = NO, 1 = YES
944
+ * @param amount - Token amount to transfer
945
+ */
946
+ async transferPositionIx(condition, outcomeMint, fromUser, fromTokenAccount, fromPosition, toUser, toTokenAccount, toPosition, payer, clobAuthority, outcomeIndex, amount) {
947
+ return this.program.methods.transferPosition(outcomeIndex, amount).accounts({
948
+ payer,
949
+ clobAuthority,
950
+ condition,
951
+ outcomeMint,
952
+ fromUser,
953
+ fromTokenAccount,
954
+ fromPosition,
955
+ toUser,
956
+ toTokenAccount,
957
+ toPosition,
958
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
959
+ systemProgram: web3_js.SystemProgram.programId
960
+ }).instruction();
961
+ }
962
+ /**
963
+ * Update the authorized CLOB on a condition.
964
+ * Only callable by the condition's oracle (the wallet that signed createQuestion).
965
+ */
966
+ async updateAuthorizedClob(condition, newAuthorizedClob) {
967
+ const sig = await this.program.methods.updateAuthorizedClob(newAuthorizedClob).accounts({
968
+ oracle: this.walletPubkey,
969
+ condition
970
+ }).rpc();
971
+ return { signature: sig };
972
+ }
973
+ // ─── Queries ─────────────────────────────────────────────────────────────────
974
+ async fetchCtfConfig() {
975
+ try {
976
+ const [ctfConfig] = PDA.ctfConfig(this.programIds);
977
+ const acc = await this.program.account.ctfConfig.fetch(ctfConfig);
978
+ return {
979
+ owner: acc.owner,
980
+ bump: acc.bump
981
+ };
982
+ } catch {
983
+ return null;
984
+ }
985
+ }
986
+ async fetchCondition(conditionPda) {
987
+ try {
988
+ const acc = await this.program.account.condition.fetch(conditionPda);
989
+ return {
990
+ oracle: acc.oracle,
991
+ questionId: new Uint8Array(acc.questionId),
992
+ outcomeSlotCount: acc.outcomeSlotCount,
993
+ payoutNumerators: acc.payoutNumerators,
994
+ payoutDenominator: acc.payoutDenominator,
995
+ totalCollateral: acc.totalCollateral,
996
+ collateralMint: acc.collateralMint,
997
+ collateralVault: acc.collateralVault,
998
+ yesMint: acc.yesMint,
999
+ noMint: acc.noMint,
1000
+ hookProgram: acc.hookProgram,
1001
+ authorizedClob: acc.authorizedClob,
1002
+ isResolved: acc.isResolved,
1003
+ resolvedAt: acc.resolvedAt?.toNumber() ?? 0,
1004
+ bump: acc.bump
1005
+ };
1006
+ } catch {
1007
+ return null;
1008
+ }
1009
+ }
1010
+ async fetchVault(collateralMint) {
1011
+ try {
1012
+ const [vaultPda] = PDA.collateralVault(collateralMint, this.programIds);
1013
+ const acc = await this.program.account.collateralVault.fetch(vaultPda);
1014
+ return {
1015
+ collateralMint: acc.collateralMint,
1016
+ vaultTokenAccount: acc.vaultTokenAccount,
1017
+ totalLocked: acc.totalLocked,
1018
+ conditionCount: acc.conditionCount,
1019
+ bump: acc.bump,
1020
+ vaultBump: acc.vaultBump
1021
+ };
1022
+ } catch {
1023
+ return null;
1024
+ }
1025
+ }
1026
+ async fetchPosition(condition, outcomeIndex, owner = this.walletPubkey) {
1027
+ try {
1028
+ const [pda] = PDA.position(condition, outcomeIndex, owner, this.programIds);
1029
+ const acc = await this.program.account.position.fetch(pda);
1030
+ return {
1031
+ owner: acc.owner,
1032
+ condition: acc.condition,
1033
+ outcomeIndex: acc.outcomeIndex,
1034
+ balance: acc.balance,
1035
+ bump: acc.bump
1036
+ };
1037
+ } catch {
1038
+ return null;
1039
+ }
1040
+ }
1041
+ /** YES = outcome index 1, NO = outcome index 0. */
1042
+ async fetchBothPositions(condition, owner = this.walletPubkey) {
1043
+ const [yes, no] = await Promise.all([
1044
+ this.fetchPosition(condition, 1, owner),
1045
+ this.fetchPosition(condition, 0, owner)
1046
+ ]);
1047
+ return { yes, no };
1048
+ }
1049
+ /** Thin wrapper: fetch position for a single outcome token (0=NO, 1=YES). */
1050
+ async fetchTokenBalance(condition, tokenId, owner = this.walletPubkey) {
1051
+ return this.fetchPosition(condition, tokenId, owner);
1052
+ }
1053
+ // ─── PDA helpers (public for consumers) ──────────────────────────────────────
1054
+ yesMintPda(condition) {
1055
+ return PDA.yesMint(condition, this.programIds)[0];
1056
+ }
1057
+ noMintPda(condition) {
1058
+ return PDA.noMint(condition, this.programIds)[0];
1059
+ }
1060
+ mintAuthorityPda(condition) {
1061
+ return PDA.mintAuthority(condition, this.programIds)[0];
1062
+ }
1063
+ collateralVaultPda(collateralMint) {
1064
+ return PDA.collateralVault(collateralMint, this.programIds)[0];
1065
+ }
1066
+ };
1067
+ function serializeOrderToBytes(order) {
1068
+ const buf = new Uint8Array(178);
1069
+ buf.set(order.maker.toBytes(), 0);
1070
+ buf.set(order.condition.toBytes(), 32);
1071
+ buf[64] = order.tokenId;
1072
+ buf[65] = order.side;
1073
+ buf.set(order.makerAmount.toArrayLike(Buffer, "le", 8), 66);
1074
+ buf.set(order.takerAmount.toArrayLike(Buffer, "le", 8), 74);
1075
+ buf.set(order.nonce.toArrayLike(Buffer, "le", 8), 82);
1076
+ buf.set(order.expiry.toArrayLike(Buffer, "le", 8), 90);
1077
+ buf.set(order.createdAt.toArrayLike(Buffer, "le", 8), 98);
1078
+ buf.set(order.fee.toArrayLike(Buffer, "le", 8), 106);
1079
+ buf.set(order.taker.toBytes(), 114);
1080
+ buf.set(order.signer.toBytes(), 146);
1081
+ return buf;
1082
+ }
1083
+ async function signOrder(order, signFn) {
1084
+ const message = serializeOrderToBytes(order);
1085
+ const signature = await signFn(message);
1086
+ if (signature.length !== 64) throw new Error("signature must be 64 bytes");
1087
+ return { order, signature };
1088
+ }
1089
+ function buildBatchedEd25519Instruction(orders) {
1090
+ const N = orders.length;
1091
+ if (N === 0) throw new Error("At least 1 order required");
1092
+ const MSG_SIZE = 178;
1093
+ const SIG_SIZE = 64;
1094
+ const PK_SIZE = 32;
1095
+ const HEADER = 2 + N * 14;
1096
+ const sigBase = HEADER;
1097
+ const pkBase = sigBase + N * SIG_SIZE;
1098
+ const msgBase = pkBase + N * PK_SIZE;
1099
+ const totalSize = msgBase + N * MSG_SIZE;
1100
+ const data = Buffer.alloc(totalSize);
1101
+ data[0] = N;
1102
+ data[1] = 0;
1103
+ for (let i = 0; i < N; i++) {
1104
+ const e = 2 + i * 14;
1105
+ data.writeUInt16LE(sigBase + i * SIG_SIZE, e);
1106
+ data.writeUInt16LE(65535, e + 2);
1107
+ data.writeUInt16LE(pkBase + i * PK_SIZE, e + 4);
1108
+ data.writeUInt16LE(65535, e + 6);
1109
+ data.writeUInt16LE(msgBase + i * MSG_SIZE, e + 8);
1110
+ data.writeUInt16LE(MSG_SIZE, e + 10);
1111
+ data.writeUInt16LE(65535, e + 12);
1112
+ data.set(orders[i].signature, sigBase + i * SIG_SIZE);
1113
+ data.set(orders[i].order.maker.toBytes(), pkBase + i * PK_SIZE);
1114
+ data.set(serializeOrderToBytes(orders[i].order), msgBase + i * MSG_SIZE);
1115
+ }
1116
+ return new web3_js.TransactionInstruction({
1117
+ keys: [],
1118
+ programId: web3_js.Ed25519Program.programId,
1119
+ data
1120
+ });
1121
+ }
1122
+ var IX_SYSVAR = web3_js.SYSVAR_INSTRUCTIONS_PUBKEY;
1123
+
1124
+ // src/programs/clob.ts
1125
+ var CLOB_WHITELIST_SEED = Buffer.from("clob_whitelist");
1126
+ var ClobClient = class {
1127
+ constructor(program, provider, programIds) {
1128
+ this.program = program;
1129
+ this.provider = provider;
1130
+ this.programIds = programIds;
1131
+ }
1132
+ get walletPubkey() {
1133
+ return this.provider.wallet.publicKey;
1134
+ }
1135
+ /**
1136
+ * Send a match transaction as versioned (v0).
1137
+ * Pass a pre-built AddressLookupTableAccount to compress account keys and
1138
+ * stay under the 1232-byte limit — required for match_complementary which
1139
+ * has ~25 accounts + 2 Ed25519 precompile instructions (~1697 bytes raw).
1140
+ *
1141
+ * If `whitelistedWallet` is provided and differs from `this.provider.wallet`,
1142
+ * both wallets sign the transaction (whitelisted operator + payer).
1143
+ */
1144
+ async _sendMatchTx(instructions, lookupTable, whitelistedWallet) {
1145
+ const { connection } = this.provider;
1146
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
1147
+ const message = new web3_js.TransactionMessage({
1148
+ payerKey: this.walletPubkey,
1149
+ recentBlockhash: blockhash,
1150
+ instructions
1151
+ }).compileToV0Message(lookupTable ? [lookupTable] : []);
1152
+ const vtx = new web3_js.VersionedTransaction(message);
1153
+ const signers = [this.provider.wallet.payer];
1154
+ if (whitelistedWallet && !whitelistedWallet.publicKey.equals(this.walletPubkey)) {
1155
+ signers.push(whitelistedWallet.payer);
1156
+ }
1157
+ vtx.sign(signers);
1158
+ const sig = await connection.sendRawTransaction(vtx.serialize(), {
1159
+ skipPreflight: false
1160
+ });
1161
+ await connection.confirmTransaction(
1162
+ { signature: sig, blockhash, lastValidBlockHeight },
1163
+ "confirmed"
1164
+ );
1165
+ return sig;
1166
+ }
1167
+ /** PDA for a CLOB whitelist entry. */
1168
+ whitelistEntryPda(address) {
1169
+ return web3_js.PublicKey.findProgramAddressSync(
1170
+ [CLOB_WHITELIST_SEED, address.toBytes()],
1171
+ this.programIds.clobExchange
1172
+ )[0];
1173
+ }
1174
+ configPda() {
1175
+ return PDA.clobConfig(this.programIds)[0];
1176
+ }
1177
+ // ─── Instructions ────────────────────────────────────────────────────────────
1178
+ /** One-time setup. Caller becomes admin. */
1179
+ async initialize(operators, feeRecipient, feeRateBps) {
1180
+ const sig = await this.program.methods.initializeClob(
1181
+ operators,
1182
+ feeRecipient,
1183
+ feeRateBps,
1184
+ this.programIds.conditionalTokens
1185
+ ).accounts({
1186
+ owner: this.walletPubkey,
1187
+ clobConfig: this.configPda(),
1188
+ systemProgram: web3_js.SystemProgram.programId
1189
+ }).rpc();
1190
+ return { signature: sig };
1191
+ }
1192
+ /** Admin adds an operator to the whitelist. */
1193
+ async addOperator(operator) {
1194
+ const sig = await this.program.methods.addOperator(operator).accounts({
1195
+ owner: this.walletPubkey,
1196
+ clobConfig: this.configPda()
1197
+ }).rpc();
1198
+ return { signature: sig };
1199
+ }
1200
+ /** Admin removes an operator from the whitelist. */
1201
+ async removeOperator(operator) {
1202
+ const sig = await this.program.methods.removeOperator(operator).accounts({
1203
+ owner: this.walletPubkey,
1204
+ clobConfig: this.configPda()
1205
+ }).rpc();
1206
+ return { signature: sig };
1207
+ }
1208
+ /** Admin pause/unpause the CLOB. */
1209
+ async setPaused(paused) {
1210
+ const sig = await this.program.methods.setPaused(paused).accounts({
1211
+ owner: this.walletPubkey,
1212
+ clobConfig: this.configPda()
1213
+ }).rpc();
1214
+ return { signature: sig };
1215
+ }
1216
+ /**
1217
+ * Maker cancels their own order.
1218
+ * OrderStatus PDA is marked cancelled — any future fill rejected.
1219
+ */
1220
+ async cancelOrder(nonce) {
1221
+ const maker = this.walletPubkey;
1222
+ const [orderStatus] = PDA.orderStatus(maker, nonce, this.programIds);
1223
+ const sig = await this.program.methods.cancelOrder(nonce).accounts({
1224
+ signer: maker,
1225
+ clobConfig: this.configPda(),
1226
+ maker,
1227
+ orderStatus,
1228
+ systemProgram: web3_js.SystemProgram.programId
1229
+ }).rpc();
1230
+ return { signature: sig };
1231
+ }
1232
+ /**
1233
+ * Emergency reset of CLOB config (upgrade authority only).
1234
+ */
1235
+ async forceResetClob(programData, newAdmin, newOperators, newFeeRecipient, newFeeRateBps) {
1236
+ const sig = await this.program.methods.forceResetClob(newAdmin, newOperators, newFeeRecipient, newFeeRateBps).accounts({
1237
+ upgradeAuthority: this.walletPubkey,
1238
+ programData,
1239
+ clobConfig: this.configPda()
1240
+ }).rpc();
1241
+ return { signature: sig };
1242
+ }
1243
+ /**
1244
+ * COMPLEMENTARY match: 1 buyer (taker) + N sellers (makers), same tokenId.
1245
+ *
1246
+ * Transaction structure:
1247
+ * ix[0] Ed25519(taker/buyer)
1248
+ * ix[1+i] Ed25519(maker_i/seller_i)
1249
+ * ix[N+1] match_complementary(buyNonce, makerNonces[])
1250
+ *
1251
+ * remaining_accounts: [hook×3] [seller×5 × N]
1252
+ */
1253
+ async matchComplementary(buySigned, makersSigned, collateralMint, feeRecipient, whitelistedWallet, lookupTable) {
1254
+ const condition = buySigned.order.condition;
1255
+ const tokenId = buySigned.order.tokenId;
1256
+ const [outcomeMint] = tokenId === 1 ? PDA.yesMint(condition, this.programIds) : PDA.noMint(condition, this.programIds);
1257
+ const buyer = buySigned.order.maker;
1258
+ const buyerCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, buyer);
1259
+ const buyerTokenAccount = splToken.getAssociatedTokenAddressSync(outcomeMint, buyer, false, splToken.TOKEN_2022_PROGRAM_ID);
1260
+ const [buyerPosition] = PDA.position(condition, tokenId, buyer, this.programIds);
1261
+ const [buyOrderStatus] = PDA.orderStatus(buyer, buySigned.order.nonce, this.programIds);
1262
+ const [extraAccountMetaList] = PDA.extraAccountMetaList(outcomeMint, this.programIds);
1263
+ const [hookConfig] = PDA.hookConfig(this.programIds);
1264
+ const hookAccounts = [
1265
+ { pubkey: extraAccountMetaList, isSigner: false, isWritable: false },
1266
+ { pubkey: hookConfig, isSigner: false, isWritable: false },
1267
+ { pubkey: this.programIds.hook, isSigner: false, isWritable: false }
1268
+ ];
1269
+ const makerAccounts = makersSigned.flatMap((m) => {
1270
+ const seller = m.order.maker;
1271
+ const sellerTokenAccount = splToken.getAssociatedTokenAddressSync(outcomeMint, seller, false, splToken.TOKEN_2022_PROGRAM_ID);
1272
+ const [sellerPosition] = PDA.position(condition, tokenId, seller, this.programIds);
1273
+ const sellerCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, seller);
1274
+ const [sellOrderStatus] = PDA.orderStatus(seller, m.order.nonce, this.programIds);
1275
+ return [
1276
+ { pubkey: seller, isSigner: false, isWritable: false },
1277
+ { pubkey: sellerTokenAccount, isSigner: false, isWritable: true },
1278
+ { pubkey: sellerPosition, isSigner: false, isWritable: true },
1279
+ { pubkey: sellerCollateral, isSigner: false, isWritable: true },
1280
+ { pubkey: sellOrderStatus, isSigner: false, isWritable: true }
1281
+ ];
1282
+ });
1283
+ const whitelisted = whitelistedWallet.publicKey;
1284
+ const whitelistEntry = this.whitelistEntryPda(whitelisted);
1285
+ const makerNonces = makersSigned.map((m) => m.order.nonce);
1286
+ const ed25519Ixs = [
1287
+ buildBatchedEd25519Instruction([buySigned, ...makersSigned])
1288
+ ];
1289
+ const matchIx = await this.program.methods.matchComplementary(buySigned.order.nonce, makerNonces).accounts({
1290
+ whitelisted,
1291
+ payer: this.walletPubkey,
1292
+ whitelistEntry,
1293
+ clobConfig: this.configPda(),
1294
+ ixSysvar: IX_SYSVAR,
1295
+ condition,
1296
+ buyer,
1297
+ buyerCollateral,
1298
+ buyerTokenAccount,
1299
+ buyerPosition,
1300
+ buyOrderStatus,
1301
+ outcomeMint,
1302
+ feeRecipient,
1303
+ conditionalTokensProgram: this.programIds.conditionalTokens,
1304
+ tokenProgram: splToken.TOKEN_PROGRAM_ID,
1305
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
1306
+ systemProgram: web3_js.SystemProgram.programId
1307
+ }).remainingAccounts([...hookAccounts, ...makerAccounts]).instruction();
1308
+ const sig = await this._sendMatchTx(
1309
+ [...ed25519Ixs, matchIx],
1310
+ lookupTable,
1311
+ whitelistedWallet
1312
+ );
1313
+ return { signature: sig };
1314
+ }
1315
+ /**
1316
+ * MINT match: 1 YES buyer (taker) + N NO buyers (makers).
1317
+ *
1318
+ * Transaction structure:
1319
+ * ix[0] Ed25519(taker/YES buyer)
1320
+ * ix[1+i] Ed25519(maker_i/NO buyer)
1321
+ * ix[N+1] match_mint_orders(yesNonce, makerNonces[])
1322
+ *
1323
+ * remaining_accounts: [maker×5 × N] (no hook accounts — mint_to doesn't fire hook)
1324
+ */
1325
+ async matchMintOrders(yesSigned, makersSigned, collateralMint, whitelistedWallet, lookupTable) {
1326
+ const condition = yesSigned.order.condition;
1327
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
1328
+ const [noMint] = PDA.noMint(condition, this.programIds);
1329
+ const [mintAuthority] = PDA.mintAuthority(condition, this.programIds);
1330
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
1331
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
1332
+ const [clobAuthority] = PDA.clobConfig(this.programIds);
1333
+ const buyerYes = yesSigned.order.maker;
1334
+ const buyerYesCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, buyerYes);
1335
+ const buyerYesTokenAccount = splToken.getAssociatedTokenAddressSync(yesMint, buyerYes, false, splToken.TOKEN_2022_PROGRAM_ID);
1336
+ const [buyerYesPosition] = PDA.position(condition, 1, buyerYes, this.programIds);
1337
+ const [yesOrderStatus] = PDA.orderStatus(buyerYes, yesSigned.order.nonce, this.programIds);
1338
+ const makerAccounts = makersSigned.flatMap((m) => {
1339
+ const maker = m.order.maker;
1340
+ const makerUsdcAta = splToken.getAssociatedTokenAddressSync(collateralMint, maker);
1341
+ const makerNoAta = splToken.getAssociatedTokenAddressSync(noMint, maker, false, splToken.TOKEN_2022_PROGRAM_ID);
1342
+ const [makerNoPosition] = PDA.position(condition, 0, maker, this.programIds);
1343
+ const [makerOrderStatus] = PDA.orderStatus(maker, m.order.nonce, this.programIds);
1344
+ return [
1345
+ { pubkey: maker, isSigner: false, isWritable: false },
1346
+ { pubkey: makerUsdcAta, isSigner: false, isWritable: true },
1347
+ { pubkey: makerNoAta, isSigner: false, isWritable: true },
1348
+ { pubkey: makerNoPosition, isSigner: false, isWritable: true },
1349
+ { pubkey: makerOrderStatus, isSigner: false, isWritable: true }
1350
+ ];
1351
+ });
1352
+ const whitelisted = whitelistedWallet.publicKey;
1353
+ const whitelistEntry = this.whitelistEntryPda(whitelisted);
1354
+ const makerNonces = makersSigned.map((m) => m.order.nonce);
1355
+ const ed25519Ixs = [
1356
+ buildBatchedEd25519Instruction([yesSigned, ...makersSigned])
1357
+ ];
1358
+ const matchIx = await this.program.methods.matchMintOrders(yesSigned.order.nonce, makerNonces).accounts({
1359
+ whitelisted,
1360
+ payer: this.walletPubkey,
1361
+ whitelistEntry,
1362
+ clobConfig: this.configPda(),
1363
+ ixSysvar: IX_SYSVAR,
1364
+ condition,
1365
+ buyerYes,
1366
+ buyerYesCollateral,
1367
+ buyerYesTokenAccount,
1368
+ buyerYesPosition,
1369
+ yesOrderStatus,
1370
+ collateralVault,
1371
+ vaultTokenAccount,
1372
+ yesMint,
1373
+ noMint,
1374
+ mintAuthority,
1375
+ clobAuthority,
1376
+ conditionalTokensProgram: this.programIds.conditionalTokens,
1377
+ collateralTokenProgram: splToken.TOKEN_PROGRAM_ID,
1378
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
1379
+ systemProgram: web3_js.SystemProgram.programId
1380
+ }).remainingAccounts(makerAccounts).instruction();
1381
+ const sig = await this._sendMatchTx(
1382
+ [...ed25519Ixs, matchIx],
1383
+ lookupTable,
1384
+ whitelistedWallet
1385
+ );
1386
+ return { signature: sig };
1387
+ }
1388
+ /**
1389
+ * MERGE match: 1 YES seller (taker) + N NO sellers (makers).
1390
+ *
1391
+ * Transaction structure:
1392
+ * ix[0] Ed25519(taker/YES seller)
1393
+ * ix[1+i] Ed25519(maker_i/NO seller)
1394
+ * ix[N+1] match_merge_orders(yesNonce, makerNonces[])
1395
+ *
1396
+ * remaining_accounts: [maker×5 × N] (no hook accounts — burn doesn't fire hook)
1397
+ */
1398
+ async matchMergeOrders(yesSigned, makersSigned, collateralMint, whitelistedWallet, lookupTable) {
1399
+ const condition = yesSigned.order.condition;
1400
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
1401
+ const [noMint] = PDA.noMint(condition, this.programIds);
1402
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
1403
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
1404
+ const [clobAuthority] = PDA.clobConfig(this.programIds);
1405
+ const sellerYes = yesSigned.order.maker;
1406
+ const sellerYesTokenAccount = splToken.getAssociatedTokenAddressSync(yesMint, sellerYes, false, splToken.TOKEN_2022_PROGRAM_ID);
1407
+ const [sellerYesPosition] = PDA.position(condition, 1, sellerYes, this.programIds);
1408
+ const sellerYesCollateral = splToken.getAssociatedTokenAddressSync(collateralMint, sellerYes);
1409
+ const [yesOrderStatus] = PDA.orderStatus(sellerYes, yesSigned.order.nonce, this.programIds);
1410
+ const makerAccounts = makersSigned.flatMap((m) => {
1411
+ const maker = m.order.maker;
1412
+ const makerNoAta = splToken.getAssociatedTokenAddressSync(noMint, maker, false, splToken.TOKEN_2022_PROGRAM_ID);
1413
+ const [makerNoPosition] = PDA.position(condition, 0, maker, this.programIds);
1414
+ const makerUsdcAta = splToken.getAssociatedTokenAddressSync(collateralMint, maker);
1415
+ const [makerOrderStatus] = PDA.orderStatus(maker, m.order.nonce, this.programIds);
1416
+ return [
1417
+ { pubkey: maker, isSigner: false, isWritable: false },
1418
+ { pubkey: makerNoAta, isSigner: false, isWritable: true },
1419
+ { pubkey: makerNoPosition, isSigner: false, isWritable: true },
1420
+ { pubkey: makerUsdcAta, isSigner: false, isWritable: true },
1421
+ { pubkey: makerOrderStatus, isSigner: false, isWritable: true }
1422
+ ];
1423
+ });
1424
+ const whitelisted = whitelistedWallet.publicKey;
1425
+ const whitelistEntry = this.whitelistEntryPda(whitelisted);
1426
+ const makerNonces = makersSigned.map((m) => m.order.nonce);
1427
+ const ed25519Ixs = [
1428
+ buildBatchedEd25519Instruction([yesSigned, ...makersSigned])
1429
+ ];
1430
+ const matchIx = await this.program.methods.matchMergeOrders(yesSigned.order.nonce, makerNonces).accounts({
1431
+ whitelisted,
1432
+ payer: this.walletPubkey,
1433
+ whitelistEntry,
1434
+ clobConfig: this.configPda(),
1435
+ ixSysvar: IX_SYSVAR,
1436
+ condition,
1437
+ sellerYes,
1438
+ sellerYesTokenAccount,
1439
+ sellerYesPosition,
1440
+ sellerYesCollateral,
1441
+ yesOrderStatus,
1442
+ collateralVault,
1443
+ vaultTokenAccount,
1444
+ yesMint,
1445
+ noMint,
1446
+ clobAuthority,
1447
+ conditionalTokensProgram: this.programIds.conditionalTokens,
1448
+ collateralTokenProgram: splToken.TOKEN_PROGRAM_ID,
1449
+ token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
1450
+ systemProgram: web3_js.SystemProgram.programId
1451
+ }).remainingAccounts(makerAccounts).instruction();
1452
+ const sig = await this._sendMatchTx(
1453
+ [...ed25519Ixs, matchIx],
1454
+ lookupTable,
1455
+ whitelistedWallet
1456
+ );
1457
+ return { signature: sig };
1458
+ }
1459
+ /**
1460
+ * Auto-detect match type and execute 1-taker + N-makers in a single transaction.
1461
+ *
1462
+ * Detection (pure, no RPC) based on taker.order vs makers[0].order:
1463
+ * taker.tokenId === makers[0].tokenId: taker BUY + all makers SELL → COMPLEMENTARY
1464
+ * taker.tokenId !== makers[0].tokenId + taker BUY + all makers BUY → MINT
1465
+ * taker.tokenId !== makers[0].tokenId + taker SELL + all makers SELL → MERGE
1466
+ * Otherwise → throws InvalidParamError
1467
+ *
1468
+ * All makers must have the same tokenId and side as makers[0].
1469
+ */
1470
+ async matchOrders(taker, makers, collateralMint, feeRecipient, whitelistedWallet, lookupTable) {
1471
+ if (makers.length === 0) throw new InvalidParamError("At least 1 maker required");
1472
+ const t = taker.order;
1473
+ const m0 = makers[0].order;
1474
+ const SIDE_BUY = 1;
1475
+ const SIDE_SELL = 0;
1476
+ if (t.tokenId === m0.tokenId) {
1477
+ if (t.side === SIDE_BUY && makers.every((m) => m.order.side === SIDE_SELL)) {
1478
+ return this.matchComplementary(taker, makers, collateralMint, feeRecipient, whitelistedWallet, lookupTable);
1479
+ }
1480
+ if (t.side === SIDE_SELL && makers.every((m) => m.order.side === SIDE_BUY)) {
1481
+ throw new InvalidParamError("COMPLEMENTARY N-maker: taker must be the BUY side");
1482
+ }
1483
+ throw new InvalidParamError("COMPLEMENTARY requires taker=BUY, makers=SELL on same tokenId");
1484
+ }
1485
+ const allBuy = t.side === SIDE_BUY && makers.every((m) => m.order.side === SIDE_BUY);
1486
+ const allSell = t.side === SIDE_SELL && makers.every((m) => m.order.side === SIDE_SELL);
1487
+ if (!allBuy && !allSell) {
1488
+ throw new InvalidParamError("MINT/MERGE: all orders must be the same side (all BUY or all SELL)");
1489
+ }
1490
+ if (t.tokenId !== 1) {
1491
+ throw new InvalidParamError("MINT/MERGE: taker must be the YES side (tokenId=1)");
1492
+ }
1493
+ if (!makers.every((m) => m.order.tokenId === 0)) {
1494
+ throw new InvalidParamError("MINT/MERGE: all makers must be the NO side (tokenId=0)");
1495
+ }
1496
+ if (allBuy) {
1497
+ return this.matchMintOrders(taker, makers, collateralMint, whitelistedWallet, lookupTable);
1498
+ }
1499
+ return this.matchMergeOrders(taker, makers, collateralMint, whitelistedWallet, lookupTable);
1500
+ }
1501
+ // ─── Whitelist admin ─────────────────────────────────────────────────────────
1502
+ /** Add an address to the CLOB whitelist (owner only). */
1503
+ async addToWhitelist(address) {
1504
+ const whitelistEntry = this.whitelistEntryPda(address);
1505
+ const sig = await this.program.methods.addToClobWhitelist(address).accounts({
1506
+ owner: this.walletPubkey,
1507
+ payer: this.walletPubkey,
1508
+ clobConfig: this.configPda(),
1509
+ whitelistEntry,
1510
+ systemProgram: web3_js.SystemProgram.programId
1511
+ }).rpc();
1512
+ return { signature: sig };
1513
+ }
1514
+ /** Batch-add addresses to the CLOB whitelist (owner only). All PDAs created in one tx. */
1515
+ async addToWhitelistBatch(addresses) {
1516
+ if (addresses.length === 0) throw new InvalidParamError("At least 1 address required");
1517
+ const remainingAccounts = addresses.map((addr) => ({
1518
+ pubkey: this.whitelistEntryPda(addr),
1519
+ isSigner: false,
1520
+ isWritable: true
1521
+ }));
1522
+ const sig = await this.program.methods.addToClobWhitelistBatch(addresses).accounts({
1523
+ owner: this.walletPubkey,
1524
+ payer: this.walletPubkey,
1525
+ clobConfig: this.configPda(),
1526
+ systemProgram: web3_js.SystemProgram.programId
1527
+ }).remainingAccounts(remainingAccounts).rpc();
1528
+ return { signature: sig };
1529
+ }
1530
+ /** Remove an address from the CLOB whitelist (owner only). */
1531
+ async removeFromWhitelist(address, rentReceiver) {
1532
+ const whitelistEntry = this.whitelistEntryPda(address);
1533
+ const sig = await this.program.methods.removeFromClobWhitelist(address).accounts({
1534
+ owner: this.walletPubkey,
1535
+ rentReceiver: rentReceiver ?? this.walletPubkey,
1536
+ clobConfig: this.configPda(),
1537
+ whitelistEntry
1538
+ }).rpc();
1539
+ return { signature: sig };
1540
+ }
1541
+ /** Fetch a whitelist entry PDA (returns null if address is not whitelisted). */
1542
+ async fetchWhitelistEntry(address) {
1543
+ try {
1544
+ const pda = this.whitelistEntryPda(address);
1545
+ const acc = await this.program.account.clobWhitelistEntry.fetch(pda);
1546
+ return { address: acc.address, bump: acc.bump };
1547
+ } catch {
1548
+ return null;
1549
+ }
1550
+ }
1551
+ /** Check if an address is on the CLOB whitelist. */
1552
+ async isWhitelisted(address) {
1553
+ return await this.fetchWhitelistEntry(address) !== null;
1554
+ }
1555
+ /** Fetch all whitelisted addresses (scans all ClobWhitelistEntry accounts). */
1556
+ async fetchWhitelist() {
1557
+ const entries = await this.program.account.clobWhitelistEntry.all();
1558
+ return entries.map((e) => e.account.address);
1559
+ }
1560
+ // ─── Queries ─────────────────────────────────────────────────────────────────
1561
+ async fetchConfig() {
1562
+ try {
1563
+ const acc = await this.program.account.clobConfig.fetch(this.configPda());
1564
+ return {
1565
+ owner: acc.owner,
1566
+ operators: acc.operators,
1567
+ operatorsLen: acc.operatorsLen,
1568
+ feeRecipient: acc.feeRecipient,
1569
+ feeRateBps: acc.feeRateBps,
1570
+ conditionalTokensProgram: acc.conditionalTokensProgram,
1571
+ isPaused: acc.isPaused,
1572
+ bump: acc.bump
1573
+ };
1574
+ } catch {
1575
+ return null;
1576
+ }
1577
+ }
1578
+ async fetchOrderStatus(maker, nonce) {
1579
+ try {
1580
+ const [pda] = PDA.orderStatus(maker, nonce, this.programIds);
1581
+ const acc = await this.program.account.orderStatus.fetch(pda);
1582
+ return {
1583
+ maker: acc.maker,
1584
+ nonce: acc.nonce,
1585
+ isCancelled: acc.isCancelled,
1586
+ filledAmount: acc.filledAmount,
1587
+ bump: acc.bump
1588
+ };
1589
+ } catch {
1590
+ return null;
1591
+ }
1592
+ }
1593
+ async isOrderCancelled(maker, nonce) {
1594
+ const status = await this.fetchOrderStatus(maker, nonce);
1595
+ return status?.isCancelled === true;
1596
+ }
1597
+ };
1598
+ var XMarketSDK = class _XMarketSDK {
1599
+ constructor(config) {
1600
+ this.networkConfig = typeof config.network === "string" ? NETWORK_CONFIGS[config.network] : config.network;
1601
+ this.provider = new anchor4__namespace.AnchorProvider(
1602
+ new web3_js.Connection(this.networkConfig.rpcUrl, "confirmed"),
1603
+ config.wallet,
1604
+ { commitment: "confirmed", preflightCommitment: "confirmed" }
1605
+ );
1606
+ anchor4__namespace.setProvider(this.provider);
1607
+ this._programIds = this.networkConfig.programIds;
1608
+ this._marketOwner = config.marketOwner ?? config.wallet.publicKey;
1609
+ }
1610
+ // ─── Lazy-initialised clients ─────────────────────────────────────────────
1611
+ _withAddress(idl, address) {
1612
+ return { ...idl, address: address.toBase58() };
1613
+ }
1614
+ /** Oracle program client */
1615
+ get oracle() {
1616
+ if (!this._oracle) {
1617
+ const program = new anchor4__namespace.Program(this._withAddress(oracleIdl__default.default, this._programIds.oracle), this.provider);
1618
+ this._oracle = new OracleClient(program, this.provider, this._programIds);
1619
+ }
1620
+ return this._oracle;
1621
+ }
1622
+ /** Transfer-hook program client */
1623
+ get hook() {
1624
+ if (!this._hook) {
1625
+ const program = new anchor4__namespace.Program(this._withAddress(hookIdl__default.default, this._programIds.hook), this.provider);
1626
+ this._hook = new HookClient(program, this.provider, this._programIds);
1627
+ }
1628
+ return this._hook;
1629
+ }
1630
+ /** QuestionMarket program client */
1631
+ get market() {
1632
+ if (!this._market) {
1633
+ const program = new anchor4__namespace.Program(this._withAddress(questionMarketIdl__default.default, this._programIds.questionMarket), this.provider);
1634
+ this._market = new MarketClient(program, this.provider, this._programIds, this._marketOwner);
1635
+ this._market.ctfClient = this.ctf;
1636
+ }
1637
+ return this._market;
1638
+ }
1639
+ /** Conditional tokens (CTF) program client */
1640
+ get ctf() {
1641
+ if (!this._ctf) {
1642
+ const program = new anchor4__namespace.Program(this._withAddress(conditionalTokensIdl__default.default, this._programIds.conditionalTokens), this.provider);
1643
+ this._ctf = new CtfClient(program, this.provider, this._programIds);
1644
+ }
1645
+ return this._ctf;
1646
+ }
1647
+ /** CLOB exchange program client */
1648
+ get clob() {
1649
+ if (!this._clob) {
1650
+ const program = new anchor4__namespace.Program(this._withAddress(clobExchangeIdl__default.default, this._programIds.clobExchange), this.provider);
1651
+ this._clob = new ClobClient(program, this.provider, this._programIds);
1652
+ }
1653
+ return this._clob;
1654
+ }
1655
+ // ─── Factory helpers ──────────────────────────────────────────────────────
1656
+ /**
1657
+ * Connect to devnet.
1658
+ * ```ts
1659
+ * const sdk = XMarketSDK.devnet(wallet);
1660
+ * ```
1661
+ */
1662
+ static devnet(wallet, marketOwner) {
1663
+ return new _XMarketSDK({ network: "devnet", wallet, marketOwner });
1664
+ }
1665
+ /**
1666
+ * Connect to localhost test validator.
1667
+ * ```ts
1668
+ * const sdk = XMarketSDK.localnet(wallet);
1669
+ * ```
1670
+ */
1671
+ static localnet(wallet, marketOwner) {
1672
+ return new _XMarketSDK({ network: "localnet", wallet, marketOwner });
1673
+ }
1674
+ /**
1675
+ * Connect to mainnet-beta.
1676
+ * ```ts
1677
+ * const sdk = XMarketSDK.mainnet(wallet);
1678
+ * ```
1679
+ */
1680
+ static mainnet(wallet, marketOwner) {
1681
+ return new _XMarketSDK({ network: "mainnet", wallet, marketOwner });
1682
+ }
1683
+ /**
1684
+ * Custom network (e.g. custom RPC, custom program IDs for a fork).
1685
+ * ```ts
1686
+ * const sdk = XMarketSDK.custom({
1687
+ * name: "staging",
1688
+ * rpcUrl: "https://my-rpc.com",
1689
+ * programIds: { ... },
1690
+ * defaultCollateral: { mint: ..., decimals: 6 },
1691
+ * }, wallet);
1692
+ * ```
1693
+ */
1694
+ static custom(config, wallet, marketOwner) {
1695
+ return new _XMarketSDK({ network: config, wallet, marketOwner });
1696
+ }
1697
+ };
1698
+ function buildOrder(params) {
1699
+ return {
1700
+ maker: params.maker,
1701
+ condition: params.condition,
1702
+ tokenId: params.tokenId,
1703
+ side: params.side,
1704
+ makerAmount: params.makerAmount,
1705
+ takerAmount: params.takerAmount,
1706
+ nonce: params.nonce ?? new BN4__default.default(Date.now()),
1707
+ expiry: params.expiry ?? new BN4__default.default(0),
1708
+ createdAt: params.createdAt ?? new BN4__default.default(Math.floor(Date.now() / 1e3)),
1709
+ fee: params.fee ?? new BN4__default.default(0),
1710
+ taker: params.taker ?? new web3_js.PublicKey(new Uint8Array(32)),
1711
+ signer: params.maker
1712
+ };
1713
+ }
1714
+ function signOrderWithKeypair(order, keypair) {
1715
+ const message = serializeOrderToBytes(order);
1716
+ const signature = nacl__namespace.sign.detached(message, keypair.secretKey);
1717
+ return { order, signature };
1718
+ }
1719
+ function getOrderSignBytes(order) {
1720
+ return serializeOrderToBytes(order);
1721
+ }
1722
+ function serializeSignedOrder(signed) {
1723
+ const orderBytes = serializeOrderToBytes(signed.order);
1724
+ const buf = new Uint8Array(242);
1725
+ buf.set(orderBytes, 0);
1726
+ buf.set(signed.signature, 178);
1727
+ return buf;
1728
+ }
1729
+ function deserializeSignedOrder(bytes) {
1730
+ if (bytes.length !== 242) throw new InvalidParamError("SignedOrder must be 242 bytes");
1731
+ const readPubkey = (offset) => new web3_js.PublicKey(bytes.slice(offset, offset + 32));
1732
+ const readU64 = (offset) => new BN4__default.default(bytes.slice(offset, offset + 8), "le");
1733
+ const readI64 = (offset) => new BN4__default.default(bytes.slice(offset, offset + 8), "le");
1734
+ const order = {
1735
+ maker: readPubkey(0),
1736
+ condition: readPubkey(32),
1737
+ tokenId: bytes[64],
1738
+ side: bytes[65],
1739
+ makerAmount: readU64(66),
1740
+ takerAmount: readU64(74),
1741
+ nonce: readU64(82),
1742
+ expiry: readI64(90),
1743
+ createdAt: readI64(98),
1744
+ fee: readU64(106),
1745
+ taker: readPubkey(114),
1746
+ signer: readPubkey(146)
1747
+ };
1748
+ const signature = bytes.slice(178, 242);
1749
+ return { order, signature };
1750
+ }
1751
+ function verifySignedOrder(signed) {
1752
+ const message = serializeOrderToBytes(signed.order);
1753
+ return nacl__namespace.sign.detached.verify(
1754
+ message,
1755
+ signed.signature,
1756
+ signed.order.signer.toBytes()
1757
+ );
1758
+ }
1759
+ function detectMatchType(a, b) {
1760
+ const _a = "order" in a ? a.order : a;
1761
+ const _b = "order" in b ? b.order : b;
1762
+ return _detectMatchType(_a, _b);
1763
+ }
1764
+ function _detectMatchType(a, b) {
1765
+ const SIDE_BUY = 1;
1766
+ if (a.tokenId === b.tokenId) {
1767
+ const aBuy2 = a.side === SIDE_BUY;
1768
+ const bBuy2 = b.side === SIDE_BUY;
1769
+ if (aBuy2 !== bBuy2) return "COMPLEMENTARY";
1770
+ throw new InvalidParamError(
1771
+ "Orders with same tokenId must be one BUY and one SELL (COMPLEMENTARY)"
1772
+ );
1773
+ }
1774
+ const aBuy = a.side === SIDE_BUY;
1775
+ const bBuy = b.side === SIDE_BUY;
1776
+ if (aBuy && bBuy) return "MINT";
1777
+ if (!aBuy && !bBuy) return "MERGE";
1778
+ throw new InvalidParamError(
1779
+ "Orders with different tokenIds must both be BUY (MINT) or both SELL (MERGE)"
1780
+ );
1781
+ }
1782
+ var MAX_APPROVE_AMOUNT = new BN4__default.default("18446744073709551615");
1783
+ function buildApproveCollateralTx(collateralMint, signer, payer, delegate, amount = MAX_APPROVE_AMOUNT) {
1784
+ const ownerAta = splToken.getAssociatedTokenAddressSync(collateralMint, signer, false, splToken.TOKEN_PROGRAM_ID);
1785
+ const approveIx = splToken.createApproveInstruction(
1786
+ ownerAta,
1787
+ delegate,
1788
+ signer,
1789
+ BigInt(amount.toString()),
1790
+ [],
1791
+ splToken.TOKEN_PROGRAM_ID
1792
+ );
1793
+ const tx = new web3_js.Transaction();
1794
+ tx.feePayer = payer;
1795
+ tx.add(approveIx);
1796
+ return tx;
1797
+ }
1798
+ function buildApproveAllOutcomeTokensTx(condition, signer, payer, delegate, programIds, amount = MAX_APPROVE_AMOUNT) {
1799
+ const [yesMint] = PDA.yesMint(condition, programIds);
1800
+ const [noMint] = PDA.noMint(condition, programIds);
1801
+ const yesAta = splToken.getAssociatedTokenAddressSync(yesMint, signer, false, splToken.TOKEN_2022_PROGRAM_ID);
1802
+ const noAta = splToken.getAssociatedTokenAddressSync(noMint, signer, false, splToken.TOKEN_2022_PROGRAM_ID);
1803
+ const amountBig = BigInt(amount.toString());
1804
+ const approveYesIx = splToken.createApproveInstruction(
1805
+ yesAta,
1806
+ delegate,
1807
+ signer,
1808
+ amountBig,
1809
+ [],
1810
+ splToken.TOKEN_2022_PROGRAM_ID
1811
+ );
1812
+ const approveNoIx = splToken.createApproveInstruction(
1813
+ noAta,
1814
+ delegate,
1815
+ signer,
1816
+ amountBig,
1817
+ [],
1818
+ splToken.TOKEN_2022_PROGRAM_ID
1819
+ );
1820
+ const tx = new web3_js.Transaction();
1821
+ tx.feePayer = payer;
1822
+ tx.add(approveYesIx, approveNoIx);
1823
+ return tx;
1824
+ }
1825
+
1826
+ exports.AccountNotFoundError = AccountNotFoundError;
1827
+ exports.ClobClient = ClobClient;
1828
+ exports.CtfClient = CtfClient;
1829
+ exports.DEVNET_CONFIG = DEVNET_CONFIG;
1830
+ exports.HookClient = HookClient;
1831
+ exports.IX_SYSVAR = IX_SYSVAR;
1832
+ exports.InvalidParamError = InvalidParamError;
1833
+ exports.LOCALNET_CONFIG = LOCALNET_CONFIG;
1834
+ exports.MAINNET_CONFIG = MAINNET_CONFIG;
1835
+ exports.MAX_APPROVE_AMOUNT = MAX_APPROVE_AMOUNT;
1836
+ exports.MarketClient = MarketClient;
1837
+ exports.NETWORK_CONFIGS = NETWORK_CONFIGS;
1838
+ exports.OracleClient = OracleClient;
1839
+ exports.PDA = PDA;
1840
+ exports.QuestionStatus = QuestionStatus;
1841
+ exports.SEEDS = SEEDS;
1842
+ exports.UnauthorizedError = UnauthorizedError;
1843
+ exports.XMarketError = XMarketError;
1844
+ exports.XMarketSDK = XMarketSDK;
1845
+ exports.buildApproveAllOutcomeTokensTx = buildApproveAllOutcomeTokensTx;
1846
+ exports.buildApproveCollateralTx = buildApproveCollateralTx;
1847
+ exports.buildBatchedEd25519Instruction = buildBatchedEd25519Instruction;
1848
+ exports.buildOrder = buildOrder;
1849
+ exports.deserializeSignedOrder = deserializeSignedOrder;
1850
+ exports.detectMatchType = detectMatchType;
1851
+ exports.generateContentHash = generateContentHash;
1852
+ exports.generateQuestionId = generateQuestionId;
1853
+ exports.getOrderSignBytes = getOrderSignBytes;
1854
+ exports.serializeOrderToBytes = serializeOrderToBytes;
1855
+ exports.serializeSignedOrder = serializeSignedOrder;
1856
+ exports.signOrder = signOrder;
1857
+ exports.signOrderWithKeypair = signOrderWithKeypair;
1858
+ exports.verifySignedOrder = verifySignedOrder;
1859
+ //# sourceMappingURL=index.js.map
1860
+ //# sourceMappingURL=index.js.map