posci-miner 0.1.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/LICENSE +21 -0
- package/README.md +259 -0
- package/bin/posci-miner.mjs +42 -0
- package/docs/logo.png +0 -0
- package/docs/logo.svg +51 -0
- package/package.json +56 -0
- package/src/commands/mine.mjs +222 -0
- package/src/commands/status.mjs +60 -0
- package/src/commands/wallet.mjs +82 -0
- package/src/lib/chain.mjs +96 -0
- package/src/lib/config.mjs +24 -0
- package/src/lib/format.mjs +38 -0
- package/src/lib/log.mjs +20 -0
- package/src/lib/wallet.mjs +150 -0
- package/src/mining/cpu-worker.mjs +99 -0
- package/src/mining/gpu-driver.mjs +267 -0
- package/src/mining/keccak256.wgsl.mjs +228 -0
- package/src/mining/manager.mjs +150 -0
- package/src/ui/dashboard.mjs +130 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Proof of Scientist (POSCI) contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/logo.png" alt="POSCI logo" width="160" height="160" />
|
|
4
|
+
|
|
5
|
+
# posci-miner
|
|
6
|
+
|
|
7
|
+
**CLI miner for [POSCI](https://scientistdapp.online) — Proof of Scientist · Ethereum mainnet**
|
|
8
|
+
|
|
9
|
+
`CPU + WebGPU + hybrid · Local encrypted wallet · Anti-MEV by design`
|
|
10
|
+
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](https://etherscan.io/token/0xD020e5E5c2724B2661C2FEF9AE878f49410a8B77)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What is POSCI?
|
|
20
|
+
|
|
21
|
+
[**POSCI**](https://scientistdapp.online) is a **21,000,000-cap, owner-less, PoW-mined ERC-20** on Ethereum mainnet. 95% of the supply is distributed via on-chain Proof-of-Work mining (Bitcoin-style halving + difficulty retarget). The mining contract is **fully renounced** — there is no admin, no mint function, no upgrade proxy. The launch LP is permanently burned to `0x000…dEaD`.
|
|
22
|
+
|
|
23
|
+
The PoW digest is `keccak256(challenge ‖ msg.sender ‖ nonce)` — your wallet address is **inside the hash**. A copied nonce produces a different digest for any other miner, so **MEV bots cannot snipe your nonce from the mempool**.
|
|
24
|
+
|
|
25
|
+
| | |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Token contract | [`0xD020e5E5c2724B2661C2FEF9AE878f49410a8B77`](https://etherscan.io/token/0xD020e5E5c2724B2661C2FEF9AE878f49410a8B77) |
|
|
28
|
+
| Mining contract | [`0x9EAdD7dF7701e03d07c3727EC1ba816C2C9De936`](https://etherscan.io/address/0x9EAdD7dF7701e03d07c3727EC1ba816C2C9De936) |
|
|
29
|
+
| Genesis contract| [`0x7bC1520Da49Cd56D5BE11aA77650cA998951459d`](https://etherscan.io/address/0x7bC1520Da49Cd56D5BE11aA77650cA998951459d) |
|
|
30
|
+
| Site / web miner| https://scientistdapp.online |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Requires Node.js ≥ 18.17
|
|
38
|
+
npm install -g posci-miner
|
|
39
|
+
|
|
40
|
+
# Or run without installing:
|
|
41
|
+
npx posci-miner --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> **GPU mining** requires Google Chrome / Chromium / Edge installed locally
|
|
45
|
+
> (the CLI launches a headless instance for the WebGPU compute kernel).
|
|
46
|
+
> Set `POSCI_CHROME_PATH=/path/to/chrome` if it's not in a default location.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. Create a fresh wallet (encrypted under ~/.posci/wallets/)
|
|
54
|
+
posci-miner wallet new alice --password mypw
|
|
55
|
+
|
|
56
|
+
# → prints address + private key (private key shown once, save it)
|
|
57
|
+
# → send some ETH to that address to pay for mine() tx fees (~0.001 ETH/win)
|
|
58
|
+
|
|
59
|
+
# 2. Check network status
|
|
60
|
+
posci-miner status --wallet alice --password mypw
|
|
61
|
+
|
|
62
|
+
# 3. Start mining
|
|
63
|
+
posci-miner mine --wallet alice --password mypw --hybrid
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's it. The CLI auto-submits successful PoW solutions and shows a live dashboard with hashrate, network difficulty, and recent hits.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
### `wallet new <name>`
|
|
73
|
+
Generate a fresh wallet, encrypt the private key with `--password` (AES-256-GCM, PBKDF2 250k rounds), store under `~/.posci/wallets/<name>.json`.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
posci-miner wallet new alice --password mypw
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `wallet list` / `wallet show <name>` / `wallet import <name>`
|
|
80
|
+
Manage local encrypted keystores. `wallet show --private` reveals the decrypted private key (requires password).
|
|
81
|
+
|
|
82
|
+
### `mine`
|
|
83
|
+
Start mining with one of three engines:
|
|
84
|
+
|
|
85
|
+
| Flag | Meaning |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `--cpu [n]` | n CPU worker threads (default: all cores) |
|
|
88
|
+
| `--gpu [n]` | n WebGPU workgroups per dispatch (default: 64). Requires Chrome. |
|
|
89
|
+
| `--hybrid` | both CPU and GPU concurrently |
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Pure CPU, all cores
|
|
95
|
+
posci-miner mine --wallet alice --password mypw
|
|
96
|
+
|
|
97
|
+
# Pure CPU, 4 workers
|
|
98
|
+
posci-miner mine --wallet alice --password mypw --cpu 4
|
|
99
|
+
|
|
100
|
+
# Pure GPU (one Chrome process)
|
|
101
|
+
posci-miner mine --wallet alice --password mypw --gpu 256
|
|
102
|
+
|
|
103
|
+
# Hybrid: CPU on N-1 cores + GPU
|
|
104
|
+
posci-miner mine --wallet alice --password mypw --hybrid
|
|
105
|
+
|
|
106
|
+
# Use raw private key instead of local wallet store
|
|
107
|
+
posci-miner mine --key 0xabcdef... --hybrid
|
|
108
|
+
|
|
109
|
+
# Use a specific RPC (defaults to a public Ethereum node)
|
|
110
|
+
posci-miner mine --wallet alice --password mypw --rpc https://eth-mainnet.g.alchemy.com/v2/<key> --hybrid
|
|
111
|
+
|
|
112
|
+
# Plain log mode (no full TUI — useful for CI / nohup)
|
|
113
|
+
posci-miner mine --wallet alice --password mypw --no-dashboard
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `status`
|
|
117
|
+
Snapshot the on-chain mining state and (optionally) your account balance.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
posci-miner status # network only
|
|
121
|
+
posci-miner status --address 0xYourAddress # + balance
|
|
122
|
+
posci-miner status --wallet alice --password mypw # + balance from local wallet
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Live dashboard
|
|
128
|
+
|
|
129
|
+
When stdout is a TTY, the `mine` command renders a live dashboard:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
POSCI miner · HYBRID · 3m 12s
|
|
133
|
+
─────────────────────────────────────────────────────────────
|
|
134
|
+
Miner 0x9Fa33C79C8bc2Ad785fdE6D10608dB97A95093fa
|
|
135
|
+
Status time ✓ · pool ✓
|
|
136
|
+
|
|
137
|
+
Hashrate
|
|
138
|
+
CPU 2.4 MH/s
|
|
139
|
+
GPU 18.7 MH/s
|
|
140
|
+
Total 21.1 MH/s
|
|
141
|
+
Network ~134 MH/s (implied by difficulty)
|
|
142
|
+
|
|
143
|
+
Network
|
|
144
|
+
Difficulty 1,920
|
|
145
|
+
Reward 1,000 POSCI/block
|
|
146
|
+
Epoch 47
|
|
147
|
+
Remaining 19,953,000 POSCI
|
|
148
|
+
|
|
149
|
+
My account
|
|
150
|
+
Balance 2,000.0000 POSCI
|
|
151
|
+
Mined this run 1,000.0000 POSCI
|
|
152
|
+
|
|
153
|
+
Recent hits
|
|
154
|
+
✓ 14:23:01 GPU +1000 POSCI 0xa1b2c3d4…
|
|
155
|
+
|
|
156
|
+
Ctrl+C to stop
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## How it works
|
|
162
|
+
|
|
163
|
+
1. **CPU engine** — N Node.js `worker_threads`, each running `js-sha3.keccak_256` in a tight loop with a unique stride to cover disjoint nonce ranges.
|
|
164
|
+
2. **GPU engine** — spawns headless Chrome with our WGSL compute shader (same one the web frontend uses). Chrome posts hits + hashrate over a localhost WebSocket back to the CLI. Reuses the battle-tested shader in `src/mining/keccak256.wgsl.mjs`.
|
|
165
|
+
3. **Hybrid** — both run concurrently with disjoint nonce strides so they never duplicate work. Dedup at the manager catches the rare cross-engine collision.
|
|
166
|
+
4. **Auto-submit** — when any engine finds a valid digest, the CLI immediately calls `mine(nonce, digest)` on the contract from your wallet. Reward lands in your wallet.
|
|
167
|
+
5. **Auto-refresh** — every 12s (configurable), the CLI polls the chain for new challenge / target / difficulty and hot-swaps the job into running workers.
|
|
168
|
+
|
|
169
|
+
The hash that the contract verifies is exactly:
|
|
170
|
+
```
|
|
171
|
+
keccak256(abi.encodePacked(challengeNumber, msg.sender, nonce))
|
|
172
|
+
```
|
|
173
|
+
Both engines produce digests with **your wallet's address** as `msg.sender`. That's why **only YOU** can submit a valid mine() tx — anyone copying your nonce from the mempool would get a different digest (since their msg.sender is different) and the contract would revert.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Wallet security
|
|
178
|
+
|
|
179
|
+
Wallets created with `wallet new`:
|
|
180
|
+
- Stored under `~/.posci/wallets/<name>.json` (POSIX 0600 perms)
|
|
181
|
+
- Private key encrypted with AES-256-GCM
|
|
182
|
+
- Key derivation: PBKDF2-HMAC-SHA256, 250,000 iterations, 16-byte random salt
|
|
183
|
+
- Plaintext private key only held in memory while mining
|
|
184
|
+
|
|
185
|
+
If you lose the password, the wallet is unrecoverable. Back up the keystore JSON + remember the password.
|
|
186
|
+
|
|
187
|
+
You can also pass a private key directly with `--key 0x...` for ephemeral use (e.g., a dedicated mining wallet), or set `POSCI_PRIVATE_KEY` / `POSCI_WALLET` + `POSCI_PASSWORD` env vars.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Realistic expectations
|
|
192
|
+
|
|
193
|
+
POSCI mining is competitive — at any given moment, the network hashrate × difficulty determines your expected wait. Some napkin math:
|
|
194
|
+
|
|
195
|
+
| Your hashrate | Network hashrate | Expected wait per block (1000 POSCI) |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| 1 MH/s | 100 MH/s | ~10 minutes |
|
|
198
|
+
| 10 MH/s | 100 MH/s | ~1 minute |
|
|
199
|
+
| 100 MH/s | 1 GH/s | ~10 minutes |
|
|
200
|
+
|
|
201
|
+
Use the live `posci-miner status` to see current network hashrate.
|
|
202
|
+
|
|
203
|
+
Each successful `mine()` tx costs ETH gas (~0.0005-0.005 ETH at typical mainnet rates). At low POSCI prices, ensure your reward × POSCI_USD_price > tx_gas_cost.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Troubleshooting
|
|
208
|
+
|
|
209
|
+
**"Mining is not yet open"** — both gates (time + pool) must be open. Time gate opens 12h after deploy. Pool gate opens after the genesis sale fills 0.5 ETH. Check live status: https://scientistdapp.online/stats
|
|
210
|
+
|
|
211
|
+
**"No Chrome/Edge/Chromium binary found"** — install Chrome or set `POSCI_CHROME_PATH`. GPU mode needs a recent Chrome (≥113) for WebGPU.
|
|
212
|
+
|
|
213
|
+
**"WebGPU NOT available in this Chrome"** — your Chrome was started without GPU access. On Linux, ensure you have GPU drivers + try `--enable-features=Vulkan` flag (the CLI passes this automatically).
|
|
214
|
+
|
|
215
|
+
**Hits show ✗ failed** — the most common cause is the challenge having rotated between when your worker hashed and when your tx landed. The CLI auto-detects this via periodic chain reads and hot-swaps. If you see persistent failures, check that your wallet has enough ETH for gas.
|
|
216
|
+
|
|
217
|
+
**"DifficultyNotMet"** — your engine's local target was stale (chain difficulty went up). The CLI will auto-correct on the next 12s refresh.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Architecture
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
posci-miner/
|
|
225
|
+
├── bin/posci-miner.mjs CLI entry (commander)
|
|
226
|
+
├── src/
|
|
227
|
+
│ ├── commands/ wallet · mine · status
|
|
228
|
+
│ ├── lib/
|
|
229
|
+
│ │ ├── chain.mjs viem clients, mining contract calls
|
|
230
|
+
│ │ ├── wallet.mjs local encrypted keystore (AES-256-GCM)
|
|
231
|
+
│ │ ├── config.mjs contract addresses + RPCs
|
|
232
|
+
│ │ ├── format.mjs hashrate / POSCI / duration formatters
|
|
233
|
+
│ │ └── log.mjs colored logging
|
|
234
|
+
│ ├── mining/
|
|
235
|
+
│ │ ├── manager.mjs orchestrator (CPU + GPU + dedup)
|
|
236
|
+
│ │ ├── cpu-worker.mjs Node worker_threads CPU keccak
|
|
237
|
+
│ │ ├── gpu-driver.mjs spawns headless Chrome for WebGPU
|
|
238
|
+
│ │ └── keccak256.wgsl.mjs the WGSL compute shader
|
|
239
|
+
│ └── ui/dashboard.mjs live TUI dashboard
|
|
240
|
+
└── docs/ logo assets
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
PRs welcome. Open an issue for design discussion before significant changes.
|
|
248
|
+
|
|
249
|
+
Please don't submit token-shilling spam — this repo is for the miner only. For protocol questions, [open an issue](https://github.com/Scientists-posci/posci-miner/issues) or come to [@scientistsdapp](https://x.com/scientistsdapp) on X.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
[MIT](LICENSE) © 2026 Proof of Scientist contributors
|
|
256
|
+
|
|
257
|
+
This software is provided as-is. Mining cryptocurrency may be regulated in your jurisdiction. You are responsible for understanding and complying with applicable laws.
|
|
258
|
+
|
|
259
|
+
The POSCI smart contracts are immutable, non-custodial, and were deployed by an EOA with no admin powers. The contributors of this miner have no control over the protocol or any user funds.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// posci-miner — CLI miner for POSCI (Proof of Scientist) on Ethereum mainnet
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import { registerWalletCommands } from '../src/commands/wallet.mjs';
|
|
12
|
+
import { registerMineCommand } from '../src/commands/mine.mjs';
|
|
13
|
+
import { registerStatusCommand } from '../src/commands/status.mjs';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
17
|
+
|
|
18
|
+
const program = new Command();
|
|
19
|
+
program
|
|
20
|
+
.name('posci-miner')
|
|
21
|
+
.description('CLI miner for POSCI (Proof of Scientist) on Ethereum mainnet.\n CPU + WebGPU + hybrid modes. Local encrypted wallet store.')
|
|
22
|
+
.version(pkg.version, '-v, --version', 'show version')
|
|
23
|
+
.addHelpText('after', `
|
|
24
|
+
Examples:
|
|
25
|
+
$ posci-miner wallet new alice --password mypw
|
|
26
|
+
$ posci-miner status --wallet alice --password mypw
|
|
27
|
+
$ posci-miner mine --wallet alice --password mypw --hybrid
|
|
28
|
+
$ posci-miner mine --key 0xabc... --cpu 8
|
|
29
|
+
$ posci-miner mine --wallet alice --password mypw --gpu 256
|
|
30
|
+
|
|
31
|
+
Live data: https://scientistdapp.online
|
|
32
|
+
Source: https://github.com/Scientists-posci/posci-miner
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
registerWalletCommands(program);
|
|
36
|
+
registerMineCommand(program);
|
|
37
|
+
registerStatusCommand(program);
|
|
38
|
+
|
|
39
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
40
|
+
console.error('FATAL:', e?.stack ?? e?.message ?? e);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
package/docs/logo.png
ADDED
|
Binary file
|
package/docs/logo.svg
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
POSCI primary token logo. Monochrome edition matching the Tesla/Starlink-
|
|
4
|
+
inflected site theme. 512×512 viewBox, recognizable at any size from 16px to
|
|
5
|
+
1024px, no gradients (rasterizes cleanly via Chrome headless).
|
|
6
|
+
-->
|
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
8
|
+
<defs>
|
|
9
|
+
<radialGradient id="orbBg" cx="35%" cy="28%" r="80%">
|
|
10
|
+
<stop offset="0%" stop-color="#3a3f4a"/>
|
|
11
|
+
<stop offset="35%" stop-color="#1a1d23"/>
|
|
12
|
+
<stop offset="100%" stop-color="#000000"/>
|
|
13
|
+
</radialGradient>
|
|
14
|
+
<radialGradient id="specular" cx="38%" cy="22%" r="35%">
|
|
15
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.55"/>
|
|
16
|
+
<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
|
|
17
|
+
</radialGradient>
|
|
18
|
+
</defs>
|
|
19
|
+
|
|
20
|
+
<!-- Outer body — slightly off-pure-black sphere, gives subtle depth -->
|
|
21
|
+
<circle cx="256" cy="256" r="248" fill="url(#orbBg)"/>
|
|
22
|
+
|
|
23
|
+
<!-- Top-light specular highlight — gives the orb a "lit" feel -->
|
|
24
|
+
<ellipse cx="200" cy="160" rx="160" ry="90" fill="url(#specular)"/>
|
|
25
|
+
|
|
26
|
+
<!-- Crisp 1px white outline ring — readable at any size -->
|
|
27
|
+
<circle cx="256" cy="256" r="248" fill="none" stroke="#ffffff" stroke-width="2" stroke-opacity="0.92"/>
|
|
28
|
+
|
|
29
|
+
<!-- Subtle inner mesh: 2 latitude lines, suggests "scientific instrument" -->
|
|
30
|
+
<g fill="none" stroke="#ffffff" stroke-opacity="0.10" stroke-width="1">
|
|
31
|
+
<ellipse cx="256" cy="256" rx="246" ry="80"/>
|
|
32
|
+
<ellipse cx="256" cy="256" rx="246" ry="160"/>
|
|
33
|
+
</g>
|
|
34
|
+
|
|
35
|
+
<!-- One thin tilted orbital ring — the project's "scientist" motif -->
|
|
36
|
+
<g transform="rotate(-22 256 256)">
|
|
37
|
+
<ellipse cx="256" cy="256" rx="234" ry="58" fill="none" stroke="#ffffff" stroke-opacity="0.42" stroke-width="2"/>
|
|
38
|
+
<circle cx="22" cy="256" r="6" fill="#ffffff"/>
|
|
39
|
+
</g>
|
|
40
|
+
|
|
41
|
+
<!-- Φ glyph — the brand. Light weight, large, perfectly centered. -->
|
|
42
|
+
<text
|
|
43
|
+
x="256" y="256"
|
|
44
|
+
text-anchor="middle"
|
|
45
|
+
dominant-baseline="central"
|
|
46
|
+
font-family="Georgia, 'Times New Roman', serif"
|
|
47
|
+
font-weight="500"
|
|
48
|
+
font-size="296"
|
|
49
|
+
fill="#ffffff"
|
|
50
|
+
>Φ</text>
|
|
51
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "posci-miner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI miner for POSCI (Proof of Scientist) — Bitcoin-style PoW token on Ethereum mainnet. CPU + WebGPU (via headless Chrome) + hybrid modes. Anti-MEV: hash includes msg.sender.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://scientistdapp.online",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Scientists-posci/posci-miner.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Scientists-posci/posci-miner/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"posci",
|
|
17
|
+
"ethereum",
|
|
18
|
+
"mining",
|
|
19
|
+
"pow",
|
|
20
|
+
"proof-of-work",
|
|
21
|
+
"erc20",
|
|
22
|
+
"webgpu",
|
|
23
|
+
"0xbitcoin",
|
|
24
|
+
"fair-launch",
|
|
25
|
+
"cli",
|
|
26
|
+
"miner"
|
|
27
|
+
],
|
|
28
|
+
"author": "scientistsdapp",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.17"
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"posci-miner": "bin/posci-miner.mjs",
|
|
34
|
+
"posci": "bin/posci-miner.mjs"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"start": "node bin/posci-miner.mjs",
|
|
38
|
+
"dev": "node bin/posci-miner.mjs --help",
|
|
39
|
+
"test": "node --test test/"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"js-sha3": "^0.9.3",
|
|
44
|
+
"kleur": "^4.1.5",
|
|
45
|
+
"viem": "^2.21.55",
|
|
46
|
+
"ws": "^8.18.0"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"bin/",
|
|
50
|
+
"src/",
|
|
51
|
+
"docs/logo.png",
|
|
52
|
+
"docs/logo.svg",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// `posci-miner mine` — start mining with CPU / GPU / hybrid.
|
|
2
|
+
|
|
3
|
+
import { availableParallelism } from 'node:os';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import { resolveAccount } from '../lib/wallet.mjs';
|
|
7
|
+
import { makePublicClient, makeWalletClient, readMiningState, readPosciBalance, submitMine } from '../lib/chain.mjs';
|
|
8
|
+
import { MiningManager } from '../mining/manager.mjs';
|
|
9
|
+
import { Dashboard } from '../ui/dashboard.mjs';
|
|
10
|
+
import { log, c } from '../lib/log.mjs';
|
|
11
|
+
import { formatPosci, formatHashrate } from '../lib/format.mjs';
|
|
12
|
+
|
|
13
|
+
export function registerMineCommand(program) {
|
|
14
|
+
program.command('mine')
|
|
15
|
+
.description('start mining POSCI (CPU / GPU / hybrid)')
|
|
16
|
+
.option('--cpu [n]', 'use n CPU workers (default: number of cores)')
|
|
17
|
+
.option('--gpu [n]', 'use GPU with n workgroups per dispatch (1-1024, default: 64). Requires Chrome.')
|
|
18
|
+
.option('--hybrid', 'use both CPU and GPU')
|
|
19
|
+
.option('--rpc <url>', 'Ethereum RPC URL (or POSCI_RPC env)')
|
|
20
|
+
.option('--wallet <name>', 'local wallet name (use `wallet new` to create one)')
|
|
21
|
+
.option('--password <pw>', 'wallet password (or POSCI_PASSWORD env)')
|
|
22
|
+
.option('--key <0x...>', 'raw private key (skip local wallet store; use with care)')
|
|
23
|
+
.option('--chrome <path>', 'path to chrome binary (or POSCI_CHROME_PATH env)')
|
|
24
|
+
.option('--no-dashboard', 'plain log mode instead of full TUI')
|
|
25
|
+
.option('--refresh <sec>', 'how often to re-poll chain state', '12')
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
// ── Resolve auth + mode ────────────────────────────────────────────
|
|
28
|
+
let acct;
|
|
29
|
+
try { acct = resolveAccount(opts); }
|
|
30
|
+
catch (e) { log.err(e.message); process.exit(1); }
|
|
31
|
+
|
|
32
|
+
let cpuN = 0, gpuN = 0;
|
|
33
|
+
const allCores = availableParallelism();
|
|
34
|
+
|
|
35
|
+
if (opts.hybrid) {
|
|
36
|
+
cpuN = Math.max(1, allCores - 1);
|
|
37
|
+
gpuN = 64;
|
|
38
|
+
} else if (opts.gpu !== undefined) {
|
|
39
|
+
gpuN = Number(opts.gpu === true ? 64 : opts.gpu);
|
|
40
|
+
if (opts.cpu !== undefined) cpuN = Number(opts.cpu === true ? allCores : opts.cpu);
|
|
41
|
+
} else if (opts.cpu !== undefined) {
|
|
42
|
+
cpuN = Number(opts.cpu === true ? allCores : opts.cpu);
|
|
43
|
+
} else {
|
|
44
|
+
// Default: CPU with all cores
|
|
45
|
+
cpuN = allCores;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cpuN = Math.max(0, Math.min(64, cpuN));
|
|
49
|
+
gpuN = Math.max(0, Math.min(1024, gpuN));
|
|
50
|
+
|
|
51
|
+
const mode = (cpuN > 0 && gpuN > 0) ? 'hybrid' : (gpuN > 0 ? 'gpu' : 'cpu');
|
|
52
|
+
|
|
53
|
+
log.banner(c.bold().cyan(' POSCI miner'));
|
|
54
|
+
log.info(` miner: ${acct.address} (${acct.source})`);
|
|
55
|
+
log.info(` mode : ${mode.toUpperCase()} (cpu=${cpuN}, gpu=${gpuN})`);
|
|
56
|
+
|
|
57
|
+
const pub = makePublicClient(opts.rpc);
|
|
58
|
+
const wallet = makeWalletClient(opts.rpc, acct.privateKey);
|
|
59
|
+
|
|
60
|
+
// ── Initial chain pull ─────────────────────────────────────────────
|
|
61
|
+
let chain = await readMiningState(pub);
|
|
62
|
+
if (!chain.canMine) {
|
|
63
|
+
log.warn(`Mining is not yet open:`);
|
|
64
|
+
log.warn(` time gate ${chain.timeGateOpen ? '✓' : '⏳'} pool gate ${chain.poolGateOpen ? '✓' : '⏳'}`);
|
|
65
|
+
log.info(` CLI will wait and check every ${opts.refresh}s.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const myMinedAtStart = await readPosciBalance(pub, acct.address);
|
|
69
|
+
let myMinedRun = 0n;
|
|
70
|
+
|
|
71
|
+
const dash = new Dashboard({ minerAddress: acct.address, mode });
|
|
72
|
+
dash.update({
|
|
73
|
+
difficulty: chain.difficulty,
|
|
74
|
+
reward: chain.miningReward,
|
|
75
|
+
remaining: chain.remainingSupply,
|
|
76
|
+
epoch: chain.epochCount,
|
|
77
|
+
poolGate: chain.poolGateOpen,
|
|
78
|
+
timeGate: chain.timeGateOpen,
|
|
79
|
+
miningStartTime: chain.miningStartTime,
|
|
80
|
+
networkHashrate: chain.networkHashrate,
|
|
81
|
+
myBalance: myMinedAtStart,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (opts.dashboard !== false) dash.start();
|
|
85
|
+
|
|
86
|
+
// ── Mining manager ─────────────────────────────────────────────────
|
|
87
|
+
const startNonce = BigInt('0x' + randomBytes(8).toString('hex'));
|
|
88
|
+
const mgr = new MiningManager({
|
|
89
|
+
minerAddress: acct.address,
|
|
90
|
+
cpuWorkers: cpuN,
|
|
91
|
+
gpuPower: gpuN,
|
|
92
|
+
gpuChromePath: opts.chrome,
|
|
93
|
+
});
|
|
94
|
+
mgr.on('error', ({ source, error }) => log.warn(`[${source}] ${error.message ?? error}`));
|
|
95
|
+
mgr.on('stats', ({ cpu, gpu }) => dash.update({ cpuRate: cpu.hashrate, gpuRate: gpu.hashrate }));
|
|
96
|
+
|
|
97
|
+
// ── Hit handling — gated on chain.canMine ─────────────────────────
|
|
98
|
+
// Workers don't know about gates; they just hash. The check that
|
|
99
|
+
// matters is "is the contract actually willing to accept mine() right
|
|
100
|
+
// now?" — driven by chain.canMine (time gate AND pool gate both open).
|
|
101
|
+
//
|
|
102
|
+
// If gates are closed, we BUFFER the most recent valid solution
|
|
103
|
+
// (against the current challenge) and re-attempt as soon as gates
|
|
104
|
+
// open. This gets you the FIRST submission window without burning gas
|
|
105
|
+
// on guaranteed-revert txs.
|
|
106
|
+
let pendingHit = null;
|
|
107
|
+
let busy = false; // ensure only one in-flight tx at a time
|
|
108
|
+
|
|
109
|
+
async function trySubmit(hit) {
|
|
110
|
+
if (busy) return;
|
|
111
|
+
if (!chain.canMine) {
|
|
112
|
+
pendingHit = hit;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
busy = true;
|
|
116
|
+
const nonceHex = '0x' + hit.nonce.toString(16);
|
|
117
|
+
dash.pushHit({ source: hit.source, nonce: nonceHex, digest: hit.digest,
|
|
118
|
+
reward: chain.miningReward, status: 'pending' });
|
|
119
|
+
try {
|
|
120
|
+
const txHash = await submitMine(wallet, pub, hit.nonce, hit.digest);
|
|
121
|
+
dash.pushHit({ source: hit.source, nonce: nonceHex, digest: hit.digest,
|
|
122
|
+
reward: chain.miningReward, txHash, status: 'pending' });
|
|
123
|
+
const receipt = await pub.waitForTransactionReceipt({ hash: txHash, timeout: 180_000 });
|
|
124
|
+
if (receipt.status === 'success') {
|
|
125
|
+
myMinedRun += chain.miningReward;
|
|
126
|
+
const bal = await readPosciBalance(pub, acct.address);
|
|
127
|
+
dash.pushHit({ source: hit.source, nonce: nonceHex, digest: hit.digest,
|
|
128
|
+
reward: chain.miningReward, txHash, status: 'success' });
|
|
129
|
+
dash.update({ myMined: myMinedRun, myBalance: bal });
|
|
130
|
+
} else {
|
|
131
|
+
dash.pushHit({ source: hit.source, nonce: nonceHex, digest: hit.digest,
|
|
132
|
+
reward: 0n, txHash, status: 'failed' });
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
dash.pushHit({ source: hit.source, nonce: nonceHex, digest: hit.digest,
|
|
136
|
+
reward: 0n, status: 'failed' });
|
|
137
|
+
log.warn(`submit error: ${e.shortMessage ?? e.message ?? e}`);
|
|
138
|
+
} finally {
|
|
139
|
+
busy = false;
|
|
140
|
+
pendingHit = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
mgr.on('hit', (hit) => {
|
|
145
|
+
if (chain.canMine) {
|
|
146
|
+
// Gates open — submit immediately
|
|
147
|
+
trySubmit(hit).catch(() => {});
|
|
148
|
+
} else {
|
|
149
|
+
// Gates closed — keep the most recent solution for current challenge.
|
|
150
|
+
// Don't try to submit yet (would burn gas on guaranteed revert).
|
|
151
|
+
pendingHit = hit;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Surface "we have a buffered solution waiting for gates" in the dashboard
|
|
156
|
+
const pendingTimer = setInterval(() => {
|
|
157
|
+
dash.update({
|
|
158
|
+
pendingNote: (!chain.canMine && pendingHit)
|
|
159
|
+
? `★ buffered solution ready — will submit the moment gates open`
|
|
160
|
+
: '',
|
|
161
|
+
});
|
|
162
|
+
}, 1000);
|
|
163
|
+
|
|
164
|
+
await mgr.start({
|
|
165
|
+
challenge: chain.challengeNumber,
|
|
166
|
+
target: chain.miningTarget,
|
|
167
|
+
startNonce,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ── Periodic chain refresh: detect new challenge / target / gates ──
|
|
171
|
+
const refreshSec = Math.max(3, Number(opts.refresh));
|
|
172
|
+
const refreshTimer = setInterval(async () => {
|
|
173
|
+
try {
|
|
174
|
+
const fresh = await readMiningState(pub);
|
|
175
|
+
const gatesJustOpened = fresh.canMine && !chain.canMine;
|
|
176
|
+
const challengeRotated = fresh.challengeNumber !== chain.challengeNumber
|
|
177
|
+
|| fresh.miningTarget !== chain.miningTarget;
|
|
178
|
+
|
|
179
|
+
if (challengeRotated) {
|
|
180
|
+
mgr.setJob({
|
|
181
|
+
challenge: fresh.challengeNumber,
|
|
182
|
+
target: fresh.miningTarget,
|
|
183
|
+
startNonce: BigInt('0x' + randomBytes(8).toString('hex')),
|
|
184
|
+
});
|
|
185
|
+
// Old buffered solution is now invalid (different challenge)
|
|
186
|
+
pendingHit = null;
|
|
187
|
+
}
|
|
188
|
+
chain = fresh;
|
|
189
|
+
dash.update({
|
|
190
|
+
difficulty: chain.difficulty,
|
|
191
|
+
reward: chain.miningReward,
|
|
192
|
+
remaining: chain.remainingSupply,
|
|
193
|
+
epoch: chain.epochCount,
|
|
194
|
+
poolGate: chain.poolGateOpen,
|
|
195
|
+
timeGate: chain.timeGateOpen,
|
|
196
|
+
miningStartTime: chain.miningStartTime,
|
|
197
|
+
networkHashrate: chain.networkHashrate,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Race-window: gates just flipped open AND we have a buffered solution
|
|
201
|
+
if (gatesJustOpened && pendingHit) {
|
|
202
|
+
log.ok(`Gates opened — submitting buffered solution from pre-warm period`);
|
|
203
|
+
trySubmit(pendingHit).catch(() => {});
|
|
204
|
+
}
|
|
205
|
+
} catch { /* ignore transient RPC errors */ }
|
|
206
|
+
}, refreshSec * 1000);
|
|
207
|
+
|
|
208
|
+
// ── Graceful exit ──────────────────────────────────────────────────
|
|
209
|
+
const cleanup = () => {
|
|
210
|
+
clearInterval(refreshTimer);
|
|
211
|
+
clearInterval(pendingTimer);
|
|
212
|
+
mgr.stop();
|
|
213
|
+
dash.stop();
|
|
214
|
+
log.banner(c.bold(' Stopped.'));
|
|
215
|
+
log.info(` Hashes computed this run: ${(mgr.cpuStats.totalHashes + mgr.gpuStats.totalHashes).toString()}`);
|
|
216
|
+
log.info(` POSCI mined this run : ${formatPosci(myMinedRun, 4)}`);
|
|
217
|
+
process.exit(0);
|
|
218
|
+
};
|
|
219
|
+
process.on('SIGINT', cleanup);
|
|
220
|
+
process.on('SIGTERM', cleanup);
|
|
221
|
+
});
|
|
222
|
+
}
|