@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/README.md +1 -0
- package/dist/clob_exchange-ATSH42KC.json +1859 -0
- package/dist/conditional_tokens-3O5V46N5.json +2215 -0
- package/dist/hook-THBRGUM6.json +481 -0
- package/dist/index.d.mts +818 -0
- package/dist/index.d.ts +818 -0
- package/dist/index.js +1860 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1797 -0
- package/dist/index.mjs.map +1 -0
- package/dist/oracle-FZJJIJGI.json +694 -0
- package/dist/question_market-CB6ZUZ5E.json +1465 -0
- package/package.json +65 -0
- package/src/idls/clob_exchange.json +1859 -0
- package/src/idls/conditional_tokens.json +2215 -0
- package/src/idls/hook.json +481 -0
- package/src/idls/oracle.json +694 -0
- package/src/idls/question_market.json +1465 -0
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
|