apow-cli 0.3.3 → 0.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # APoW CLI
2
2
 
3
- Mining client for the [APoW (Agentic Proof of Work)](https://github.com/Agentoshi/apow-core) protocol on Base. Solve SMHL challenges with any LLM to mine $AGENT tokens.
3
+ Mining client for the [APoW (Agentic Proof of Work)](https://github.com/Agentoshi/apow-core) protocol on Base. Prove your agent identity once by minting an ERC-8004 Mining Rig, then compete on hash power to mine $AGENT tokens.
4
4
 
5
5
  **Your agent does all the work — you just fund a wallet.**
6
6
 
@@ -36,22 +36,23 @@ npx apow-cli wallet new
36
36
  # → Captures address + private key from stdout
37
37
 
38
38
  # 2. Write .env (no interactive prompts)
39
+ # LLM config is only needed for minting — mining uses optimized SMHL solving
39
40
  cat > .env << 'EOF'
40
41
  PRIVATE_KEY=0x<from step 1>
41
42
  RPC_URL=https://mainnet.base.org # UNRELIABLE — get a free Alchemy URL (see above)
42
- LLM_PROVIDER=openai
43
- LLM_MODEL=gpt-4o-mini
44
- LLM_API_KEY=<your key>
43
+ LLM_PROVIDER=openai # Required for minting only
44
+ LLM_MODEL=gpt-4o-mini # Required for minting only
45
+ LLM_API_KEY=<your key> # Required for minting only
45
46
  EOF
46
47
 
47
48
  # 3. Fund the wallet (bridge from Solana or send ETH on Base)
48
49
  npx apow-cli fund --solana # bridge SOL → ETH on Base
49
50
  # Or ask your user to send ≥0.005 ETH on Base directly
50
51
 
51
- # 4. Mint a mining rig NFT (solves SMHL challenge via LLM)
52
+ # 4. Mint a mining rig NFT (proves agent identity via LLM — one-time)
52
53
  npx apow-cli mint
53
54
 
54
- # 5. Start mining (runs forever, auto-detects best rig)
55
+ # 5. Start mining (runs forever, no LLM needed, multi-threaded)
55
56
  npx apow-cli mine
56
57
  ```
57
58
 
@@ -108,9 +109,9 @@ Create a `.env` file or use `apow setup`:
108
109
  ```bash
109
110
  PRIVATE_KEY=0x... # Your wallet private key
110
111
  RPC_URL=https://mainnet.base.org # UNRELIABLE — strongly recommend a free Alchemy URL instead (see above)
111
- LLM_PROVIDER=openai # openai | anthropic | gemini | ollama | claude-code | codex
112
- LLM_MODEL=gpt-4o-mini
113
- LLM_API_KEY=sk-...
112
+ LLM_PROVIDER=openai # openai | anthropic | gemini | ollama | claude-code | codex (for minting)
113
+ LLM_MODEL=gpt-4o-mini # Required for minting only — mining uses optimized SMHL solving
114
+ LLM_API_KEY=sk-... # Required for minting only
114
115
  # Solana bridging (only for `apow fund --solana`)
115
116
  # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
116
117
  # SQUID_INTEGRATOR_ID= # free, get at squidrouter.com (deposit address flow only)
@@ -121,7 +122,9 @@ LLM_API_KEY=sk-...
121
122
 
122
123
  See [.env.example](.env.example) for all options.
123
124
 
124
- ## LLM Providers
125
+ ## LLM Providers (for Minting)
126
+
127
+ An LLM is required to mint your Mining Rig NFT (one-time identity verification). Once minted, mining uses optimized algorithmic SMHL solving — no LLM needed.
125
128
 
126
129
  | Provider | Model | Cost/call | Notes |
127
130
  |----------|-------|-----------|-------|
@@ -132,6 +135,13 @@ See [.env.example](.env.example) for all options.
132
135
  | Claude Code | `default` | Subscription | No API key needed |
133
136
  | Codex | `default` | Subscription | No API key needed |
134
137
 
138
+ ## Speed Mining (v0.4.0+)
139
+
140
+ Mining in v0.4.0 uses two key optimizations:
141
+
142
+ - **Algorithmic SMHL**: Mining SMHL challenges are solved algorithmically in microseconds (no LLM call). Your agent identity was already proven when you minted your ERC-8004 Mining Rig.
143
+ - **Multi-threaded nonce grinding**: Hash computation is parallelized across all CPU cores via `worker_threads`. Set `MINER_THREADS` in `.env` to override the default (all cores).
144
+
135
145
  ## Protocol
136
146
 
137
147
  The APoW protocol contracts and documentation live in [apow-core](https://github.com/Agentoshi/apow-core).
package/dist/config.js CHANGED
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.config = void 0;
4
7
  exports.requirePrivateKey = requirePrivateKey;
@@ -7,6 +10,7 @@ exports.isExpensiveModel = isExpensiveModel;
7
10
  exports.writeEnvFile = writeEnvFile;
8
11
  const dotenv_1 = require("dotenv");
9
12
  const promises_1 = require("node:fs/promises");
13
+ const node_os_1 = __importDefault(require("node:os"));
10
14
  const node_path_1 = require("node:path");
11
15
  const chains_1 = require("viem/chains");
12
16
  (0, dotenv_1.config)();
@@ -83,6 +87,7 @@ exports.config = {
83
87
  chainName,
84
88
  miningAgentAddress: parseAddress("MINING_AGENT_ADDRESS", DEFAULT_MINING_AGENT_ADDRESS),
85
89
  agentCoinAddress: parseAddress("AGENT_COIN_ADDRESS", DEFAULT_AGENT_COIN_ADDRESS),
90
+ minerThreads: parseInt(process.env.MINER_THREADS ?? String(node_os_1.default.cpus().length), 10),
86
91
  };
87
92
  function requirePrivateKey() {
88
93
  if (!exports.config.privateKey) {
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-threaded nonce grinding via worker_threads.
4
+ * Spawns N workers that search different nonce ranges in parallel.
5
+ * First worker to find a valid nonce wins; all others are terminated.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.grindNonceParallel = grindNonceParallel;
12
+ const node_worker_threads_1 = require("node:worker_threads");
13
+ const node_os_1 = __importDefault(require("node:os"));
14
+ const viem_1 = require("viem");
15
+ // ── Worker thread logic ──────────────────────────────────────────────
16
+ if (!node_worker_threads_1.isMainThread && node_worker_threads_1.parentPort) {
17
+ const params = node_worker_threads_1.workerData;
18
+ const target = BigInt(params.targetHex);
19
+ let nonce = BigInt(params.startNonce);
20
+ let attempts = 0n;
21
+ const reportInterval = 50000n;
22
+ while (true) {
23
+ const digest = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["bytes32", "address", "uint256"], [params.challengeNumber, params.minerAddress, nonce])));
24
+ attempts += 1n;
25
+ if (digest < target) {
26
+ node_worker_threads_1.parentPort.postMessage({
27
+ type: "found",
28
+ nonce: nonce.toString(),
29
+ attempts: attempts.toString(),
30
+ });
31
+ break;
32
+ }
33
+ if (attempts % reportInterval === 0n) {
34
+ node_worker_threads_1.parentPort.postMessage({
35
+ type: "progress",
36
+ attempts: attempts.toString(),
37
+ });
38
+ }
39
+ nonce += 1n;
40
+ }
41
+ }
42
+ // ── Main thread: spawn workers, collect results ──────────────────────
43
+ async function grindNonceParallel(params) {
44
+ const threadCount = params.threads ?? node_os_1.default.cpus().length;
45
+ const start = process.hrtime();
46
+ const workers = [];
47
+ const workerAttempts = new Map();
48
+ let totalAttempts = 0n;
49
+ return new Promise((resolve, reject) => {
50
+ let settled = false;
51
+ function cleanup() {
52
+ for (const w of workers) {
53
+ w.terminate().catch(() => { });
54
+ }
55
+ }
56
+ function elapsed() {
57
+ const [s, ns] = process.hrtime(start);
58
+ return s + ns / 1_000_000_000;
59
+ }
60
+ for (let i = 0; i < threadCount; i++) {
61
+ // Each worker starts at a random offset to avoid overlap
62
+ const startNonce = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
63
+ const worker = new node_worker_threads_1.Worker(__filename, {
64
+ workerData: {
65
+ challengeNumber: params.challengeNumber,
66
+ targetHex: params.target.toString(),
67
+ minerAddress: params.minerAddress,
68
+ startNonce: startNonce.toString(),
69
+ },
70
+ });
71
+ workerAttempts.set(i, 0n);
72
+ worker.on("message", (msg) => {
73
+ if (msg.type === "found" && !settled) {
74
+ settled = true;
75
+ // Sum all worker attempts
76
+ const workerFinalAttempts = BigInt(msg.attempts ?? "0");
77
+ workerAttempts.set(i, workerFinalAttempts);
78
+ totalAttempts = 0n;
79
+ for (const a of workerAttempts.values())
80
+ totalAttempts += a;
81
+ const e = elapsed();
82
+ cleanup();
83
+ resolve({
84
+ nonce: BigInt(msg.nonce),
85
+ attempts: totalAttempts,
86
+ hashrate: e > 0 ? Number(totalAttempts) / e : Number(totalAttempts),
87
+ elapsed: e,
88
+ });
89
+ }
90
+ else if (msg.type === "progress") {
91
+ const workerProgressAttempts = BigInt(msg.attempts ?? "0");
92
+ workerAttempts.set(i, workerProgressAttempts);
93
+ totalAttempts = 0n;
94
+ for (const a of workerAttempts.values())
95
+ totalAttempts += a;
96
+ if (params.onProgress) {
97
+ const e = elapsed();
98
+ const hashrate = e > 0 ? Number(totalAttempts) / e : Number(totalAttempts);
99
+ params.onProgress(totalAttempts, hashrate);
100
+ }
101
+ }
102
+ });
103
+ worker.on("error", (err) => {
104
+ if (!settled) {
105
+ settled = true;
106
+ cleanup();
107
+ reject(err);
108
+ }
109
+ });
110
+ workers.push(worker);
111
+ }
112
+ });
113
+ }
package/dist/index.js CHANGED
@@ -192,8 +192,10 @@ async function setupWizard() {
192
192
  ui.hint("Continuing anyway — you can fix RPC_URL in .env later");
193
193
  }
194
194
  console.log("");
195
- // Step 3: LLM
196
- console.log(` ${ui.bold("Step 3/3: LLM Provider")}`);
195
+ // Step 3: LLM (for minting)
196
+ console.log(` ${ui.bold("Step 3/3: LLM Provider (for minting)")}`);
197
+ console.log(` ${ui.dim("An LLM solves the SMHL challenge when minting your Mining Rig.")}`);
198
+ console.log(` ${ui.dim("Mining uses optimized solving — no LLM needed after minting.")}`);
197
199
  const providerInput = await ui.prompt("Provider (openai/anthropic/gemini/ollama/deepseek/qwen/claude-code/codex)", "openai");
198
200
  const provider = (["openai", "anthropic", "gemini", "ollama", "deepseek", "qwen", "claude-code", "codex"].includes(providerInput) ? providerInput : "openai");
199
201
  values.LLM_PROVIDER = provider;
@@ -265,7 +267,7 @@ async function main() {
265
267
  });
266
268
  program
267
269
  .name("apow")
268
- .description("Mine AGENT tokens on Base L2 with AI-powered proof of work")
270
+ .description("Mine AGENT tokens on Base L2 with Agentic Proof of Work")
269
271
  .version(version);
270
272
  program
271
273
  .command("setup")
package/dist/miner.js CHANGED
@@ -44,6 +44,7 @@ const config_1 = require("./config");
44
44
  const detect_1 = require("./detect");
45
45
  const errors_1 = require("./errors");
46
46
  const explorer_1 = require("./explorer");
47
+ const grinder_1 = require("./grinder");
47
48
  const smhl_1 = require("./smhl");
48
49
  const ui = __importStar(require("./ui"));
49
50
  const wallet_1 = require("./wallet");
@@ -234,19 +235,26 @@ async function startMining(tokenId) {
234
235
  }));
235
236
  const [challengeNumber, target, rawSmhl] = miningChallenge;
236
237
  const smhl = (0, smhl_1.normalizeSmhlChallenge)(rawSmhl);
237
- // Solve SMHL with spinner
238
- const smhlSpinner = ui.spinner("Solving SMHL challenge...");
238
+ // Solve SMHL algorithmically (sub-millisecond)
239
239
  const smhlStart = process.hrtime();
240
- const smhlSolution = await (0, smhl_1.solveSmhlChallenge)(smhl, (attempt) => {
241
- smhlSpinner.update(`Solving SMHL challenge... attempt ${attempt}/5`);
242
- });
240
+ const smhlSolution = (0, smhl_1.solveSmhlAlgorithmic)(smhl);
241
+ const smhlIssues = (0, smhl_1.validateSmhlSolution)(smhlSolution, smhl);
242
+ if (smhlIssues.length > 0) {
243
+ throw new Error(`SMHL generation failed: ${smhlIssues.join(", ")}`);
244
+ }
243
245
  const smhlElapsed = elapsedSeconds(smhlStart);
244
- smhlSpinner.stop(`Solving SMHL challenge... done (${smhlElapsed.toFixed(1)}s)`);
245
- // Grind nonce with spinner
246
+ console.log(` ${ui.dim(`SMHL solved (${(smhlElapsed * 1000).toFixed(1)}ms)`)}`);
247
+ // Grind nonce with multi-threaded spinner
246
248
  const nonceSpinner = ui.spinner("Grinding nonce...");
247
- const grind = await grindNonce(challengeNumber, target, account.address, (attempts, hashrate) => {
248
- const khs = (hashrate / 1000).toFixed(0);
249
- nonceSpinner.update(`Grinding nonce... ${khs}k H/s (${attempts.toLocaleString()} attempts)`);
249
+ const grind = await (0, grinder_1.grindNonceParallel)({
250
+ challengeNumber,
251
+ target,
252
+ minerAddress: account.address,
253
+ threads: config_1.config.minerThreads,
254
+ onProgress: (attempts, hashrate) => {
255
+ const khs = (hashrate / 1000).toFixed(0);
256
+ nonceSpinner.update(`Grinding nonce... ${khs}k H/s (${attempts.toLocaleString()} attempts)`);
257
+ },
250
258
  });
251
259
  const khs = (grind.hashrate / 1000).toFixed(0);
252
260
  nonceSpinner.stop(`Grinding nonce... done (${grind.elapsed.toFixed(1)}s, ${khs}k H/s)`);
package/dist/preflight.js CHANGED
@@ -118,22 +118,24 @@ async function runPreflight(level) {
118
118
  });
119
119
  }
120
120
  }
121
- // Check 5: LLM key set
122
- if (config_1.config.llmProvider === "ollama") {
123
- results.push({ label: `LLM provider: ollama (${config_1.config.ollamaUrl})`, passed: true });
124
- }
125
- else if (config_1.config.llmProvider === "claude-code" || config_1.config.llmProvider === "codex") {
126
- results.push({ label: `LLM provider: ${config_1.config.llmProvider} (local CLI)`, passed: true });
127
- }
128
- else if (config_1.config.llmApiKey) {
129
- results.push({ label: `LLM provider: ${config_1.config.llmProvider} (key set)`, passed: true });
130
- }
131
- else {
132
- results.push({
133
- label: `LLM API key not set for ${config_1.config.llmProvider}`,
134
- passed: false,
135
- fix: "Set LLM_API_KEY (or OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY) in .env, or run `apow setup`",
136
- });
121
+ // Check 5: LLM key set (only required for minting, not mining)
122
+ if (level === "wallet") {
123
+ if (config_1.config.llmProvider === "ollama") {
124
+ results.push({ label: `LLM provider: ollama (${config_1.config.ollamaUrl})`, passed: true });
125
+ }
126
+ else if (config_1.config.llmProvider === "claude-code" || config_1.config.llmProvider === "codex") {
127
+ results.push({ label: `LLM provider: ${config_1.config.llmProvider} (local CLI)`, passed: true });
128
+ }
129
+ else if (config_1.config.llmApiKey) {
130
+ results.push({ label: `LLM provider: ${config_1.config.llmProvider} (key set)`, passed: true });
131
+ }
132
+ else {
133
+ results.push({
134
+ label: `LLM API key not set for ${config_1.config.llmProvider}`,
135
+ passed: false,
136
+ fix: "Set LLM_API_KEY (or OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY) in .env, or run `apow setup`",
137
+ });
138
+ }
137
139
  }
138
140
  }
139
141
  // Check 6: Contracts exist on-chain (bytecode check)
package/dist/smhl.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.normalizeSmhlChallenge = normalizeSmhlChallenge;
7
7
  exports.buildSmhlPrompt = buildSmhlPrompt;
8
8
  exports.validateSmhlSolution = validateSmhlSolution;
9
+ exports.solveSmhlAlgorithmic = solveSmhlAlgorithmic;
9
10
  exports.solveSmhlChallenge = solveSmhlChallenge;
10
11
  const node_child_process_1 = require("node:child_process");
11
12
  const openai_1 = __importDefault(require("openai"));
@@ -329,6 +330,34 @@ async function requestProviderSolution(prompt) {
329
330
  return requestOpenAiSolution(prompt);
330
331
  }
331
332
  }
333
+ /**
334
+ * Solve SMHL challenge algorithmically (no LLM needed).
335
+ * On-chain _verifySMHL only checks: totalLength ±5, charValue anywhere, wordCount ±2.
336
+ * Generates a valid solution in microseconds.
337
+ */
338
+ function solveSmhlAlgorithmic(challenge) {
339
+ const requiredChar = String.fromCharCode(challenge.charValue);
340
+ const targetWords = challenge.wordCount;
341
+ const targetLen = challenge.totalLength;
342
+ const spaces = targetWords - 1;
343
+ const letterBudget = targetLen - spaces;
344
+ if (letterBudget <= 0 || targetWords <= 0) {
345
+ return requiredChar.repeat(targetLen);
346
+ }
347
+ const baseWordLen = Math.floor(letterBudget / targetWords);
348
+ const extraChars = letterBudget - baseWordLen * targetWords;
349
+ const words = [];
350
+ for (let i = 0; i < targetWords; i++) {
351
+ const len = Math.max(1, baseWordLen + (i < extraChars ? 1 : 0));
352
+ if (i === 0) {
353
+ words.push(requiredChar + "a".repeat(len - 1));
354
+ }
355
+ else {
356
+ words.push("a".repeat(len));
357
+ }
358
+ }
359
+ return words.join(" ");
360
+ }
332
361
  async function solveSmhlChallenge(challenge, onAttempt) {
333
362
  let feedback;
334
363
  let lastIssues = "provider did not return a valid response";
package/dist/wallet.js CHANGED
@@ -5,7 +5,9 @@ exports.requireWallet = requireWallet;
5
5
  exports.getEthBalance = getEthBalance;
6
6
  const viem_1 = require("viem");
7
7
  const accounts_1 = require("viem/accounts");
8
+ const erc8021_1 = require("ox/erc8021");
8
9
  const config_1 = require("./config");
10
+ const DATA_SUFFIX = erc8021_1.Attribution.toDataSuffix({ codes: ["bc_6wfeb1kd"] });
9
11
  exports.publicClient = (0, viem_1.createPublicClient)({
10
12
  chain: config_1.config.chain,
11
13
  transport: (0, viem_1.http)(config_1.config.rpcUrl),
@@ -18,6 +20,7 @@ exports.walletClient = exports.account
18
20
  account: exports.account,
19
21
  chain: config_1.config.chain,
20
22
  transport: (0, viem_1.http)(config_1.config.rpcUrl),
23
+ dataSuffix: DATA_SUFFIX,
21
24
  })
22
25
  : null;
23
26
  function requireWallet() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apow-cli",
3
- "version": "0.3.3",
4
- "description": "Mine AGENT tokens on Base L2 with AI-powered proof of work",
3
+ "version": "0.4.0",
4
+ "description": "Mine AGENT tokens on Base L2 with Agentic Proof of Work",
5
5
  "keywords": [
6
6
  "apow",
7
7
  "agentcoin",
@@ -43,6 +43,7 @@
43
43
  "commander": "^14.0.0",
44
44
  "dotenv": "^17.2.3",
45
45
  "openai": "^6.6.0",
46
+ "ox": "^0.14.8",
46
47
  "qrcode-terminal": "^0.12.0",
47
48
  "viem": "^2.38.2"
48
49
  },
package/skill.md CHANGED
@@ -65,15 +65,15 @@ npx apow-cli wallet new
65
65
  # Captures address + private key from output (also saved to wallet-<address>.txt)
66
66
 
67
67
  # 2. Write .env directly (no interactive prompts needed)
68
- # IMPORTANT: Use an API-based provider (openai/anthropic/gemini/deepseek/qwen), NOT claude-code/codex.
69
- # Session-based providers are too slow for the 20-second mint challenge window.
68
+ # LLM config is only needed for minting (one-time) mining uses algorithmic SMHL.
69
+ # IMPORTANT: Use an API-based provider for minting (openai/anthropic/gemini/deepseek/qwen).
70
70
  # IMPORTANT: The public Base RPC is unreliable — get a free Alchemy URL (see RPC Recommendations).
71
71
  cat > .env << 'EOF'
72
72
  PRIVATE_KEY=0x<from step 1>
73
73
  RPC_URL=https://mainnet.base.org
74
- LLM_PROVIDER=openai
75
- LLM_MODEL=gpt-4o-mini
76
- LLM_API_KEY=<your key>
74
+ LLM_PROVIDER=openai # Required for minting only
75
+ LLM_MODEL=gpt-4o-mini # Required for minting only
76
+ LLM_API_KEY=<your key> # Required for minting only
77
77
  MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
78
78
  AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
79
79
  EOF
@@ -92,19 +92,22 @@ npx apow-cli mine
92
92
 
93
93
  ## 1. What is APoW?
94
94
 
95
- Agent Proof-of-Work (APoW) is a mining protocol where AI agents solve constrained string-generation challenges called SMHL ("Show Me Human Language") to mine AGENT tokens on the Base L2 network. Mining requires owning a Miner NFT (ERC-721 with rarity-based hashpower) and access to an LLM that can solve the SMHL constraints. Rewards start at 3 AGENT per mine (scaled by hashpower) and decay by 10% every 500,000 total network mines, with a hard cap of 21,000,000 AGENT.
95
+ Agent Proof-of-Work (APoW) is a mining protocol on Base L2 where AI agents prove their identity once by minting an ERC-8004 Mining Rig NFT (requires LLM to solve an SMHL challenge), then compete on hash power to mine AGENT tokens. Mining requires owning a Miner NFT (ERC-721 with rarity-based hashpower) no LLM needed after minting. Rewards start at 3 AGENT per mine (scaled by hashpower) and decay by 10% every 500,000 total network mines, with a hard cap of 21,000,000 AGENT.
96
96
 
97
97
  ### SMHL Challenge Format
98
98
 
99
- Your LLM receives a prompt like: "Generate a sentence that is approximately N characters long, contains approximately W words, and includes the letter 'X'."
99
+ SMHL ("Show Me Human Language") serves two different roles in APoW:
100
+
101
+ **SMHL for Minting (identity verification):** When minting a Mining Rig, your LLM solves an SMHL challenge to prove agent capability. This is the "prove yourself" gate — your agent demonstrates it can solve constrained text generation. The LLM receives a prompt like: "Generate a sentence that is approximately N characters long, contains approximately W words, and includes the letter 'X'."
100
102
 
101
- On-chain verification checks:
103
+ **SMHL for Mining (algorithmic):** During mining, SMHL solutions are generated algorithmically in microseconds — no LLM needed. Your agent identity was already established when you minted your ERC-8004 Mining Rig. Mining is a hash power competition, not a language puzzle.
104
+
105
+ On-chain verification checks (both minting and mining):
102
106
  1. **Length** (in bytes): within ±5 of the target
103
107
  2. **Word count**: within ±2 of the target
104
108
  3. **Character presence**: the specified letter appears at least once
105
- 4. **ASCII only** (client-side convention): all characters should be printable ASCII (bytes 32-126). Note: this is NOT enforced on-chain — the contract's `_verifySMHL` does not check for ASCII-only characters. The miner client validates this locally to improve reliability.
106
109
 
107
- The miner client validates locally before submitting. If validation fails, it retries (up to 3 attempts).
110
+ The miner client validates locally before submitting.
108
111
 
109
112
  ---
110
113
 
@@ -114,7 +117,7 @@ The miner client validates locally before submitting. If validation fails, it re
114
117
  |---|---|
115
118
  | **Node.js** | v18 or higher |
116
119
  | **Base wallet** | A private key with ETH on Base (for gas + mint fee) |
117
- | **LLM access** | API key (OpenAI, Anthropic, Gemini, DeepSeek, Qwen), local Ollama, or Claude Code / Codex CLI |
120
+ | **LLM access** | API key (OpenAI, Anthropic, Gemini, DeepSeek, Qwen), local Ollama, or Claude Code / Codex CLI — **required for minting only** |
118
121
  | **git** | Only if installing from source (not needed for npm) |
119
122
 
120
123
  ---
@@ -242,7 +245,7 @@ PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE
242
245
  MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
243
246
  AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
244
247
 
245
- # === LLM Configuration ===
248
+ # === LLM Configuration (required for minting only — mining uses optimized solving) ===
246
249
 
247
250
  # Provider: "openai" | "anthropic" | "ollama" | "gemini" | "deepseek" | "qwen" | "claude-code" | "codex"
248
251
  LLM_PROVIDER=openai
@@ -270,17 +273,20 @@ CHAIN=base
270
273
  | `PRIVATE_KEY` | Yes | -- | Wallet private key (0x + 64 hex chars) |
271
274
  | `MINING_AGENT_ADDRESS` | Yes | -- | Deployed MiningAgent contract address |
272
275
  | `AGENT_COIN_ADDRESS` | Yes | -- | Deployed AgentCoin contract address |
273
- | `LLM_PROVIDER` | No | `openai` | LLM provider: `openai`, `anthropic`, `ollama`, `gemini`, `deepseek`, `qwen`, `claude-code`, or `codex` |
274
- | `LLM_API_KEY` | Conditional | -- | API key. Falls back to `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `DEEPSEEK_API_KEY` / `DASHSCOPE_API_KEY` per provider. Not needed for `ollama`, `claude-code`, or `codex` |
275
- | `LLM_MODEL` | No | `gpt-4o-mini` | Model identifier passed to the provider |
276
+ | `LLM_PROVIDER` | For minting | `openai` | LLM provider for minting: `openai`, `anthropic`, `ollama`, `gemini`, `deepseek`, `qwen`, `claude-code`, or `codex`. Not needed for mining. |
277
+ | `LLM_API_KEY` | For minting | -- | API key for minting. Falls back to `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `DEEPSEEK_API_KEY` / `DASHSCOPE_API_KEY` per provider. Not needed for `ollama`, `claude-code`, `codex`, or mining. |
278
+ | `LLM_MODEL` | For minting | `gpt-4o-mini` | Model identifier passed to the provider (minting only) |
279
+ | `MINER_THREADS` | No | All CPU cores | Number of threads for parallel nonce grinding |
276
280
  | `RPC_URL` | **Strongly recommended** | `https://mainnet.base.org` | Base JSON-RPC endpoint. **The default public RPC is unreliable — use Alchemy (free) or another dedicated provider.** |
277
281
  | `CHAIN` | No | `base` | Network selector; auto-detects `baseSepolia` if RPC URL contains "sepolia" |
278
282
  | `SOLANA_RPC_URL` | No | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint (only for `apow fund --solana`) |
279
283
  | `SQUID_INTEGRATOR_ID` | No | -- | Squid Router integrator ID for deposit address flow (free at [squidrouter.com](https://app.squidrouter.com/)) |
280
284
 
281
- ### LLM Provider Recommendations
285
+ ### LLM Provider Recommendations (for Minting)
282
286
 
283
- > **For AI agents:** Always use an API-based provider (OpenAI, Anthropic, Gemini, DeepSeek, or Qwen). Session-based providers (`claude-code`, `codex`) spawn a CLI subprocess to solve challenges and are too slow to reliably complete the 20-second mint window. They may work for mining (which has no time limit) but will frequently fail during minting.
287
+ > An LLM is only needed for **minting** your Mining Rig NFT (one-time identity verification). Mining uses optimized algorithmic SMHL solving no LLM needed.
288
+ >
289
+ > **For AI agents:** Use an API-based provider (OpenAI, Anthropic, Gemini, DeepSeek, or Qwen) for minting. Session-based providers (`claude-code`, `codex`) spawn a CLI subprocess and are too slow to reliably complete the 20-second mint window.
284
290
 
285
291
  | Provider | Model | Cost per call | Notes |
286
292
  |---|---|---|---|
@@ -400,9 +406,9 @@ npx apow-cli mine <tokenId> # or specify a rig by token ID
400
406
  3. **Fetch challenge** -- reads `getMiningChallenge()` from the AgentCoin contract, which returns:
401
407
  - `challengeNumber` (bytes32) -- the current PoW challenge hash
402
408
  - `miningTarget` (uint256) -- the difficulty target
403
- - `smhl` -- the SMHL string-generation challenge
404
- 4. **Solve SMHL** -- sends the SMHL constraints to your LLM. The client retries up to 3 times with local validation before submission.
405
- 5. **Grind nonce** -- brute-force searches for a `nonce` where `keccak256(challengeNumber, minerAddress, nonce) < miningTarget`.
409
+ - `smhl` -- the SMHL format challenge
410
+ 4. **Solve SMHL** -- generates a valid SMHL solution algorithmically (sub-millisecond, no LLM needed).
411
+ 5. **Grind nonce** -- multi-threaded brute-force search for a `nonce` where `keccak256(challengeNumber, minerAddress, nonce) < miningTarget`. Uses all CPU cores by default.
406
412
  6. **Submit proof** -- calls `mine(nonce, smhlSolution, tokenId)` on AgentCoin. The contract verifies both the hash and SMHL solution on-chain.
407
413
  7. **Collect reward** -- AGENT tokens are minted directly to your wallet.
408
414
  8. **Wait for next block** -- the protocol enforces one mine per block network-wide. The client waits for block advancement before the next cycle.
@@ -434,8 +440,8 @@ A Mythic miner (5.00x) earns 15.00 AGENT per mine in Era 0.
434
440
  ### Cost Per Mine
435
441
 
436
442
  - **Gas:** ~0.001 ETH per `mine()` transaction on Base
437
- - **LLM:** varies by provider ($0.001--$0.005 per SMHL solve)
438
- - **Total:** ~$0.005--$0.02 per mining cycle at typical gas prices
443
+ - **LLM:** $0 (mining uses algorithmic SMHL — no LLM calls)
444
+ - **Total:** ~$0.003--$0.005 per mining cycle (gas only)
439
445
 
440
446
  ### Error Handling
441
447