-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
226 lines (190 loc) · 7.25 KB
/
Copy pathindex.js
File metadata and controls
226 lines (190 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
'use strict';
require('dotenv').config();
const fs = require('node:fs');
const cron = require('node-cron');
/**
* @fileoverview Automated BTC market creation bot for Worm.wtf
* @description Creates prediction markets every 15 minutes with live BTC prices
*/
/** @constant {Object} Application configuration */
const CONFIG = Object.freeze({
BINANCE_API_URL: 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
WORM_API_URL: 'https://api.worm.wtf/api/market/group/',
AUTH_TOKEN: process.env.WORM_AUTH_TOKEN,
CRON_SCHEDULE: '*/15 * * * *',
BODY_TEMPLATE_PATH: './body.json',
LOGO_PATH: './assets/btc-logo.base64',
REQUEST_TIMEOUT_MS: 10000
});
/** @constant {Object} Placeholder patterns for template replacement */
const PLACEHOLDERS = Object.freeze({
TIME: '{{time}}',
DATE: '{{date}}',
PRICE: '{{price}}'
});
/**
* Formats a numeric price into USD currency string
* @param {number} price - Raw price value
* @returns {string} Formatted price (e.g., "$95,432")
*/
function formatPrice(price) {
return `$${Math.round(price).toLocaleString('en-US')}`;
}
/**
* Calculates the next quarter-hour timestamp
* @returns {{ time: string, date: string }} UTC time and date strings
*/
function getNextQuarterTime() {
const nextQuarter = new Date();
const minutesToAdd = 15 - (nextQuarter.getUTCMinutes() % 15);
nextQuarter.setUTCMinutes(nextQuarter.getUTCMinutes() + minutesToAdd, 0, 0);
return {
time: nextQuarter.toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit',
timeZone: 'UTC'
}),
date: nextQuarter.toLocaleDateString('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC'
})
};
}
/**
* Replaces template placeholders with actual values
* @param {string} text - Template text with {{placeholders}}
* @param {{ time: string, date: string, price: string }} values - Replacement values
* @returns {string} Processed text
*/
function applyTemplateValues(text, { time, date, price }) {
return text
.replaceAll(PLACEHOLDERS.TIME, time)
.replaceAll(PLACEHOLDERS.DATE, date)
.replaceAll(PLACEHOLDERS.PRICE, price);
}
/**
* Updates all template fields in market body data
* @param {Object} bodyData - Market body template
* @param {{ time: string, date: string, price: string }} values - Values to apply
* @returns {Object} Updated body data
*/
function updateMarketBody(bodyData, values) {
const apply = (text) => applyTemplateValues(text, values);
const updated = { ...bodyData };
updated.title = apply(updated.title);
updated.description = apply(updated.description);
if (updated.markets?.[0]) {
const market = { ...updated.markets[0] };
market.title = apply(market.title);
market.description = apply(market.description);
market.rules = market.rules?.map(apply);
updated.markets = [market, ...updated.markets.slice(1)];
}
return updated;
}
/**
* Fetches current BTC price from Binance API
* @returns {Promise<number>} Current BTC price in USD
* @throws {Error} If API request fails
*/
async function fetchBTCPrice() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT_MS);
try {
const response = await fetch(CONFIG.BINANCE_API_URL, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Binance API error: ${response.status}`);
}
const data = await response.json();
return parseFloat(data.price);
} finally {
clearTimeout(timeoutId);
}
}
/**
* Sends market creation request to Worm API
* @param {Object} bodyData - Market creation payload
* @returns {Promise<Object>} API response
* @throws {Error} If API request fails
*/
async function submitMarket(bodyData) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT_MS);
try {
const response = await fetch(CONFIG.WORM_API_URL, {
method: 'POST',
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Authorization': CONFIG.AUTH_TOKEN,
'Content-Type': 'application/json',
'Referer': 'https://www.worm.wtf/'
},
body: JSON.stringify(bodyData)
});
if (!response.ok) {
throw new Error(`Worm API error: ${response.status}`);
}
return response.json();
} finally {
clearTimeout(timeoutId);
}
}
/**
* Reads and parses the market body template file
* Loads logo from separate file for cleaner organization
* @returns {Object} Parsed template data with logo
* @throws {Error} If file read or parse fails
*/
function loadBodyTemplate() {
const rawData = fs.readFileSync(CONFIG.BODY_TEMPLATE_PATH, 'utf8');
const template = JSON.parse(rawData);
// Load logo from separate file
const logo = fs.readFileSync(CONFIG.LOGO_PATH, 'utf8').trim();
template.logo = logo;
return template;
}
/**
* Main market creation workflow
* @returns {Promise<void>}
*/
async function createMarket() {
const timestamp = new Date().toISOString();
try {
console.log(`\n[${timestamp}] Starting market creation...`);
const btcPrice = await fetchBTCPrice();
const formattedPrice = formatPrice(btcPrice);
const { time, date } = getNextQuarterTime();
console.log(` → BTC Price: ${formattedPrice}`);
console.log(` → Target: ${time} UTC (${date})`);
const template = loadBodyTemplate();
const marketData = updateMarketBody(template, { time, date, price: formattedPrice });
const result = await submitMarket(marketData);
console.log(` → API Response:`, JSON.stringify(result, null, 2));
console.log(`[${timestamp}] ✓ Market created successfully\n`);
} catch (error) {
console.error(`[${timestamp}] ✗ Market creation failed: ${error.message}`);
if (error.name === 'AbortError') {
console.error(' → Request timed out');
}
}
}
/** Handles graceful shutdown */
function handleShutdown(signal) {
console.log(`\n[${new Date().toISOString()}] Received ${signal}, shutting down...`);
process.exit(0);
}
// Register shutdown handlers
process.on('SIGINT', () => handleShutdown('SIGINT'));
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
// Initialize scheduler
const scheduledTask = cron.schedule(CONFIG.CRON_SCHEDULE, createMarket);
console.log('═══════════════════════════════════════════════');
console.log(' 🤖 Worm Market Bot - Started');
console.log('═══════════════════════════════════════════════');
console.log(` Schedule: Every 15 minutes`);
console.log(` Template: ${CONFIG.BODY_TEMPLATE_PATH}`);
console.log(' Press Ctrl+C to stop');
console.log('═══════════════════════════════════════════════\n');