apow-cli 0.3.4 → 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 +20 -10
- package/dist/config.js +5 -0
- package/dist/grinder.js +113 -0
- package/dist/index.js +5 -3
- package/dist/miner.js +18 -10
- package/dist/preflight.js +18 -16
- package/dist/smhl.js +29 -0
- package/package.json +2 -2
- package/skill.md +28 -22
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.
|
|
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 (
|
|
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,
|
|
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) {
|
package/dist/grinder.js
ADDED
|
@@ -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
|
|
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
|
|
238
|
-
const smhlSpinner = ui.spinner("Solving SMHL challenge...");
|
|
238
|
+
// Solve SMHL algorithmically (sub-millisecond)
|
|
239
239
|
const smhlStart = process.hrtime();
|
|
240
|
-
const smhlSolution =
|
|
241
|
-
|
|
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
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
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 (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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/package.json
CHANGED
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
|
-
#
|
|
69
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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` |
|
|
274
|
-
| `LLM_API_KEY` |
|
|
275
|
-
| `LLM_MODEL` |
|
|
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
|
-
>
|
|
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
|
|
404
|
-
4. **Solve SMHL** --
|
|
405
|
-
5. **Grind nonce** -- brute-force
|
|
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:**
|
|
438
|
-
- **Total:** ~$0.
|
|
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
|
|