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 +339 -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/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
|