CoolFace
Apppublic

heraldiis/nara-mining-template

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
mine-loop.js239 linesDownload Raw Back to root
1#!/usr/bin/env node2/**3 * NARA PoMI Mining Loop - HF Space Template4 * - Fetch quest every 3s5 * - When new round detected with slots available → AI answer → submit immediately6 */7 8const { execSync, spawn } = require('child_process');9const http = require('http');10const https = require('https');11const fs = require('fs');12const path = require('path');13 14const WALLET = process.env.WALLET_PATH || '/home/user/app/wallets/W1.json';15const LOG_DIR = process.env.LOG_DIR || '/home/user/app/logs';16 17const AI_BASE_URL = process.env.AI_BASE_URL || '';18const AI_API_KEY = process.env.AI_API_KEY || '';19const AI_MODEL = process.env.AI_MODEL || 'gpt-4';20const AGENT_MODEL = process.env.NARA_AGENT_MODEL || AI_MODEL || 'gpt-4';21const POLL_INTERVAL = 3000;22const WALLET_NAME = process.env.WALLET_NAME || 'W1';23 24let lastRound = null;25let submittedRounds = new Set();26 27// Check if wallet exists, if not wait for it28function checkWallet() {29  if (!fs.existsSync(WALLET)) {30    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Wallet not found: ${WALLET}`);31    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Waiting for wallet file...`);32    return false;33  }34  return true;35}36 37function logToFile(msg) {38  const timestamp = new Date().toISOString();39  const entry = `[${timestamp}] [${WALLET_NAME}] ${msg}`;40  console.log(entry);41  42  const logFile = path.join(LOG_DIR, `${WALLET_NAME}.log`);43  try {44    if (!fs.existsSync(LOG_DIR)) {45      fs.mkdirSync(LOG_DIR, { recursive: true });46    }47    fs.appendFileSync(logFile, entry + '\n');48  } catch (e) {49    console.error(`[${WALLET_NAME}] Log write error:`, e.message);50  }51}52 53async function askAI(question) {54  return new Promise((resolve, reject) => {55    if (!AI_BASE_URL) {56      return reject(new Error('AI_BASE_URL not set'));57    }58    59    const isMultiChoice = /\nA\.|\nA\)/.test(question);60    let systemPrompt, userContent;61    62    if (isMultiChoice) {63      systemPrompt = 'You answer multiple choice questions with ONLY the letter (A, B, C, or D). No explanation.';64      userContent = question;65    } else {66      systemPrompt = 'You answer with ONLY the shortest possible exact answer.';67      userContent = question;68    }69 70    const body = JSON.stringify({71      model: AI_MODEL,72      messages: [73        { role: 'system', content: systemPrompt },74        { role: 'user', content: userContent }75      ],76      temperature: 0,77      max_tokens: 1078    });79 80    const base = new URL(AI_BASE_URL);81    const isHttps = base.protocol === 'https:';82    const client = isHttps ? https : http;83    const pathBase = base.pathname.endsWith('/') ? base.pathname.slice(0, -1) : base.pathname;84    const reqPath = `${pathBase}/chat/completions`;85 86    const req = client.request({87      protocol: base.protocol,88      hostname: base.hostname,89      port: base.port || (isHttps ? 443 : 80),90      path: reqPath,91      method: 'POST',92      headers: {93        'Content-Type': 'application/json',94        'Authorization': `Bearer ${AI_API_KEY}`,95        'Content-Length': Buffer.byteLength(body)96      }97    }, (res) => {98      let data = '';99      res.on('data', d => data += d);100      res.on('end', () => {101        try {102          const parsed = JSON.parse(data || '{}');103          const answer = parsed.choices?.[0]?.message?.content?.trim();104          if (!answer) {105            return reject(new Error('AI empty response'));106          }107          resolve(answer);108        } catch (e) {109          reject(new Error('AI parse error'));110        }111      });112    });113    req.on('error', reject);114    req.write(body);115    req.end();116  });117}118 119function getQuest() {120  try {121    const out = execSync(`npx naracli quest get --json --wallet "${WALLET}"`, { timeout: 15000 }).toString();122    return JSON.parse(out);123  } catch (e) {124    return null;125  }126}127 128function submitAnswer(answer) {129  return new Promise((resolve) => {130    const proc = spawn('npx', [131      'naracli', 'quest', 'answer', answer,132      '--relay',133      '--agent', 'openclaw',134      '--model', AGENT_MODEL,135      '--wallet', WALLET136    ], { timeout: 60000 });137 138    let out = '';139    proc.stdout.on('data', d => out += d);140    proc.stderr.on('data', d => out += d);141    proc.on('close', (code) => resolve({ code, out }));142  });143}144 145async function loop() {146  try {147    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] NARA mining loop started`);148    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Wallet: ${WALLET}`);149    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] AI_BASE_URL: ${AI_BASE_URL || 'NOT SET'}`);150    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] AI_MODEL: ${AI_MODEL || 'NOT SET'}`);151 152    // Check if AI_BASE_URL is set153    if (!AI_BASE_URL) {154      console.error(`[${new Date().toISOString()}] [${WALLET_NAME}] ERROR: AI_BASE_URL not set!`);155      console.error(`[${new Date().toISOString()}] [${WALLET_NAME}] Please set AI_BASE_URL in Settings → Secrets`);156      while (true) {157        await new Promise(r => setTimeout(r, 30000)); // Wait forever158      }159    }160 161    // Wait for wallet file to exist162    while (!checkWallet()) {163      console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Waiting for wallet...`);164      await new Promise(r => setTimeout(r, 10000));165    }166    console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Wallet found! Starting mining...`);167 168    while (true) {169      try {170        const quest = getQuest();171        if (!quest) {172          await new Promise(r => setTimeout(r, POLL_INTERVAL));173          continue;174        }175 176        const round = quest.round;177        const slots = quest.remainingRewardSlots;178        const question = quest.question;179 180        if (round !== lastRound) {181          console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] New round: ${round} | slots: ${slots}`);182          console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Q: ${question.slice(0, 100)}`);183          lastRound = round;184        }185 186        if (slots > 0 && !submittedRounds.has(round)) {187          console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Slots available (${slots})! Getting AI answer...`);188          submittedRounds.add(round);189 190          let answer;191          try {192            answer = await askAI(question);193            if (!answer || answer.trim() === '') {194              console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] AI returned empty, skipping`);195              submittedRounds.delete(round);196              await new Promise(r => setTimeout(r, POLL_INTERVAL));197              continue;198            }199            answer = answer.trim().replace(/^["']|["']$/g, '');200            console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] AI answer: "${answer}"`);201          } catch (e) {202            console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] AI error: ${e.message}`);203            submittedRounds.delete(round);204            await new Promise(r => setTimeout(r, POLL_INTERVAL));205            continue;206          }207 208          const { code, out } = await submitAnswer(answer);209          console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Submit result (${code}): ${out.trim().substring(0, 200)}`);210 211          if (out.includes('Congratulations! Reward')) {212            const rewardMatch = out.match(/received: ([\d.]+) NARA/);213            const reward = rewardMatch ? rewardMatch[1] : '?';214            console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] WIN - ${reward} NARA`);215          } else if (out.includes('Correct but no slot')) {216            console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] No Slot`);217          } else if (out.includes('Wrong answer')) {218            console.log(`[${new Date().toISOString()}] [${WALLET_NAME}] Wrong`);219          }220        }221      } catch (e) {222        console.error(`[${new Date().toISOString()}] [${WALLET_NAME}] Loop error: ${e.message}`);223      }224 225      await new Promise(r => setTimeout(r, POLL_INTERVAL));226    }227  } catch (err) {228    console.error(`[${new Date().toISOString()}] [${WALLET_NAME}] FATAL LOOP ERROR: ${err.message}`);229    console.error(err.stack);230    throw err;231  }232}233 234// Start with error handling235loop().catch(err => {236  console.error(`[${WALLET_NAME}] Unhandled error: ${err.message}`);237  process.exit(1);238});239