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/README.md +396 -0
- package/bin/claude-predict.js +5 -0
- package/bin/pnpfucius.js +8 -0
- package/package.json +71 -0
- package/src/agent.js +1037 -0
- package/src/ai/index.js +6 -0
- package/src/ai/market-generator.js +186 -0
- package/src/ai/resolver.js +172 -0
- package/src/ai/scorer.js +184 -0
- package/src/analytics/aggregator.js +198 -0
- package/src/cli.js +948 -0
- package/src/collateral/privacy-tokens.js +183 -0
- package/src/config.js +128 -0
- package/src/daemon/index.js +321 -0
- package/src/daemon/lifecycle.js +168 -0
- package/src/daemon/scheduler.js +252 -0
- package/src/events/emitter.js +147 -0
- package/src/helius/client.js +221 -0
- package/src/helius/transaction-tracker.js +192 -0
- package/src/helius/webhooks.js +233 -0
- package/src/index.js +139 -0
- package/src/monitoring/news-monitor.js +262 -0
- package/src/monitoring/news-scorer.js +236 -0
- package/src/predict/agent.js +291 -0
- package/src/predict/prompts.js +69 -0
- package/src/predict/slash-commands.js +361 -0
- package/src/predict/tools/analytics-tools.js +83 -0
- package/src/predict/tools/bash-tool.js +87 -0
- package/src/predict/tools/file-tools.js +140 -0
- package/src/predict/tools/index.js +120 -0
- package/src/predict/tools/market-tools.js +851 -0
- package/src/predict/tools/news-tools.js +130 -0
- package/src/predict/ui/renderer.js +215 -0
- package/src/predict/ui/welcome.js +146 -0
- package/src/privacy-markets.js +194 -0
- package/src/storage/market-store.js +418 -0
- package/src/utils/spinner.js +172 -0
|
@@ -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
|
+
}
|