pnpfucius 2.0.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/src/agent.js ADDED
@@ -0,0 +1,1037 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { PNPClient } from 'pnp-sdk';
3
+ import { getConfig, validateConfig } from './config.js';
4
+ import { generatePrivacyMarket, generateMultipleMarkets } from './privacy-markets.js';
5
+
6
+ export class PrivacyOracleAgent {
7
+ constructor(options = {}) {
8
+ this.config = options.config || getConfig();
9
+ this.client = null;
10
+ this.initialized = false;
11
+ this.verbose = options.verbose || false;
12
+ }
13
+
14
+ log(message, level = 'info') {
15
+ if (this.verbose || level === 'error') {
16
+ const prefix = level === 'error' ? '[ERROR]' : '[INFO]';
17
+ console.log(`${prefix} ${message}`);
18
+ }
19
+ }
20
+
21
+ async initialize() {
22
+ if (this.initialized) return;
23
+
24
+ const validation = validateConfig(this.config);
25
+
26
+ if (validation.warnings.length > 0) {
27
+ validation.warnings.forEach(w => this.log(w, 'warn'));
28
+ }
29
+
30
+ if (!validation.valid && this.config.walletKey) {
31
+ throw new Error(`Configuration errors: ${validation.errors.join(', ')}`);
32
+ }
33
+
34
+ this.log(`Connecting to ${this.config.network} via Helius RPC...`);
35
+
36
+ if (this.config.walletKey) {
37
+ let privateKey = this.config.walletKey;
38
+
39
+ if (typeof privateKey === 'string') {
40
+ if (privateKey.startsWith('[')) {
41
+ privateKey = Uint8Array.from(JSON.parse(privateKey));
42
+ }
43
+ }
44
+
45
+ this.client = new PNPClient(this.config.rpcUrl, privateKey);
46
+ this.log('Client initialized with signer');
47
+ } else {
48
+ this.client = new PNPClient(this.config.rpcUrl);
49
+ this.log('Client initialized in read-only mode');
50
+ }
51
+
52
+ this.initialized = true;
53
+ }
54
+
55
+ async createMarket(options) {
56
+ await this.initialize();
57
+
58
+ if (!this.client.market) {
59
+ throw new Error('Market module not available. Ensure wallet is configured.');
60
+ }
61
+
62
+ const question = options.question;
63
+ const durationDays = options.durationDays || this.config.defaultDurationDays;
64
+ const liquidity = options.liquidity || this.config.defaultLiquidity;
65
+
66
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60));
67
+
68
+ this.log(`Creating market: "${question}"`);
69
+ this.log(`Duration: ${durationDays} days, Liquidity: ${liquidity}`);
70
+
71
+ const result = await this.client.market.createMarket({
72
+ question,
73
+ initialLiquidity: liquidity,
74
+ endTime,
75
+ baseMint: this.config.collateralMint
76
+ });
77
+
78
+ return {
79
+ success: true,
80
+ signature: result.signature,
81
+ market: result.market?.toBase58?.() || result.market?.toString?.() || result.market,
82
+ question,
83
+ durationDays,
84
+ liquidity: liquidity.toString()
85
+ };
86
+ }
87
+
88
+ async createP2PMarket(options) {
89
+ await this.initialize();
90
+
91
+ const question = options.question;
92
+ const side = options.side || 'yes';
93
+ const amount = options.amount || this.config.defaultLiquidity;
94
+ const cap = options.cap || amount * 5n;
95
+ const durationDays = options.durationDays || this.config.defaultDurationDays;
96
+
97
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60));
98
+
99
+ this.log(`Creating P2P market: "${question}"`);
100
+ this.log(`Side: ${side}, Amount: ${amount}, Cap: ${cap}`);
101
+
102
+ const result = await this.client.createP2PMarketGeneral({
103
+ question,
104
+ initialAmount: amount,
105
+ side,
106
+ creatorSideCap: cap,
107
+ endTime,
108
+ collateralTokenMint: this.config.collateralMint
109
+ });
110
+
111
+ return {
112
+ success: true,
113
+ signature: result.signature,
114
+ market: result.market,
115
+ yesTokenMint: result.yesTokenMint,
116
+ noTokenMint: result.noTokenMint,
117
+ question,
118
+ side,
119
+ durationDays
120
+ };
121
+ }
122
+
123
+ async createPrivacyMarket(options = {}) {
124
+ const marketIdea = generatePrivacyMarket();
125
+
126
+ this.log(`Generated market idea: ${marketIdea.category}`);
127
+
128
+ return this.createMarket({
129
+ question: options.question || marketIdea.question,
130
+ durationDays: options.durationDays || marketIdea.durationDays,
131
+ liquidity: options.liquidity || marketIdea.suggestedLiquidity
132
+ });
133
+ }
134
+
135
+ async createBatchMarkets(count = 3) {
136
+ await this.initialize();
137
+
138
+ const ideas = generateMultipleMarkets(count);
139
+ const results = [];
140
+
141
+ for (const idea of ideas) {
142
+ try {
143
+ this.log(`Creating: "${idea.question}"`);
144
+
145
+ const result = await this.createMarket({
146
+ question: idea.question,
147
+ durationDays: idea.durationDays,
148
+ liquidity: idea.suggestedLiquidity
149
+ });
150
+
151
+ results.push({ ...result, category: idea.category });
152
+
153
+ await this.sleep(2000);
154
+
155
+ } catch (error) {
156
+ results.push({
157
+ success: false,
158
+ question: idea.question,
159
+ error: error.message
160
+ });
161
+ }
162
+ }
163
+
164
+ return results;
165
+ }
166
+
167
+ async fetchMarkets() {
168
+ await this.initialize();
169
+
170
+ try {
171
+ const response = await this.client.fetchMarkets();
172
+ return response;
173
+ } catch (error) {
174
+ this.log(`Error fetching markets: ${error.message}`, 'error');
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ async fetchMarketInfo(marketAddress) {
180
+ await this.initialize();
181
+
182
+ const market = new PublicKey(marketAddress);
183
+ const info = await this.client.fetchMarket(market);
184
+
185
+ return {
186
+ address: marketAddress,
187
+ question: info.account.question,
188
+ creator: new PublicKey(info.account.creator).toBase58(),
189
+ resolved: info.account.resolved,
190
+ resolvable: info.account.resolvable,
191
+ endTime: new Date(Number(info.account.end_time) * 1000),
192
+ winningToken: info.account.winning_token_id || null
193
+ };
194
+ }
195
+
196
+ async getMarketAddresses() {
197
+ await this.initialize();
198
+ return this.client.fetchMarketAddresses();
199
+ }
200
+
201
+ // ========== TRADING METHODS ==========
202
+
203
+ async buyTokens(options) {
204
+ await this.initialize();
205
+
206
+ const { marketAddress, side, amountUsdc } = options;
207
+ const market = new PublicKey(marketAddress);
208
+
209
+ this.log(`Buying ${side.toUpperCase()} tokens for ${amountUsdc} USDC on ${marketAddress}`);
210
+
211
+ if (!this.client.trading) {
212
+ throw new Error('Trading module not available. Ensure wallet is configured.');
213
+ }
214
+
215
+ const result = await this.client.trading.buyTokensUsdc({
216
+ market,
217
+ usdcAmount: BigInt(Math.floor(amountUsdc * 1_000_000)),
218
+ tokenId: side === 'yes' ? 0 : 1
219
+ });
220
+
221
+ return {
222
+ success: true,
223
+ signature: result.signature,
224
+ market: marketAddress,
225
+ side,
226
+ amountUsdc,
227
+ tokensReceived: result.tokensReceived?.toString() || 'unknown'
228
+ };
229
+ }
230
+
231
+ async sellTokens(options) {
232
+ await this.initialize();
233
+
234
+ const { marketAddress, side, amount } = options;
235
+ const market = new PublicKey(marketAddress);
236
+
237
+ this.log(`Selling ${amount} ${side.toUpperCase()} tokens on ${marketAddress}`);
238
+
239
+ if (!this.client.trading) {
240
+ throw new Error('Trading module not available. Ensure wallet is configured.');
241
+ }
242
+
243
+ const result = await this.client.trading.sellTokensBase({
244
+ market,
245
+ tokenAmount: BigInt(Math.floor(amount * 1_000_000)),
246
+ tokenId: side === 'yes' ? 0 : 1
247
+ });
248
+
249
+ return {
250
+ success: true,
251
+ signature: result.signature,
252
+ market: marketAddress,
253
+ side,
254
+ amount,
255
+ usdcReceived: result.usdcReceived?.toString() || 'unknown'
256
+ };
257
+ }
258
+
259
+ async getMarketPrices(marketAddress) {
260
+ await this.initialize();
261
+
262
+ const market = new PublicKey(marketAddress);
263
+
264
+ if (this.client.trading?.getPrices) {
265
+ const prices = await this.client.trading.getPrices(market);
266
+ return {
267
+ market: marketAddress,
268
+ yesPrice: prices.yesPrice || prices[0],
269
+ noPrice: prices.noPrice || prices[1],
270
+ timestamp: new Date().toISOString()
271
+ };
272
+ }
273
+
274
+ // Fallback: fetch market info and calculate from pool
275
+ const info = await this.client.fetchMarket(market);
276
+ return {
277
+ market: marketAddress,
278
+ yesPrice: 'N/A - use fetchMarket for pool data',
279
+ noPrice: 'N/A - use fetchMarket for pool data',
280
+ raw: info
281
+ };
282
+ }
283
+
284
+ async getBalances(marketAddress) {
285
+ await this.initialize();
286
+
287
+ const market = new PublicKey(marketAddress);
288
+
289
+ if (this.client.trading?.getBalances) {
290
+ const balances = await this.client.trading.getBalances(market);
291
+ return {
292
+ market: marketAddress,
293
+ yesBalance: balances.yesBalance?.toString() || '0',
294
+ noBalance: balances.noBalance?.toString() || '0'
295
+ };
296
+ }
297
+
298
+ return {
299
+ market: marketAddress,
300
+ yesBalance: '0',
301
+ noBalance: '0',
302
+ note: 'Balance check requires wallet connection'
303
+ };
304
+ }
305
+
306
+ // ========== REDEMPTION METHODS ==========
307
+
308
+ async redeemPosition(marketAddress) {
309
+ await this.initialize();
310
+
311
+ const market = new PublicKey(marketAddress);
312
+
313
+ this.log(`Redeeming position on ${marketAddress}`);
314
+
315
+ const result = await this.client.redeemPosition(market);
316
+
317
+ return {
318
+ success: true,
319
+ signature: result.signature,
320
+ market: marketAddress,
321
+ amountRedeemed: result.amount?.toString() || 'unknown'
322
+ };
323
+ }
324
+
325
+ async claimRefund(marketAddress) {
326
+ await this.initialize();
327
+
328
+ const market = new PublicKey(marketAddress);
329
+
330
+ this.log(`Claiming refund on ${marketAddress}`);
331
+
332
+ const result = await this.client.claimMarketRefund(market);
333
+
334
+ return {
335
+ success: true,
336
+ signature: result.signature,
337
+ market: marketAddress,
338
+ amountRefunded: result.amount?.toString() || 'unknown'
339
+ };
340
+ }
341
+
342
+ // ========== URL-AWARE MARKET CREATION ==========
343
+
344
+ async createMarketFromSource(options) {
345
+ await this.initialize();
346
+
347
+ const { question, sourceUrl, sourceType, durationDays, liquidity } = options;
348
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + ((durationDays || 30) * 24 * 60 * 60));
349
+ const amount = liquidity || this.config.defaultLiquidity;
350
+
351
+ this.log(`Creating ${sourceType || 'standard'} market: "${question}"`);
352
+
353
+ let result;
354
+
355
+ if (sourceType === 'twitter' && this.client.createMarketTwitter) {
356
+ result = await this.client.createMarketTwitter({
357
+ question,
358
+ tweetUrl: sourceUrl,
359
+ initialLiquidity: amount,
360
+ endTime,
361
+ baseMint: this.config.collateralMint
362
+ });
363
+ } else if (sourceType === 'youtube' && this.client.createMarketYoutube) {
364
+ result = await this.client.createMarketYoutube({
365
+ question,
366
+ youtubeUrl: sourceUrl,
367
+ initialLiquidity: amount,
368
+ endTime,
369
+ baseMint: this.config.collateralMint
370
+ });
371
+ } else if (sourceType === 'defi' && this.client.createMarketDefiLlama) {
372
+ result = await this.client.createMarketDefiLlama({
373
+ question,
374
+ metric: sourceUrl,
375
+ initialLiquidity: amount,
376
+ endTime,
377
+ baseMint: this.config.collateralMint
378
+ });
379
+ } else {
380
+ // Fallback to standard market creation
381
+ result = await this.client.market.createMarket({
382
+ question,
383
+ initialLiquidity: amount,
384
+ endTime,
385
+ baseMint: this.config.collateralMint
386
+ });
387
+ }
388
+
389
+ return {
390
+ success: true,
391
+ signature: result.signature,
392
+ market: result.market?.toBase58?.() || result.market?.toString?.() || result.market,
393
+ question,
394
+ sourceType: sourceType || 'standard',
395
+ sourceUrl
396
+ };
397
+ }
398
+
399
+ sleep(ms) {
400
+ return new Promise(resolve => setTimeout(resolve, ms));
401
+ }
402
+
403
+ // ========== PNP ORACLE/SETTLEMENT METHODS ==========
404
+
405
+ /**
406
+ * Fetch settlement criteria from PNP's LLM oracle
407
+ * This returns the AI-generated criteria for how the market should be resolved
408
+ */
409
+ async fetchSettlementCriteria(marketAddress, options = {}) {
410
+ await this.initialize();
411
+
412
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
413
+
414
+ this.log(`Fetching settlement criteria for ${market}`);
415
+
416
+ try {
417
+ const criteria = await this.client.fetchSettlementCriteria(market, options.baseUrl);
418
+ return {
419
+ success: true,
420
+ market,
421
+ criteria
422
+ };
423
+ } catch (error) {
424
+ this.log(`Error fetching settlement criteria: ${error.message}`, 'error');
425
+ throw error;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Get settlement data (resolution result) from PNP's LLM oracle
431
+ */
432
+ async getSettlementData(marketAddress, options = {}) {
433
+ await this.initialize();
434
+
435
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
436
+
437
+ this.log(`Fetching settlement data for ${market}`);
438
+
439
+ try {
440
+ const data = await this.client.getSettlementData(market, options.baseUrl);
441
+ return {
442
+ success: true,
443
+ market,
444
+ data
445
+ };
446
+ } catch (error) {
447
+ this.log(`Error fetching settlement data: ${error.message}`, 'error');
448
+ throw error;
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Wait for settlement criteria to become available (with retries)
454
+ */
455
+ async waitForSettlementCriteria(marketAddress, options = {}) {
456
+ await this.initialize();
457
+
458
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
459
+ const { retryDelayMs = 2000, maxRetryTimeMs = 30000, baseUrl } = options;
460
+
461
+ this.log(`Waiting for settlement criteria for ${market}...`);
462
+
463
+ try {
464
+ const result = await this.client.waitForSettlementCriteria(market, baseUrl, {
465
+ retryDelayMs,
466
+ maxRetryTimeMs
467
+ });
468
+ return {
469
+ success: true,
470
+ market,
471
+ resolvable: result.resolvable,
472
+ answer: result.answer,
473
+ criteria: result.criteria
474
+ };
475
+ } catch (error) {
476
+ this.log(`Error waiting for settlement criteria: ${error.message}`, 'error');
477
+ throw error;
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Settle a market using the oracle (set the winning outcome)
483
+ * NOTE: This requires oracle authority
484
+ */
485
+ async settleMarket(marketAddress, yesWinner) {
486
+ await this.initialize();
487
+
488
+ const market = new PublicKey(marketAddress);
489
+
490
+ this.log(`Settling market ${marketAddress} with outcome: ${yesWinner ? 'YES' : 'NO'}`);
491
+
492
+ try {
493
+ const result = await this.client.settleMarket({
494
+ market,
495
+ yesWinner
496
+ });
497
+ return {
498
+ success: true,
499
+ signature: result.signature,
500
+ market: marketAddress,
501
+ outcome: yesWinner ? 'yes' : 'no'
502
+ };
503
+ } catch (error) {
504
+ this.log(`Error settling market: ${error.message}`, 'error');
505
+ throw error;
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Set market as resolvable (devnet only)
511
+ */
512
+ async setMarketResolvable(marketAddress, resolvable = true, forceResolve = false) {
513
+ await this.initialize();
514
+
515
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
516
+
517
+ this.log(`Setting market ${market} resolvable: ${resolvable}`);
518
+
519
+ try {
520
+ const result = await this.client.setMarketResolvable(market, resolvable, forceResolve);
521
+ return {
522
+ success: true,
523
+ signature: result.signature,
524
+ market,
525
+ resolvable
526
+ };
527
+ } catch (error) {
528
+ this.log(`Error setting resolvable: ${error.message}`, 'error');
529
+ throw error;
530
+ }
531
+ }
532
+
533
+ // ========== MARKET DISCOVERY METHODS ==========
534
+
535
+ /**
536
+ * Discover all markets on PNP Exchange (not just created by this agent)
537
+ */
538
+ async discoverMarkets(options = {}) {
539
+ await this.initialize();
540
+
541
+ this.log('Discovering all PNP markets...');
542
+
543
+ try {
544
+ const markets = await this.client.fetchMarkets();
545
+ return {
546
+ success: true,
547
+ markets: markets,
548
+ count: Array.isArray(markets) ? markets.length : Object.keys(markets).length
549
+ };
550
+ } catch (error) {
551
+ this.log(`Error discovering markets: ${error.message}`, 'error');
552
+ throw error;
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Fetch all market addresses (V2)
558
+ */
559
+ async fetchAllMarketAddresses(baseUrl) {
560
+ await this.initialize();
561
+
562
+ try {
563
+ const addresses = await this.client.fetchMarketAddresses(baseUrl);
564
+ return {
565
+ success: true,
566
+ addresses,
567
+ count: addresses.length
568
+ };
569
+ } catch (error) {
570
+ this.log(`Error fetching market addresses: ${error.message}`, 'error');
571
+ throw error;
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Fetch all V3 market addresses
577
+ */
578
+ async fetchV3MarketAddresses(baseUrl) {
579
+ await this.initialize();
580
+
581
+ try {
582
+ const addresses = await this.client.fetchV3MarketAddresses(baseUrl);
583
+ return {
584
+ success: true,
585
+ addresses,
586
+ count: addresses.length
587
+ };
588
+ } catch (error) {
589
+ this.log(`Error fetching V3 market addresses: ${error.message}`, 'error');
590
+ throw error;
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Get market metadata (volume, image, etc.)
596
+ */
597
+ async getMarketMetadata(marketAddress, baseUrl) {
598
+ await this.initialize();
599
+
600
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
601
+
602
+ try {
603
+ const meta = await this.client.getMarketMeta(market, baseUrl);
604
+ return {
605
+ success: true,
606
+ market,
607
+ metadata: meta
608
+ };
609
+ } catch (error) {
610
+ this.log(`Error fetching market metadata: ${error.message}`, 'error');
611
+ throw error;
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Get metadata for multiple markets at once
617
+ */
618
+ async getMarketMetadataBatch(marketAddresses, baseUrl) {
619
+ await this.initialize();
620
+
621
+ const markets = marketAddresses.map(m => typeof m === 'string' ? m : m.toString());
622
+
623
+ try {
624
+ const metas = await this.client.getMarketMetaBatch(markets, baseUrl);
625
+ return {
626
+ success: true,
627
+ markets: metas,
628
+ count: metas.length
629
+ };
630
+ } catch (error) {
631
+ this.log(`Error fetching batch metadata: ${error.message}`, 'error');
632
+ throw error;
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Get detailed V2 market info
638
+ */
639
+ async getV2MarketInfo(marketAddress, options = {}) {
640
+ await this.initialize();
641
+
642
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
643
+
644
+ try {
645
+ const info = await this.client.getV2MarketInfo(market, options);
646
+ return {
647
+ success: true,
648
+ market,
649
+ info
650
+ };
651
+ } catch (error) {
652
+ this.log(`Error fetching V2 market info: ${error.message}`, 'error');
653
+ throw error;
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Get detailed P2P market info
659
+ */
660
+ async getP2PMarketInfo(marketAddress, options = {}) {
661
+ await this.initialize();
662
+
663
+ const market = typeof marketAddress === 'string' ? marketAddress : marketAddress.toString();
664
+
665
+ try {
666
+ const info = await this.client.getP2PMarketInfo(market, options);
667
+ return {
668
+ success: true,
669
+ market,
670
+ info
671
+ };
672
+ } catch (error) {
673
+ this.log(`Error fetching P2P market info: ${error.message}`, 'error');
674
+ throw error;
675
+ }
676
+ }
677
+
678
+ // ========== P2P MARKET WITH CUSTOM ODDS ==========
679
+
680
+ /**
681
+ * Create a simple P2P market (UI-friendly with USDC amounts)
682
+ */
683
+ async createP2PMarketSimple(options) {
684
+ await this.initialize();
685
+
686
+ const {
687
+ question,
688
+ side = 'yes',
689
+ amountUsdc = 1,
690
+ durationDays = 30,
691
+ capMultiplier = 5,
692
+ maxPotRatio
693
+ } = options;
694
+
695
+ this.log(`Creating simple P2P market: "${question}"`);
696
+ this.log(`Side: ${side}, Amount: ${amountUsdc} USDC, Cap multiplier: ${capMultiplier}x`);
697
+
698
+ try {
699
+ const result = await this.client.createP2PMarketSimple({
700
+ question,
701
+ side,
702
+ amountUsdc,
703
+ daysUntilEnd: durationDays,
704
+ creatorSideCapMultiplier: capMultiplier,
705
+ collateralTokenMint: this.config.collateralMint,
706
+ maxPotRatio
707
+ });
708
+
709
+ return {
710
+ success: true,
711
+ signature: result.signature,
712
+ market: result.market,
713
+ yesTokenMint: result.yesTokenMint,
714
+ noTokenMint: result.noTokenMint,
715
+ question,
716
+ side,
717
+ amountUsdc,
718
+ durationDays
719
+ };
720
+ } catch (error) {
721
+ this.log(`Error creating simple P2P market: ${error.message}`, 'error');
722
+ throw error;
723
+ }
724
+ }
725
+
726
+ /**
727
+ * Create a P2P market with custom odds
728
+ */
729
+ async createP2PMarketWithOdds(options) {
730
+ await this.initialize();
731
+
732
+ const {
733
+ question,
734
+ side = 'yes',
735
+ amount,
736
+ cap,
737
+ durationDays = 30,
738
+ oddsBps, // Odds in basis points (e.g., 7000 = 70%)
739
+ maxPotRatio
740
+ } = options;
741
+
742
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60));
743
+ const initialAmount = amount || this.config.defaultLiquidity;
744
+ const creatorSideCap = cap || initialAmount * 5n;
745
+
746
+ this.log(`Creating P2P market with odds: "${question}"`);
747
+ this.log(`Side: ${side}, Odds: ${oddsBps / 100}%`);
748
+
749
+ try {
750
+ const result = await this.client.createMarketP2PWithCustomOdds({
751
+ question,
752
+ initialAmount,
753
+ side,
754
+ creatorSideCap,
755
+ endTime,
756
+ oddsBps,
757
+ maxPotRatio,
758
+ collateralTokenMint: this.config.collateralMint
759
+ });
760
+
761
+ return {
762
+ success: true,
763
+ signature: result.signature,
764
+ market: result.market,
765
+ yesTokenMint: result.yesTokenMint,
766
+ noTokenMint: result.noTokenMint,
767
+ question,
768
+ side,
769
+ oddsBps,
770
+ durationDays
771
+ };
772
+ } catch (error) {
773
+ this.log(`Error creating P2P market with odds: ${error.message}`, 'error');
774
+ throw error;
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Create an AMM market with custom starting odds
780
+ */
781
+ async createAMMMarketWithOdds(options) {
782
+ await this.initialize();
783
+
784
+ const {
785
+ question,
786
+ liquidity,
787
+ durationDays = 30,
788
+ yesOddsBps // Starting YES odds in basis points (e.g., 5000 = 50%)
789
+ } = options;
790
+
791
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60));
792
+ const initialLiquidity = liquidity || this.config.defaultLiquidity;
793
+
794
+ this.log(`Creating AMM market with odds: "${question}"`);
795
+ this.log(`YES odds: ${yesOddsBps / 100}%, Liquidity: ${initialLiquidity}`);
796
+
797
+ try {
798
+ const result = await this.client.createMarketV2WithCustomOdds({
799
+ question,
800
+ initialLiquidity,
801
+ endTime,
802
+ collateralTokenMint: this.config.collateralMint,
803
+ yesOddsBps
804
+ });
805
+
806
+ return {
807
+ success: true,
808
+ signature: result.signature,
809
+ market: result.market,
810
+ question,
811
+ yesOddsBps,
812
+ durationDays
813
+ };
814
+ } catch (error) {
815
+ this.log(`Error creating AMM market with odds: ${error.message}`, 'error');
816
+ throw error;
817
+ }
818
+ }
819
+
820
+ // ========== CUSTOM ORACLE SUPPORT ==========
821
+
822
+ /**
823
+ * Create a market with a custom oracle/settler address
824
+ */
825
+ async createMarketWithCustomOracle(options) {
826
+ await this.initialize();
827
+
828
+ const {
829
+ question,
830
+ liquidity,
831
+ durationDays = 30,
832
+ settlerAddress, // The address that will resolve this market
833
+ yesOddsBps = 5000 // Default 50/50
834
+ } = options;
835
+
836
+ const endTime = BigInt(Math.floor(Date.now() / 1000) + (durationDays * 24 * 60 * 60));
837
+ const initialLiquidity = liquidity || this.config.defaultLiquidity;
838
+ const settler = new PublicKey(settlerAddress);
839
+
840
+ this.log(`Creating market with custom oracle: "${question}"`);
841
+ this.log(`Oracle: ${settlerAddress}`);
842
+
843
+ try {
844
+ const result = await this.client.createMarketWithCustomOracle({
845
+ question,
846
+ initialLiquidity,
847
+ endTime,
848
+ collateralMint: this.config.collateralMint,
849
+ settlerAddress: settler,
850
+ yesOddsBps
851
+ });
852
+
853
+ return {
854
+ success: true,
855
+ signature: result.signature,
856
+ market: result.market?.toBase58?.() || result.market?.toString?.() || result.market,
857
+ question,
858
+ settlerAddress,
859
+ durationDays
860
+ };
861
+ } catch (error) {
862
+ this.log(`Error creating market with custom oracle: ${error.message}`, 'error');
863
+ throw error;
864
+ }
865
+ }
866
+
867
+ // ========== V3 MARKET SUPPORT ==========
868
+
869
+ /**
870
+ * Buy tokens on a V3 market
871
+ */
872
+ async buyV3Tokens(options) {
873
+ await this.initialize();
874
+
875
+ const { marketAddress, side, amountUsdc } = options;
876
+ const market = new PublicKey(marketAddress);
877
+
878
+ this.log(`Buying V3 ${side.toUpperCase()} tokens for ${amountUsdc} USDC on ${marketAddress}`);
879
+
880
+ try {
881
+ const result = await this.client.buyV3TokensUsdc({
882
+ market,
883
+ buyYesToken: side === 'yes',
884
+ amountUsdc
885
+ });
886
+
887
+ return {
888
+ success: true,
889
+ signature: result.signature,
890
+ market: marketAddress,
891
+ side,
892
+ amountUsdc
893
+ };
894
+ } catch (error) {
895
+ this.log(`Error buying V3 tokens: ${error.message}`, 'error');
896
+ throw error;
897
+ }
898
+ }
899
+
900
+ /**
901
+ * Redeem position on a V3 market
902
+ */
903
+ async redeemV3Position(marketAddress) {
904
+ await this.initialize();
905
+
906
+ const market = new PublicKey(marketAddress);
907
+
908
+ this.log(`Redeeming V3 position on ${marketAddress}`);
909
+
910
+ try {
911
+ const result = await this.client.redeemV3Position(market);
912
+ return {
913
+ success: true,
914
+ signature: result.signature,
915
+ market: marketAddress
916
+ };
917
+ } catch (error) {
918
+ this.log(`Error redeeming V3 position: ${error.message}`, 'error');
919
+ throw error;
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Redeem P2P market position
925
+ */
926
+ async redeemP2PPosition(marketAddress) {
927
+ await this.initialize();
928
+
929
+ this.log(`Redeeming P2P position on ${marketAddress}`);
930
+
931
+ try {
932
+ const result = await this.client.redeemP2PPosition(marketAddress);
933
+ return {
934
+ success: true,
935
+ signature: result.signature,
936
+ market: marketAddress
937
+ };
938
+ } catch (error) {
939
+ this.log(`Error redeeming P2P position: ${error.message}`, 'error');
940
+ throw error;
941
+ }
942
+ }
943
+
944
+ /**
945
+ * Claim P2P market refund
946
+ */
947
+ async claimP2PRefund(marketAddress) {
948
+ await this.initialize();
949
+
950
+ this.log(`Claiming P2P refund on ${marketAddress}`);
951
+
952
+ try {
953
+ const result = await this.client.claimP2PMarketRefund(marketAddress);
954
+ return {
955
+ success: true,
956
+ signature: result.signature,
957
+ market: marketAddress
958
+ };
959
+ } catch (error) {
960
+ this.log(`Error claiming P2P refund: ${error.message}`, 'error');
961
+ throw error;
962
+ }
963
+ }
964
+
965
+ // ========== URL DETECTION HELPERS ==========
966
+
967
+ /**
968
+ * Detect Twitter URL in question text
969
+ */
970
+ detectTwitterUrl(questionText) {
971
+ if (this.client.detectTwitterUrl) {
972
+ return this.client.detectTwitterUrl(questionText);
973
+ }
974
+ // Fallback regex
975
+ const twitterRegex = /https?:\/\/(twitter\.com|x\.com)\/\w+\/status\/\d+/i;
976
+ const match = questionText.match(twitterRegex);
977
+ return {
978
+ question: questionText,
979
+ twitterUrl: match ? match[0] : undefined
980
+ };
981
+ }
982
+
983
+ /**
984
+ * Detect YouTube URL in question text
985
+ */
986
+ detectYoutubeUrl(questionText) {
987
+ if (this.client.detectYoutubeUrl) {
988
+ return this.client.detectYoutubeUrl(questionText);
989
+ }
990
+ // Fallback regex
991
+ const youtubeRegex = /https?:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)[^\s]+/i;
992
+ const match = questionText.match(youtubeRegex);
993
+ return {
994
+ question: questionText,
995
+ youtubeUrl: match ? match[0] : undefined
996
+ };
997
+ }
998
+
999
+ /**
1000
+ * Detect DeFi Llama metric in question text
1001
+ */
1002
+ detectDefiLlamaUrl(questionText) {
1003
+ if (this.client.detectDefiLlamaUrl) {
1004
+ return this.client.detectDefiLlamaUrl(questionText);
1005
+ }
1006
+ return { question: questionText };
1007
+ }
1008
+
1009
+ // ========== GLOBAL CONFIG ==========
1010
+
1011
+ /**
1012
+ * Fetch PNP global configuration
1013
+ */
1014
+ async fetchGlobalConfig() {
1015
+ await this.initialize();
1016
+
1017
+ try {
1018
+ const config = await this.client.fetchGlobalConfig();
1019
+ return {
1020
+ success: true,
1021
+ config: {
1022
+ publicKey: config.publicKey?.toBase58?.() || config.publicKey,
1023
+ account: config.account
1024
+ }
1025
+ };
1026
+ } catch (error) {
1027
+ this.log(`Error fetching global config: ${error.message}`, 'error');
1028
+ throw error;
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ export async function createAgent(options = {}) {
1034
+ const agent = new PrivacyOracleAgent(options);
1035
+ await agent.initialize();
1036
+ return agent;
1037
+ }