@corpus-core/colibri-stateless 0.2.2 → 0.2.3

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,289 @@
1
+ import { ProviderRpcError } from './types.js'; // Removed unused Config, DataRequest, RequestArguments
2
+ const DEFAULT_POLLING_INTERVAL = 12000; // 12 seconds
3
+ export class SubscriptionManager {
4
+ activeSubscriptions = new Map();
5
+ eventEmitter;
6
+ rpcCall;
7
+ config;
8
+ nextSubscriptionId = 0;
9
+ constructor(rpcCallCallback, eventEmitter, config = {}) {
10
+ this.rpcCall = rpcCallCallback;
11
+ this.eventEmitter = eventEmitter;
12
+ this.config = {
13
+ pollingInterval: DEFAULT_POLLING_INTERVAL,
14
+ ...config,
15
+ };
16
+ }
17
+ // New method to handle requests related to subscriptions and filters
18
+ handleRequest(method, paramsArray) {
19
+ switch (method) {
20
+ case 'eth_subscribe':
21
+ if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
22
+ return Promise.reject(new ProviderRpcError(-32602, 'Invalid params for eth_subscribe: missing subscription type.'));
23
+ }
24
+ return this.subscribe(paramsArray[0], paramsArray.slice(1));
25
+ case 'eth_unsubscribe':
26
+ if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
27
+ return Promise.reject(new ProviderRpcError(-32602, 'Invalid params for eth_unsubscribe: missing or invalid subscription ID.'));
28
+ }
29
+ return this.unsubscribe(paramsArray[0]);
30
+ case 'eth_newBlockFilter':
31
+ return this.newFilter('block');
32
+ case 'eth_newPendingTransactionFilter':
33
+ // This will internally throw, but we return the promise from newFilter
34
+ return this.newFilter('pendingTransaction');
35
+ case 'eth_newFilter': // For log filters
36
+ return this.newFilter('log', paramsArray);
37
+ case 'eth_getFilterChanges':
38
+ if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
39
+ return Promise.reject(new ProviderRpcError(-32602, 'Invalid params for eth_getFilterChanges: missing or invalid filter ID.'));
40
+ }
41
+ return this.getFilterChanges(paramsArray[0]);
42
+ case 'eth_uninstallFilter':
43
+ if (paramsArray.length < 1 || typeof paramsArray[0] !== 'string') {
44
+ return Promise.reject(new ProviderRpcError(-32602, 'Invalid params for eth_uninstallFilter: missing or invalid filter ID.'));
45
+ }
46
+ return this.uninstallFilter(paramsArray[0]);
47
+ default:
48
+ // Not a method handled by SubscriptionManager
49
+ return null;
50
+ }
51
+ }
52
+ generateSubscriptionId() {
53
+ this.nextSubscriptionId++;
54
+ // Simple hex string, similar to what nodes return
55
+ return `0x${this.nextSubscriptionId.toString(16)}`;
56
+ }
57
+ // For eth_subscribe
58
+ async subscribe(kind, params = []) {
59
+ if (this.config.debug) {
60
+ console.log(`[SubMan] eth_subscribe: kind=${kind}, params=`, params);
61
+ }
62
+ const subId = this.generateSubscriptionId();
63
+ let subscriptionDetails = {
64
+ id: subId,
65
+ subscriptionMethod: 'eth_subscribe',
66
+ kind: kind,
67
+ };
68
+ switch (kind) {
69
+ case 'newHeads':
70
+ // No specific params needed from client for newHeads
71
+ break;
72
+ case 'logs':
73
+ subscriptionDetails.params = params[0] || {};
74
+ break;
75
+ default:
76
+ // Should not happen if kind is correctly typed
77
+ throw new ProviderRpcError(4200, `Subscription kind "${kind}" is not supported by eth_subscribe.`);
78
+ }
79
+ const finalSubscription = subscriptionDetails;
80
+ this.activeSubscriptions.set(subId, finalSubscription);
81
+ this.schedulePoll(finalSubscription, 0); // Start active polling
82
+ if (this.config.debug) {
83
+ console.log(`[SubMan] Created eth_subscribe subscription: ${subId}`, finalSubscription);
84
+ }
85
+ return subId;
86
+ }
87
+ // For eth_newFilter, eth_newBlockFilter, eth_newPendingTransactionFilter
88
+ async newFilter(kind, params = []) {
89
+ if (this.config.debug) {
90
+ console.log(`[SubMan] eth_newFilter: kind=${kind}, params=`, params);
91
+ }
92
+ if (kind === 'pendingTransaction') {
93
+ if (this.config.debug)
94
+ console.warn('[SubMan] eth_newPendingTransactionFilter is not supported.');
95
+ throw new ProviderRpcError(4200, 'eth_newPendingTransactionFilter is not supported.');
96
+ }
97
+ const filterId = this.generateSubscriptionId();
98
+ const filterDetails = {
99
+ id: filterId,
100
+ subscriptionMethod: 'eth_newFilter',
101
+ kind: kind,
102
+ params: kind === 'log' ? (params[0] || {}) : undefined,
103
+ // No timeoutId as polling is manual via getFilterChanges
104
+ };
105
+ this.activeSubscriptions.set(filterId, filterDetails);
106
+ if (this.config.debug) {
107
+ console.log(`[SubMan] Created eth_newFilter filter: ${filterId}`, filterDetails);
108
+ }
109
+ return filterId;
110
+ }
111
+ // For eth_getFilterChanges
112
+ async getFilterChanges(filterId) {
113
+ const sub = this.activeSubscriptions.get(filterId);
114
+ if (!sub || sub.subscriptionMethod !== 'eth_newFilter') {
115
+ if (this.config.debug)
116
+ console.warn(`[SubMan] getFilterChanges: Filter not found or not a filter type for ID ${filterId}`);
117
+ throw new ProviderRpcError(-32000, 'Filter not found.'); // Standard Ethereum error for missing filter
118
+ }
119
+ if (this.config.debug) {
120
+ console.log(`[SubMan] getFilterChanges for filter: ${filterId}, kind: ${sub.kind}`);
121
+ }
122
+ // Perform a one-time poll based on the filter kind
123
+ return this.executePollLogic(sub, true); // Pass true to indicate results should be returned, not emitted
124
+ }
125
+ // For eth_uninstallFilter
126
+ async uninstallFilter(filterId) {
127
+ const sub = this.activeSubscriptions.get(filterId);
128
+ if (sub && sub.subscriptionMethod === 'eth_newFilter') {
129
+ this.activeSubscriptions.delete(filterId);
130
+ // No timeout to clear for filters
131
+ if (this.config.debug) {
132
+ console.log(`[SubMan] Uninstalled filter: ${filterId}`);
133
+ }
134
+ return true;
135
+ }
136
+ if (this.config.debug) {
137
+ console.warn(`[SubMan] UninstallFilter failed: Filter not found or not a filter type for ID ${filterId}`);
138
+ }
139
+ return false;
140
+ }
141
+ // Common polling logic, can return results or emit them via eventEmitter
142
+ async executePollLogic(sub, returnResults = false) {
143
+ let results = [];
144
+ let currentVerifiableBlockNumber = null;
145
+ try {
146
+ const blockNumberHex = await this.rpcCall('eth_blockNumber', []);
147
+ if (blockNumberHex)
148
+ currentVerifiableBlockNumber = BigInt(blockNumberHex);
149
+ }
150
+ catch (e) {
151
+ if (this.config.debug)
152
+ console.warn('[SubMan] Could not fetch eth_blockNumber for polling context:', e);
153
+ }
154
+ if (sub.kind === 'newHeads' || sub.kind === 'block') { // block is for eth_newBlockFilter
155
+ if (currentVerifiableBlockNumber) {
156
+ let fromBlock = sub.lastPolledBlockNumber ? sub.lastPolledBlockNumber + 1n : currentVerifiableBlockNumber;
157
+ if (sub.kind === 'newHeads') { // For newHeads, we only care if the latest is newer
158
+ if (!sub.lastPolledBlockNumber || currentVerifiableBlockNumber <= sub.lastPolledBlockNumber) {
159
+ return []; // No new head yet for eth_subscribe('newHeads')
160
+ }
161
+ // If new, target currentVerifiableBlockNumber for eth_getBlockByNumber
162
+ fromBlock = currentVerifiableBlockNumber;
163
+ }
164
+ for (let blockNum = fromBlock; blockNum <= currentVerifiableBlockNumber; blockNum++) {
165
+ try {
166
+ const block = await this.rpcCall('eth_getBlockByNumber', ['0x' + blockNum.toString(16), sub.kind === 'newHeads' /* full block for newHeads */]);
167
+ if (block && this.activeSubscriptions.has(sub.id)) {
168
+ if (sub.kind === 'newHeads')
169
+ results.push(block);
170
+ else if (sub.kind === 'block')
171
+ results.push(block.hash); // eth_newBlockFilter returns block hashes
172
+ }
173
+ }
174
+ catch (e) {
175
+ if (this.config.debug)
176
+ console.error(`[SubMan] Error fetching block ${blockNum} for sub/filter ${sub.id}:`, e);
177
+ if (sub.kind === 'block')
178
+ break; // For block filters, stop if a block in sequence fails
179
+ }
180
+ }
181
+ if (results.length > 0 && sub.kind === 'newHeads' && !returnResults && this.activeSubscriptions.has(sub.id)) {
182
+ // For newHeads subscription, emit each block individually or as an array?
183
+ // EIP-1193 usually implies one result per message for newHeads
184
+ results.forEach(blockResult => {
185
+ this.eventEmitter.emit('message', {
186
+ type: 'eth_subscription',
187
+ data: { subscription: sub.id, result: blockResult }
188
+ });
189
+ });
190
+ if (this.config.debug)
191
+ console.log(`[SubMan] Emitted ${results.length} newHead(s) for sub ${sub.id}`);
192
+ }
193
+ sub.lastPolledBlockNumber = currentVerifiableBlockNumber;
194
+ }
195
+ }
196
+ else if (sub.kind === 'logs' || sub.kind === 'log') { // 'log' is for eth_newFilter(logs)
197
+ const filterParams = { ...(sub.params || {}) };
198
+ if (sub.lastPolledBlockNumber) {
199
+ filterParams.fromBlock = '0x' + (sub.lastPolledBlockNumber + 1n).toString(16);
200
+ }
201
+ else if (!filterParams.fromBlock && currentVerifiableBlockNumber) {
202
+ filterParams.fromBlock = '0x' + currentVerifiableBlockNumber.toString(16);
203
+ }
204
+ if (currentVerifiableBlockNumber) {
205
+ filterParams.toBlock = '0x' + currentVerifiableBlockNumber.toString(16);
206
+ }
207
+ else if (!filterParams.toBlock) {
208
+ filterParams.toBlock = 'latest';
209
+ }
210
+ if (filterParams.fromBlock && filterParams.toBlock && filterParams.toBlock !== 'latest' && BigInt(filterParams.fromBlock) > BigInt(filterParams.toBlock)) {
211
+ // Skip if fromBlock is ahead
212
+ }
213
+ else {
214
+ try {
215
+ const logs = await this.rpcCall('eth_getLogs', [filterParams]);
216
+ if (logs && logs.length > 0 && this.activeSubscriptions.has(sub.id)) {
217
+ results = logs;
218
+ if (!returnResults) { // For eth_subscribe('logs')
219
+ this.eventEmitter.emit('message', {
220
+ type: 'eth_subscription',
221
+ data: { subscription: sub.id, result: logs } // Usually an array of logs
222
+ });
223
+ if (this.config.debug)
224
+ console.log(`[SubMan] Emitted ${logs.length} logs for sub ${sub.id}`);
225
+ }
226
+ }
227
+ if (filterParams.toBlock !== 'latest')
228
+ sub.lastPolledBlockNumber = BigInt(filterParams.toBlock);
229
+ else if (currentVerifiableBlockNumber)
230
+ sub.lastPolledBlockNumber = currentVerifiableBlockNumber;
231
+ }
232
+ catch (e) {
233
+ if (this.config.debug)
234
+ console.error(`[SubMan] Error fetching logs for sub/filter ${sub.id}:`, e);
235
+ }
236
+ }
237
+ }
238
+ return results;
239
+ }
240
+ schedulePoll(sub, delay) {
241
+ if (!this.activeSubscriptions.has(sub.id) || sub.subscriptionMethod === 'eth_newFilter')
242
+ return;
243
+ sub.timeoutId = setTimeout(async () => {
244
+ try {
245
+ if (!this.activeSubscriptions.has(sub.id))
246
+ return;
247
+ await this.pollSubscriptionAndEmit(sub); // Renamed from pollSubscription for clarity
248
+ }
249
+ catch (error) {
250
+ console.error(`[SubMan] Unexpected error in poll scheduling for subscription ${sub.id}:`, error);
251
+ }
252
+ }, delay);
253
+ }
254
+ // Wrapper for eth_subscribe polling that emits results and reschedules
255
+ async pollSubscriptionAndEmit(sub) {
256
+ if (!this.activeSubscriptions.has(sub.id)) {
257
+ if (this.config.debug)
258
+ console.log(`[SubMan] Subscription ${sub.id} was unsubscribed. Skipping poll.`);
259
+ if (sub.timeoutId)
260
+ clearTimeout(sub.timeoutId);
261
+ return;
262
+ }
263
+ if (this.config.debug) {
264
+ console.log(`[SubMan] Polling for eth_subscribe: ${sub.id}, kind: ${sub.kind}`);
265
+ }
266
+ await this.executePollLogic(sub, false); // false means emit results, don't return them
267
+ if (this.activeSubscriptions.has(sub.id)) {
268
+ this.schedulePoll(sub, this.config.pollingInterval || DEFAULT_POLLING_INTERVAL);
269
+ }
270
+ }
271
+ // For eth_unsubscribe (specific to eth_subscribe subscriptions)
272
+ async unsubscribe(subscriptionId) {
273
+ const sub = this.activeSubscriptions.get(subscriptionId);
274
+ if (sub && sub.subscriptionMethod === 'eth_subscribe') {
275
+ if (sub.timeoutId) {
276
+ clearTimeout(sub.timeoutId);
277
+ }
278
+ this.activeSubscriptions.delete(subscriptionId);
279
+ if (this.config.debug) {
280
+ console.log(`[SubMan] Unsubscribed (eth_subscribe): ${subscriptionId}`);
281
+ }
282
+ return true;
283
+ }
284
+ if (this.config.debug) {
285
+ console.warn(`[SubMan] Unsubscribe failed: Subscription not found or not an eth_subscribe type for ID ${subscriptionId}`);
286
+ }
287
+ return false;
288
+ }
289
+ }
package/types.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ export declare class ProviderRpcError extends Error {
2
+ code: number;
3
+ data?: unknown;
4
+ constructor(code: number, message: string, data?: unknown);
5
+ static createError(error: any): ProviderRpcError;
6
+ }
7
+ export interface RequestArguments {
8
+ readonly method: string;
9
+ readonly params?: readonly unknown[] | object;
10
+ }
11
+ export interface ProviderConnectInfo {
12
+ readonly chainId: string;
13
+ }
14
+ export interface ProviderMessage {
15
+ readonly type: string;
16
+ readonly data: unknown;
17
+ }
18
+ export interface Cache {
19
+ cacheable(req: DataRequest): boolean;
20
+ get(req: DataRequest): Uint8Array | undefined;
21
+ set(req: DataRequest, data: Uint8Array): void;
22
+ }
23
+ export interface Config {
24
+ chainId: number;
25
+ beacon_apis: string[];
26
+ rpcs: string[];
27
+ proofer?: string[];
28
+ trusted_block_hashes: string[];
29
+ cache?: Cache;
30
+ debug?: boolean;
31
+ include_code?: boolean;
32
+ verify?: (method: string, args: any[]) => boolean;
33
+ pollingInterval?: number;
34
+ }
35
+ export interface DataRequest {
36
+ method: string;
37
+ chain_id: number;
38
+ encoding: string;
39
+ type: string;
40
+ exclude_mask: number;
41
+ url: string;
42
+ payload: any;
43
+ req_ptr: number;
44
+ }
45
+ export declare enum MethodType {
46
+ PROOFABLE = 1,
47
+ UNPROOFABLE = 2,
48
+ NOT_SUPPORTED = 3,
49
+ LOCAL = 4
50
+ }
package/types.js ADDED
@@ -0,0 +1,45 @@
1
+ // --- EIP-1193 & C4Client Specific Types ---
2
+ // From EIP-1193: Error structure
3
+ export class ProviderRpcError extends Error {
4
+ code;
5
+ data;
6
+ constructor(code, message, data) {
7
+ super(message);
8
+ this.name = 'ProviderRpcError';
9
+ this.code = code;
10
+ if (data !== undefined) {
11
+ this.data = data;
12
+ }
13
+ Object.setPrototypeOf(this, ProviderRpcError.prototype);
14
+ }
15
+ static createError(error) {
16
+ let providerError;
17
+ if (error instanceof ProviderRpcError) {
18
+ providerError = error;
19
+ }
20
+ else {
21
+ let code;
22
+ let message = error?.message || 'An unknown error occurred';
23
+ const originalData = error?.data;
24
+ if (typeof message === 'string' && message.startsWith('Method ') && message.endsWith(' is not supported')) {
25
+ code = 4200;
26
+ }
27
+ else if (typeof message === 'string' && (message.includes('HTTP error!') || message.includes('All nodes failed') || message.includes('Failed to fetch'))) {
28
+ code = 4900;
29
+ }
30
+ else {
31
+ code = (typeof error?.code === 'number') ? error.code : -32603;
32
+ }
33
+ providerError = new ProviderRpcError(code, message, originalData);
34
+ }
35
+ return providerError;
36
+ }
37
+ }
38
+ // Enum for RPC method types
39
+ export var MethodType;
40
+ (function (MethodType) {
41
+ MethodType[MethodType["PROOFABLE"] = 1] = "PROOFABLE";
42
+ MethodType[MethodType["UNPROOFABLE"] = 2] = "UNPROOFABLE";
43
+ MethodType[MethodType["NOT_SUPPORTED"] = 3] = "NOT_SUPPORTED";
44
+ MethodType[MethodType["LOCAL"] = 4] = "LOCAL";
45
+ })(MethodType || (MethodType = {}));
package/server.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import Client, { Config } from "./index.js";
2
- export default class Server {
3
- client: Client;
4
- constructor(conf: Partial<Config>);
5
- private toHexString;
6
- private handleRequest;
7
- start(port?: number): Promise<void>;
8
- }
package/server.js DELETED
@@ -1,177 +0,0 @@
1
- import Client from "./index.js";
2
- import * as http from 'http';
3
- import * as fs from 'fs';
4
- // ensure cache_dir
5
- let cache_memory_limit = parseInt(process.env.CACHE_RAM_LIMIT || '100'); // in MB
6
- let cache_dir = process.env.CACHE_DIR || '.cache';
7
- if (!fs.existsSync(cache_dir))
8
- fs.mkdirSync(cache_dir, { recursive: true });
9
- function cache_key(req) {
10
- if (req.type == 'eth_rpc' && req.payload)
11
- return `rpc_${req.payload?.method}_${req.payload?.params?.join('_')}`;
12
- return req.url;
13
- }
14
- const cached_methods = ["eth_getBlockByNumber", "eth_getBlockByHash", "eth_getBlockReceipts"];
15
- const memory_cache = new Map();
16
- function check_limit() {
17
- let total_size = 0;
18
- for (const [k, v] of memory_cache.entries()) {
19
- total_size += v.data.length;
20
- }
21
- while (total_size > cache_memory_limit * 1024 * 1024) {
22
- // find the oldest entry
23
- let oldest_k = null;
24
- let oldest_v = null;
25
- for (const [k, v] of memory_cache.entries()) {
26
- if (oldest_v == null || v.timestamp < oldest_v.timestamp) {
27
- oldest_k = k;
28
- oldest_v = v;
29
- }
30
- }
31
- if (oldest_k)
32
- memory_cache.delete(oldest_k);
33
- }
34
- }
35
- function is_timeout(key, entry) {
36
- if (key.endsWith('latest') || key.endsWith('head')) {
37
- if (!entry)
38
- return true;
39
- return entry.timestamp < Date.now() - 1000 * 6;
40
- }
41
- return false;
42
- }
43
- const cache = {
44
- cacheable: (req) => !!(req.encoding == "ssz" || (req.type == 'eth_rpc' && req.payload && cached_methods.includes(req.payload?.method))),
45
- get: (req) => {
46
- const key = cache_key(req);
47
- let entry = memory_cache.get(key);
48
- if (is_timeout(key, entry))
49
- entry = undefined;
50
- if (!entry && !is_timeout(key)) {
51
- const file_path = cache_dir + '/' + key;
52
- if (fs.existsSync(file_path)) {
53
- let data = fs.readFileSync(file_path);
54
- memory_cache.set(key, { data, timestamp: fs.statSync(file_path).mtime.getTime() });
55
- setTimeout(check_limit, 0);
56
- }
57
- }
58
- return entry?.data;
59
- },
60
- set: (req, data) => {
61
- const key = cache_key(req);
62
- memory_cache.set(key, { data, timestamp: Date.now() });
63
- if (!is_timeout(key))
64
- fs.writeFile(cache_dir + '/' + key, Buffer.from(data), () => { });
65
- check_limit();
66
- }
67
- };
68
- export default class Server {
69
- client;
70
- constructor(conf) {
71
- conf = conf || {};
72
- if (process.env.CHAIN_ID)
73
- conf.chainId = parseInt(process.env.CHAIN_ID);
74
- if (process.env.BEACON_API)
75
- conf.beacon_apis = process.env.BEACON_API.split(",");
76
- if (process.env.RPC)
77
- conf.rpcs = process.env.RPC.split(",");
78
- conf.cache = cache;
79
- conf.debug = true;
80
- this.client = new Client(conf);
81
- }
82
- toHexString(bytes) {
83
- return '0x' + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
84
- }
85
- async handleRequest(req, res) {
86
- // Only accept POST requests
87
- if (req.method !== 'POST') {
88
- const error = 'Method not allowed';
89
- console.error(`[${new Date().toISOString()}] Error: ${error}`);
90
- res.writeHead(405, { 'Content-Type': 'application/json' });
91
- res.end(JSON.stringify({ error }));
92
- return;
93
- }
94
- // Read request body
95
- let body = '';
96
- req.on('data', (chunk) => {
97
- body += chunk.toString();
98
- });
99
- req.on('end', async () => {
100
- try {
101
- const jsonRpc = JSON.parse(body);
102
- if (!jsonRpc.method || !jsonRpc.params || !Array.isArray(jsonRpc.params)) {
103
- throw new Error('Invalid JSON-RPC request');
104
- }
105
- let start = Date.now();
106
- console.log(` ==> ${jsonRpc.method} (${jsonRpc.params.join(',')}) ...`);
107
- // Create proof using the client
108
- const proof = await this.client.createProof(jsonRpc.method, jsonRpc.params);
109
- let end = Date.now();
110
- console.log(` <== ${jsonRpc.method} (${jsonRpc.params.join(',')}) ... ${end - start}ms (${proof.length} bytes)`);
111
- // Check Accept header to determine response format
112
- const acceptHeader = req.headers.accept;
113
- if (acceptHeader === 'application/octet-stream') {
114
- res.writeHead(200, {
115
- 'Content-Type': 'application/octet-stream',
116
- 'Content-Length': proof.length
117
- });
118
- res.end(Buffer.from(proof));
119
- }
120
- else {
121
- const response = {
122
- jsonrpc: '2.0',
123
- id: jsonRpc.id,
124
- result: this.toHexString(proof)
125
- };
126
- res.writeHead(200, { 'Content-Type': 'application/json' });
127
- res.end(JSON.stringify(response));
128
- }
129
- }
130
- catch (error) {
131
- const errorMessage = error instanceof Error ? error.message : 'Internal error';
132
- console.error(`[${new Date().toISOString()}] Error processing request:`, errorMessage);
133
- if (error instanceof Error && error.stack) {
134
- console.error(`Stack trace:`, error.stack);
135
- }
136
- res.writeHead(400, { 'Content-Type': 'application/json' });
137
- const errorResponse = {
138
- jsonrpc: '2.0',
139
- id: JSON.parse(body)?.id,
140
- error: {
141
- code: -32603,
142
- message: errorMessage
143
- }
144
- };
145
- res.end(JSON.stringify(errorResponse));
146
- console.log(`[${new Date().toISOString()}] Sent error response:`, errorResponse);
147
- }
148
- });
149
- req.on('error', (error) => {
150
- console.error(`[${new Date().toISOString()}] Request error:`, error);
151
- res.writeHead(500, { 'Content-Type': 'application/json' });
152
- res.end(JSON.stringify({
153
- jsonrpc: '2.0',
154
- error: {
155
- code: -32603,
156
- message: 'Internal server error'
157
- }
158
- }));
159
- });
160
- }
161
- async start(port = 8545) {
162
- const server = http.createServer((req, res) => this.handleRequest(req, res));
163
- return new Promise((resolve, reject) => {
164
- server.listen(port, () => {
165
- console.log(`[${new Date().toISOString()}] Server running at http://localhost:${port}/`);
166
- resolve();
167
- });
168
- server.on('error', (error) => {
169
- console.error(`[${new Date().toISOString()}] Server error:`, error);
170
- reject(error);
171
- });
172
- });
173
- }
174
- }
175
- // Start the server directly
176
- const server = new Server({});
177
- server.start(parseInt(process.env.PORT || '8545'));