xmarket-solana-sdk 1.0.11

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 ADDED
@@ -0,0 +1,339 @@
1
+ # @prediction-market-sdk/xmarket-sdk
2
+
3
+ Solana SDK for XMarket — binary prediction markets with on-chain CLOB, conditional tokens (Token-2022), and Ed25519 order signing.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @prediction-market-sdk/xmarket-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { XMarketSDK, signOrder } from "@prediction-market-sdk/xmarket-sdk";
15
+ import * as anchor from "@coral-xyz/anchor";
16
+
17
+ const sdk = XMarketSDK.devnet(new anchor.Wallet(keypair));
18
+
19
+ sdk.market // QuestionMarket program
20
+ sdk.ctf // Conditional Tokens program
21
+ sdk.clob // CLOB exchange
22
+ sdk.oracle // Oracle program
23
+ sdk.hook // Transfer-hook program
24
+ ```
25
+
26
+ ---
27
+
28
+ ## XMarketSDK
29
+
30
+ ```typescript
31
+ XMarketSDK.devnet(wallet: anchor.Wallet, marketOwner?: PublicKey): XMarketSDK
32
+ XMarketSDK.localnet(wallet: anchor.Wallet, marketOwner?: PublicKey): XMarketSDK
33
+ XMarketSDK.mainnet(wallet: anchor.Wallet, marketOwner?: PublicKey): XMarketSDK
34
+ XMarketSDK.custom(config: NetworkConfig, wallet: anchor.Wallet, marketOwner?: PublicKey): XMarketSDK
35
+ ```
36
+
37
+ ---
38
+
39
+ ## sdk.market — MarketClient
40
+
41
+ ### Instructions (sign + send)
42
+
43
+ ```typescript
44
+ market.initialize(admin: PublicKey, oracle: PublicKey): Promise<TxResult>
45
+
46
+ market.createQuestion(params: CreateQuestionParams, oracle: PublicKey): Promise<TxResult & {
47
+ questionPda: PublicKey; conditionPda: PublicKey; questionId: Uint8Array
48
+ }>
49
+
50
+ market.approveQuestion(questionPda: PublicKey): Promise<TxResult>
51
+ market.rejectQuestion(questionPda: PublicKey): Promise<TxResult>
52
+ market.resolveQuestion(questionId: Uint8Array, oracle: PublicKey): Promise<TxResult>
53
+ market.updateConfig(params: { newAdmin?: PublicKey; newOracle?: PublicKey; isPaused?: boolean }): Promise<TxResult>
54
+ ```
55
+
56
+ ### Transaction builders (build only — no sign, no send)
57
+
58
+ Each `*Tx` method returns a `Transaction` (or `{ tx, ...pdas }` for `createQuestionTx`).
59
+ The caller is responsible for setting `feePayer`, signing, and submitting.
60
+ Signer pubkey params default to `wallet.publicKey` if omitted.
61
+
62
+ ```typescript
63
+ market.initializeTx(admin: PublicKey, oracle: PublicKey, owner?: PublicKey): Promise<Transaction>
64
+
65
+ market.createQuestionTx(
66
+ params: CreateQuestionParams,
67
+ oracle: PublicKey,
68
+ creator?: PublicKey
69
+ ): Promise<{ tx: Transaction; questionPda: PublicKey; conditionPda: PublicKey; questionId: Uint8Array }>
70
+
71
+ market.approveQuestionTx(questionPda: PublicKey, admin?: PublicKey): Promise<Transaction>
72
+ market.rejectQuestionTx(questionPda: PublicKey, authority?: PublicKey): Promise<Transaction>
73
+ market.resolveQuestionTx(questionId: Uint8Array, oracle: PublicKey, payer?: PublicKey): Promise<Transaction>
74
+ market.updateConfigTx(
75
+ params: { newAdmin?: PublicKey; newOracle?: PublicKey; isPaused?: boolean },
76
+ authority?: PublicKey
77
+ ): Promise<Transaction>
78
+ ```
79
+
80
+ **Backend pattern** — build tx, sign externally (e.g. Privy), submit via relay:
81
+
82
+ ```typescript
83
+ const { tx, questionPda } = await sdk.market.createQuestionTx(params, oracle, userPubkey);
84
+
85
+ tx.feePayer = beWallet.publicKey;
86
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
87
+
88
+ // sign with Privy (creator) + BE wallet (feePayer)
89
+ const signedTx = await privySigner.signTransaction(tx);
90
+ beWallet.signTransaction(signedTx);
91
+
92
+ await connection.sendRawTransaction(signedTx.serialize());
93
+ ```
94
+
95
+ ### Queries
96
+
97
+ ```typescript
98
+ market.fetchConfig(): Promise<QuestionMarketConfig | null>
99
+ market.fetchQuestion(questionPda: PublicKey): Promise<Question | null>
100
+ market.questionPda(questionId: Uint8Array): PublicKey
101
+ market.configPda: PublicKey
102
+ ```
103
+
104
+ ### CreateQuestionParams
105
+
106
+ ```typescript
107
+ {
108
+ content: string // question text
109
+ expirationTime: number // unix timestamp
110
+ collateralMint: PublicKey // USDC mint
111
+ hookProgram: PublicKey // transfer-hook program ID
112
+ authorizedClob: PublicKey // use PDA.clobConfig(programIds)[0]
113
+ questionId?: Uint8Array // auto-derived from content if omitted
114
+ contentHash?: Uint8Array // auto-derived from content if omitted
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## sdk.ctf — CtfClient
121
+
122
+ ```typescript
123
+ ctf.initializeVault(collateralMint: PublicKey): Promise<TxResult>
124
+
125
+ ctf.splitPosition(condition: PublicKey, collateralMint: PublicKey, amount: BN, user?: PublicKey): Promise<TxResult>
126
+ ctf.mergePosition(condition: PublicKey, collateralMint: PublicKey, amount: BN, user?: PublicKey): Promise<TxResult>
127
+ ctf.redeemPositions(condition: PublicKey, collateralMint: PublicKey, user?: PublicKey): Promise<TxResult>
128
+
129
+ ctf.reportPayouts(condition: PublicKey, payoutNumerators: number[]): Promise<TxResult>
130
+ ctf.updateAuthorizedClob(condition: PublicKey, newAuthorizedClob: PublicKey): Promise<TxResult>
131
+ ctf.transferPositionIx(...): Promise<TransactionInstruction> // CPI only
132
+
133
+ ctf.fetchCondition(conditionPda: PublicKey): Promise<Condition | null>
134
+ ctf.fetchVault(collateralMint: PublicKey): Promise<CollateralVault | null>
135
+ ctf.fetchPosition(condition: PublicKey, outcomeIndex: number, owner?: PublicKey): Promise<Position | null>
136
+ ctf.fetchBothPositions(condition: PublicKey, owner?: PublicKey): Promise<{ yes: Position | null; no: Position | null }>
137
+
138
+ ctf.yesMintPda(condition: PublicKey): PublicKey // outcome index 1
139
+ ctf.noMintPda(condition: PublicKey): PublicKey // outcome index 0
140
+ ctf.mintAuthorityPda(condition: PublicKey): PublicKey
141
+ ctf.collateralVaultPda(collateralMint: PublicKey): PublicKey
142
+ ```
143
+
144
+ > `splitPosition(amount)` — locks `amount` USDC, mints `amount` YES + `amount` NO (6 decimals, 1:1).
145
+ > `redeemPositions` — requires both YES and NO position accounts. Call `splitPosition(new BN(1))` to init a missing position before redeeming.
146
+
147
+ ---
148
+
149
+ ## sdk.clob — ClobClient
150
+
151
+ ```typescript
152
+ clob.initialize(operators: PublicKey[], feeRecipient: PublicKey, feeRateBps: number): Promise<TxResult>
153
+ clob.addOperator(operator: PublicKey): Promise<TxResult>
154
+ clob.removeOperator(operator: PublicKey): Promise<TxResult>
155
+ clob.setPaused(paused: boolean): Promise<TxResult>
156
+ clob.cancelOrder(nonce: BN): Promise<TxResult>
157
+
158
+ // Secondary market: buyer pays USDC, seller gives YES/NO
159
+ clob.matchComplementary(
160
+ buySigned: SignedOrder, sellSigned: SignedOrder, fillAmount: BN,
161
+ collateralMint: PublicKey, feeRecipient: PublicKey,
162
+ lookupTable?: AddressLookupTableAccount
163
+ ): Promise<TxResult>
164
+
165
+ // Primary market: two buyers mint YES+NO from collateral
166
+ clob.matchMintOrders(
167
+ yesSigned: SignedOrder, noSigned: SignedOrder, fillAmount: BN,
168
+ collateralMint: PublicKey, lookupTable?: AddressLookupTableAccount
169
+ ): Promise<TxResult>
170
+
171
+ // Liquidity exit: two sellers merge YES+NO back to collateral
172
+ clob.matchMergeOrders(
173
+ yesSigned: SignedOrder, noSigned: SignedOrder, fillAmount: BN,
174
+ collateralMint: PublicKey, feeRecipient: PublicKey,
175
+ lookupTable?: AddressLookupTableAccount
176
+ ): Promise<TxResult>
177
+
178
+ clob.fetchConfig(): Promise<ClobConfig | null>
179
+ clob.fetchOrderStatus(maker: PublicKey, nonce: BN): Promise<OrderStatus | null>
180
+ clob.isOrderCancelled(maker: PublicKey, nonce: BN): Promise<boolean>
181
+ clob.configPda(): PublicKey
182
+ ```
183
+
184
+ ---
185
+
186
+ ## sdk.oracle — OracleClient
187
+
188
+ ```typescript
189
+ oracle.initialize(admin: PublicKey): Promise<TxResult>
190
+ oracle.addReporter(reporterAddress: PublicKey, ownerPubkey?: PublicKey): Promise<TxResult>
191
+ oracle.removeReporter(reporterAddress: PublicKey, ownerPubkey?: PublicKey): Promise<TxResult>
192
+
193
+ // payoutNumerators: index 0 = NO, index 1 = YES
194
+ // YES wins → [0, 1] NO wins → [1, 0]
195
+ oracle.resolveQuestion(questionId: Uint8Array, outcomeCount: number, payoutNumerators: number[], ownerPubkey?: PublicKey): Promise<TxResult>
196
+
197
+ oracle.updateAdmin(newAdmin: PublicKey, ownerPubkey?: PublicKey): Promise<TxResult>
198
+ oracle.transferOwnership(newOwner: PublicKey, ownerPubkey?: PublicKey): Promise<TxResult>
199
+ oracle.pause(paused: boolean, ownerPubkey?: PublicKey): Promise<TxResult>
200
+
201
+ oracle.fetchConfig(owner?: PublicKey): Promise<OracleConfig | null>
202
+ oracle.fetchReporter(reporterAddress: PublicKey, ownerPubkey?: PublicKey): Promise<OracleReporter | null>
203
+ oracle.fetchQuestionResult(questionId: Uint8Array, ownerPubkey?: PublicKey): Promise<QuestionResult | null>
204
+ oracle.isReporter(reporterAddress: PublicKey, ownerPubkey?: PublicKey): Promise<boolean>
205
+ oracle.configPda(owner?: PublicKey): PublicKey
206
+ ```
207
+
208
+ ---
209
+
210
+ ## sdk.hook — HookClient
211
+
212
+ ```typescript
213
+ hook.initialize(initialWhitelist: PublicKey[]): Promise<TxResult>
214
+
215
+ // Call once per YES/NO mint after condition creation
216
+ hook.initializeExtraAccountMetaList(mint: PublicKey): Promise<TxResult>
217
+
218
+ hook.addToWhitelist(program: PublicKey): Promise<TxResult>
219
+ hook.removeFromWhitelist(program: PublicKey): Promise<TxResult>
220
+ hook.freezeWhitelist(): Promise<TxResult>
221
+
222
+ hook.fetchConfig(): Promise<HookConfig | null>
223
+ hook.isWhitelisted(program: PublicKey): Promise<boolean>
224
+ hook.configPda(): PublicKey
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Ed25519 Order Signing
230
+
231
+ ```typescript
232
+ import { signOrder } from "@prediction-market-sdk/xmarket-sdk";
233
+ import nacl from "tweetnacl";
234
+ import BN from "bn.js";
235
+
236
+ const signed = await signOrder(
237
+ {
238
+ maker: wallet.publicKey,
239
+ condition: conditionPda,
240
+ tokenId: 1, // 1 = YES, 0 = NO
241
+ side: 1, // 1 = BUY, 0 = SELL
242
+ priceBps: new BN(7_800), // 0.78 USDC per token
243
+ amount: new BN(10_000_000_000), // 10,000 tokens (6 decimals)
244
+ nonce: new BN(Date.now()),
245
+ expiry: new BN(0), // 0 = no expiry
246
+ },
247
+ (msg) => nacl.sign.detached(msg, keypair.secretKey)
248
+ );
249
+
250
+ // Pass SignedOrder directly — Ed25519 precompile bundled automatically
251
+ await sdk.clob.matchComplementary(buySigned, sellSigned, fillAmount, usdcMint, feeRecipient, alt);
252
+ ```
253
+
254
+ ---
255
+
256
+ ## PDA Utilities
257
+
258
+ ```typescript
259
+ import { PDA, generateQuestionId, generateContentHash } from "@prediction-market-sdk/xmarket-sdk";
260
+
261
+ PDA.questionMarketConfig(owner, programIds): [PublicKey, number]
262
+ PDA.question(config, questionId, programIds): [PublicKey, number]
263
+ PDA.condition(oracle, questionId, programIds): [PublicKey, number]
264
+ PDA.collateralVault(collateralMint, programIds): [PublicKey, number]
265
+ PDA.yesMint(condition, programIds): [PublicKey, number] // outcome 1
266
+ PDA.noMint(condition, programIds): [PublicKey, number] // outcome 0
267
+ PDA.mintAuthority(condition, programIds): [PublicKey, number]
268
+ PDA.position(condition, outcomeIndex, owner, programIds): [PublicKey, number]
269
+ PDA.clobConfig(programIds): [PublicKey, number]
270
+ PDA.orderStatus(maker, nonce, programIds): [PublicKey, number]
271
+ PDA.hookConfig(programIds): [PublicKey, number]
272
+ PDA.extraAccountMetaList(mint, programIds): [PublicKey, number]
273
+ PDA.oracleConfig(owner, programIds): [PublicKey, number]
274
+ PDA.reporter(oracleConfig, reporterAddress, programIds): [PublicKey, number]
275
+ PDA.questionResult(oracleConfig, questionId, programIds): [PublicKey, number]
276
+
277
+ generateQuestionId(content: string, salt?: number): Uint8Array
278
+ generateContentHash(content: string): Uint8Array
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Full Flow Example
284
+
285
+ ```typescript
286
+ import { XMarketSDK, PDA, signOrder, DEVNET_CONFIG } from "@prediction-market-sdk/xmarket-sdk";
287
+ import * as anchor from "@coral-xyz/anchor";
288
+ import nacl from "tweetnacl";
289
+ import BN from "bn.js";
290
+
291
+ const sdk = XMarketSDK.devnet(new anchor.Wallet(operatorKeypair));
292
+
293
+ // 1. Create question
294
+ const { conditionPda, questionPda, questionId } = await sdk.market.createQuestion({
295
+ content: "Will BTC exceed $100k by end of 2025?",
296
+ expirationTime: Math.floor(Date.now() / 1000) + 30 * 86400,
297
+ collateralMint: USDC_MINT,
298
+ hookProgram: DEVNET_CONFIG.programIds.hook,
299
+ authorizedClob: PDA.clobConfig(DEVNET_CONFIG.programIds)[0],
300
+ }, oracleConfigPda);
301
+
302
+ // 2. Approve + init hook metas
303
+ await sdk.market.approveQuestion(questionPda);
304
+ await sdk.hook.initializeExtraAccountMetaList(sdk.ctf.yesMintPda(conditionPda));
305
+ await sdk.hook.initializeExtraAccountMetaList(sdk.ctf.noMintPda(conditionPda));
306
+
307
+ // 3. Seller splits 10,000 USDC → YES + NO
308
+ const sellerSdk = XMarketSDK.devnet(new anchor.Wallet(sellerKeypair));
309
+ await sellerSdk.ctf.splitPosition(conditionPda, USDC_MINT, new BN(10_000_000_000));
310
+
311
+ // 4. Match: buyer buys 10,000 YES @ 0.78 USDC
312
+ const buySigned = await signOrder(
313
+ { maker: buyerKp.publicKey, condition: conditionPda, tokenId: 1, side: 1,
314
+ priceBps: new BN(7_800), amount: new BN(10_000_000_000), nonce: new BN(Date.now()), expiry: new BN(0) },
315
+ (msg) => nacl.sign.detached(msg, buyerKp.secretKey)
316
+ );
317
+ const sellSigned = await signOrder(
318
+ { maker: sellerKp.publicKey, condition: conditionPda, tokenId: 1, side: 0,
319
+ priceBps: new BN(7_800), amount: new BN(10_000_000_000), nonce: new BN(Date.now() + 1), expiry: new BN(0) },
320
+ (msg) => nacl.sign.detached(msg, sellerKp.secretKey)
321
+ );
322
+ await sdk.clob.matchComplementary(buySigned, sellSigned, new BN(10_000_000_000),
323
+ USDC_MINT, feeRecipientPubkey, lookupTable);
324
+
325
+ // 5. Oracle resolves YES wins → [NO=0, YES=1]
326
+ await sdk.oracle.resolveQuestion(questionId, 2, [0, 1]);
327
+ await sdk.market.resolveQuestion(questionId, oracleConfigPda);
328
+
329
+ // 6. Redeem
330
+ const buyerSdk = XMarketSDK.devnet(new anchor.Wallet(buyerKeypair));
331
+ await buyerSdk.ctf.redeemPositions(conditionPda, USDC_MINT); // winner
332
+ await sellerSdk.ctf.redeemPositions(conditionPda, USDC_MINT); // loser → 0
333
+ ```
334
+
335
+ ---
336
+
337
+ ## License
338
+
339
+ MIT