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.
@@ -0,0 +1,183 @@
1
+ // Privacy token collateral support for prediction markets
2
+ // Supports Token-2022 confidential transfers and configurable collateral
3
+
4
+ import { PublicKey, Connection } from '@solana/web3.js';
5
+
6
+ // Known token addresses
7
+ export const TOKENS = {
8
+ mainnet: {
9
+ // Standard stablecoins
10
+ USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
11
+ USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
12
+
13
+ // Native SOL (wrapped)
14
+ WSOL: 'So11111111111111111111111111111111111111112',
15
+
16
+ // Token-2022 program ID for reference
17
+ TOKEN_2022_PROGRAM: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
18
+ },
19
+ devnet: {
20
+ // Devnet USDC
21
+ USDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
22
+
23
+ // Wrapped SOL
24
+ WSOL: 'So11111111111111111111111111111111111111112',
25
+
26
+ // Token-2022 program ID
27
+ TOKEN_2022_PROGRAM: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
28
+ }
29
+ };
30
+
31
+ // Privacy-focused token metadata
32
+ export const PRIVACY_TOKEN_INFO = {
33
+ // These would be mints with confidential transfer extension enabled
34
+ // In practice, you'd register actual Token-2022 confidential mints here
35
+ description: `
36
+ Privacy-focused collateral support for the PNP bounty requirement.
37
+
38
+ Solana's native privacy mechanism is the Token-2022 Confidential Transfer extension,
39
+ which provides:
40
+ - Encrypted balances using ElGamal encryption
41
+ - Zero-knowledge proofs for transfer validity
42
+ - Optional auditor key for compliance
43
+
44
+ To use privacy-focused collateral:
45
+ 1. Use a Token-2022 mint with confidential transfer extension enabled
46
+ 2. Configure the mint address in COLLATERAL_TOKEN env variable
47
+ 3. Ensure your wallet has configured confidential transfer on the account
48
+ `
49
+ };
50
+
51
+ export function getCollateralMint(symbol, network = 'devnet') {
52
+ const tokens = TOKENS[network] || TOKENS.devnet;
53
+
54
+ // Check if it's a known symbol
55
+ if (tokens[symbol]) {
56
+ return new PublicKey(tokens[symbol]);
57
+ }
58
+
59
+ // Try to parse as a direct public key
60
+ try {
61
+ return new PublicKey(symbol);
62
+ } catch {
63
+ throw new Error(`Unknown token: ${symbol}. Use a token symbol (USDC, USDT) or a valid mint address.`);
64
+ }
65
+ }
66
+
67
+ export function listSupportedTokens(network = 'devnet') {
68
+ const tokens = TOKENS[network] || TOKENS.devnet;
69
+
70
+ return Object.entries(tokens)
71
+ .filter(([key]) => key !== 'TOKEN_2022_PROGRAM')
72
+ .map(([symbol, address]) => ({
73
+ symbol,
74
+ address,
75
+ network
76
+ }));
77
+ }
78
+
79
+ // Check if a mint supports Token-2022 confidential transfers
80
+ export async function checkConfidentialTransferSupport(connection, mintAddress) {
81
+ try {
82
+ const mint = new PublicKey(mintAddress);
83
+ const accountInfo = await connection.getAccountInfo(mint);
84
+
85
+ if (!accountInfo) {
86
+ return { supported: false, reason: 'Mint account not found' };
87
+ }
88
+
89
+ // Check if owned by Token-2022 program
90
+ const token2022Program = new PublicKey(TOKENS.mainnet.TOKEN_2022_PROGRAM);
91
+ const isToken2022 = accountInfo.owner.equals(token2022Program);
92
+
93
+ if (!isToken2022) {
94
+ return {
95
+ supported: false,
96
+ reason: 'Not a Token-2022 mint',
97
+ isToken2022: false
98
+ };
99
+ }
100
+
101
+ // Token-2022 mints have extension data
102
+ // The confidential transfer extension has type discriminator 10
103
+ // This is a simplified check - full implementation would parse extensions
104
+ const hasExtensions = accountInfo.data.length > 82; // Base mint is 82 bytes
105
+
106
+ return {
107
+ supported: hasExtensions,
108
+ isToken2022: true,
109
+ reason: hasExtensions
110
+ ? 'Token-2022 mint with extensions (may support confidential transfers)'
111
+ : 'Token-2022 mint without extensions'
112
+ };
113
+ } catch (error) {
114
+ return {
115
+ supported: false,
116
+ reason: `Error checking mint: ${error.message}`
117
+ };
118
+ }
119
+ }
120
+
121
+ // Get token info including decimals
122
+ export async function getTokenInfo(connection, mintAddress) {
123
+ try {
124
+ const mint = new PublicKey(mintAddress);
125
+ const accountInfo = await connection.getAccountInfo(mint);
126
+
127
+ if (!accountInfo) {
128
+ throw new Error('Mint not found');
129
+ }
130
+
131
+ // Parse basic mint data (works for both Token and Token-2022)
132
+ // Mint structure: mintAuthorityOption (4) + mintAuthority (32) + supply (8) + decimals (1) + ...
133
+ const data = accountInfo.data;
134
+ const decimals = data[44]; // Offset to decimals field
135
+
136
+ return {
137
+ address: mintAddress,
138
+ decimals,
139
+ owner: accountInfo.owner.toBase58()
140
+ };
141
+ } catch (error) {
142
+ throw new Error(`Failed to get token info: ${error.message}`);
143
+ }
144
+ }
145
+
146
+ // Format amount with decimals
147
+ export function formatTokenAmount(amount, decimals) {
148
+ const divisor = BigInt(10 ** decimals);
149
+ const wholePart = amount / divisor;
150
+ const fractionalPart = amount % divisor;
151
+
152
+ if (fractionalPart === 0n) {
153
+ return wholePart.toString();
154
+ }
155
+
156
+ const fractionalStr = fractionalPart.toString().padStart(decimals, '0');
157
+ const trimmed = fractionalStr.replace(/0+$/, '');
158
+
159
+ return `${wholePart}.${trimmed}`;
160
+ }
161
+
162
+ // Parse amount string to base units
163
+ export function parseTokenAmount(amountStr, decimals) {
164
+ const [whole, fraction = ''] = amountStr.split('.');
165
+ const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals);
166
+
167
+ return BigInt(whole + paddedFraction);
168
+ }
169
+
170
+ // Collateral configuration helper
171
+ export function getCollateralConfig(config = {}) {
172
+ const network = config.network || process.env.NETWORK || 'devnet';
173
+ const tokenSymbol = config.token || process.env.COLLATERAL_TOKEN || 'USDC';
174
+
175
+ const mint = getCollateralMint(tokenSymbol, network);
176
+
177
+ return {
178
+ mint,
179
+ symbol: tokenSymbol,
180
+ network,
181
+ preferConfidential: config.preferConfidential || process.env.PREFER_CONFIDENTIAL_COLLATERAL === 'true'
182
+ };
183
+ }
package/src/config.js ADDED
@@ -0,0 +1,128 @@
1
+ import dotenv from 'dotenv';
2
+ import { PublicKey } from '@solana/web3.js';
3
+
4
+ dotenv.config();
5
+
6
+ // Token addresses
7
+ const USDC_MAINNET = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
8
+ const USDC_DEVNET = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU';
9
+
10
+ // Get collateral mint based on config
11
+ function getCollateralMintAddress(network, tokenSymbol) {
12
+ const isMainnet = network === 'mainnet';
13
+
14
+ // If a specific token is configured, try to use it
15
+ if (tokenSymbol && tokenSymbol !== 'USDC') {
16
+ // Try to parse as public key
17
+ try {
18
+ return new PublicKey(tokenSymbol);
19
+ } catch {
20
+ // Fall back to USDC
21
+ }
22
+ }
23
+
24
+ return new PublicKey(isMainnet ? USDC_MAINNET : USDC_DEVNET);
25
+ }
26
+
27
+ export function getConfig() {
28
+ const network = process.env.NETWORK || 'devnet';
29
+ const isMainnet = network === 'mainnet';
30
+
31
+ const heliusKey = process.env.HELIUS_API_KEY;
32
+ if (!heliusKey) {
33
+ console.warn('Warning: HELIUS_API_KEY not set. Using public RPC (rate limited).');
34
+ }
35
+
36
+ const rpcUrl = heliusKey
37
+ ? `https://${isMainnet ? 'mainnet' : 'devnet'}.helius-rpc.com/?api-key=${heliusKey}`
38
+ : `https://api.${isMainnet ? 'mainnet-beta' : 'devnet'}.solana.com`;
39
+
40
+ const walletKey = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY;
41
+ const collateralToken = process.env.COLLATERAL_TOKEN || 'USDC';
42
+
43
+ return {
44
+ // Network
45
+ network,
46
+ isMainnet,
47
+ rpcUrl,
48
+ heliusKey,
49
+ walletKey,
50
+
51
+ // Collateral
52
+ collateralMint: getCollateralMintAddress(network, collateralToken),
53
+ collateralToken,
54
+ preferConfidential: process.env.PREFER_CONFIDENTIAL_COLLATERAL === 'true',
55
+
56
+ // Market defaults
57
+ defaultLiquidity: BigInt(process.env.DEFAULT_LIQUIDITY || '1000000'),
58
+ defaultDurationDays: parseInt(process.env.DEFAULT_DURATION_DAYS || '30', 10),
59
+ proxyBaseUrl: process.env.PNP_PROXY_URL || 'https://api.pnp.exchange',
60
+
61
+ // Daemon settings
62
+ daemon: {
63
+ schedule: process.env.DAEMON_SCHEDULE || '1h',
64
+ marketsPerRound: parseInt(process.env.DAEMON_MARKETS_PER_ROUND || '1', 10),
65
+ storagePath: process.env.DAEMON_STORAGE_PATH || null
66
+ },
67
+
68
+ // News monitoring
69
+ news: {
70
+ enabled: process.env.NEWS_ENABLED === 'true',
71
+ checkInterval: parseInt(process.env.NEWS_CHECK_INTERVAL || '300000', 10)
72
+ },
73
+
74
+ // Webhook server
75
+ webhook: {
76
+ enabled: process.env.WEBHOOK_ENABLED === 'true',
77
+ port: parseInt(process.env.WEBHOOK_PORT || '3000', 10),
78
+ authToken: process.env.WEBHOOK_AUTH_TOKEN || null
79
+ },
80
+
81
+ // AI settings
82
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY || null,
83
+ ai: {
84
+ enabled: process.env.AI_ENABLED !== 'false',
85
+ minRelevanceScore: parseInt(process.env.AI_MIN_RELEVANCE_SCORE || '50', 10)
86
+ }
87
+ };
88
+ }
89
+
90
+ export function validateConfig(config) {
91
+ const errors = [];
92
+ const warnings = [];
93
+
94
+ if (!config.walletKey) {
95
+ errors.push('WALLET_PRIVATE_KEY or PRIVATE_KEY is required for creating markets');
96
+ }
97
+
98
+ if (!config.heliusKey) {
99
+ warnings.push('HELIUS_API_KEY not set - using public RPC (rate limited)');
100
+ }
101
+
102
+ if (config.webhook.enabled && !config.webhook.authToken) {
103
+ warnings.push('WEBHOOK_AUTH_TOKEN not set - webhook endpoints are unprotected');
104
+ }
105
+
106
+ if (config.ai.enabled && !config.anthropicApiKey) {
107
+ warnings.push('ANTHROPIC_API_KEY not set - AI features will be unavailable');
108
+ }
109
+
110
+ return {
111
+ valid: errors.length === 0,
112
+ errors,
113
+ warnings
114
+ };
115
+ }
116
+
117
+ // Get Helius websocket URL
118
+ export function getHeliusWsUrl(config) {
119
+ if (!config.heliusKey) return null;
120
+
121
+ const network = config.isMainnet ? 'mainnet' : 'devnet';
122
+ return `wss://${network}.helius-rpc.com/?api-key=${config.heliusKey}`;
123
+ }
124
+
125
+ // Get Helius API URL (for REST endpoints)
126
+ export function getHeliusApiUrl() {
127
+ return 'https://api.helius.xyz/v0';
128
+ }
@@ -0,0 +1,321 @@
1
+ // Main daemon orchestrator for autonomous market creation
2
+ // Coordinates agent, storage, scheduling, news monitoring, and webhooks
3
+
4
+ import { PrivacyOracleAgent } from '../agent.js';
5
+ import { Scheduler } from './scheduler.js';
6
+ import { setupGracefulShutdown, HealthMonitor } from './lifecycle.js';
7
+ import { agentEvents, AgentEvents } from '../events/emitter.js';
8
+ import { MarketStore } from '../storage/market-store.js';
9
+ import { NewsMonitor } from '../monitoring/news-monitor.js';
10
+ import { WebhookServer } from '../helius/webhooks.js';
11
+ import { getConfig } from '../config.js';
12
+
13
+ export class PrivacyOracleDaemon {
14
+ constructor(config = {}) {
15
+ this.config = {
16
+ schedule: config.schedule || '1h',
17
+ maxIterations: config.maxIterations || null,
18
+ marketsPerRound: config.marketsPerRound || 1,
19
+ dryRun: config.dryRun || false,
20
+ enableNewsMonitoring: config.enableNewsMonitoring || false,
21
+ enableWebhooks: config.enableWebhooks || false,
22
+ webhookPort: config.webhookPort || 3000,
23
+ storagePath: config.storagePath || null,
24
+ verbose: config.verbose || false,
25
+ ...config
26
+ };
27
+
28
+ this.agent = null;
29
+ this.scheduler = new Scheduler();
30
+ this.store = null;
31
+ this.newsMonitor = null;
32
+ this.webhookServer = null;
33
+ this.healthMonitor = null;
34
+ this.lifecycle = null;
35
+
36
+ this.isRunning = false;
37
+ this.iterationCount = 0;
38
+ this.startTime = null;
39
+ }
40
+
41
+ log(message, level = 'info') {
42
+ if (this.config.verbose || level === 'error') {
43
+ const prefix = level === 'error' ? '[ERROR]' : '[DAEMON]';
44
+ console.log(`${prefix} ${message}`);
45
+ }
46
+ }
47
+
48
+ async start() {
49
+ if (this.isRunning) {
50
+ throw new Error('Daemon is already running');
51
+ }
52
+
53
+ this.startTime = Date.now();
54
+ this.log('Starting Privacy Oracle Daemon...');
55
+
56
+ // 1. Initialize agent
57
+ const agentConfig = getConfig();
58
+ this.agent = new PrivacyOracleAgent({
59
+ config: agentConfig,
60
+ verbose: this.config.verbose
61
+ });
62
+ await this.agent.initialize();
63
+ this.log('Agent initialized');
64
+
65
+ // 2. Initialize storage
66
+ this.store = new MarketStore({ storagePath: this.config.storagePath });
67
+ await this.store.initialize();
68
+ this.log('Storage initialized');
69
+
70
+ // 3. Restore state if available
71
+ await this.restoreState();
72
+
73
+ // 4. Setup graceful shutdown
74
+ this.lifecycle = setupGracefulShutdown(this, { timeout: 30000 });
75
+
76
+ // 5. Start health monitoring
77
+ this.healthMonitor = new HealthMonitor({
78
+ checkInterval: 60000,
79
+ maxMemoryMB: 512,
80
+ onUnhealthy: (health) => {
81
+ this.log(`Health check failed: ${health.issues.join(', ')}`, 'error');
82
+ }
83
+ });
84
+ this.healthMonitor.start();
85
+
86
+ // 6. Optionally start news monitoring
87
+ if (this.config.enableNewsMonitoring) {
88
+ this.newsMonitor = new NewsMonitor();
89
+ await this.newsMonitor.start();
90
+ this.log('News monitoring started');
91
+ }
92
+
93
+ // 7. Optionally start webhook server
94
+ if (this.config.enableWebhooks) {
95
+ this.webhookServer = new WebhookServer({
96
+ port: this.config.webhookPort,
97
+ authToken: process.env.WEBHOOK_AUTH_TOKEN
98
+ });
99
+
100
+ // Register handlers
101
+ this.webhookServer.on('getStats', () => this.store.getStats());
102
+ this.webhookServer.on('getMarkets', (opts) => this.store.getAllMarkets(opts));
103
+
104
+ await this.webhookServer.start();
105
+ this.log(`Webhook server started on port ${this.config.webhookPort}`);
106
+ }
107
+
108
+ // 8. Schedule main market creation task
109
+ this.scheduler.addTask({
110
+ name: 'market-creation',
111
+ schedule: this.config.schedule,
112
+ task: () => this.executeCycle(),
113
+ runImmediately: true
114
+ });
115
+
116
+ this.scheduler.start();
117
+ this.isRunning = true;
118
+
119
+ agentEvents.emitTyped(AgentEvents.DAEMON_STARTED, {
120
+ config: this.config,
121
+ startTime: this.startTime
122
+ });
123
+
124
+ this.log('Daemon started successfully');
125
+ return this;
126
+ }
127
+
128
+ async executeCycle() {
129
+ // Check iteration limit
130
+ if (this.config.maxIterations && this.iterationCount >= this.config.maxIterations) {
131
+ this.log(`Reached max iterations (${this.config.maxIterations}), stopping...`);
132
+ await this.stop();
133
+ return;
134
+ }
135
+
136
+ this.iterationCount++;
137
+ const cycleStart = Date.now();
138
+
139
+ agentEvents.emitTyped(AgentEvents.CYCLE_START, {
140
+ iteration: this.iterationCount,
141
+ timestamp: cycleStart
142
+ });
143
+
144
+ this.log(`Starting cycle ${this.iterationCount}...`);
145
+
146
+ try {
147
+ // Get news context if available
148
+ let newsContext = null;
149
+ if (this.newsMonitor) {
150
+ newsContext = this.newsMonitor.getRecentEvents(5);
151
+ if (newsContext.length > 0) {
152
+ this.log(`Found ${newsContext.length} relevant news events`);
153
+ }
154
+ }
155
+
156
+ // Generate and create markets
157
+ const results = [];
158
+
159
+ if (this.config.dryRun) {
160
+ // Dry run - just generate ideas
161
+ const { generateMultipleMarkets } = await import('../privacy-markets.js');
162
+ const ideas = generateMultipleMarkets(this.config.marketsPerRound);
163
+
164
+ for (const idea of ideas) {
165
+ this.log(`[DRY RUN] Would create: ${idea.question}`);
166
+ results.push({
167
+ success: true,
168
+ dryRun: true,
169
+ question: idea.question,
170
+ category: idea.category
171
+ });
172
+ }
173
+ } else {
174
+ // Actually create markets
175
+ const batchResults = await this.agent.createBatchMarkets(this.config.marketsPerRound);
176
+
177
+ for (const result of batchResults) {
178
+ if (result.success) {
179
+ // Store in database
180
+ await this.store.saveMarket({
181
+ address: result.market,
182
+ question: result.question,
183
+ category: result.category,
184
+ categoryKey: result.categoryKey,
185
+ creationTime: Date.now(),
186
+ creationSignature: result.signature,
187
+ initialLiquidity: result.liquidity,
188
+ durationDays: result.durationDays,
189
+ status: 'active',
190
+ metadata: { newsContext, iteration: this.iterationCount }
191
+ });
192
+
193
+ agentEvents.emitTyped(AgentEvents.MARKET_CREATED, result);
194
+ this.log(`Created market: ${result.market}`);
195
+ } else {
196
+ agentEvents.emitTyped(AgentEvents.MARKET_FAILED, result);
197
+ this.log(`Failed to create market: ${result.error}`, 'error');
198
+ }
199
+
200
+ results.push(result);
201
+ }
202
+ }
203
+
204
+ // Save state after each cycle
205
+ await this.saveState();
206
+
207
+ const cycleDuration = Date.now() - cycleStart;
208
+
209
+ agentEvents.emitTyped(AgentEvents.CYCLE_COMPLETE, {
210
+ iteration: this.iterationCount,
211
+ results,
212
+ duration: cycleDuration,
213
+ successCount: results.filter(r => r.success).length,
214
+ failCount: results.filter(r => !r.success).length
215
+ });
216
+
217
+ this.log(`Cycle ${this.iterationCount} complete (${cycleDuration}ms)`);
218
+
219
+ } catch (error) {
220
+ agentEvents.emitTyped(AgentEvents.CYCLE_ERROR, {
221
+ iteration: this.iterationCount,
222
+ error: error.message
223
+ });
224
+
225
+ this.log(`Cycle ${this.iterationCount} failed: ${error.message}`, 'error');
226
+ }
227
+ }
228
+
229
+ async stop() {
230
+ if (!this.isRunning) return;
231
+
232
+ this.log('Stopping daemon...');
233
+
234
+ // Stop scheduler
235
+ this.scheduler.stop();
236
+
237
+ // Stop news monitoring
238
+ if (this.newsMonitor) {
239
+ await this.newsMonitor.stop();
240
+ }
241
+
242
+ // Stop webhook server
243
+ if (this.webhookServer) {
244
+ await this.webhookServer.stop();
245
+ }
246
+
247
+ // Stop health monitor
248
+ if (this.healthMonitor) {
249
+ this.healthMonitor.stop();
250
+ }
251
+
252
+ // Save final state
253
+ await this.saveState();
254
+
255
+ // Close storage
256
+ if (this.store) {
257
+ await this.store.close();
258
+ }
259
+
260
+ this.isRunning = false;
261
+
262
+ agentEvents.emitTyped(AgentEvents.DAEMON_STOPPED, {
263
+ totalIterations: this.iterationCount,
264
+ uptime: Date.now() - this.startTime
265
+ });
266
+
267
+ this.log('Daemon stopped');
268
+ }
269
+
270
+ async saveState() {
271
+ if (!this.store) return;
272
+
273
+ const state = {
274
+ iterationCount: this.iterationCount,
275
+ lastRunTime: Date.now(),
276
+ config: {
277
+ schedule: this.config.schedule,
278
+ marketsPerRound: this.config.marketsPerRound
279
+ }
280
+ };
281
+
282
+ await this.store.saveState('daemon', state);
283
+ agentEvents.emitTyped(AgentEvents.STATE_SAVED, state);
284
+ }
285
+
286
+ async restoreState() {
287
+ if (!this.store) return null;
288
+
289
+ try {
290
+ const state = await this.store.getState('daemon');
291
+
292
+ if (state) {
293
+ this.iterationCount = state.iterationCount || 0;
294
+ this.log(`Restored state: ${this.iterationCount} previous iterations`);
295
+
296
+ agentEvents.emitTyped(AgentEvents.STATE_RESTORED, state);
297
+ return state;
298
+ }
299
+ } catch (error) {
300
+ this.log('No previous state found, starting fresh');
301
+ }
302
+
303
+ return null;
304
+ }
305
+
306
+ getStatus() {
307
+ return {
308
+ isRunning: this.isRunning,
309
+ iterationCount: this.iterationCount,
310
+ startTime: this.startTime,
311
+ uptime: this.startTime ? Date.now() - this.startTime : 0,
312
+ config: this.config,
313
+ scheduler: this.scheduler.getAllTasks(),
314
+ health: this.healthMonitor?.getHealth() || null
315
+ };
316
+ }
317
+ }
318
+
319
+ export function createDaemon(config) {
320
+ return new PrivacyOracleDaemon(config);
321
+ }