drama-pm-client 0.1.0

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,521 @@
1
+ # Drama PM Client SDK
2
+
3
+ TypeScript 客户端 SDK,用于与 Drama 预测市场 Solana 智能合约交互。
4
+
5
+ ## 特性
6
+
7
+ - ✅ **完整的类型支持** - 使用 TypeScript 编写,提供完整的类型定义
8
+ - ✅ **自动 ATA 管理** - 自动检测和创建关联代币账户 (Associated Token Account)
9
+ - ✅ **简洁的 API** - 提供简单易用的方法接口
10
+ - ✅ **灵活的交易构建** - 支持获取单个指令或完整交易
11
+ - ✅ **PDA 计算工具** - 导出所有 PDA 派生函数
12
+ - ✅ **错误处理** - 完善的错误处理机制
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install drama-pm-client
18
+ # 或
19
+ yarn add drama-pm-client
20
+ ```
21
+
22
+ ## 快速开始
23
+
24
+ ```typescript
25
+ import { Connection, PublicKey } from "@solana/web3.js";
26
+ import { DramaPmClient } from "drama-pm-client";
27
+ import { BN } from "@coral-xyz/anchor";
28
+
29
+ // 初始化连接和客户端
30
+ const connection = new Connection("https://api.devnet.solana.com");
31
+ const client = new DramaPmClient(connection);
32
+
33
+ // 创建市场
34
+ const createMarketTx = await client.buildCreateMarketTx({
35
+ admin: adminPublicKey,
36
+ marketId: new BN(1),
37
+ yesPrice: new BN(1_200_000), // 1.2 USDC
38
+ noPrice: new BN(800_000), // 0.8 USDC
39
+ endTime: new BN(Date.now() / 1000 + 3600), // 1小时后
40
+ bettingToken: usdcMint,
41
+ });
42
+
43
+ // 下注 (自动创建 ATA)
44
+ const placeBetTx = await client.buildPlaceBetTx({
45
+ user: userPublicKey,
46
+ admin: adminPublicKey,
47
+ marketId: new BN(1),
48
+ amount: new BN(1_000_000), // 1 USDC
49
+ outcome: "Yes",
50
+ bettingToken: usdcMint,
51
+ createAta: true, // 自动创建 ATA
52
+ });
53
+ ```
54
+
55
+ ## API 文档
56
+
57
+ ### DramaPmClient
58
+
59
+ 主客户端类,提供所有合约交互方法。
60
+
61
+ #### 构造函数
62
+
63
+ ```typescript
64
+ constructor(connection: Connection, wallet?: anchor.Wallet)
65
+ ```
66
+
67
+ - `connection`: Solana RPC 连接
68
+ - `wallet`: (可选) Anchor 钱包,用于读取操作
69
+
70
+ #### 方法
71
+
72
+ ##### createMarket()
73
+
74
+ 创建新的预测市场。
75
+
76
+ ```typescript
77
+ async createMarket(params: CreateMarketParams): Promise<TransactionInstruction>
78
+ ```
79
+
80
+ **参数:**
81
+ ```typescript
82
+ interface CreateMarketParams {
83
+ admin: PublicKey; // 市场管理员
84
+ marketId: number | BN; // 唯一市场 ID
85
+ yesPrice: number | BN; // YES 选项价格 (原始单位)
86
+ noPrice: number | BN; // NO 选项价格 (原始单位)
87
+ endTime: number | BN; // 市场结束时间 (Unix 时间戳)
88
+ bettingToken: PublicKey; // 投注代币 Mint 地址
89
+ }
90
+ ```
91
+
92
+ **返回:** `TransactionInstruction`
93
+
94
+ **示例:**
95
+ ```typescript
96
+ const ix = await client.createMarket({
97
+ admin: admin.publicKey,
98
+ marketId: new BN(123),
99
+ yesPrice: new BN(1_200_000),
100
+ noPrice: new BN(800_000),
101
+ endTime: new BN(Math.floor(Date.now() / 1000) + 3600),
102
+ bettingToken: usdcMint,
103
+ });
104
+ ```
105
+
106
+ ##### buildCreateMarketTx()
107
+
108
+ 构建完整的创建市场交易。
109
+
110
+ ```typescript
111
+ async buildCreateMarketTx(params: CreateMarketParams): Promise<Transaction>
112
+ ```
113
+
114
+ 返回可直接发送的完整交易对象。
115
+
116
+ ---
117
+
118
+ ##### placeBet()
119
+
120
+ 创建下注指令 (不包含 ATA 创建)。
121
+
122
+ ```typescript
123
+ async placeBet(params: PlaceBetParams): Promise<TransactionInstruction>
124
+ ```
125
+
126
+ **参数:**
127
+ ```typescript
128
+ interface PlaceBetParams {
129
+ user: PublicKey; // 用户公钥
130
+ admin: PublicKey; // 市场管理员
131
+ marketId: number | BN; // 市场 ID
132
+ amount: number | BN; // 下注金额 (原始单位)
133
+ outcome: "Yes" | "No"; // 下注选项
134
+ bettingToken: PublicKey; // 投注代币 Mint
135
+ createAta?: boolean; // (buildPlaceBetTx 专用) 是否自动创建 ATA
136
+ }
137
+ ```
138
+
139
+ **返回:** `TransactionInstruction`
140
+
141
+ ##### buildPlaceBetTx()
142
+
143
+ 构建完整的下注交易,支持自动创建 ATA。
144
+
145
+ ```typescript
146
+ async buildPlaceBetTx(params: PlaceBetParams): Promise<Transaction>
147
+ ```
148
+
149
+ **特性:**
150
+ - 自动检查 ATA 是否存在
151
+ - 如果 `createAta: true` 且 ATA 不存在,自动添加创建指令
152
+ - 返回完整的交易对象
153
+
154
+ **示例:**
155
+ ```typescript
156
+ // 自动处理 ATA 创建
157
+ const tx = await client.buildPlaceBetTx({
158
+ user: user.publicKey,
159
+ admin: admin.publicKey,
160
+ marketId: new BN(123),
161
+ amount: new BN(2_000_000), // 2 USDC
162
+ outcome: "Yes",
163
+ bettingToken: usdcMint,
164
+ createAta: true, // 自动创建 ATA
165
+ });
166
+
167
+ await sendAndConfirmTransaction(connection, tx, [user]);
168
+ ```
169
+
170
+ ---
171
+
172
+ ##### claimRewards()
173
+
174
+ 创建领取奖励指令。
175
+
176
+ ```typescript
177
+ async claimRewards(params: ClaimRewardsParams): Promise<TransactionInstruction>
178
+ ```
179
+
180
+ **参数:**
181
+ ```typescript
182
+ interface ClaimRewardsParams {
183
+ user: PublicKey; // 用户公钥
184
+ admin: PublicKey; // 市场管理员
185
+ marketId: number | BN; // 市场 ID
186
+ bettingToken: PublicKey; // 投注代币 Mint
187
+ winningOutcome: "Yes" | "No"; // 获胜选项
188
+ }
189
+ ```
190
+
191
+ ##### buildClaimRewardsTx()
192
+
193
+ 构建完整的领取奖励交易。
194
+
195
+ ```typescript
196
+ async buildClaimRewardsTx(params: ClaimRewardsParams): Promise<Transaction>
197
+ ```
198
+
199
+ ---
200
+
201
+ ##### refund()
202
+
203
+ 创建退款指令 (当市场被标记为退款状态时)。
204
+
205
+ ```typescript
206
+ async refund(params: RefundParams): Promise<TransactionInstruction>
207
+ ```
208
+
209
+ **参数:**
210
+ ```typescript
211
+ interface RefundParams {
212
+ user: PublicKey; // 用户公钥
213
+ admin: PublicKey; // 市场管理员
214
+ marketId: number | BN; // 市场 ID
215
+ bettingToken: PublicKey; // 投注代币 Mint
216
+ outcome: "Yes" | "No"; // 要退款的选项
217
+ }
218
+ ```
219
+
220
+ ##### buildRefundTx()
221
+
222
+ 构建完整的退款交易。
223
+
224
+ ```typescript
225
+ async buildRefundTx(params: RefundParams): Promise<Transaction>
226
+ ```
227
+
228
+ ---
229
+
230
+ ##### resolveMarket()
231
+
232
+ 创建决算市场指令 (仅管理员)。
233
+
234
+ ```typescript
235
+ async resolveMarket(
236
+ admin: PublicKey,
237
+ marketId: number | BN,
238
+ winningOutcome: "Yes" | "No" | null
239
+ ): Promise<TransactionInstruction>
240
+ ```
241
+
242
+ **参数:**
243
+ - `admin`: 管理员公钥
244
+ - `marketId`: 市场 ID
245
+ - `winningOutcome`: 获胜选项,`null` 表示退款
246
+
247
+ ##### buildResolveMarketTx()
248
+
249
+ 构建完整的决算市场交易。
250
+
251
+ ```typescript
252
+ async buildResolveMarketTx(
253
+ admin: PublicKey,
254
+ marketId: number | BN,
255
+ winningOutcome: "Yes" | "No" | null
256
+ ): Promise<Transaction>
257
+ ```
258
+
259
+ ---
260
+
261
+ ##### closeMarket()
262
+
263
+ 创建关闭市场指令 (仅管理员)。
264
+
265
+ ```typescript
266
+ async closeMarket(
267
+ admin: PublicKey,
268
+ marketId: number | BN
269
+ ): Promise<TransactionInstruction>
270
+ ```
271
+
272
+ ##### buildCloseMarketTx()
273
+
274
+ 构建完整的关闭市场交易。
275
+
276
+ ```typescript
277
+ async buildCloseMarketTx(
278
+ admin: PublicKey,
279
+ marketId: number | BN
280
+ ): Promise<Transaction>
281
+ ```
282
+
283
+ ---
284
+
285
+ ##### getMarketAccount()
286
+
287
+ 获取市场账户数据。
288
+
289
+ ```typescript
290
+ async getMarketAccount(admin: PublicKey, marketId: number | BN)
291
+ ```
292
+
293
+ **返回:** 市场账户数据对象
294
+
295
+ **示例:**
296
+ ```typescript
297
+ const market = await client.getMarketAccount(admin.publicKey, new BN(123));
298
+ console.log("Status:", market.status);
299
+ console.log("Total YES supply:", market.totalYesSupply.toString());
300
+ console.log("Total NO supply:", market.totalNoSupply.toString());
301
+ ```
302
+
303
+ ---
304
+
305
+ ##### ataExists()
306
+
307
+ 检查关联代币账户是否存在。
308
+
309
+ ```typescript
310
+ async ataExists(ata: PublicKey): Promise<boolean>
311
+ ```
312
+
313
+ ##### createAtaInstruction()
314
+
315
+ 创建 ATA 初始化指令。
316
+
317
+ ```typescript
318
+ createAtaInstruction(
319
+ payer: PublicKey,
320
+ owner: PublicKey,
321
+ mint: PublicKey
322
+ ): TransactionInstruction
323
+ ```
324
+
325
+ ### 工具函数
326
+
327
+ SDK 导出了所有 PDA 计算函数:
328
+
329
+ ```typescript
330
+ import {
331
+ getMarketPda,
332
+ getYesMintPda,
333
+ getNoMintPda,
334
+ getVaultPda,
335
+ getMarketPdas, // 一次性获取所有 PDA
336
+ } from "drama-pm-client";
337
+
338
+ // 计算单个 PDA
339
+ const marketPda = getMarketPda(programId, admin, marketId);
340
+
341
+ // 一次性获取所有 PDA
342
+ const { market, yesMint, noMint, vault } = getMarketPdas(
343
+ programId,
344
+ admin,
345
+ marketId
346
+ );
347
+ ```
348
+
349
+ ## SDK 优化说明
350
+
351
+ 相比初始版本,SDK 进行了以下优化:
352
+
353
+ ### 1. **接口参数化**
354
+ - ✅ 使用 TypeScript 接口定义参数,提高代码可读性
355
+ - ✅ 统一的参数结构,减少函数签名复杂度
356
+
357
+ ### 2. **自动 ATA 管理**
358
+ - ✅ `buildPlaceBetTx()` 支持自动检测和创建 ATA
359
+ - ✅ 提供 `ataExists()` 检查方法
360
+ - ✅ 提供 `createAtaInstruction()` 手动创建 ATA
361
+
362
+ ### 3. **双层 API 设计**
363
+ - ✅ 指令级方法: `createMarket()`, `placeBet()` 等
364
+ - ✅ 交易级方法: `buildCreateMarketTx()`, `buildPlaceBetTx()` 等
365
+ - ✅ 灵活满足不同使用场景
366
+
367
+ ### 4. **PDA 工具优化**
368
+ - ✅ 提供 `getMarketPdas()` 一次性获取所有 PDA
369
+ - ✅ 导出所有 PDA 计算函数供外部使用
370
+ - ✅ 内部使用 PDA 缓存减少重复计算
371
+
372
+ ### 5. **错误处理改进**
373
+ - ✅ 使用 `accountsPartial()` 替代 `accounts()`,更安全
374
+ - ✅ ATA 存在性检查,避免重复创建错误
375
+ - ✅ 完整的 TypeScript 类型检查
376
+
377
+ ### 6. **对齐测试代码**
378
+ - ✅ 参数顺序和命名与测试代码一致
379
+ - ✅ ATA 创建模式与测试代码相同
380
+ - ✅ 支持与测试代码相同的使用模式
381
+
382
+ ## 使用模式对比
383
+
384
+ ### 模式 1: 完整交易构建 (推荐)
385
+
386
+ ```typescript
387
+ // SDK 自动处理 ATA 创建
388
+ const tx = await client.buildPlaceBetTx({
389
+ user: user.publicKey,
390
+ admin: admin.publicKey,
391
+ marketId: new BN(1),
392
+ amount: new BN(1_000_000),
393
+ outcome: "Yes",
394
+ bettingToken: usdcMint,
395
+ createAta: true,
396
+ });
397
+
398
+ await sendAndConfirmTransaction(connection, tx, [user]);
399
+ ```
400
+
401
+ ### 模式 2: 手动组合指令
402
+
403
+ ```typescript
404
+ import { Transaction } from "@solana/web3.js";
405
+ import { getAssociatedTokenAddressSync } from "@solana/spl-token";
406
+
407
+ const tx = new Transaction();
408
+ const pdas = getMarketPdas(programId, admin.publicKey, marketId);
409
+ const userOutcomeToken = getAssociatedTokenAddressSync(
410
+ pdas.yesMint,
411
+ user.publicKey
412
+ );
413
+
414
+ // 检查并创建 ATA
415
+ if (!(await client.ataExists(userOutcomeToken))) {
416
+ const createAtaIx = client.createAtaInstruction(
417
+ user.publicKey,
418
+ user.publicKey,
419
+ pdas.yesMint
420
+ );
421
+ tx.add(createAtaIx);
422
+ }
423
+
424
+ // 添加下注指令
425
+ const placeBetIx = await client.placeBet({
426
+ user: user.publicKey,
427
+ admin: admin.publicKey,
428
+ marketId: new BN(1),
429
+ amount: new BN(1_000_000),
430
+ outcome: "Yes",
431
+ bettingToken: usdcMint,
432
+ });
433
+ tx.add(placeBetIx);
434
+
435
+ await sendAndConfirmTransaction(connection, tx, [user]);
436
+ ```
437
+
438
+ ### 模式 3: 仅获取指令
439
+
440
+ ```typescript
441
+ // 适用于需要自定义交易构建的场景
442
+ const ix = await client.placeBet({
443
+ user: user.publicKey,
444
+ admin: admin.publicKey,
445
+ marketId: new BN(1),
446
+ amount: new BN(1_000_000),
447
+ outcome: "Yes",
448
+ bettingToken: usdcMint,
449
+ });
450
+
451
+ // 添加到自定义交易
452
+ const customTx = new Transaction();
453
+ customTx.add(someOtherIx);
454
+ customTx.add(ix);
455
+ customTx.add(anotherIx);
456
+ ```
457
+
458
+ ## 完整示例
459
+
460
+ 查看 `app/` 目录下的完整 demo 应用,展示了:
461
+ - ✅ 市场创建
462
+ - ✅ 多用户下注
463
+ - ✅ 市场关闭和决算
464
+ - ✅ 奖励领取
465
+ - ✅ 完整的错误处理
466
+
467
+ 运行 demo:
468
+ ```bash
469
+ cd app
470
+ npm install
471
+ npm run dev
472
+ ```
473
+
474
+ ## 类型定义
475
+
476
+ ```typescript
477
+ export type Outcome = "Yes" | "No";
478
+
479
+ export interface CreateMarketParams {
480
+ admin: PublicKey;
481
+ marketId: number | BN;
482
+ yesPrice: number | BN;
483
+ noPrice: number | BN;
484
+ endTime: number | BN;
485
+ bettingToken: PublicKey;
486
+ }
487
+
488
+ export interface PlaceBetParams {
489
+ user: PublicKey;
490
+ admin: PublicKey;
491
+ marketId: number | BN;
492
+ amount: number | BN;
493
+ outcome: Outcome;
494
+ bettingToken: PublicKey;
495
+ createAta?: boolean;
496
+ }
497
+
498
+ export interface ClaimRewardsParams {
499
+ user: PublicKey;
500
+ admin: PublicKey;
501
+ marketId: number | BN;
502
+ bettingToken: PublicKey;
503
+ winningOutcome: Outcome;
504
+ }
505
+
506
+ export interface RefundParams {
507
+ user: PublicKey;
508
+ admin: PublicKey;
509
+ marketId: number | BN;
510
+ bettingToken: PublicKey;
511
+ outcome: Outcome;
512
+ }
513
+ ```
514
+
515
+ ## 许可证
516
+
517
+ MIT
518
+
519
+ ## 贡献
520
+
521
+ 欢迎提交 Issue 和 Pull Request!
package/dist/idl.d.ts ADDED
@@ -0,0 +1,208 @@
1
+ export declare const IDL: {
2
+ address: string;
3
+ metadata: {
4
+ name: string;
5
+ version: string;
6
+ spec: string;
7
+ description: string;
8
+ };
9
+ instructions: ({
10
+ name: string;
11
+ discriminator: number[];
12
+ accounts: ({
13
+ name: string;
14
+ writable: boolean;
15
+ pda: {
16
+ seeds: ({
17
+ kind: string;
18
+ value: number[];
19
+ path?: undefined;
20
+ } | {
21
+ kind: string;
22
+ path: string;
23
+ value?: undefined;
24
+ })[];
25
+ };
26
+ docs?: undefined;
27
+ signer?: undefined;
28
+ address?: undefined;
29
+ } | {
30
+ name: string;
31
+ docs: string[];
32
+ writable?: undefined;
33
+ pda?: undefined;
34
+ signer?: undefined;
35
+ address?: undefined;
36
+ } | {
37
+ name: string;
38
+ writable: boolean;
39
+ signer: boolean;
40
+ pda?: undefined;
41
+ docs?: undefined;
42
+ address?: undefined;
43
+ } | {
44
+ name: string;
45
+ address: string;
46
+ writable?: undefined;
47
+ pda?: undefined;
48
+ docs?: undefined;
49
+ signer?: undefined;
50
+ })[];
51
+ args: {
52
+ name: string;
53
+ type: {
54
+ defined: {
55
+ name: string;
56
+ };
57
+ };
58
+ }[];
59
+ } | {
60
+ name: string;
61
+ discriminator: number[];
62
+ accounts: ({
63
+ name: string;
64
+ writable: boolean;
65
+ signer?: undefined;
66
+ address?: undefined;
67
+ } | {
68
+ name: string;
69
+ writable: boolean;
70
+ signer: boolean;
71
+ address?: undefined;
72
+ } | {
73
+ name: string;
74
+ address: string;
75
+ writable?: undefined;
76
+ signer?: undefined;
77
+ })[];
78
+ args: {
79
+ name: string;
80
+ type: {
81
+ defined: {
82
+ name: string;
83
+ };
84
+ };
85
+ }[];
86
+ } | {
87
+ name: string;
88
+ discriminator: number[];
89
+ accounts: ({
90
+ name: string;
91
+ writable: boolean;
92
+ pda?: undefined;
93
+ signer?: undefined;
94
+ address?: undefined;
95
+ } | {
96
+ name: string;
97
+ writable: boolean;
98
+ pda: {
99
+ seeds: ({
100
+ kind: string;
101
+ value: number[];
102
+ path?: undefined;
103
+ } | {
104
+ kind: string;
105
+ path: string;
106
+ value?: undefined;
107
+ })[];
108
+ };
109
+ signer?: undefined;
110
+ address?: undefined;
111
+ } | {
112
+ name: string;
113
+ signer: boolean;
114
+ writable?: undefined;
115
+ pda?: undefined;
116
+ address?: undefined;
117
+ } | {
118
+ name: string;
119
+ address: string;
120
+ writable?: undefined;
121
+ pda?: undefined;
122
+ signer?: undefined;
123
+ })[];
124
+ args: {
125
+ name: string;
126
+ type: {
127
+ defined: {
128
+ name: string;
129
+ };
130
+ };
131
+ }[];
132
+ } | {
133
+ name: string;
134
+ discriminator: number[];
135
+ accounts: ({
136
+ name: string;
137
+ writable: boolean;
138
+ signer?: undefined;
139
+ relations?: undefined;
140
+ } | {
141
+ name: string;
142
+ signer: boolean;
143
+ relations: string[];
144
+ writable?: undefined;
145
+ })[];
146
+ args: {
147
+ name: string;
148
+ type: {
149
+ option: {
150
+ defined: {
151
+ name: string;
152
+ };
153
+ };
154
+ };
155
+ }[];
156
+ })[];
157
+ accounts: {
158
+ name: string;
159
+ discriminator: number[];
160
+ }[];
161
+ events: {
162
+ name: string;
163
+ discriminator: number[];
164
+ }[];
165
+ errors: {
166
+ code: number;
167
+ name: string;
168
+ msg: string;
169
+ }[];
170
+ types: ({
171
+ name: string;
172
+ type: {
173
+ kind: string;
174
+ fields: ({
175
+ name: string;
176
+ type: string;
177
+ } | {
178
+ name: string;
179
+ type: {
180
+ defined: {
181
+ name: string;
182
+ };
183
+ option?: undefined;
184
+ };
185
+ } | {
186
+ name: string;
187
+ type: {
188
+ option: {
189
+ defined: {
190
+ name: string;
191
+ };
192
+ };
193
+ defined?: undefined;
194
+ };
195
+ })[];
196
+ variants?: undefined;
197
+ };
198
+ } | {
199
+ name: string;
200
+ type: {
201
+ kind: string;
202
+ variants: {
203
+ name: string;
204
+ }[];
205
+ fields?: undefined;
206
+ };
207
+ })[];
208
+ };