#!/usr/bin/env node /* * CharmMine miner (Node.js >= 18, single file, no npm packages). * * Finds a (nonce, anchorBlock) pair that satisfies the on-chain proof of work, then prints the * calldata for `mine(uint256 nonce, uint256 anchorBlock, uint8 minTier)` and the price to attach. * It NEVER signs or sends transactions; it only makes read-only JSON-RPC calls (eth_call, * eth_chainId, eth_blockNumber, eth_getBlockByNumber, eth_getCode). Hand the output to your * wallet tooling (e.g. Binance Agentic Wallet: `baw contract-call preview ...` then `execute`). * * Usage: * node mine.js --contract 0x... --wallet 0x... [--rpc https://bsc-dataseed.bnbchain.org] * [--want common|rare|epic|legendary] [--min-tier 0-3|name] * [--max-seconds 50] [--chain-id N] * node mine.js --contract 0x... --wallet 0x... --check --nonce N --anchor BLOCK [--min-tier ...] * dry run: checkWork(address,uint256,uint256) plus an eth_call simulation of mine() from * the wallet with the quoted value, decoding any revert (TooFast, TierBelowMin, ...) * node mine.js --selftest keccak test vectors + hash-rate benchmark * * --min-tier sets mine()'s minTier argument (default: the --want tier). If the tier decided at * execution (after retargets and per-tier supply caps) is lower, the tx reverts with * TierBelowMin(got, min) instead of silently minting a lower tier, e.g. after a front-run. * --min-tier 0 (common) accepts any tier. * * Output (stdout) is the mine.py JSON plus "minTier", "checkedOnChain" and "waitUntil" (null, or the * tooFastUntil() unix time before which mine() reverts TooFast; a WARN is printed, the script does not * wait); progress goes to stderr. Status line "left" per tier = tierReleased - tierMinted (supply * caps are released progressively as charms are minted); "you: 0/1 Legendary, 0/3 Epic" shows the * per-wallet caps (walletTierCap / walletTierMinted). A tier downgrades when either is exhausted, and * the script exits early if --want cannot be reached for this wallet. * The chain id is read from the RPC (eth_chainId); --chain-id is optional and, if given, * must match it (exit 1 otherwise). Every run ends with a result on stdout or an error on stderr. * Exit codes: 0 ok; 1 error / not open / nothing found; 2 --check says do not submit; * 3 time limit hit with an unconfirmed result (do not submit); 4 --check: TooFast, retry later. * * Troubleshooting mine() reverts (run --check with the same --nonce/--anchor to decode them): * * TooFast(windowOpensAt): 16 mints landed within 40 s. Wait until that unix timestamp and * resubmit the same calldata; the proof stays valid (while the anchor is inside the window * and you have not minted since). * * TierBelowMin(got, min): the charm would have been a lower tier than minTier (difficulty * rose or a tier filled up). Mine again, or use --min-tier 0 to accept any tier. * * BadWork / BadAnchor: target rose, or the anchor aged out / is before startBlock. Mine again. * * NotOpen(startBlock): mining opens at that block; anchors older than startBlock are invalid, * so the miner picks anchor = max(latest - 2, startBlock). * * Notes: * * work = keccak256(abi.encodePacked(uint256 chainid, address contract, address miner, * uint32 seq, bytes32 anchor, uint256 nonce)) = 140 bytes, i.e. two keccak blocks (rate 136). * The miner keeps the upper 28 bytes of the nonce fixed (random) and varies only the low * 4 bytes, which all fall into the second block, so the first block's permutation is * computed once per anchor and each attempt costs a single keccak-f. * * The anchor block must be within status().anchorMaxAge blocks when the tx is mined (8000 * blocks, ~60 min on BSC through the EIP-2935 history contract; only 256 blocks on chains * without it, which the script detects with eth_getCode). With a window >= 1000 blocks it * mines up to 600 s per anchor and reports * submitWithinSeconds = max(5, floor(min(anchorMaxAge, 7200) * 0.45 - secondsSinceAnchor) - 60); * with a shorter window it mines up to 30 s per anchor and reports * submitWithinSeconds = max(5, floor(58 - secondsSinceAnchor)). * * Before printing a result it confirms it with the contract's checkWork() (eth_call) and * reports "checkedOnChain". If the contract disagrees it re-reads status() and re-anchors; * only when --max-seconds is hit is an unconfirmed result printed (checkedOnChain false, * exit code 3: do not submit it). * * valueWei is the exact quote. mine() refunds overpayment, but send exactly this value. * * Unlike mine.py the starting nonce is random (crypto.randomBytes) rather than derived from * the start time; this has no effect on validity. */ 'use strict'; const crypto = require('crypto'); const fs = require('fs'); // ----------------------------------------------------------------- keccak-256 (32-bit lanes) // round constants as [lo, hi] pairs const RC = new Int32Array([ 1, 0, 32898, 0, 32906, -2147483648, -2147450880, -2147483648, 32907, 0, -2147483647, 0, -2147450751, -2147483648, 32777, -2147483648, 138, 0, 136, 0, -2147450871, 0, -2147483638, 0, -2147450741, 0, 139, -2147483648, 32905, -2147483648, 32771, -2147483648, 32770, -2147483648, 128, -2147483648, 32778, 0, -2147483638, -2147483648, -2147450751, -2147483648, 32896, -2147483648, -2147483647, 0, -2147450872, -2147483648, ]); // Unrolled keccak-f[1600] (generated). s: Int32Array(50), lane i = x + 5y, lo word at 2i, hi at 2i+1. function keccakF(s) { let a0l = s[0] | 0, a0h = s[1] | 0, a1l = s[2] | 0, a1h = s[3] | 0, a2l = s[4] | 0, a2h = s[5] | 0, a3l = s[6] | 0, a3h = s[7] | 0, a4l = s[8] | 0, a4h = s[9] | 0, a5l = s[10] | 0, a5h = s[11] | 0, a6l = s[12] | 0, a6h = s[13] | 0, a7l = s[14] | 0, a7h = s[15] | 0, a8l = s[16] | 0, a8h = s[17] | 0, a9l = s[18] | 0, a9h = s[19] | 0, a10l = s[20] | 0, a10h = s[21] | 0, a11l = s[22] | 0, a11h = s[23] | 0, a12l = s[24] | 0, a12h = s[25] | 0, a13l = s[26] | 0, a13h = s[27] | 0, a14l = s[28] | 0, a14h = s[29] | 0, a15l = s[30] | 0, a15h = s[31] | 0, a16l = s[32] | 0, a16h = s[33] | 0, a17l = s[34] | 0, a17h = s[35] | 0, a18l = s[36] | 0, a18h = s[37] | 0, a19l = s[38] | 0, a19h = s[39] | 0, a20l = s[40] | 0, a20h = s[41] | 0, a21l = s[42] | 0, a21h = s[43] | 0, a22l = s[44] | 0, a22h = s[45] | 0, a23l = s[46] | 0, a23h = s[47] | 0, a24l = s[48] | 0, a24h = s[49] | 0; let c0l, c0h, c1l, c1h, c2l, c2h, c3l, c3h, c4l, c4h, dl, dh; let b0l, b0h, b1l, b1h, b2l, b2h, b3l, b3h, b4l, b4h, b5l, b5h, b6l, b6h, b7l, b7h, b8l, b8h, b9l, b9h, b10l, b10h, b11l, b11h, b12l, b12h, b13l, b13h, b14l, b14h, b15l, b15h, b16l, b16h, b17l, b17h, b18l, b18h, b19l, b19h, b20l, b20h, b21l, b21h, b22l, b22h, b23l, b23h, b24l, b24h; for (let r = 0; r < 48; r += 2) { c0l = a0l ^ a5l ^ a10l ^ a15l ^ a20l; c0h = a0h ^ a5h ^ a10h ^ a15h ^ a20h; c1l = a1l ^ a6l ^ a11l ^ a16l ^ a21l; c1h = a1h ^ a6h ^ a11h ^ a16h ^ a21h; c2l = a2l ^ a7l ^ a12l ^ a17l ^ a22l; c2h = a2h ^ a7h ^ a12h ^ a17h ^ a22h; c3l = a3l ^ a8l ^ a13l ^ a18l ^ a23l; c3h = a3h ^ a8h ^ a13h ^ a18h ^ a23h; c4l = a4l ^ a9l ^ a14l ^ a19l ^ a24l; c4h = a4h ^ a9h ^ a14h ^ a19h ^ a24h; dl = c4l ^ ((c1l << 1) | (c1h >>> 31)); dh = c4h ^ ((c1h << 1) | (c1l >>> 31)); a0l ^= dl; a0h ^= dh; a5l ^= dl; a5h ^= dh; a10l ^= dl; a10h ^= dh; a15l ^= dl; a15h ^= dh; a20l ^= dl; a20h ^= dh; dl = c0l ^ ((c2l << 1) | (c2h >>> 31)); dh = c0h ^ ((c2h << 1) | (c2l >>> 31)); a1l ^= dl; a1h ^= dh; a6l ^= dl; a6h ^= dh; a11l ^= dl; a11h ^= dh; a16l ^= dl; a16h ^= dh; a21l ^= dl; a21h ^= dh; dl = c1l ^ ((c3l << 1) | (c3h >>> 31)); dh = c1h ^ ((c3h << 1) | (c3l >>> 31)); a2l ^= dl; a2h ^= dh; a7l ^= dl; a7h ^= dh; a12l ^= dl; a12h ^= dh; a17l ^= dl; a17h ^= dh; a22l ^= dl; a22h ^= dh; dl = c2l ^ ((c4l << 1) | (c4h >>> 31)); dh = c2h ^ ((c4h << 1) | (c4l >>> 31)); a3l ^= dl; a3h ^= dh; a8l ^= dl; a8h ^= dh; a13l ^= dl; a13h ^= dh; a18l ^= dl; a18h ^= dh; a23l ^= dl; a23h ^= dh; dl = c3l ^ ((c0l << 1) | (c0h >>> 31)); dh = c3h ^ ((c0h << 1) | (c0l >>> 31)); a4l ^= dl; a4h ^= dh; a9l ^= dl; a9h ^= dh; a14l ^= dl; a14h ^= dh; a19l ^= dl; a19h ^= dh; a24l ^= dl; a24h ^= dh; b0l = a0l; b0h = a0h; b16l = (a5h << 4) | (a5l >>> 28); b16h = (a5l << 4) | (a5h >>> 28); b7l = (a10l << 3) | (a10h >>> 29); b7h = (a10h << 3) | (a10l >>> 29); b23l = (a15h << 9) | (a15l >>> 23); b23h = (a15l << 9) | (a15h >>> 23); b14l = (a20l << 18) | (a20h >>> 14); b14h = (a20h << 18) | (a20l >>> 14); b10l = (a1l << 1) | (a1h >>> 31); b10h = (a1h << 1) | (a1l >>> 31); b1l = (a6h << 12) | (a6l >>> 20); b1h = (a6l << 12) | (a6h >>> 20); b17l = (a11l << 10) | (a11h >>> 22); b17h = (a11h << 10) | (a11l >>> 22); b8l = (a16h << 13) | (a16l >>> 19); b8h = (a16l << 13) | (a16h >>> 19); b24l = (a21l << 2) | (a21h >>> 30); b24h = (a21h << 2) | (a21l >>> 30); b20l = (a2h << 30) | (a2l >>> 2); b20h = (a2l << 30) | (a2h >>> 2); b11l = (a7l << 6) | (a7h >>> 26); b11h = (a7h << 6) | (a7l >>> 26); b2l = (a12h << 11) | (a12l >>> 21); b2h = (a12l << 11) | (a12h >>> 21); b18l = (a17l << 15) | (a17h >>> 17); b18h = (a17h << 15) | (a17l >>> 17); b9l = (a22h << 29) | (a22l >>> 3); b9h = (a22l << 29) | (a22h >>> 3); b5l = (a3l << 28) | (a3h >>> 4); b5h = (a3h << 28) | (a3l >>> 4); b21l = (a8h << 23) | (a8l >>> 9); b21h = (a8l << 23) | (a8h >>> 9); b12l = (a13l << 25) | (a13h >>> 7); b12h = (a13h << 25) | (a13l >>> 7); b3l = (a18l << 21) | (a18h >>> 11); b3h = (a18h << 21) | (a18l >>> 11); b19l = (a23h << 24) | (a23l >>> 8); b19h = (a23l << 24) | (a23h >>> 8); b15l = (a4l << 27) | (a4h >>> 5); b15h = (a4h << 27) | (a4l >>> 5); b6l = (a9l << 20) | (a9h >>> 12); b6h = (a9h << 20) | (a9l >>> 12); b22l = (a14h << 7) | (a14l >>> 25); b22h = (a14l << 7) | (a14h >>> 25); b13l = (a19l << 8) | (a19h >>> 24); b13h = (a19h << 8) | (a19l >>> 24); b4l = (a24l << 14) | (a24h >>> 18); b4h = (a24h << 14) | (a24l >>> 18); a0l = b0l ^ (~b1l & b2l); a0h = b0h ^ (~b1h & b2h); a1l = b1l ^ (~b2l & b3l); a1h = b1h ^ (~b2h & b3h); a2l = b2l ^ (~b3l & b4l); a2h = b2h ^ (~b3h & b4h); a3l = b3l ^ (~b4l & b0l); a3h = b3h ^ (~b4h & b0h); a4l = b4l ^ (~b0l & b1l); a4h = b4h ^ (~b0h & b1h); a5l = b5l ^ (~b6l & b7l); a5h = b5h ^ (~b6h & b7h); a6l = b6l ^ (~b7l & b8l); a6h = b6h ^ (~b7h & b8h); a7l = b7l ^ (~b8l & b9l); a7h = b7h ^ (~b8h & b9h); a8l = b8l ^ (~b9l & b5l); a8h = b8h ^ (~b9h & b5h); a9l = b9l ^ (~b5l & b6l); a9h = b9h ^ (~b5h & b6h); a10l = b10l ^ (~b11l & b12l); a10h = b10h ^ (~b11h & b12h); a11l = b11l ^ (~b12l & b13l); a11h = b11h ^ (~b12h & b13h); a12l = b12l ^ (~b13l & b14l); a12h = b12h ^ (~b13h & b14h); a13l = b13l ^ (~b14l & b10l); a13h = b13h ^ (~b14h & b10h); a14l = b14l ^ (~b10l & b11l); a14h = b14h ^ (~b10h & b11h); a15l = b15l ^ (~b16l & b17l); a15h = b15h ^ (~b16h & b17h); a16l = b16l ^ (~b17l & b18l); a16h = b16h ^ (~b17h & b18h); a17l = b17l ^ (~b18l & b19l); a17h = b17h ^ (~b18h & b19h); a18l = b18l ^ (~b19l & b15l); a18h = b18h ^ (~b19h & b15h); a19l = b19l ^ (~b15l & b16l); a19h = b19h ^ (~b15h & b16h); a20l = b20l ^ (~b21l & b22l); a20h = b20h ^ (~b21h & b22h); a21l = b21l ^ (~b22l & b23l); a21h = b21h ^ (~b22h & b23h); a22l = b22l ^ (~b23l & b24l); a22h = b22h ^ (~b23h & b24h); a23l = b23l ^ (~b24l & b20l); a23h = b23h ^ (~b24h & b20h); a24l = b24l ^ (~b20l & b21l); a24h = b24h ^ (~b20h & b21h); a0l ^= RC[r]; a0h ^= RC[r + 1]; } s[0] = a0l; s[1] = a0h; s[2] = a1l; s[3] = a1h; s[4] = a2l; s[5] = a2h; s[6] = a3l; s[7] = a3h; s[8] = a4l; s[9] = a4h; s[10] = a5l; s[11] = a5h; s[12] = a6l; s[13] = a6h; s[14] = a7l; s[15] = a7h; s[16] = a8l; s[17] = a8h; s[18] = a9l; s[19] = a9h; s[20] = a10l; s[21] = a10h; s[22] = a11l; s[23] = a11h; s[24] = a12l; s[25] = a12h; s[26] = a13l; s[27] = a13h; s[28] = a14l; s[29] = a14h; s[30] = a15l; s[31] = a15h; s[32] = a16l; s[33] = a16h; s[34] = a17l; s[35] = a17h; s[36] = a18l; s[37] = a18h; s[38] = a19l; s[39] = a19h; s[40] = a20l; s[41] = a20h; s[42] = a21l; s[43] = a21h; s[44] = a22l; s[45] = a22h; s[46] = a23l; s[47] = a23h; s[48] = a24l; s[49] = a24h; } /** Absorb one 136-byte block (bytes at buf[off..off+135]) into state s and permute. */ function absorb(s, buf, off) { for (let i = 0; i < 34; i++) { const p = off + 4 * i; s[i] ^= buf[p] | (buf[p + 1] << 8) | (buf[p + 2] << 16) | (buf[p + 3] << 24); } keccakF(s); } /** Generic keccak-256 (Ethereum padding 0x01 .. 0x80). Returns a 32-byte Buffer. */ function keccak256(data) { data = Buffer.from(data); const rate = 136; const padLen = rate - (data.length % rate); const buf = Buffer.alloc(data.length + padLen); data.copy(buf); buf[data.length] = 0x01; buf[buf.length - 1] |= 0x80; const s = new Int32Array(50); for (let off = 0; off < buf.length; off += rate) absorb(s, buf, off); return digestBytes(s); } // ----------------------------------------------------------------- fast work-hash path // Preimage (140 bytes): head(108) = chainid(32) | contract(20) | miner(20) | seq(4) | anchor(32), // then nonce(32, big-endian) = hi28 (fixed) || lo32 (counter). // Block 1 = head + hi28 (136 bytes, constant per anchor). Block 2 = lo32 BE | 0x01 | 0.. | 0x80. class WorkHasher { constructor(head108, hi28) { const blk = Buffer.concat([head108, hi28]); if (blk.length !== 136) throw new Error('bad preimage head'); this.base = new Int32Array(50); absorb(this.base, blk, 0); // constant part of block 2: byte 4 = 0x01 (lane 0, hi word, low byte), // byte 135 = 0x80 (lane 16, hi word, top byte) this.base[1] ^= 0x01; this.base[33] ^= 0x80000000 | 0; this.s = new Int32Array(50); } /** Hash with nonce low word `lo` (uint32). Returns the state; digest = words 0..7 (LE). */ run(lo) { const s = this.s; s.set(this.base); // bytes 0..3 of block 2 are lo in big-endian; lane words are little-endian -> byte swap s[0] ^= ((lo >>> 24) & 0xff) | (((lo >>> 16) & 0xff) << 8) | (((lo >>> 8) & 0xff) << 16) | ((lo & 0xff) << 24); keccakF(s); return s; } } /** Big-endian uint32 value of digest word k (0..7) held in state s. */ function digestWordBE(s, k) { const w = s[k]; return (((w & 0xff) << 24) | (((w >>> 8) & 0xff) << 16) | (((w >>> 16) & 0xff) << 8) | ((w >>> 24) & 0xff)) >>> 0; } function digestBytes(s) { const out = Buffer.alloc(32); for (let i = 0; i < 8; i++) out.writeInt32LE(s[i], 4 * i); return out; } // ----------------------------------------------------------------- helpers const hex = (b) => Buffer.from(b).toString('hex'); const fromHex = (h) => Buffer.from(String(h).replace(/^0x/i, ''), 'hex'); const u256 = (x) => Buffer.from(BigInt(x).toString(16).padStart(64, '0'), 'hex'); const toBig = (b) => (b.length ? BigInt('0x' + hex(b)) : 0n); const padAddr = (a) => Buffer.concat([Buffer.alloc(12), fromHex(a)]); const sel = (sig) => keccak256(Buffer.from(sig, 'utf8')).subarray(0, 4); // All output is written synchronously (fs.writeSync). process.stdout/stderr are asynchronous on // Windows consoles (and on some pipes), so a write followed by process.exit() could be lost; // nothing below calls process.exit() before its output has been written. let resultPrinted = false; class Exit extends Error { constructor(code) { super('exit ' + code); this.code = code; } } function writeFd(fd, text) { const buf = Buffer.from(text, 'utf8'); let off = 0; while (off < buf.length) { try { off += fs.writeSync(fd, buf, off); } catch (e) { if (e.code !== 'EAGAIN') throw e; } } } function writeOut(text) { writeFd(1, text + '\n'); resultPrinted = true; } function log(msg) { try { writeFd(2, msg + '\n'); } catch { /* stderr gone: nothing to do */ } } /** Print an error to stderr and stop with the given exit code (unwinds to main()). */ function die(msg, code = 1) { log(msg); resultPrinted = true; throw new Exit(code); } const bitLength = (x) => (x === 0n ? 0 : x.toString(2).length); /** Marks a number to be printed the way Python's json prints a float. */ class PyFloat { constructor(v) { this.v = v; } } function pyFloat(x) { if (!Number.isFinite(x)) return String(x); if (x === 0) return '0.0'; const ax = Math.abs(x); if (ax < 1e-4 || ax >= 1e16) { const [m, e] = x.toExponential().split('e'); const sign = e[0] === '-' ? '-' : '+'; return `${m}e${sign}${e.replace(/^[+-]/, '').padStart(2, '0')}`; } const s = String(x); return /[.e]/.test(s) ? s : s + '.0'; } /** Equivalent of Python json.dumps(obj, indent=2) for a flat object: big ints unquoted, floats Python-style. */ function pyJson(obj) { const raw = {}; const tagged = {}; let k = 0; for (const [key, v] of Object.entries(obj)) { if (typeof v === 'bigint' || v instanceof PyFloat) { const t = `@@RAW${k++}@@`; raw[t] = typeof v === 'bigint' ? v.toString() : pyFloat(v.v); tagged[key] = t; } else tagged[key] = v; } return JSON.stringify(tagged, null, 2).replace(/"(@@RAW\d+@@)"/g, (_, t) => raw[t]); } // ----------------------------------------------------------------- rpc (read-only) let rpcId = 0; async function rpc(url, method, params) { let res; // up to 3 attempts on network errors: the mining loop blocks the event loop for minutes, so the // server usually closes the idle keep-alive socket and the first request afterwards gets ECONNRESET for (let attempt = 1; ; attempt++) { try { res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: ++rpcId, method, params }), signal: AbortSignal.timeout(30000), }); break; } catch (e) { const why = (e.cause && (e.cause.code || e.cause.message)) || e.message; if (attempt >= 3 || why === 'bad port') throw new Error(`rpc ${method} to ${url} failed: ${why}`); await new Promise((r) => setTimeout(r, 300 * attempt)); } } let r; try { r = await res.json(); } catch { throw new Error(`rpc ${method} to ${url}: HTTP ${res.status}, response is not JSON`); } if (r.error) { const err = new Error(`rpc ${method}: ${JSON.stringify(r.error)}`); err.rpcError = r.error; // keeps revert data for simulateMine() throw err; } return r.result; } const ethCall = (url, to, data) => rpc(url, 'eth_call', [{ to, data: '0x' + hex(data) }, 'latest']); function words(rawHex) { const b = fromHex(rawHex); const out = []; for (let i = 0; i + 32 <= b.length; i += 32) out.push(toBig(b.subarray(i, i + 32))); return out; } const TIER = ['Common', 'Rare', 'Epic', 'Legendary']; const DIV = { common: 1n, rare: 4n, epic: 20n, legendary: 200n }; const TIER_INDEX = { common: 0, rare: 1, epic: 2, legendary: 3 }; const STATUS_FIELDS = ['totalMinted', 'remaining', 'currentPrice', 'yourPrice', 'epoch', 'nextPriceAt', 'target', 'easiestTarget', 'freeRemaining', 'open', 'startBlock', 'hasAgentId', 'mintedByYou', 'anchorMaxAge']; // ----------------------------------------------------------------- cli function parseArgs(argv) { const a = { rpc: 'https://bsc-dataseed.bnbchain.org', want: 'common', maxSeconds: 0, chainId: undefined }; const flags = { '--contract': 'contract', '--wallet': 'wallet', '--rpc': 'rpc', '--want': 'want', '--max-seconds': 'maxSeconds', '--chain-id': 'chainId', '--nonce': 'nonce', '--anchor': 'anchor', '--min-tier': 'minTier' }; for (let i = 0; i < argv.length; i++) { let arg = argv[i]; let val; if (arg === '--selftest') { a.selftest = true; continue; } if (arg === '--check') { a.check = true; continue; } if (arg === '-h' || arg === '--help') { a.help = true; continue; } const eq = arg.indexOf('='); if (eq > 0) { val = arg.slice(eq + 1); arg = arg.slice(0, eq); } const key = flags[arg]; if (!key) die(`unknown argument: ${argv[i]}`); if (val === undefined) { val = argv[++i]; if (val === undefined) die(`missing value for ${arg}`); } a[key] = val; } a.maxSeconds = Number(a.maxSeconds); if (a.chainId !== undefined) a.chainId = Number(a.chainId); if (!Number.isFinite(a.maxSeconds) || a.maxSeconds < 0) die('--max-seconds must be a number >= 0'); if (a.chainId !== undefined && (!Number.isInteger(a.chainId) || a.chainId <= 0)) die('--chain-id must be a positive integer'); if (!(a.want in DIV)) die(`--want must be one of ${Object.keys(DIV).join(', ')}`); // minTier for mine(nonce, anchorBlock, minTier): default = the --want tier; 0 accepts any tier if (a.minTier === undefined) a.minTier = TIER_INDEX[a.want]; else { const v = String(a.minTier).toLowerCase(); a.minTier = v in TIER_INDEX ? TIER_INDEX[v] : /^[0-3]$/.test(v) ? Number(v) : NaN; if (Number.isNaN(a.minTier)) die('--min-tier must be 0-3 or common|rare|epic|legendary'); } return a; } /** Read eth_chainId from the RPC; --chain-id, if given, must match it. Sets a.chainId. */ async function resolveChainId(a) { const rpcChain = Number(BigInt(await rpc(a.rpc, 'eth_chainId', []))); if (a.chainId !== undefined && a.chainId !== rpcChain) { die(`--chain-id ${a.chainId} does not match the RPC's chain id ${rpcChain} (${a.rpc}). ` + 'The chain id is part of the hashed preimage, so every proof would be rejected by the contract. ' + 'Drop --chain-id (it is read from the RPC) or point --rpc at the right chain.'); } a.chainId = rpcChain; } function needAddr(a, name) { const v = a[name]; if (!v) die(`--${name} is required`); if (!/^0x[0-9a-fA-F]{40}$/.test(v)) die(`--${name} must be a 0x-prefixed 20-byte address`); } // ----------------------------------------------------------------- --selftest function selftest() { let ok = true; const vectors = [ ['', 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'], ['Transfer(address,address,uint256)', 'ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], ['transfer(address,uint256)', 'a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b'], ]; for (const [msg, want] of vectors) { const got = hex(keccak256(Buffer.from(msg, 'utf8'))); const pass = got === want; ok = ok && pass; writeOut(`${pass ? 'PASS' : 'FAIL'} keccak256(${JSON.stringify(msg)}) = ${got}`); } for (const sig of ['mine(uint256,uint256,uint8)', 'status(address)', 'checkWork(address,uint256,uint256)', 'rarityTarget(uint256)', 'tierCap(uint8)', 'tierReleased(uint8)', 'tierMinted(uint256)', 'tooFastUntil()', 'walletTierCap(uint8)', 'walletTierMinted(address,uint8)']) { writeOut(`info selector ${sig} = 0x${hex(sel(sig))}`); } // fast 140-byte path must equal the generic keccak256 of the full preimage const head = crypto.randomBytes(108); const hi28 = crypto.randomBytes(28); const wh = new WorkHasher(head, hi28); let fastOk = true; const los = [0, 1, 0x7fffffff, 0x80000000, 0xffffffff, crypto.randomBytes(4).readUInt32BE(0)]; for (const lo of los) { const lob = Buffer.alloc(4); lob.writeUInt32BE(lo >>> 0); const ref = hex(keccak256(Buffer.concat([head, hi28, lob]))); const s = wh.run(lo >>> 0); const w0 = digestWordBE(s, 0).toString(16).padStart(8, '0'); if (hex(digestBytes(s)) !== ref || w0 !== ref.slice(0, 8)) fastOk = false; } writeOut(`${fastOk ? 'PASS' : 'FAIL'} fast 140-byte work hash == generic keccak256 (${los.length} nonces incl. edge values)`); ok = ok && fastOk; // benchmark: the exact per-attempt work done by the miner loop (permutation + fast reject) let n = 0; let hits = 0; const t0 = process.hrtime.bigint(); const end = t0 + 3000000000n; do { for (let i = 0; i < 20000; i++) { if (digestWordBE(wh.run(n >>> 0), 0) <= 0x3fff) hits++; n++; } } while (process.hrtime.bigint() < end); const secs = Number(process.hrtime.bigint() - t0) / 1e9; const rate = n / secs; writeOut(`bench ${n} work hashes in ${secs.toFixed(2)} s = ${Math.round(rate).toLocaleString('en-US')} hashes/s ` + `(${hits} below 2^238 easiest target, expected ~${(n / 262144).toFixed(1)})`); writeOut(`expected time per easiest-target solution (2^18 hashes): ${(262144 / rate).toFixed(2)} s`); writeOut(ok ? 'selftest OK' : 'selftest FAILED'); process.exitCode = ok ? 0 : 1; } // ----------------------------------------------------------------- contract views // EIP-2935 block-hash history contract (same constant as CharmMine.HISTORY) const HISTORY = '0x0000F90827F1C53a10cb7A02335B175320002935'; const SECONDS_PER_BLOCK = 0.45; // BSC block time used to turn anchorMaxAge into seconds async function readStatus(a) { const w = words(await ethCall(a.rpc, a.contract, Buffer.concat([sel('status(address)'), padAddr(a.wallet)]))); if (w.length < STATUS_FIELDS.length) die(`unexpected status() return data (${w.length} words, expected ${STATUS_FIELDS.length}; wrong contract or old ABI?)`); const st = {}; STATUS_FIELDS.forEach((f, i) => { st[f] = w[i]; }); return st; } /** * Per-tier supply for tiers 0..3: [{ cap, released, minted, wcap, wminted }] * (cap / wcap 0 = unlimited; wcap / wminted are the per-wallet cap and this wallet's count). */ async function readTiers(a) { const view = async (sig, ...args) => { const [v] = words(await ethCall(a.rpc, a.contract, Buffer.concat([sel(sig), ...args]))); if (v === undefined) die(`unexpected ${sig} return data (old contract?)`); return v; }; const out = []; for (let t = 0; t < 4; t++) { // tierCap/tierReleased take uint8; tierMinted is a public uint32[4], whose getter takes uint256 out.push({ cap: await view('tierCap(uint8)', u256(t)), released: await view('tierReleased(uint8)', u256(t)), // caps open up progressively with total minted minted: await view('tierMinted(uint256)', u256(t)), wcap: await view('walletTierCap(uint8)', u256(t)), wminted: await view('walletTierMinted(address,uint8)', padAddr(a.wallet), u256(t)), }); } return out; } /** Why `tier` downgrades right now: 'supply', 'wallet' or null. Same rule as CharmMine._applyCaps(tier, miner). */ function tierBlocked(t) { if (t.minted >= t.released) return 'supply'; if (t.wcap !== 0n && t.wminted >= t.wcap) return 'wallet'; return null; } function applyCaps(tier, tiers) { while (tier > 0 && tierBlocked(tiers[tier])) tier--; return tier; } /** "Rare 2 left (released 5/40), ... you: 0/1 Legendary, 0/3 Epic" */ function tiersLine(tiers) { const supply = tiers.map((t, i) => (t.cap === 0n ? `${TIER[i]} unlimited` : `${TIER[i]} ${t.released > t.minted ? t.released - t.minted : 0n} left (released ${t.released}/${t.cap})`)).join(', '); const you = [3, 2, 1, 0].filter((i) => tiers[i].wcap !== 0n).map((i) => `${tiers[i].wminted}/${tiers[i].wcap} ${TIER[i]}`); return supply + (you.length ? ` you: ${you.join(', ')}` : ''); } /** tooFastUntil(): 0 = a mint now is fine; else the unix time before which the next mint reverts TooFast. */ async function tooFastUntil(a) { try { const [v] = words(await ethCall(a.rpc, a.contract, sel('tooFastUntil()'))); if (v === undefined || v === 0n) return null; log(`WARN: 16 mints in this window already; mine() reverts TooFast until unix ${v} ` + `(${new Date(Number(v) * 1000).toISOString()}). Submit after that; the proof stays valid.`); return v; } catch (e) { if (e instanceof Exit) throw e; log(`WARN: tooFastUntil() call failed: ${e.message}`); return null; } } /** Usable anchor age in blocks: status().anchorMaxAge, but only 256 if the chain has no EIP-2935 history contract. */ async function anchorWindowBlocks(a, st, verbose) { const maxAge = Number(st.anchorMaxAge); if (maxAge <= 256) return maxAge; const code = await rpc(a.rpc, 'eth_getCode', [HISTORY, 'latest']); if (code && code !== '0x') return maxAge; if (verbose) log(`note: no EIP-2935 history contract on this chain; anchors limited to 256 blocks (contract allows ${maxAge})`); return 256; } /** Average block time over the last 100 blocks (falls back to 0.45 s). */ async function blockTime(a, latest) { try { const n = Math.min(100, latest); if (n < 1) return SECONDS_PER_BLOCK; const [b1, b0] = await Promise.all([ rpc(a.rpc, 'eth_getBlockByNumber', ['0x' + latest.toString(16), false]), rpc(a.rpc, 'eth_getBlockByNumber', ['0x' + (latest - n).toString(16), false]), ]); const dt = (Number(BigInt(b1.timestamp)) - Number(BigInt(b0.timestamp))) / n; return dt > 0 ? dt : SECONDS_PER_BLOCK; } catch (e) { if (e instanceof Exit) throw e; return SECONDS_PER_BLOCK; } } /** Dies with "opens at block N (in ~X s)" or "sold out" when status().open is false. */ async function requireOpen(a, st) { if (st.open) return; if (st.remaining === 0n) die('sold out: all 4096 charms are minted'); const latest = Number(BigInt(await rpc(a.rpc, 'eth_blockNumber', []))); const start = Number(st.startBlock); const left = Math.max(0, start - latest); const secs = Math.round(left * await blockTime(a, latest)); die(`mint not open yet: opens at block ${start} (in ~${secs} s, ${left} blocks; latest ${latest})`); } /** eth_call checkWork(miner, nonce, anchorBlock) -> { ok, tier, work, target }; tier is after per-tier caps. */ async function checkWorkOnChain(a, nonce, anchorBlock) { const data = Buffer.concat([sel('checkWork(address,uint256,uint256)'), padAddr(a.wallet), u256(nonce), u256(anchorBlock)]); const w = words(await ethCall(a.rpc, a.contract, data)); if (w.length < 4) die('unexpected checkWork return data (wrong contract?)'); return { ok: w[0] !== 0n, tier: Number(w[1]), work: w[2], target: w[3] }; } const mineCalldata = (nonce, anchorBlock, minTier) => Buffer.concat([sel('mine(uint256,uint256,uint8)'), u256(nonce), u256(anchorBlock), u256(minTier)]); // custom errors of CharmMine, decoded from revert data const ERRORS = {}; for (const [sig, types] of [['NotOpen(uint64)', ['startBlock']], ['TooFast(uint64)', ['windowOpensAt']], ['TierBelowMin(uint8,uint8)', ['got', 'min']], ['NoAgentId()', []], ['SoldOut()', []], ['WrongPayment(uint256)', ['required']], ['BadAnchor()', []], ['BadWork()', []], ['PayoutFailed()', []], ['RefundFailed()', []]]) { ERRORS[hex(sel(sig))] = { name: sig.slice(0, sig.indexOf('(')), fields: types }; } const ADVICE = { TooFast: (v) => `16 mints landed within 40 s. Wait until unix ${v.windowOpensAt} (${new Date(Number(v.windowOpensAt) * 1000).toISOString()}) ` + 'and resubmit the same calldata; the proof stays valid (as long as the anchor is within the window and you have not minted since).', TierBelowMin: (v) => `the tier at execution would be ${TIER[Number(v.got)]} (after retargets and per-tier caps), below minTier ${TIER[Number(v.min)]}. ` + 'Mine again, or pass --min-tier 0 to accept any tier.', BadWork: () => 'work >= current target (difficulty rose, or seq/chain mismatch). Mine again.', BadAnchor: () => 'anchor block too old, too new, before startBlock, or its hash is unavailable. Mine again with a fresh anchor.', NotOpen: (v) => `mining opens at block ${v.startBlock}.`, NoAgentId: () => 'this wallet holds no .ai Agent ID.', SoldOut: () => 'all charms are minted.', WrongPayment: (v) => `send at least ${v.required} wei.`, }; /** eth_call mine(nonce, anchorBlock, minTier) from the wallet with the quoted value: what would happen now. */ async function simulateMine(a, st, nonce, anchorBlock, minTier) { const tx = { from: a.wallet, to: a.contract, data: '0x' + hex(mineCalldata(nonce, anchorBlock, minTier)), value: '0x' + st.yourPrice.toString(16) }; try { const r = await rpc(a.rpc, 'eth_call', [tx, 'latest']); const [tokenId] = words(r); return { ok: true, error: null, tokenId: tokenId === undefined ? null : tokenId.toString(), advice: 'would mint now' }; } catch (e) { if (e instanceof Exit) throw e; const err = e.rpcError; let d = err && err.data; if (d && typeof d === 'object') d = d.data || d.result || null; const raw = typeof d === 'string' && /^0x[0-9a-fA-F]*$/.test(d) ? fromHex(d) : null; if (!raw || raw.length < 4) return { ok: false, error: err ? err.message : e.message, advice: 'eth_call failed without revert data' }; const spec = ERRORS[hex(raw.subarray(0, 4))]; if (!spec) return { ok: false, error: `revert 0x${hex(raw.subarray(0, 4))}`, advice: '' }; const vals = {}; const w = words('0x' + hex(raw.subarray(4))); spec.fields.forEach((f, i) => { vals[f] = w[i] === undefined ? null : w[i].toString(); }); return { ok: false, error: spec.name, args: vals, advice: ADVICE[spec.name] ? ADVICE[spec.name](vals) : '' }; } } // ----------------------------------------------------------------- --check async function check(a) { needAddr(a, 'contract'); needAddr(a, 'wallet'); if (a.nonce === undefined || a.anchor === undefined) die('--check needs --nonce and --anchor'); await resolveChainId(a); let nonce; let anchor; try { nonce = BigInt(a.nonce); anchor = BigInt(a.anchor); } catch { die('--nonce/--anchor must be integers (decimal or 0x hex)'); } const minTier = a.minTier; const chk = await checkWorkOnChain(a, nonce, anchor); const st = await readStatus(a); const latest = BigInt(await rpc(a.rpc, 'eth_blockNumber', [])); const maxAge = BigInt(await anchorWindowBlocks(a, st, false)); const sim = await simulateMine(a, st, nonce, anchor, minTier); writeOut(pyJson({ contract: a.contract, wallet: a.wallet, nonce, anchorBlock: anchor, ok: chk.ok, tier: chk.ok ? TIER[chk.tier] : null, minTier: TIER[minTier], work: '0x' + chk.work.toString(16).padStart(64, '0'), target: '0x' + chk.target.toString(16).padStart(64, '0'), latestBlock: latest, blocksLeft: chk.ok ? maxAge - (latest - anchor) : 0n, wouldMintNow: sim.ok, revert: sim.ok ? null : sim.error, revertArgs: sim.args || null, advice: sim.advice, calldata: '0x' + hex(mineCalldata(nonce, anchor, minTier)), })); if (!chk.ok) die('contract says NO: anchor too old/too new or before startBlock, wrong wallet/seq, or work >= target', 2); if (!sim.ok && sim.error === 'TooFast') die(`TooFast: ${sim.advice}`, 4); if (!sim.ok) die(`mine() would revert now: ${sim.error}. ${sim.advice}`, 2); } // ----------------------------------------------------------------- mining async function mine(a) { needAddr(a, 'contract'); needAddr(a, 'wallet'); const url = a.rpc; await resolveChainId(a); let st = await readStatus(a); const maxAge = await anchorWindowBlocks(a, st, true); const longWindow = maxAge >= 1000; const minePerAnchorMs = longWindow ? 600000 : 30000; /** seconds left to get the tx mined, given seconds elapsed since the anchor was fetched */ const submitWithin = (sinceAnchor) => (longWindow ? Math.max(5, Math.floor(Math.min(maxAge, 7200) * SECONDS_PER_BLOCK - sinceAnchor) - 60) : Math.max(5, Math.trunc(58 - sinceAnchor))); const minTier = a.minTier; const wantIdx = Math.max(TIER_INDEX[a.want], minTier); // never stop on a tier that minTier would revert if (wantIdx > TIER_INDEX[a.want]) log(`note: --min-tier ${TIER[minTier]} is above --want ${a.want}; mining for ${TIER[wantIdx]}`); const wantDiv = DIV[TIER[wantIdx].toLowerCase()]; const t0 = Date.now(); const elapsed = () => (Date.now() - t0) / 1000; const timeUp = () => a.maxSeconds && elapsed() > a.maxSeconds; let tries = 0; let first = true; let rejected = null; // last result the contract did not confirm: { best, st, why, anchoredAt } for (;;) { if (!first) st = await readStatus(a); // seq / target / price may have changed const tiers = await readTiers(a); log(`minted ${st.totalMinted}/4096 price for you ${(Number(st.yourPrice) / 1e18).toFixed(4)} BNB ` + `target 2^${bitLength(st.target) - 1} open=${st.open ? 'True' : 'False'} agentId=${st.hasAgentId ? 'True' : 'False'}` + ` left: ${tiersLine(tiers)}` + (first ? ` free left ${st.freeRemaining} anchor window ${maxAge} blocks (mine <= ${minePerAnchorMs / 1000} s per anchor) minTier ${TIER[minTier]}` : '')); first = false; if (!st.hasAgentId) die('wallet has no Agent ID; mint would revert'); await requireOpen(a, st); // --want must be reachable for this wallet: a capped tier would downgrade on chain anyway if (applyCaps(wantIdx, tiers) < wantIdx) { const t = tiers[wantIdx]; const why = tierBlocked(t) === 'wallet' ? `this wallet already holds ${t.wminted} of its ${t.wcap} allowed ${TIER[wantIdx]}` : `${TIER[wantIdx]} has no released supply left (minted ${t.minted} of ${t.released} released, cap ${t.cap})`; const best = applyCaps(wantIdx, tiers); die(`--want ${TIER[wantIdx].toLowerCase()} cannot be satisfied: ${why}; such a charm would mint as ${TIER[best]}. ` + `Use --want ${TIER[best].toLowerCase()} or lower.`); } const target = st.target; // validity: work < effectiveTarget() // top 32 bits of the target: a hash whose first big-endian word is larger can never be < target const targetTop = Number(target >> 224n); // anchor = max(latest - 2, startBlock): anchors older than startBlock are invalid, and the anchor // must be strictly below the block that includes the tx (and below `latest` for checkWork) const startBlock = Number(st.startBlock); let latest = Number(BigInt(await rpc(url, 'eth_blockNumber', []))); const anchorBlock = Math.max(latest - 2, startBlock); if (anchorBlock >= latest) log(` waiting for block ${anchorBlock + 1} (anchor = startBlock ${startBlock})`); while (anchorBlock >= latest) { await new Promise((r) => setTimeout(r, 1000)); latest = Number(BigInt(await rpc(url, 'eth_blockNumber', []))); } const blk = await rpc(url, 'eth_getBlockByNumber', ['0x' + anchorBlock.toString(16), false]); if (!blk || !blk.hash) die(`could not read block ${anchorBlock}`); // rarity bands: the contract judges tiers against rarityTarget(anchorBlock) = // min(effectiveTarget(), targetAt(anchorBlock)), which can be stricter than the validity target const rw = words(await ethCall(url, a.contract, Buffer.concat([sel('rarityTarget(uint256)'), u256(anchorBlock)]))); if (rw.length < 1) die('unexpected rarityTarget() return data (old contract?)'); const rarity = rw[0]; const t200 = rarity / 200n; const t20 = rarity / 20n; const t4 = rarity / 4n; // a Common-or-better want only needs a valid proof; rarer tiers use the rarity bands const wantTarget = wantIdx === 0 ? target : rarity / wantDiv; if (rarity < target) log(` rarity target for anchor ${anchorBlock} is 2^${bitLength(rarity) - 1} (stricter than validity target)`); // abi.encodePacked(uint256 chainid, address this, address miner, uint32 seq, bytes32 anchor, uint256 nonce) const seqB = Buffer.alloc(4); seqB.writeUInt32BE(Number(st.mintedByYou)); const head = Buffer.concat([u256(a.chainId), fromHex(a.contract), fromHex(a.wallet), seqB, fromHex(blk.hash)]); const anchoredAt = Date.now(); const deadline = anchoredAt + minePerAnchorMs; let hiBig = 0n; let wh = null; const newHigh = () => { const hi28 = crypto.randomBytes(28); hiBig = toBig(hi28) << 32n; wh = new WorkHasher(head, hi28); }; newHigh(); let lo = 0; let best = null; // { work, nonce, anchorBlock, tier } (tier after caps, local estimate) let nextProgress = Date.now() + 10000; let reason = null; // why mining on this anchor stopped: 'want' | 'time' | 'expiring' mining: while (Date.now() < deadline) { for (let i = 0; i < 2000; i++) { const s = wh.run(lo); tries++; if (digestWordBE(s, 0) <= targetTop) { // rare: full 256-bit comparison const work = toBig(digestBytes(s)); if (work < target) { const tier = applyCaps(work < t200 ? 3 : work < t20 ? 2 : work < t4 ? 1 : 0, tiers); if (best === null || work < best.work) best = { work, nonce: hiBig | BigInt(lo), anchorBlock, tier }; if (work < wantTarget && tier >= minTier) { reason = 'want'; break mining; } } } lo = (lo + 1) >>> 0; if (lo === 0) newHigh(); // 2^32 attempts on one prefix: pick a fresh random prefix } if (timeUp()) { if (!best && rejected) { const r = rejected; await emit(a, r.st, r.best, tries, t0, `time limit: only result found was not confirmed (${r.why}); do not submit`, submitWithin((Date.now() - r.anchoredAt) / 1000), false); process.exitCode = 3; return; } if (!best) die('time limit reached, nothing found'); reason = 'time'; break; } if (Date.now() >= nextProgress) { nextProgress += 10000; log(` ${tries} hashes, ${Math.round(tries / elapsed())} h/s, best so far: ${best ? TIER[best.tier] : 'none'}`); } } // anchor about to expire: if we hold a valid result below the wanted tier, emit it rather than lose it if (!reason && best) reason = 'expiring'; if (best) { const note = { want: '', time: 'time limit: best found so far', expiring: 'anchor expiring: emitting best found' }[reason]; let chk = null; try { chk = await checkWorkOnChain(a, best.nonce, best.anchorBlock); } catch (e) { if (e instanceof Exit) throw e; log(`checkWork call failed: ${e.message}`); } const sinceAnchor = (Date.now() - anchoredAt) / 1000; if (chk && chk.ok && chk.tier >= minTier) { await emit(a, st, { ...best, tier: chk.tier }, tries, t0, note, submitWithin(sinceAnchor), true); return; } const why = !chk ? 'could not confirm on chain' : chk.ok ? `checkWork gives ${TIER[chk.tier]} after tier caps, below minTier ${TIER[minTier]} (mine() would revert TierBelowMin)` : chk.target === 0n ? 'contract rejected it (anchor block not usable)' : `contract rejected it (work 0x${chk.work.toString(16).padStart(64, '0').slice(0, 12)}..., target 2^${bitLength(chk.target) - 1}: wrong seq/chain or target changed)`; rejected = { best: chk && chk.ok ? { ...best, tier: chk.tier } : best, st, why, anchoredAt }; if (timeUp()) { await emit(a, st, rejected.best, tries, t0, `time limit: best found so far, but ${why}; do not submit`, submitWithin(sinceAnchor), false); process.exitCode = 3; return; } log(`found ${TIER[best.tier]} but ${why}; re-reading status and re-anchoring`); continue; } log(`re-anchoring after ${tries} hashes (${Math.round(tries / elapsed())} h/s)`); } } async function emit(a, st, best, tries, t0, note, submitWithinSeconds, checkedOnChain) { const waitUntil = await tooFastUntil(a); // warn only; never blocks const { work, nonce, anchorBlock, tier } = best; const data = mineCalldata(nonce, anchorBlock, a.minTier); const out = { contract: a.contract, wallet: a.wallet, nonce, anchorBlock: BigInt(anchorBlock), tier: TIER[tier], minTier: TIER[a.minTier], work: '0x' + work.toString(16).padStart(64, '0'), valueWei: st.yourPrice.toString(), // exact quote; mine() refunds any excess, but send exactly this valueBNB: new PyFloat(Number(st.yourPrice) / 1e18), calldata: '0x' + hex(data), hashes: BigInt(tries), seconds: new PyFloat(Math.round((Date.now() - t0) / 100) / 10), submitWithinSeconds, checkedOnChain, waitUntil, // null, or unix time before which mine() reverts TooFast note, baw_preview: `baw contract-call preview --binanceChainId ${a.chainId} --from ${a.wallet} --to ${a.contract} ` + `--value ${st.yourPrice} --inputData 0x${hex(data)} --json`, }; writeOut(pyJson(out)); } // ----------------------------------------------------------------- main async function main() { const a = parseArgs(process.argv.slice(2)); if (a.help) { const src = fs.readFileSync(__filename, 'utf8'); writeOut(src.slice(0, src.indexOf('*/') + 2)); return; } if (a.selftest) return selftest(); if (a.check) return check(a); return mine(a); } if (require.main === module) { process.on('exit', (code) => { if (!resultPrinted) log(`mine.js: exiting with code ${code} without a result or error message (unexpected; please report)`); }); main() .catch((e) => { if (e instanceof Exit) { process.exitCode = e.code; return; } log(`error: ${e && e.message ? e.message : e}`); resultPrinted = true; process.exitCode = 1; }) // every result/error has been written synchronously by now, so exiting cannot lose output; // exiting explicitly also stops idle keep-alive RPC sockets from delaying the exit .then(() => process.exit(process.exitCode || 0)); } module.exports = { keccak256, keccakF, WorkHasher };