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
package/src/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// PNPFUCIUS - The PNP Exchange SDK
|
|
2
|
+
// Create, trade, and settle prediction markets on Solana
|
|
3
|
+
// Uses Helius RPC + PNP SDK
|
|
4
|
+
|
|
5
|
+
// Core agent
|
|
6
|
+
export { PrivacyOracleAgent, createAgent } from './agent.js';
|
|
7
|
+
export { getConfig, validateConfig, getHeliusWsUrl, getHeliusApiUrl } from './config.js';
|
|
8
|
+
export {
|
|
9
|
+
generatePrivacyMarket,
|
|
10
|
+
generateMultipleMarkets,
|
|
11
|
+
getMarketsByCategory,
|
|
12
|
+
listCategories,
|
|
13
|
+
PRIVACY_CATEGORIES
|
|
14
|
+
} from './privacy-markets.js';
|
|
15
|
+
|
|
16
|
+
// Helius integration
|
|
17
|
+
export { HeliusClient, createHeliusClient } from './helius/client.js';
|
|
18
|
+
export { TransactionTracker, createTransactionTracker } from './helius/transaction-tracker.js';
|
|
19
|
+
export { WebhookServer, createWebhookServer } from './helius/webhooks.js';
|
|
20
|
+
|
|
21
|
+
// Daemon mode
|
|
22
|
+
export { PrivacyOracleDaemon, createDaemon } from './daemon/index.js';
|
|
23
|
+
export { Scheduler, createScheduler } from './daemon/scheduler.js';
|
|
24
|
+
export { setupGracefulShutdown, HealthMonitor } from './daemon/lifecycle.js';
|
|
25
|
+
|
|
26
|
+
// Storage
|
|
27
|
+
export { MarketStore, createMarketStore } from './storage/market-store.js';
|
|
28
|
+
|
|
29
|
+
// Events
|
|
30
|
+
export { agentEvents, AgentEvents } from './events/emitter.js';
|
|
31
|
+
|
|
32
|
+
// Monitoring
|
|
33
|
+
export { NewsMonitor, createNewsMonitor, DEFAULT_NEWS_SOURCES } from './monitoring/news-monitor.js';
|
|
34
|
+
export { scoreRelevance, generateMarketFromNews, PRIVACY_KEYWORDS } from './monitoring/news-scorer.js';
|
|
35
|
+
|
|
36
|
+
// Analytics
|
|
37
|
+
export { DashboardAggregator, createAggregator, formatNumber, formatDuration } from './analytics/aggregator.js';
|
|
38
|
+
|
|
39
|
+
// Privacy tokens
|
|
40
|
+
export {
|
|
41
|
+
TOKENS,
|
|
42
|
+
PRIVACY_TOKEN_INFO,
|
|
43
|
+
getCollateralMint,
|
|
44
|
+
listSupportedTokens,
|
|
45
|
+
checkConfidentialTransferSupport,
|
|
46
|
+
getTokenInfo,
|
|
47
|
+
formatTokenAmount,
|
|
48
|
+
parseTokenAmount,
|
|
49
|
+
getCollateralConfig
|
|
50
|
+
} from './collateral/privacy-tokens.js';
|
|
51
|
+
|
|
52
|
+
// CLI utilities
|
|
53
|
+
export {
|
|
54
|
+
createSpinner,
|
|
55
|
+
withSpinner,
|
|
56
|
+
withProgress,
|
|
57
|
+
createIndeterminateProgress,
|
|
58
|
+
StepProgress,
|
|
59
|
+
statusLine,
|
|
60
|
+
successLine,
|
|
61
|
+
errorLine,
|
|
62
|
+
infoLine
|
|
63
|
+
} from './utils/spinner.js';
|
|
64
|
+
|
|
65
|
+
// PNPFUCIUS - Interactive CLI Agent
|
|
66
|
+
export { PnpfuciusAgent, createPnpfuciusAgent } from './predict/agent.js';
|
|
67
|
+
export { tools as pnpTools, executeTool } from './predict/tools/index.js';
|
|
68
|
+
|
|
69
|
+
// Quick start function for programmatic use
|
|
70
|
+
export async function quickCreate(question, options = {}) {
|
|
71
|
+
const { createAgent } = await import('./agent.js');
|
|
72
|
+
const agent = await createAgent({ verbose: options.verbose });
|
|
73
|
+
|
|
74
|
+
if (question) {
|
|
75
|
+
return agent.createMarket({ question, ...options });
|
|
76
|
+
} else {
|
|
77
|
+
return agent.createPrivacyMarket(options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Run agent as daemon (recommended over runAutonomous)
|
|
82
|
+
export async function startDaemon(config = {}) {
|
|
83
|
+
const { PrivacyOracleDaemon } = await import('./daemon/index.js');
|
|
84
|
+
const { getConfig } = await import('./config.js');
|
|
85
|
+
|
|
86
|
+
const baseConfig = getConfig();
|
|
87
|
+
const mergedConfig = {
|
|
88
|
+
...baseConfig,
|
|
89
|
+
...config,
|
|
90
|
+
daemon: {
|
|
91
|
+
...baseConfig.daemon,
|
|
92
|
+
...config.daemon
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const daemon = new PrivacyOracleDaemon(mergedConfig);
|
|
97
|
+
await daemon.start();
|
|
98
|
+
|
|
99
|
+
return daemon;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Legacy autonomous mode (use startDaemon instead)
|
|
103
|
+
export async function runAutonomous(config = {}) {
|
|
104
|
+
console.warn('runAutonomous is deprecated. Use startDaemon for better functionality.');
|
|
105
|
+
|
|
106
|
+
const { createAgent } = await import('./agent.js');
|
|
107
|
+
const { generateMultipleMarkets } = await import('./privacy-markets.js');
|
|
108
|
+
|
|
109
|
+
const count = config.count || 1;
|
|
110
|
+
const interval = config.intervalMs || 3600000;
|
|
111
|
+
|
|
112
|
+
const agent = await createAgent({ verbose: true });
|
|
113
|
+
|
|
114
|
+
console.log(`Starting autonomous mode: ${count} markets every ${interval / 60000} minutes`);
|
|
115
|
+
|
|
116
|
+
const createRound = async () => {
|
|
117
|
+
const ideas = generateMultipleMarkets(count);
|
|
118
|
+
|
|
119
|
+
for (const idea of ideas) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await agent.createMarket({
|
|
122
|
+
question: idea.question,
|
|
123
|
+
durationDays: idea.durationDays,
|
|
124
|
+
liquidity: idea.suggestedLiquidity
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log(`Created: ${result.market}`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`Failed: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await createRound();
|
|
135
|
+
|
|
136
|
+
if (config.continuous) {
|
|
137
|
+
setInterval(createRound, interval);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// RSS feed parsing for privacy-related news monitoring
|
|
2
|
+
// Provides context for timely market generation
|
|
3
|
+
|
|
4
|
+
import Parser from 'rss-parser';
|
|
5
|
+
import { agentEvents, AgentEvents } from '../events/emitter.js';
|
|
6
|
+
import { scoreRelevance, PRIVACY_KEYWORDS } from './news-scorer.js';
|
|
7
|
+
|
|
8
|
+
// Default privacy news sources
|
|
9
|
+
const DEFAULT_SOURCES = [
|
|
10
|
+
{
|
|
11
|
+
name: 'EFF',
|
|
12
|
+
url: 'https://www.eff.org/rss/updates.xml',
|
|
13
|
+
keywords: ['privacy', 'encryption', 'surveillance', 'data protection', 'FISA'],
|
|
14
|
+
weight: 1.0
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Decrypt',
|
|
18
|
+
url: 'https://decrypt.co/feed',
|
|
19
|
+
keywords: ['privacy', 'zk', 'zero-knowledge', 'tornado', 'zcash', 'monero', 'mixer'],
|
|
20
|
+
weight: 0.9
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'CoinDesk',
|
|
24
|
+
url: 'https://www.coindesk.com/arc/outboundfeeds/rss/',
|
|
25
|
+
keywords: ['privacy', 'regulation', 'sanctions', 'compliance', 'OFAC'],
|
|
26
|
+
weight: 0.7
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'The Block',
|
|
30
|
+
url: 'https://www.theblock.co/rss.xml',
|
|
31
|
+
keywords: ['privacy', 'zk-rollup', 'confidential', 'anonymous'],
|
|
32
|
+
weight: 0.8
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export class NewsMonitor {
|
|
37
|
+
constructor(config = {}) {
|
|
38
|
+
this.sources = config.sources || DEFAULT_SOURCES;
|
|
39
|
+
this.parser = new Parser({
|
|
40
|
+
timeout: 10000,
|
|
41
|
+
headers: {
|
|
42
|
+
'User-Agent': 'PrivacyOracleAgent/1.0'
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.checkInterval = config.checkInterval || 300000; // 5 minutes
|
|
46
|
+
this.recentEvents = [];
|
|
47
|
+
this.maxEvents = config.maxEvents || 100;
|
|
48
|
+
this.seenIds = new Set();
|
|
49
|
+
this.timer = null;
|
|
50
|
+
this.isRunning = false;
|
|
51
|
+
this.lastCheck = null;
|
|
52
|
+
this.errorCount = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async start() {
|
|
56
|
+
if (this.isRunning) return this;
|
|
57
|
+
|
|
58
|
+
this.isRunning = true;
|
|
59
|
+
|
|
60
|
+
// Initial check
|
|
61
|
+
await this.checkFeeds();
|
|
62
|
+
|
|
63
|
+
// Schedule periodic checks
|
|
64
|
+
this.timer = setInterval(() => this.checkFeeds(), this.checkInterval);
|
|
65
|
+
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async stop() {
|
|
70
|
+
this.isRunning = false;
|
|
71
|
+
|
|
72
|
+
if (this.timer) {
|
|
73
|
+
clearInterval(this.timer);
|
|
74
|
+
this.timer = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async checkFeeds() {
|
|
81
|
+
if (!this.isRunning) return;
|
|
82
|
+
|
|
83
|
+
this.lastCheck = Date.now();
|
|
84
|
+
const newEvents = [];
|
|
85
|
+
|
|
86
|
+
for (const source of this.sources) {
|
|
87
|
+
try {
|
|
88
|
+
const feed = await this.parser.parseURL(source.url);
|
|
89
|
+
|
|
90
|
+
for (const item of feed.items || []) {
|
|
91
|
+
const id = item.guid || item.link || item.title;
|
|
92
|
+
|
|
93
|
+
if (this.seenIds.has(id)) continue;
|
|
94
|
+
|
|
95
|
+
const event = this.processItem(item, source);
|
|
96
|
+
|
|
97
|
+
// Only keep events with relevance score >= 30
|
|
98
|
+
if (event.relevanceScore >= 30) {
|
|
99
|
+
this.addEvent(event);
|
|
100
|
+
newEvents.push(event);
|
|
101
|
+
|
|
102
|
+
agentEvents.emitTyped(AgentEvents.NEWS_EVENT, event);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.seenIds.add(id);
|
|
106
|
+
|
|
107
|
+
// Limit seen IDs to prevent memory growth
|
|
108
|
+
if (this.seenIds.size > 10000) {
|
|
109
|
+
const idsArray = Array.from(this.seenIds);
|
|
110
|
+
this.seenIds = new Set(idsArray.slice(-5000));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.errorCount++;
|
|
115
|
+
console.error(`Failed to fetch ${source.name}:`, error.message);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
agentEvents.emitTyped(AgentEvents.NEWS_CHECK_COMPLETE, {
|
|
120
|
+
sourcesChecked: this.sources.length,
|
|
121
|
+
newEventsFound: newEvents.length,
|
|
122
|
+
totalEvents: this.recentEvents.length,
|
|
123
|
+
errorCount: this.errorCount
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return newEvents;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
processItem(item, source) {
|
|
130
|
+
const title = item.title || '';
|
|
131
|
+
const content = item.contentSnippet || item.content || '';
|
|
132
|
+
const text = `${title} ${content}`.toLowerCase();
|
|
133
|
+
|
|
134
|
+
const { score, matchedKeywords, suggestedCategory, urgency } = scoreRelevance(
|
|
135
|
+
text,
|
|
136
|
+
source.keywords,
|
|
137
|
+
source.weight
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id: item.guid || item.link || `${source.name}-${Date.now()}`,
|
|
142
|
+
title: title,
|
|
143
|
+
link: item.link || '',
|
|
144
|
+
source: source.name,
|
|
145
|
+
publishedAt: item.pubDate ? new Date(item.pubDate).getTime() : Date.now(),
|
|
146
|
+
relevanceScore: score,
|
|
147
|
+
matchedKeywords,
|
|
148
|
+
suggestedCategory,
|
|
149
|
+
urgency,
|
|
150
|
+
snippet: content.slice(0, 200)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
addEvent(event) {
|
|
155
|
+
this.recentEvents.unshift(event);
|
|
156
|
+
|
|
157
|
+
if (this.recentEvents.length > this.maxEvents) {
|
|
158
|
+
this.recentEvents = this.recentEvents.slice(0, this.maxEvents);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getRecentEvents(limit = 10) {
|
|
163
|
+
return this.recentEvents.slice(0, limit);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getEventsByCategory(category, limit = 10) {
|
|
167
|
+
return this.recentEvents
|
|
168
|
+
.filter(e => e.suggestedCategory === category)
|
|
169
|
+
.slice(0, limit);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getHighUrgencyEvents(limit = 5) {
|
|
173
|
+
return this.recentEvents
|
|
174
|
+
.filter(e => e.urgency === 'breaking' || e.urgency === 'timely')
|
|
175
|
+
.slice(0, limit);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getStatus() {
|
|
179
|
+
return {
|
|
180
|
+
isRunning: this.isRunning,
|
|
181
|
+
sourcesCount: this.sources.length,
|
|
182
|
+
eventsCount: this.recentEvents.length,
|
|
183
|
+
lastCheck: this.lastCheck,
|
|
184
|
+
errorCount: this.errorCount,
|
|
185
|
+
seenIdsCount: this.seenIds.size
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add a custom news source
|
|
190
|
+
addSource(source) {
|
|
191
|
+
if (!source.name || !source.url) {
|
|
192
|
+
throw new Error('Source requires name and url');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.sources.push({
|
|
196
|
+
keywords: PRIVACY_KEYWORDS,
|
|
197
|
+
weight: 0.5,
|
|
198
|
+
...source
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Remove a news source
|
|
205
|
+
removeSource(name) {
|
|
206
|
+
this.sources = this.sources.filter(s => s.name !== name);
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createNewsMonitor(config) {
|
|
212
|
+
return new NewsMonitor(config);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Mock news source for testing
|
|
216
|
+
export class MockNewsSource {
|
|
217
|
+
constructor() {
|
|
218
|
+
this.events = [
|
|
219
|
+
{
|
|
220
|
+
title: 'EU Proposes New Digital Privacy Framework with Strict Encryption Rules',
|
|
221
|
+
relevanceScore: 85,
|
|
222
|
+
suggestedCategory: 'regulation',
|
|
223
|
+
urgency: 'timely'
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
title: 'Major ZK Protocol Reaches $1B TVL Milestone',
|
|
227
|
+
relevanceScore: 90,
|
|
228
|
+
suggestedCategory: 'technology',
|
|
229
|
+
urgency: 'breaking'
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
title: 'Signal Reports Record User Growth Amid Privacy Concerns',
|
|
233
|
+
relevanceScore: 75,
|
|
234
|
+
suggestedCategory: 'adoption',
|
|
235
|
+
urgency: 'timely'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
title: 'Data Breach Affects 50M Users at Major Tech Company',
|
|
239
|
+
relevanceScore: 80,
|
|
240
|
+
suggestedCategory: 'events',
|
|
241
|
+
urgency: 'breaking'
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
title: 'Tornado Cash Developer Case Reaches New Development',
|
|
245
|
+
relevanceScore: 95,
|
|
246
|
+
suggestedCategory: 'regulation',
|
|
247
|
+
urgency: 'breaking'
|
|
248
|
+
}
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getEvents(limit = 5) {
|
|
253
|
+
return this.events.slice(0, limit).map(e => ({
|
|
254
|
+
...e,
|
|
255
|
+
id: `mock-${Math.random().toString(36).slice(2)}`,
|
|
256
|
+
link: 'https://example.com',
|
|
257
|
+
source: 'mock',
|
|
258
|
+
publishedAt: Date.now(),
|
|
259
|
+
matchedKeywords: ['privacy']
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Relevance scoring algorithm for privacy news
|
|
2
|
+
// Determines how relevant a news item is for market generation
|
|
3
|
+
|
|
4
|
+
// Core privacy-related keywords with weights
|
|
5
|
+
export const PRIVACY_KEYWORDS = {
|
|
6
|
+
// High relevance - direct privacy tech
|
|
7
|
+
'zero-knowledge': 3,
|
|
8
|
+
'zk-proof': 3,
|
|
9
|
+
'zk-snark': 3,
|
|
10
|
+
'zk-stark': 3,
|
|
11
|
+
'zkrollup': 3,
|
|
12
|
+
'confidential': 2.5,
|
|
13
|
+
'encryption': 2.5,
|
|
14
|
+
'encrypted': 2.5,
|
|
15
|
+
'privacy-preserving': 3,
|
|
16
|
+
'private transaction': 3,
|
|
17
|
+
|
|
18
|
+
// Privacy protocols
|
|
19
|
+
'tornado cash': 3,
|
|
20
|
+
'zcash': 2.5,
|
|
21
|
+
'monero': 2.5,
|
|
22
|
+
'light protocol': 3,
|
|
23
|
+
'elusiv': 3,
|
|
24
|
+
'aztec': 2.5,
|
|
25
|
+
'railgun': 2.5,
|
|
26
|
+
'secret network': 2.5,
|
|
27
|
+
|
|
28
|
+
// Regulatory
|
|
29
|
+
'gdpr': 2,
|
|
30
|
+
'privacy law': 2.5,
|
|
31
|
+
'data protection': 2,
|
|
32
|
+
'surveillance': 2.5,
|
|
33
|
+
'sanctions': 2,
|
|
34
|
+
'ofac': 2.5,
|
|
35
|
+
'compliance': 1.5,
|
|
36
|
+
'kyc': 1.5,
|
|
37
|
+
'aml': 1.5,
|
|
38
|
+
|
|
39
|
+
// General privacy
|
|
40
|
+
'privacy': 1.5,
|
|
41
|
+
'anonymous': 2,
|
|
42
|
+
'anonymity': 2,
|
|
43
|
+
'pseudonymous': 1.5,
|
|
44
|
+
'private': 1,
|
|
45
|
+
'mixer': 2,
|
|
46
|
+
'mixing': 2,
|
|
47
|
+
'shielded': 2.5,
|
|
48
|
+
|
|
49
|
+
// Tech terms
|
|
50
|
+
'homomorphic': 3,
|
|
51
|
+
'mpc': 2.5,
|
|
52
|
+
'secure enclave': 2,
|
|
53
|
+
'tee': 2,
|
|
54
|
+
'trusted execution': 2,
|
|
55
|
+
|
|
56
|
+
// Events
|
|
57
|
+
'data breach': 2.5,
|
|
58
|
+
'leak': 1.5,
|
|
59
|
+
'hack': 1.5,
|
|
60
|
+
'compromised': 1.5,
|
|
61
|
+
|
|
62
|
+
// Solana specific
|
|
63
|
+
'solana privacy': 3,
|
|
64
|
+
'spl confidential': 3,
|
|
65
|
+
'token-2022 confidential': 3
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Category mappings
|
|
69
|
+
const CATEGORY_KEYWORDS = {
|
|
70
|
+
regulation: ['law', 'regulation', 'gdpr', 'sanctions', 'ofac', 'compliance', 'legislation', 'ban', 'restrict'],
|
|
71
|
+
technology: ['zk', 'protocol', 'launch', 'release', 'upgrade', 'mainnet', 'testnet', 'tvl', 'smart contract'],
|
|
72
|
+
adoption: ['users', 'growth', 'adoption', 'enterprise', 'mainstream', 'wallet', 'integration'],
|
|
73
|
+
events: ['breach', 'hack', 'leak', 'scandal', 'arrest', 'raid', 'lawsuit', 'verdict']
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Urgency indicators
|
|
77
|
+
const URGENCY_KEYWORDS = {
|
|
78
|
+
breaking: ['breaking', 'just in', 'urgent', 'alert', 'confirmed', 'arrested', 'breached'],
|
|
79
|
+
timely: ['announces', 'launches', 'releases', 'proposes', 'reaches', 'exceeds', 'surpasses']
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function scoreRelevance(text, sourceKeywords = [], sourceWeight = 1.0) {
|
|
83
|
+
const lowerText = text.toLowerCase();
|
|
84
|
+
let score = 0;
|
|
85
|
+
const matchedKeywords = [];
|
|
86
|
+
|
|
87
|
+
// Score based on privacy keywords
|
|
88
|
+
for (const [keyword, weight] of Object.entries(PRIVACY_KEYWORDS)) {
|
|
89
|
+
if (lowerText.includes(keyword)) {
|
|
90
|
+
score += weight * 10;
|
|
91
|
+
matchedKeywords.push(keyword);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Additional score from source-specific keywords
|
|
96
|
+
for (const keyword of sourceKeywords) {
|
|
97
|
+
if (lowerText.includes(keyword.toLowerCase())) {
|
|
98
|
+
score += 5;
|
|
99
|
+
if (!matchedKeywords.includes(keyword)) {
|
|
100
|
+
matchedKeywords.push(keyword);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply source weight
|
|
106
|
+
score = Math.round(score * sourceWeight);
|
|
107
|
+
|
|
108
|
+
// Cap at 100
|
|
109
|
+
score = Math.min(score, 100);
|
|
110
|
+
|
|
111
|
+
// Determine suggested category
|
|
112
|
+
const suggestedCategory = determineCategory(lowerText);
|
|
113
|
+
|
|
114
|
+
// Determine urgency
|
|
115
|
+
const urgency = determineUrgency(lowerText);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
score,
|
|
119
|
+
matchedKeywords,
|
|
120
|
+
suggestedCategory,
|
|
121
|
+
urgency
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function determineCategory(text) {
|
|
126
|
+
const scores = {
|
|
127
|
+
regulation: 0,
|
|
128
|
+
technology: 0,
|
|
129
|
+
adoption: 0,
|
|
130
|
+
events: 0
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
134
|
+
for (const keyword of keywords) {
|
|
135
|
+
if (text.includes(keyword)) {
|
|
136
|
+
scores[category] += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Find highest scoring category
|
|
142
|
+
let maxScore = 0;
|
|
143
|
+
let bestCategory = 'technology'; // default
|
|
144
|
+
|
|
145
|
+
for (const [category, score] of Object.entries(scores)) {
|
|
146
|
+
if (score > maxScore) {
|
|
147
|
+
maxScore = score;
|
|
148
|
+
bestCategory = category;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return bestCategory;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function determineUrgency(text) {
|
|
156
|
+
for (const keyword of URGENCY_KEYWORDS.breaking) {
|
|
157
|
+
if (text.includes(keyword)) {
|
|
158
|
+
return 'breaking';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const keyword of URGENCY_KEYWORDS.timely) {
|
|
163
|
+
if (text.includes(keyword)) {
|
|
164
|
+
return 'timely';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return 'evergreen';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Generate a market question from a news event
|
|
172
|
+
export function generateMarketFromNews(newsEvent) {
|
|
173
|
+
const { title, suggestedCategory, urgency } = newsEvent;
|
|
174
|
+
|
|
175
|
+
// Extract key entities from title
|
|
176
|
+
const entities = extractEntities(title);
|
|
177
|
+
|
|
178
|
+
// Generate appropriate duration based on urgency
|
|
179
|
+
const durationDays = urgency === 'breaking' ? 14 :
|
|
180
|
+
urgency === 'timely' ? 30 : 90;
|
|
181
|
+
|
|
182
|
+
// Generate question based on category
|
|
183
|
+
let question;
|
|
184
|
+
|
|
185
|
+
switch (suggestedCategory) {
|
|
186
|
+
case 'regulation':
|
|
187
|
+
question = `Will regulatory action be taken regarding "${entities.topic || 'this development'}" within ${durationDays} days?`;
|
|
188
|
+
break;
|
|
189
|
+
case 'technology':
|
|
190
|
+
question = `Will the technology mentioned in "${entities.topic || 'this news'}" see significant adoption by ${formatFutureDate(durationDays)}?`;
|
|
191
|
+
break;
|
|
192
|
+
case 'adoption':
|
|
193
|
+
question = `Will user adoption metrics exceed expectations for "${entities.topic || 'this platform'}" by ${formatFutureDate(durationDays)}?`;
|
|
194
|
+
break;
|
|
195
|
+
case 'events':
|
|
196
|
+
question = `Will there be follow-up developments on "${entities.topic || 'this event'}" within ${durationDays} days?`;
|
|
197
|
+
break;
|
|
198
|
+
default:
|
|
199
|
+
question = `Will "${entities.topic || 'this development'}" have significant impact by ${formatFutureDate(durationDays)}?`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
question,
|
|
204
|
+
category: suggestedCategory,
|
|
205
|
+
durationDays,
|
|
206
|
+
urgency,
|
|
207
|
+
sourceEvent: newsEvent
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function extractEntities(title) {
|
|
212
|
+
// Simple entity extraction - could be enhanced with NLP
|
|
213
|
+
const words = title.split(' ');
|
|
214
|
+
|
|
215
|
+
// Look for capitalized words as potential entities
|
|
216
|
+
const entities = words.filter(w =>
|
|
217
|
+
w.length > 2 &&
|
|
218
|
+
w[0] === w[0].toUpperCase() &&
|
|
219
|
+
!['The', 'And', 'For', 'New', 'With'].includes(w)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
topic: title.slice(0, 50),
|
|
224
|
+
entities
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function formatFutureDate(daysFromNow) {
|
|
229
|
+
const date = new Date();
|
|
230
|
+
date.setDate(date.getDate() + daysFromNow);
|
|
231
|
+
|
|
232
|
+
const months = ['January', 'February', 'March', 'April', 'May', 'June',
|
|
233
|
+
'July', 'August', 'September', 'October', 'November', 'December'];
|
|
234
|
+
|
|
235
|
+
return `${months[date.getMonth()]} ${date.getFullYear()}`;
|
|
236
|
+
}
|