clawreum-sdk 1.0.1 → 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/package.json +1 -1
- package/src/index.js +273 -11
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Clawreum Mining SDK
|
|
2
|
+
* Clawreum Mining SDK v2.0.0
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Rock-based mining system (compete for rocks)
|
|
6
|
+
* - AI chat integration (chatHandler callback)
|
|
7
|
+
* - Context system for AI decision making
|
|
3
8
|
*
|
|
4
9
|
* Usage:
|
|
5
10
|
* const ClawreumMiner = require('clawreum-sdk');
|
|
@@ -7,7 +12,11 @@
|
|
|
7
12
|
* const miner = new ClawreumMiner({
|
|
8
13
|
* platform: 'telegram',
|
|
9
14
|
* botId: '123456789',
|
|
10
|
-
* botName: 'MyMiningBot'
|
|
15
|
+
* botName: 'MyMiningBot',
|
|
16
|
+
* chatHandler: async (context) => {
|
|
17
|
+
* // Your AI logic here (OpenAI, Claude, Ollama, etc.)
|
|
18
|
+
* return "I'm mining!";
|
|
19
|
+
* }
|
|
11
20
|
* });
|
|
12
21
|
*
|
|
13
22
|
* miner.on('reward', (data) => console.log(`+${data.reward} CLAWREUM`));
|
|
@@ -31,6 +40,9 @@ class ClawreumMiner extends EventEmitter {
|
|
|
31
40
|
* @param {string} [options.ownerWallet] - Wallet address for rewards
|
|
32
41
|
* @param {boolean} [options.autoReconnect] - Auto reconnect (default: true)
|
|
33
42
|
* @param {number} [options.miningInterval] - Mining action interval in ms (default: 250)
|
|
43
|
+
* @param {number} [options.moveSpeed] - Movement speed (default: 5)
|
|
44
|
+
* @param {Function} [options.chatHandler] - Async function for AI chat (context) => string
|
|
45
|
+
* @param {number} [options.chatInterval] - Chat interval in ms when idle (default: 30000)
|
|
34
46
|
*/
|
|
35
47
|
constructor(options = {}) {
|
|
36
48
|
super();
|
|
@@ -43,6 +55,8 @@ class ClawreumMiner extends EventEmitter {
|
|
|
43
55
|
server: DEFAULT_SERVER,
|
|
44
56
|
autoReconnect: true,
|
|
45
57
|
miningInterval: 250,
|
|
58
|
+
moveSpeed: 5,
|
|
59
|
+
chatInterval: 30000,
|
|
46
60
|
...options
|
|
47
61
|
};
|
|
48
62
|
|
|
@@ -51,16 +65,33 @@ class ClawreumMiner extends EventEmitter {
|
|
|
51
65
|
this.sessionSecret = null;
|
|
52
66
|
this.currentRoom = null;
|
|
53
67
|
this.miningLoop = null;
|
|
68
|
+
this.chatLoop = null;
|
|
54
69
|
this.reconnectAttempts = 0;
|
|
55
70
|
this.maxReconnectAttempts = 10;
|
|
56
71
|
this.botId = null;
|
|
57
72
|
|
|
73
|
+
// Position and movement
|
|
74
|
+
this.position = { x: 0, y: 0, z: 0 };
|
|
75
|
+
this.targetPosition = null;
|
|
76
|
+
this.moveInterval = null;
|
|
77
|
+
|
|
78
|
+
// Rock system
|
|
79
|
+
this.rocks = [];
|
|
80
|
+
this.currentRockId = null;
|
|
81
|
+
this.status = 'idle'; // idle | moving | mining | racing
|
|
82
|
+
|
|
83
|
+
// Chat system
|
|
84
|
+
this.nearbyChat = [];
|
|
85
|
+
this.waitStartTime = null;
|
|
86
|
+
|
|
58
87
|
// Stats
|
|
59
88
|
this.stats = {
|
|
60
89
|
totalMined: 0,
|
|
61
90
|
miningCount: 0,
|
|
62
91
|
connectedAt: null,
|
|
63
|
-
lastRewardAt: null
|
|
92
|
+
lastRewardAt: null,
|
|
93
|
+
racesWon: 0,
|
|
94
|
+
racesLost: 0
|
|
64
95
|
};
|
|
65
96
|
}
|
|
66
97
|
|
|
@@ -91,6 +122,8 @@ class ClawreumMiner extends EventEmitter {
|
|
|
91
122
|
*/
|
|
92
123
|
stop() {
|
|
93
124
|
this._stopMiningLoop();
|
|
125
|
+
this._stopChatLoop();
|
|
126
|
+
this._stopMovement();
|
|
94
127
|
if (this.ws) {
|
|
95
128
|
this.ws.close();
|
|
96
129
|
this.ws = null;
|
|
@@ -98,6 +131,7 @@ class ClawreumMiner extends EventEmitter {
|
|
|
98
131
|
this.isConnected = false;
|
|
99
132
|
this.sessionSecret = null;
|
|
100
133
|
this.currentRoom = null;
|
|
134
|
+
this.status = 'idle';
|
|
101
135
|
this.emit('stopped');
|
|
102
136
|
}
|
|
103
137
|
|
|
@@ -109,6 +143,10 @@ class ClawreumMiner extends EventEmitter {
|
|
|
109
143
|
connected: this.isConnected,
|
|
110
144
|
room: this.currentRoom,
|
|
111
145
|
botId: this.botId,
|
|
146
|
+
status: this.status,
|
|
147
|
+
position: this.position,
|
|
148
|
+
currentRockId: this.currentRockId,
|
|
149
|
+
rocks: this.rocks,
|
|
112
150
|
stats: { ...this.stats },
|
|
113
151
|
uptime: this.stats.connectedAt
|
|
114
152
|
? Date.now() - this.stats.connectedAt
|
|
@@ -116,6 +154,23 @@ class ClawreumMiner extends EventEmitter {
|
|
|
116
154
|
};
|
|
117
155
|
}
|
|
118
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Get context for AI chat
|
|
159
|
+
*/
|
|
160
|
+
getContext() {
|
|
161
|
+
return {
|
|
162
|
+
status: this.status,
|
|
163
|
+
waitTime: this.waitStartTime ? Math.floor((Date.now() - this.waitStartTime) / 1000) : 0,
|
|
164
|
+
position: this.position,
|
|
165
|
+
currentRockId: this.currentRockId,
|
|
166
|
+
availableRocks: this.rocks.filter(r => !r.occupied).length,
|
|
167
|
+
totalRocks: this.rocks.length,
|
|
168
|
+
competitors: this.rocks.filter(r => r.occupied).length,
|
|
169
|
+
nearbyChat: this.nearbyChat.slice(-5),
|
|
170
|
+
stats: this.stats
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
119
174
|
// ============================================================
|
|
120
175
|
// Private Methods
|
|
121
176
|
// ============================================================
|
|
@@ -136,7 +191,7 @@ class ClawreumMiner extends EventEmitter {
|
|
|
136
191
|
platformBotId: this.options.botId,
|
|
137
192
|
platformBotName: this.options.botName,
|
|
138
193
|
binaryHash,
|
|
139
|
-
version: '
|
|
194
|
+
version: '2.0.0',
|
|
140
195
|
hardwareFingerprint: fingerprint,
|
|
141
196
|
vmReport: { hypervisorPresent: false, runningProcesses: [] },
|
|
142
197
|
ownerWallet: this.options.ownerWallet || null,
|
|
@@ -185,6 +240,8 @@ class ClawreumMiner extends EventEmitter {
|
|
|
185
240
|
clearTimeout(timeout);
|
|
186
241
|
this.isConnected = false;
|
|
187
242
|
this._stopMiningLoop();
|
|
243
|
+
this._stopChatLoop();
|
|
244
|
+
this._stopMovement();
|
|
188
245
|
this.emit('disconnected', { code, reason: reason.toString() });
|
|
189
246
|
|
|
190
247
|
if (this.options.autoReconnect && code !== 4002) {
|
|
@@ -224,8 +281,16 @@ class ClawreumMiner extends EventEmitter {
|
|
|
224
281
|
|
|
225
282
|
case 'joinRoomSuccess':
|
|
226
283
|
this.currentRoom = data.roomCode;
|
|
284
|
+
this.position = data.position || { x: 0, y: 0, z: 0 };
|
|
227
285
|
this.emit('joined', { room: data.roomCode, botId: data.botId });
|
|
228
|
-
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case 'rockStatus':
|
|
289
|
+
this._handleRockStatus(data);
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case 'rockAvailable':
|
|
293
|
+
this._handleRockAvailable(data);
|
|
229
294
|
break;
|
|
230
295
|
|
|
231
296
|
case 'syncBalance':
|
|
@@ -237,6 +302,23 @@ class ClawreumMiner extends EventEmitter {
|
|
|
237
302
|
});
|
|
238
303
|
break;
|
|
239
304
|
|
|
305
|
+
case 'miningStarted':
|
|
306
|
+
if (data.id === this.botId) {
|
|
307
|
+
this.status = 'mining';
|
|
308
|
+
this.currentRockId = data.rockId;
|
|
309
|
+
this._startMiningLoop();
|
|
310
|
+
this._stopChatLoop();
|
|
311
|
+
this._triggerChat('mining_started');
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
case 'miningFailed':
|
|
316
|
+
this.emit('log', `Mining failed: ${data.reason}`);
|
|
317
|
+
this.stats.racesLost++;
|
|
318
|
+
this._triggerChat('lost_race');
|
|
319
|
+
this._findAndMoveToRock();
|
|
320
|
+
break;
|
|
321
|
+
|
|
240
322
|
case 'miningProgress':
|
|
241
323
|
this.emit('progress', { progress: data.progress });
|
|
242
324
|
break;
|
|
@@ -280,16 +362,159 @@ class ClawreumMiner extends EventEmitter {
|
|
|
280
362
|
});
|
|
281
363
|
break;
|
|
282
364
|
|
|
365
|
+
case 'roomUserChat':
|
|
366
|
+
this.nearbyChat.push({
|
|
367
|
+
id: data.id,
|
|
368
|
+
name: data.name,
|
|
369
|
+
message: data.message,
|
|
370
|
+
time: Date.now()
|
|
371
|
+
});
|
|
372
|
+
if (this.nearbyChat.length > 20) {
|
|
373
|
+
this.nearbyChat.shift();
|
|
374
|
+
}
|
|
375
|
+
this.emit('chat', data);
|
|
376
|
+
break;
|
|
377
|
+
|
|
283
378
|
case 'error':
|
|
284
379
|
this.emit('error', new Error(data.message));
|
|
285
380
|
break;
|
|
286
381
|
|
|
287
382
|
default:
|
|
288
|
-
// Other messages
|
|
289
383
|
break;
|
|
290
384
|
}
|
|
291
385
|
}
|
|
292
386
|
|
|
387
|
+
_handleRockStatus(data) {
|
|
388
|
+
this.rocks = data.rocks || [];
|
|
389
|
+
this.emit('rockStatus', {
|
|
390
|
+
rocks: this.rocks,
|
|
391
|
+
available: data.availableRocks,
|
|
392
|
+
total: data.totalRocks
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// If not mining, find a rock
|
|
396
|
+
if (this.status !== 'mining') {
|
|
397
|
+
this._findAndMoveToRock();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
_handleRockAvailable(data) {
|
|
402
|
+
this.emit('log', `Rock ${data.rockId} is now available!`);
|
|
403
|
+
this.emit('rockAvailable', data);
|
|
404
|
+
|
|
405
|
+
// If waiting, race to the rock
|
|
406
|
+
if (this.status === 'idle' || this.status === 'waiting') {
|
|
407
|
+
this._triggerChat('rock_available');
|
|
408
|
+
this.status = 'racing';
|
|
409
|
+
this._moveToPosition(data.position, () => {
|
|
410
|
+
this._tryOccupyRock(data.rockId);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_findAndMoveToRock() {
|
|
416
|
+
const availableRocks = this.rocks.filter(r => !r.occupied);
|
|
417
|
+
|
|
418
|
+
if (availableRocks.length === 0) {
|
|
419
|
+
// No rocks available, wait
|
|
420
|
+
this.status = 'waiting';
|
|
421
|
+
if (!this.waitStartTime) {
|
|
422
|
+
this.waitStartTime = Date.now();
|
|
423
|
+
}
|
|
424
|
+
this._startChatLoop();
|
|
425
|
+
this.emit('log', 'No rocks available, waiting...');
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Find nearest rock
|
|
430
|
+
const nearest = this._findNearestRock(availableRocks);
|
|
431
|
+
this.status = 'moving';
|
|
432
|
+
this.waitStartTime = null;
|
|
433
|
+
|
|
434
|
+
this.emit('log', `Moving to rock ${nearest.id}`);
|
|
435
|
+
|
|
436
|
+
this._moveToPosition(nearest.position, () => {
|
|
437
|
+
this._tryOccupyRock(nearest.id);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_findNearestRock(rocks) {
|
|
442
|
+
let nearest = rocks[0];
|
|
443
|
+
let minDist = this._distance(this.position, nearest.position);
|
|
444
|
+
|
|
445
|
+
for (const rock of rocks) {
|
|
446
|
+
const dist = this._distance(this.position, rock.position);
|
|
447
|
+
if (dist < minDist) {
|
|
448
|
+
minDist = dist;
|
|
449
|
+
nearest = rock;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return nearest;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
_distance(a, b) {
|
|
457
|
+
const dx = (a.x || 0) - (b.x || 0);
|
|
458
|
+
const dz = (a.z || 0) - (b.z || 0);
|
|
459
|
+
return Math.sqrt(dx * dx + dz * dz);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
_moveToPosition(target, callback) {
|
|
463
|
+
this._stopMovement();
|
|
464
|
+
this.targetPosition = target;
|
|
465
|
+
|
|
466
|
+
this.moveInterval = setInterval(() => {
|
|
467
|
+
const dx = target.x - this.position.x;
|
|
468
|
+
const dz = target.z - this.position.z;
|
|
469
|
+
const dist = Math.sqrt(dx * dx + dz * dz);
|
|
470
|
+
|
|
471
|
+
if (dist < 1) {
|
|
472
|
+
// Arrived
|
|
473
|
+
this._stopMovement();
|
|
474
|
+
if (callback) callback();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Move towards target
|
|
479
|
+
const speed = this.options.moveSpeed;
|
|
480
|
+
const ratio = Math.min(speed / dist, 1);
|
|
481
|
+
this.position.x += dx * ratio;
|
|
482
|
+
this.position.z += dz * ratio;
|
|
483
|
+
|
|
484
|
+
// Send move packet
|
|
485
|
+
this._send({
|
|
486
|
+
type: 'move',
|
|
487
|
+
data: {
|
|
488
|
+
position: this.position,
|
|
489
|
+
direction: this._getDirection(dx, dz)
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
}, 100);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
_stopMovement() {
|
|
496
|
+
if (this.moveInterval) {
|
|
497
|
+
clearInterval(this.moveInterval);
|
|
498
|
+
this.moveInterval = null;
|
|
499
|
+
}
|
|
500
|
+
this.targetPosition = null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
_getDirection(dx, dz) {
|
|
504
|
+
if (Math.abs(dx) > Math.abs(dz)) {
|
|
505
|
+
return dx > 0 ? 'right' : 'left';
|
|
506
|
+
}
|
|
507
|
+
return dz > 0 ? 'up' : 'down';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
_tryOccupyRock(rockId) {
|
|
511
|
+
this.emit('log', `Trying to occupy rock ${rockId}`);
|
|
512
|
+
this._send({
|
|
513
|
+
type: 'botMineStart',
|
|
514
|
+
data: { rockId }
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
293
518
|
_handleChallenge(challenge, isReAttest = false) {
|
|
294
519
|
const { challengeId, nonce, difficulty } = challenge;
|
|
295
520
|
|
|
@@ -334,15 +559,14 @@ class ClawreumMiner extends EventEmitter {
|
|
|
334
559
|
_startMiningLoop() {
|
|
335
560
|
if (this.miningLoop) return;
|
|
336
561
|
|
|
337
|
-
this._send({ type: 'botMineStart', data: {} });
|
|
338
|
-
|
|
339
562
|
this.miningLoop = setInterval(() => {
|
|
340
|
-
if (this.isConnected && this.
|
|
563
|
+
if (this.isConnected && this.status === 'mining') {
|
|
341
564
|
this._send({ type: 'botMine', data: {} });
|
|
342
565
|
}
|
|
343
566
|
}, this.options.miningInterval);
|
|
344
567
|
|
|
345
|
-
this.emit('mining', { started: true });
|
|
568
|
+
this.emit('mining', { started: true, rockId: this.currentRockId });
|
|
569
|
+
this.stats.racesWon++;
|
|
346
570
|
}
|
|
347
571
|
|
|
348
572
|
_stopMiningLoop() {
|
|
@@ -352,6 +576,44 @@ class ClawreumMiner extends EventEmitter {
|
|
|
352
576
|
}
|
|
353
577
|
}
|
|
354
578
|
|
|
579
|
+
_startChatLoop() {
|
|
580
|
+
if (this.chatLoop || !this.options.chatHandler) return;
|
|
581
|
+
|
|
582
|
+
this.chatLoop = setInterval(() => {
|
|
583
|
+
this._triggerChat('idle');
|
|
584
|
+
}, this.options.chatInterval);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
_stopChatLoop() {
|
|
588
|
+
if (this.chatLoop) {
|
|
589
|
+
clearInterval(this.chatLoop);
|
|
590
|
+
this.chatLoop = null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async _triggerChat(event) {
|
|
595
|
+
if (!this.options.chatHandler) return;
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
const context = {
|
|
599
|
+
...this.getContext(),
|
|
600
|
+
event
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const message = await this.options.chatHandler(context);
|
|
604
|
+
|
|
605
|
+
if (message && typeof message === 'string' && message.trim()) {
|
|
606
|
+
this._send({
|
|
607
|
+
type: 'chat',
|
|
608
|
+
data: { message: message.trim() }
|
|
609
|
+
});
|
|
610
|
+
this.emit('chatSent', { message, event });
|
|
611
|
+
}
|
|
612
|
+
} catch (err) {
|
|
613
|
+
this.emit('error', new Error(`Chat handler error: ${err.message}`));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
355
617
|
_scheduleReconnect() {
|
|
356
618
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
357
619
|
this.emit('error', new Error('Max reconnection attempts exceeded'));
|
|
@@ -390,7 +652,7 @@ class ClawreumMiner extends EventEmitter {
|
|
|
390
652
|
|
|
391
653
|
_generateBinaryHash() {
|
|
392
654
|
return crypto.createHash('sha256')
|
|
393
|
-
.update(`clawreum-sdk-
|
|
655
|
+
.update(`clawreum-sdk-v2.0.0-${os.hostname()}`)
|
|
394
656
|
.digest('hex');
|
|
395
657
|
}
|
|
396
658
|
}
|