clawreum-sdk 1.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.
Files changed (3) hide show
  1. package/README.md +141 -0
  2. package/package.json +32 -0
  3. package/src/index.js +398 -0
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # clawreum-sdk
2
+
3
+ Clawreum Mining SDK - Node.js client for mining CLAWREUM tokens.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install clawreum-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ const ClawreumMiner = require('clawreum-sdk');
15
+
16
+ const miner = new ClawreumMiner({
17
+ platform: 'telegram', // 'telegram' | 'discord' | 'whatsapp'
18
+ botId: '123456789', // 플랫폼 봇 ID
19
+ botName: 'MyMiningBot' // 봇 표시 이름
20
+ });
21
+
22
+ // 이벤트 리스너
23
+ miner.on('reward', (data) => {
24
+ console.log(`+${data.reward.toFixed(4)} CLAWREUM`);
25
+ });
26
+
27
+ miner.on('balance', (data) => {
28
+ console.log(`정제중: ${data.refining}, 클레임 가능: ${data.claimable}`);
29
+ });
30
+
31
+ miner.on('error', (err) => {
32
+ console.error('에러:', err.message);
33
+ });
34
+
35
+ // 채굴 시작
36
+ miner.start();
37
+ ```
38
+
39
+ ## Options
40
+
41
+ | Option | Type | Required | Default | Description |
42
+ |--------|------|----------|---------|-------------|
43
+ | `platform` | string | ✓ | - | 봇 플랫폼 (`telegram`, `discord`, `whatsapp`) |
44
+ | `botId` | string | ✓ | - | 플랫폼 봇 ID |
45
+ | `botName` | string | ✓ | - | 봇 표시 이름 |
46
+ | `server` | string | | `wss://api.clawreum.com` | WebSocket 서버 URL |
47
+ | `ownerWallet` | string | | - | 보상 받을 지갑 주소 |
48
+ | `autoReconnect` | boolean | | `true` | 자동 재연결 |
49
+ | `miningInterval` | number | | `250` | 채굴 액션 간격 (ms) |
50
+
51
+ ## Events
52
+
53
+ | Event | Data | Description |
54
+ |-------|------|-------------|
55
+ | `registered` | `{ botId, characterName }` | 봇 등록 완료 |
56
+ | `authenticated` | `{ botName }` | 인증 성공 |
57
+ | `joined` | `{ room, botId }` | 방 입장 |
58
+ | `mining` | `{ started: true }` | 채굴 루프 시작 |
59
+ | `progress` | `{ progress }` | 채굴 진행률 (0~1) |
60
+ | `reward` | `{ reward, base, bonus, packId }` | 채굴 보상 |
61
+ | `balance` | `{ refining, claimable, world, boost }` | 잔액 동기화 |
62
+ | `block` | `{ block, pack, difficulty }` | 블록 업데이트 |
63
+ | `disconnected` | `{ code, reason }` | 연결 종료 |
64
+ | `stopped` | - | 채굴 중지 |
65
+ | `error` | `Error` | 에러 발생 |
66
+ | `log` | `string` | 로그 메시지 |
67
+
68
+ ## Methods
69
+
70
+ ### `start()`
71
+ 채굴을 시작합니다.
72
+
73
+ ### `stop()`
74
+ 채굴을 중지하고 연결을 종료합니다.
75
+
76
+ ### `getStatus()`
77
+ 현재 상태를 반환합니다.
78
+
79
+ ```javascript
80
+ const status = miner.getStatus();
81
+ // {
82
+ // connected: true,
83
+ // room: 'server1',
84
+ // botId: 'tg_123456789',
85
+ // stats: { totalMined: 150.5, miningCount: 12, ... },
86
+ // uptime: 3600000
87
+ // }
88
+ ```
89
+
90
+ ## Example: Full Usage
91
+
92
+ ```javascript
93
+ const ClawreumMiner = require('clawreum-sdk');
94
+
95
+ const miner = new ClawreumMiner({
96
+ platform: 'telegram',
97
+ botId: process.env.BOT_ID,
98
+ botName: process.env.BOT_NAME,
99
+ ownerWallet: '0x...',
100
+ autoReconnect: true
101
+ });
102
+
103
+ miner.on('log', console.log);
104
+ miner.on('error', console.error);
105
+
106
+ miner.on('registered', ({ botId }) => {
107
+ console.log(`봇 등록됨: ${botId}`);
108
+ });
109
+
110
+ miner.on('reward', ({ reward, bonus }) => {
111
+ console.log(`채굴 성공! +${reward.toFixed(4)} (보너스: ${bonus.toFixed(4)})`);
112
+ });
113
+
114
+ miner.on('balance', ({ refining, claimable }) => {
115
+ console.log(`잔액 - 정제중: ${refining.toFixed(2)}, 클레임: ${claimable.toFixed(2)}`);
116
+ });
117
+
118
+ // 시작
119
+ miner.start();
120
+
121
+ // 10분 후 상태 확인
122
+ setTimeout(() => {
123
+ const status = miner.getStatus();
124
+ console.log(`총 채굴량: ${status.stats.totalMined.toFixed(4)} CLAWREUM`);
125
+ }, 600000);
126
+
127
+ // 종료 처리
128
+ process.on('SIGINT', () => {
129
+ miner.stop();
130
+ process.exit();
131
+ });
132
+ ```
133
+
134
+ ## Requirements
135
+
136
+ - Node.js 18+
137
+ - 등록된 봇 (Telegram/Discord/WhatsApp)
138
+
139
+ ## License
140
+
141
+ MIT
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "clawreum-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Clawreum Mining SDK - Bot client for mining CLAWREUM tokens",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "keywords": [
8
+ "clawreum",
9
+ "mining",
10
+ "blockchain",
11
+ "bsc",
12
+ "crypto",
13
+ "bot",
14
+ "sdk"
15
+ ],
16
+ "author": "Clawreum Team",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/clawreum/clawreum-sdk"
21
+ },
22
+ "homepage": "https://clawreum.com",
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "dependencies": {
27
+ "ws": "^8.16.0"
28
+ },
29
+ "scripts": {
30
+ "test": "node test/test.js"
31
+ }
32
+ }
package/src/index.js ADDED
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Clawreum Mining SDK
3
+ *
4
+ * 사용법:
5
+ * const ClawreumMiner = require('clawreum-sdk');
6
+ *
7
+ * const miner = new ClawreumMiner({
8
+ * platform: 'telegram',
9
+ * botId: '123456789',
10
+ * botName: 'MyMiningBot'
11
+ * });
12
+ *
13
+ * miner.on('reward', (data) => console.log(`+${data.reward} CLAWREUM`));
14
+ * miner.start();
15
+ */
16
+
17
+ const WebSocket = require('ws');
18
+ const crypto = require('crypto');
19
+ const os = require('os');
20
+ const { EventEmitter } = require('events');
21
+
22
+ const DEFAULT_SERVER = 'wss://api.clawreum.com';
23
+
24
+ class ClawreumMiner extends EventEmitter {
25
+ /**
26
+ * @param {Object} options
27
+ * @param {string} options.platform - 'telegram' | 'discord' | 'whatsapp'
28
+ * @param {string} options.botId - 플랫폼 봇 ID
29
+ * @param {string} options.botName - 봇 표시 이름
30
+ * @param {string} [options.server] - WebSocket 서버 URL (기본: wss://api.clawreum.com)
31
+ * @param {string} [options.ownerWallet] - 보상 받을 지갑 주소
32
+ * @param {boolean} [options.autoReconnect] - 자동 재연결 (기본: true)
33
+ * @param {number} [options.miningInterval] - 채굴 액션 간격 ms (기본: 250)
34
+ */
35
+ constructor(options = {}) {
36
+ super();
37
+
38
+ if (!options.platform || !options.botId || !options.botName) {
39
+ throw new Error('필수 옵션: platform, botId, botName');
40
+ }
41
+
42
+ this.options = {
43
+ server: DEFAULT_SERVER,
44
+ autoReconnect: true,
45
+ miningInterval: 250,
46
+ ...options
47
+ };
48
+
49
+ this.ws = null;
50
+ this.isConnected = false;
51
+ this.sessionSecret = null;
52
+ this.currentRoom = null;
53
+ this.miningLoop = null;
54
+ this.reconnectAttempts = 0;
55
+ this.maxReconnectAttempts = 10;
56
+ this.botId = null;
57
+
58
+ // 통계
59
+ this.stats = {
60
+ totalMined: 0,
61
+ miningCount: 0,
62
+ connectedAt: null,
63
+ lastRewardAt: null
64
+ };
65
+ }
66
+
67
+ /**
68
+ * 채굴 시작
69
+ */
70
+ async start() {
71
+ if (this.isConnected) {
72
+ this.emit('warn', '이미 연결되어 있습니다');
73
+ return;
74
+ }
75
+
76
+ this.emit('log', `서버 연결 중: ${this.options.server}`);
77
+
78
+ try {
79
+ const wsToken = await this._registerBot();
80
+ await this._connect(wsToken);
81
+ } catch (err) {
82
+ this.emit('error', err);
83
+ if (this.options.autoReconnect) {
84
+ this._scheduleReconnect();
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * 채굴 중지
91
+ */
92
+ stop() {
93
+ this._stopMiningLoop();
94
+ if (this.ws) {
95
+ this.ws.close();
96
+ this.ws = null;
97
+ }
98
+ this.isConnected = false;
99
+ this.sessionSecret = null;
100
+ this.currentRoom = null;
101
+ this.emit('stopped');
102
+ }
103
+
104
+ /**
105
+ * 현재 상태 조회
106
+ */
107
+ getStatus() {
108
+ return {
109
+ connected: this.isConnected,
110
+ room: this.currentRoom,
111
+ botId: this.botId,
112
+ stats: { ...this.stats },
113
+ uptime: this.stats.connectedAt
114
+ ? Date.now() - this.stats.connectedAt
115
+ : 0
116
+ };
117
+ }
118
+
119
+ // ============================================================
120
+ // Private Methods
121
+ // ============================================================
122
+
123
+ async _registerBot() {
124
+ const fingerprint = this._generateFingerprint();
125
+ const binaryHash = this._generateBinaryHash();
126
+
127
+ const apiUrl = this.options.server
128
+ .replace('wss://', 'https://')
129
+ .replace('ws://', 'http://');
130
+
131
+ const response = await fetch(`${apiUrl}/api/bot/register`, {
132
+ method: 'POST',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify({
135
+ platform: this.options.platform,
136
+ platformBotId: this.options.botId,
137
+ platformBotName: this.options.botName,
138
+ binaryHash,
139
+ version: '1.0.0',
140
+ hardwareFingerprint: fingerprint,
141
+ vmReport: { hypervisorPresent: false, runningProcesses: [] },
142
+ ownerWallet: this.options.ownerWallet || null,
143
+ snsVerification: { verified: true }
144
+ })
145
+ });
146
+
147
+ const result = await response.json();
148
+
149
+ if (!result.success) {
150
+ throw new Error(result.message || '봇 등록 실패');
151
+ }
152
+
153
+ this.botId = result.botId;
154
+ this.emit('registered', { botId: result.botId, characterName: result.characterName });
155
+
156
+ return result.wsToken;
157
+ }
158
+
159
+ async _connect(wsToken) {
160
+ return new Promise((resolve, reject) => {
161
+ const wsUrl = `${this.options.server}?token=${wsToken}`;
162
+ this.ws = new WebSocket(wsUrl);
163
+
164
+ const timeout = setTimeout(() => {
165
+ reject(new Error('연결 타임아웃'));
166
+ this.ws.close();
167
+ }, 30000);
168
+
169
+ this.ws.on('open', () => {
170
+ clearTimeout(timeout);
171
+ this.emit('log', 'WebSocket 연결됨');
172
+ this.reconnectAttempts = 0;
173
+ });
174
+
175
+ this.ws.on('message', (data) => {
176
+ try {
177
+ const msg = JSON.parse(data.toString());
178
+ this._handleMessage(msg, resolve, reject);
179
+ } catch (err) {
180
+ this.emit('error', new Error(`메시지 파싱 실패: ${err.message}`));
181
+ }
182
+ });
183
+
184
+ this.ws.on('close', (code, reason) => {
185
+ clearTimeout(timeout);
186
+ this.isConnected = false;
187
+ this._stopMiningLoop();
188
+ this.emit('disconnected', { code, reason: reason.toString() });
189
+
190
+ if (this.options.autoReconnect && code !== 4002) {
191
+ this._scheduleReconnect();
192
+ }
193
+ });
194
+
195
+ this.ws.on('error', (err) => {
196
+ clearTimeout(timeout);
197
+ this.emit('error', err);
198
+ reject(err);
199
+ });
200
+ });
201
+ }
202
+
203
+ _handleMessage(msg, resolveConnect, rejectConnect) {
204
+ const { type, data } = msg;
205
+
206
+ switch (type) {
207
+ case 'attestChallenge':
208
+ this._handleChallenge(data);
209
+ break;
210
+
211
+ case 'attestSuccess':
212
+ this.sessionSecret = data.sessionSecret;
213
+ this.isConnected = true;
214
+ this.stats.connectedAt = Date.now();
215
+ this.emit('authenticated', { botName: data.botDisplayName });
216
+ this._joinRoom();
217
+ if (resolveConnect) resolveConnect();
218
+ break;
219
+
220
+ case 'attestFail':
221
+ this.emit('error', new Error(`인증 실패: ${data.reason}`));
222
+ if (rejectConnect) rejectConnect(new Error(data.reason));
223
+ break;
224
+
225
+ case 'joinRoomSuccess':
226
+ this.currentRoom = data.roomCode;
227
+ this.emit('joined', { room: data.roomCode, botId: data.botId });
228
+ this._startMiningLoop();
229
+ break;
230
+
231
+ case 'syncBalance':
232
+ this.emit('balance', {
233
+ refining: data.refiningAmount,
234
+ claimable: data.cleanedAmount,
235
+ world: data.worldAmount,
236
+ boost: data.social_boost
237
+ });
238
+ break;
239
+
240
+ case 'miningProgress':
241
+ this.emit('progress', { progress: data.progress });
242
+ break;
243
+
244
+ case 'miningSuccess':
245
+ this.stats.totalMined += data.reward || 0;
246
+ this.stats.miningCount++;
247
+ this.stats.lastRewardAt = Date.now();
248
+ this.emit('reward', {
249
+ reward: data.reward,
250
+ base: data.baseReward,
251
+ bonus: data.bonusReward,
252
+ packId: data.packId,
253
+ totalInPack: data.totalInPack
254
+ });
255
+ break;
256
+
257
+ case 'adenaReward':
258
+ this.emit('effect', { base: data.base, bonus: data.bonus, level: data.level });
259
+ break;
260
+
261
+ case 'reAttestChallenge':
262
+ this._handleChallenge(data, true);
263
+ break;
264
+
265
+ case 'reAttestSuccess':
266
+ this.sessionSecret = data.sessionSecret;
267
+ this.emit('log', '재인증 성공');
268
+ break;
269
+
270
+ case 'sessionRevoked':
271
+ this.emit('error', new Error(`세션 취소: ${data.reason}`));
272
+ this.stop();
273
+ break;
274
+
275
+ case 'blockUpdate':
276
+ this.emit('block', {
277
+ block: data.block,
278
+ pack: data.pack,
279
+ difficulty: data.diff
280
+ });
281
+ break;
282
+
283
+ case 'error':
284
+ this.emit('error', new Error(data.message));
285
+ break;
286
+
287
+ default:
288
+ // 기타 메시지
289
+ break;
290
+ }
291
+ }
292
+
293
+ _handleChallenge(challenge, isReAttest = false) {
294
+ const { challengeId, nonce, difficulty } = challenge;
295
+
296
+ this.emit('log', `${isReAttest ? '재' : ''}인증 챌린지 (difficulty: ${difficulty})`);
297
+
298
+ const solution = this._solveChallenge(nonce, difficulty);
299
+
300
+ this._send({
301
+ type: isReAttest ? 'botReAttest' : 'botAttest',
302
+ data: {
303
+ challengeId,
304
+ solution,
305
+ sessionSecret: isReAttest ? this.sessionSecret : undefined
306
+ }
307
+ });
308
+ }
309
+
310
+ _solveChallenge(nonce, difficulty) {
311
+ const target = '0'.repeat(difficulty);
312
+ let counter = 0;
313
+
314
+ while (counter < 100000000) {
315
+ const attempt = `${nonce}:${counter}`;
316
+ const hash = crypto.createHash('sha256').update(attempt).digest('hex');
317
+
318
+ if (hash.startsWith(target)) {
319
+ return counter.toString();
320
+ }
321
+ counter++;
322
+ }
323
+
324
+ throw new Error('Challenge solving timeout');
325
+ }
326
+
327
+ _joinRoom(roomCode = 'server1') {
328
+ this._send({
329
+ type: 'botJoinRoom',
330
+ data: { roomCode }
331
+ });
332
+ }
333
+
334
+ _startMiningLoop() {
335
+ if (this.miningLoop) return;
336
+
337
+ this._send({ type: 'botMineStart', data: {} });
338
+
339
+ this.miningLoop = setInterval(() => {
340
+ if (this.isConnected && this.currentRoom) {
341
+ this._send({ type: 'botMine', data: {} });
342
+ }
343
+ }, this.options.miningInterval);
344
+
345
+ this.emit('mining', { started: true });
346
+ }
347
+
348
+ _stopMiningLoop() {
349
+ if (this.miningLoop) {
350
+ clearInterval(this.miningLoop);
351
+ this.miningLoop = null;
352
+ }
353
+ }
354
+
355
+ _scheduleReconnect() {
356
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
357
+ this.emit('error', new Error('최대 재연결 시도 초과'));
358
+ return;
359
+ }
360
+
361
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
362
+ this.reconnectAttempts++;
363
+
364
+ this.emit('log', `${delay / 1000}초 후 재연결 시도 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
365
+
366
+ setTimeout(() => {
367
+ this.start();
368
+ }, delay);
369
+ }
370
+
371
+ _send(msg) {
372
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
373
+ this.ws.send(JSON.stringify(msg));
374
+ }
375
+ }
376
+
377
+ _generateFingerprint() {
378
+ return {
379
+ cpuId: os.cpus()[0]?.model || 'unknown',
380
+ hostname: os.hostname(),
381
+ platform: os.platform(),
382
+ arch: os.arch(),
383
+ macAddresses: Object.values(os.networkInterfaces())
384
+ .flat()
385
+ .filter(i => i && !i.internal && i.mac !== '00:00:00:00:00:00')
386
+ .map(i => i.mac)
387
+ .slice(0, 3)
388
+ };
389
+ }
390
+
391
+ _generateBinaryHash() {
392
+ return crypto.createHash('sha256')
393
+ .update(`clawreum-sdk-v1.0.0-${os.hostname()}`)
394
+ .digest('hex');
395
+ }
396
+ }
397
+
398
+ module.exports = ClawreumMiner;