apow-cli 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/.env.example +24 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/abi/AgentCoin.json +173 -0
- package/dist/abi/MiningAgent.json +186 -0
- package/dist/config.js +113 -0
- package/dist/detect.js +62 -0
- package/dist/errors.js +101 -0
- package/dist/explorer.js +20 -0
- package/dist/index.js +466 -0
- package/dist/miner.js +327 -0
- package/dist/mint.js +256 -0
- package/dist/preflight.js +183 -0
- package/dist/smhl.js +317 -0
- package/dist/stats.js +166 -0
- package/dist/ui.js +142 -0
- package/dist/wallet.js +33 -0
- package/package.json +43 -0
- package/skill.md +553 -0
package/dist/miner.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.startMining = startMining;
|
|
40
|
+
const viem_1 = require("viem");
|
|
41
|
+
const AgentCoin_json_1 = __importDefault(require("./abi/AgentCoin.json"));
|
|
42
|
+
const MiningAgent_json_1 = __importDefault(require("./abi/MiningAgent.json"));
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
const detect_1 = require("./detect");
|
|
45
|
+
const errors_1 = require("./errors");
|
|
46
|
+
const explorer_1 = require("./explorer");
|
|
47
|
+
const smhl_1 = require("./smhl");
|
|
48
|
+
const ui = __importStar(require("./ui"));
|
|
49
|
+
const wallet_1 = require("./wallet");
|
|
50
|
+
const agentCoinAbi = AgentCoin_json_1.default;
|
|
51
|
+
const miningAgentAbi = MiningAgent_json_1.default;
|
|
52
|
+
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
53
|
+
const BASE_BACKOFF_MS = 2_000;
|
|
54
|
+
const MAX_BACKOFF_MS = 60_000;
|
|
55
|
+
const BASE_REWARD = 3n * 10n ** 18n;
|
|
56
|
+
const REWARD_DECAY_NUM = 90n;
|
|
57
|
+
const REWARD_DECAY_DEN = 100n;
|
|
58
|
+
function elapsedSeconds(start) {
|
|
59
|
+
const [seconds, nanoseconds] = process.hrtime(start);
|
|
60
|
+
return seconds + nanoseconds / 1_000_000_000;
|
|
61
|
+
}
|
|
62
|
+
function sleep(ms) {
|
|
63
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
64
|
+
}
|
|
65
|
+
function backoffMs(failures) {
|
|
66
|
+
const base = Math.min(BASE_BACKOFF_MS * 2 ** (failures - 1), MAX_BACKOFF_MS);
|
|
67
|
+
const jitter = Math.random() * base * 0.3;
|
|
68
|
+
return base + jitter;
|
|
69
|
+
}
|
|
70
|
+
function estimateReward(totalMines, eraInterval, hashpower) {
|
|
71
|
+
const era = totalMines / eraInterval;
|
|
72
|
+
let reward = BASE_REWARD;
|
|
73
|
+
for (let i = 0n; i < era; i++) {
|
|
74
|
+
reward = (reward * REWARD_DECAY_NUM) / REWARD_DECAY_DEN;
|
|
75
|
+
}
|
|
76
|
+
return (reward * hashpower) / 100n;
|
|
77
|
+
}
|
|
78
|
+
function formatBaseReward(era) {
|
|
79
|
+
let reward = 3;
|
|
80
|
+
for (let i = 0n; i < era; i++) {
|
|
81
|
+
reward *= 0.9;
|
|
82
|
+
}
|
|
83
|
+
return reward.toFixed(2);
|
|
84
|
+
}
|
|
85
|
+
async function waitForNextBlock(lastMineBlock) {
|
|
86
|
+
const deadline = Date.now() + 60_000; // 60 seconds
|
|
87
|
+
while (Date.now() < deadline) {
|
|
88
|
+
const currentBlock = await wallet_1.publicClient.getBlockNumber();
|
|
89
|
+
if (currentBlock > lastMineBlock) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await sleep(500);
|
|
93
|
+
}
|
|
94
|
+
throw new Error("Timed out waiting for next block (60s)");
|
|
95
|
+
}
|
|
96
|
+
async function grindNonce(challengeNumber, target, minerAddress, onProgress) {
|
|
97
|
+
let nonce = 0n;
|
|
98
|
+
let attempts = 0n;
|
|
99
|
+
const start = process.hrtime();
|
|
100
|
+
while (true) {
|
|
101
|
+
const digest = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["bytes32", "address", "uint256"], [challengeNumber, minerAddress, nonce])));
|
|
102
|
+
attempts += 1n;
|
|
103
|
+
if (digest < target) {
|
|
104
|
+
const elapsed = elapsedSeconds(start);
|
|
105
|
+
const hashrate = elapsed > 0 ? Number(attempts) / elapsed : Number(attempts);
|
|
106
|
+
return { nonce, attempts, hashrate, elapsed };
|
|
107
|
+
}
|
|
108
|
+
if (onProgress && attempts % 50000n === 0n) {
|
|
109
|
+
const elapsed = elapsedSeconds(start);
|
|
110
|
+
const hashrate = elapsed > 0 ? Number(attempts) / elapsed : Number(attempts);
|
|
111
|
+
onProgress(attempts, hashrate);
|
|
112
|
+
}
|
|
113
|
+
nonce += 1n;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function showStartupBanner(tokenId) {
|
|
117
|
+
const { account } = (0, wallet_1.requireWallet)();
|
|
118
|
+
const [ethBalance, totalMines, totalMinted, mineableSupply, eraInterval, hashpowerRaw, rarityRaw] = await Promise.all([
|
|
119
|
+
(0, wallet_1.getEthBalance)(),
|
|
120
|
+
wallet_1.publicClient.readContract({
|
|
121
|
+
address: config_1.config.agentCoinAddress,
|
|
122
|
+
abi: agentCoinAbi,
|
|
123
|
+
functionName: "totalMines",
|
|
124
|
+
}),
|
|
125
|
+
wallet_1.publicClient.readContract({
|
|
126
|
+
address: config_1.config.agentCoinAddress,
|
|
127
|
+
abi: agentCoinAbi,
|
|
128
|
+
functionName: "totalMinted",
|
|
129
|
+
}),
|
|
130
|
+
wallet_1.publicClient.readContract({
|
|
131
|
+
address: config_1.config.agentCoinAddress,
|
|
132
|
+
abi: agentCoinAbi,
|
|
133
|
+
functionName: "MINEABLE_SUPPLY",
|
|
134
|
+
}),
|
|
135
|
+
wallet_1.publicClient.readContract({
|
|
136
|
+
address: config_1.config.agentCoinAddress,
|
|
137
|
+
abi: agentCoinAbi,
|
|
138
|
+
functionName: "ERA_INTERVAL",
|
|
139
|
+
}),
|
|
140
|
+
wallet_1.publicClient.readContract({
|
|
141
|
+
address: config_1.config.miningAgentAddress,
|
|
142
|
+
abi: miningAgentAbi,
|
|
143
|
+
functionName: "hashpower",
|
|
144
|
+
args: [tokenId],
|
|
145
|
+
}),
|
|
146
|
+
wallet_1.publicClient.readContract({
|
|
147
|
+
address: config_1.config.miningAgentAddress,
|
|
148
|
+
abi: miningAgentAbi,
|
|
149
|
+
functionName: "rarity",
|
|
150
|
+
args: [tokenId],
|
|
151
|
+
}),
|
|
152
|
+
]);
|
|
153
|
+
const rarity = Number(rarityRaw);
|
|
154
|
+
const hashpower = Number(hashpowerRaw);
|
|
155
|
+
const era = totalMines / eraInterval;
|
|
156
|
+
const supplyPct = Number(totalMinted * 10000n / mineableSupply) / 100;
|
|
157
|
+
console.log("");
|
|
158
|
+
ui.banner([`AgentCoin Miner v${config_1.config.chainName === "baseSepolia" ? "0.1.0-testnet" : "0.1.0"}`]);
|
|
159
|
+
ui.table([
|
|
160
|
+
["Wallet", `${account.address.slice(0, 6)}...${account.address.slice(-4)} (${Number((0, viem_1.formatEther)(ethBalance)).toFixed(4)} ETH)`],
|
|
161
|
+
["Miner", `#${tokenId} (${detect_1.rarityLabels[rarity] ?? `Tier ${rarity}`}, ${(0, detect_1.formatHashpower)(hashpower)})`],
|
|
162
|
+
["Network", config_1.config.chain.name],
|
|
163
|
+
["Era", `${era} — reward: ${formatBaseReward(era)} AGENT/mine`],
|
|
164
|
+
["Supply", `${supplyPct.toFixed(2)}% mined (${Number((0, viem_1.formatEther)(totalMinted)).toLocaleString()} / ${Number((0, viem_1.formatEther)(mineableSupply)).toLocaleString()} AGENT)`],
|
|
165
|
+
]);
|
|
166
|
+
console.log("");
|
|
167
|
+
}
|
|
168
|
+
async function startMining(tokenId) {
|
|
169
|
+
const { account, walletClient } = (0, wallet_1.requireWallet)();
|
|
170
|
+
let consecutiveFailures = 0;
|
|
171
|
+
let mineCount = 0;
|
|
172
|
+
let runningTotal = 0n;
|
|
173
|
+
await showStartupBanner(tokenId);
|
|
174
|
+
while (true) {
|
|
175
|
+
try {
|
|
176
|
+
// Pre-flight ownership check
|
|
177
|
+
const owner = (await wallet_1.publicClient.readContract({
|
|
178
|
+
address: config_1.config.miningAgentAddress,
|
|
179
|
+
abi: miningAgentAbi,
|
|
180
|
+
functionName: "ownerOf",
|
|
181
|
+
args: [tokenId],
|
|
182
|
+
}));
|
|
183
|
+
if (owner.toLowerCase() !== account.address.toLowerCase()) {
|
|
184
|
+
ui.error(`Miner #${tokenId} is owned by ${owner}, not your wallet.`);
|
|
185
|
+
ui.hint("Check token ID or verify ownership on Basescan");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Supply exhaustion pre-check
|
|
189
|
+
const [totalMines, totalMinted, mineableSupply, eraInterval, hashpower] = await Promise.all([
|
|
190
|
+
wallet_1.publicClient.readContract({
|
|
191
|
+
address: config_1.config.agentCoinAddress,
|
|
192
|
+
abi: agentCoinAbi,
|
|
193
|
+
functionName: "totalMines",
|
|
194
|
+
}),
|
|
195
|
+
wallet_1.publicClient.readContract({
|
|
196
|
+
address: config_1.config.agentCoinAddress,
|
|
197
|
+
abi: agentCoinAbi,
|
|
198
|
+
functionName: "totalMinted",
|
|
199
|
+
}),
|
|
200
|
+
wallet_1.publicClient.readContract({
|
|
201
|
+
address: config_1.config.agentCoinAddress,
|
|
202
|
+
abi: agentCoinAbi,
|
|
203
|
+
functionName: "MINEABLE_SUPPLY",
|
|
204
|
+
}),
|
|
205
|
+
wallet_1.publicClient.readContract({
|
|
206
|
+
address: config_1.config.agentCoinAddress,
|
|
207
|
+
abi: agentCoinAbi,
|
|
208
|
+
functionName: "ERA_INTERVAL",
|
|
209
|
+
}),
|
|
210
|
+
wallet_1.publicClient.readContract({
|
|
211
|
+
address: config_1.config.miningAgentAddress,
|
|
212
|
+
abi: miningAgentAbi,
|
|
213
|
+
functionName: "hashpower",
|
|
214
|
+
args: [tokenId],
|
|
215
|
+
}),
|
|
216
|
+
]);
|
|
217
|
+
const estimatedReward = estimateReward(totalMines, eraInterval, BigInt(hashpower));
|
|
218
|
+
if (totalMinted + estimatedReward > mineableSupply) {
|
|
219
|
+
ui.error(`Supply nearly exhausted. Remaining: ${(0, viem_1.formatEther)(mineableSupply - totalMinted)} AGENT.`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Era transition alert
|
|
223
|
+
const currentEra = totalMines / eraInterval;
|
|
224
|
+
const minesUntilNextEra = eraInterval - (totalMines % eraInterval);
|
|
225
|
+
if (minesUntilNextEra <= 10n) {
|
|
226
|
+
ui.warn(`Era transition in ${minesUntilNextEra} mines! Reward will decrease.`);
|
|
227
|
+
}
|
|
228
|
+
mineCount++;
|
|
229
|
+
console.log(` ${ui.bold(`[Mine #${mineCount}]`)}`);
|
|
230
|
+
const miningChallenge = (await wallet_1.publicClient.readContract({
|
|
231
|
+
address: config_1.config.agentCoinAddress,
|
|
232
|
+
abi: agentCoinAbi,
|
|
233
|
+
functionName: "getMiningChallenge",
|
|
234
|
+
}));
|
|
235
|
+
const [challengeNumber, target, rawSmhl] = miningChallenge;
|
|
236
|
+
const smhl = (0, smhl_1.normalizeSmhlChallenge)(rawSmhl);
|
|
237
|
+
// Solve SMHL with spinner
|
|
238
|
+
const smhlSpinner = ui.spinner("Solving SMHL challenge...");
|
|
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
|
+
});
|
|
243
|
+
const smhlElapsed = elapsedSeconds(smhlStart);
|
|
244
|
+
smhlSpinner.stop(`Solving SMHL challenge... done (${smhlElapsed.toFixed(1)}s)`);
|
|
245
|
+
// Grind nonce with spinner
|
|
246
|
+
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)`);
|
|
250
|
+
});
|
|
251
|
+
const khs = (grind.hashrate / 1000).toFixed(0);
|
|
252
|
+
nonceSpinner.stop(`Grinding nonce... done (${grind.elapsed.toFixed(1)}s, ${khs}k H/s)`);
|
|
253
|
+
// Submit transaction with spinner
|
|
254
|
+
const txSpinner = ui.spinner("Submitting transaction...");
|
|
255
|
+
const txHash = await walletClient.writeContract({
|
|
256
|
+
address: config_1.config.agentCoinAddress,
|
|
257
|
+
abi: agentCoinAbi,
|
|
258
|
+
account,
|
|
259
|
+
functionName: "mine",
|
|
260
|
+
args: [grind.nonce, smhlSolution, tokenId],
|
|
261
|
+
});
|
|
262
|
+
txSpinner.update("Waiting for confirmation...");
|
|
263
|
+
const receipt = await wallet_1.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
264
|
+
if (receipt.status === "reverted") {
|
|
265
|
+
throw new Error("Mine transaction reverted on-chain");
|
|
266
|
+
}
|
|
267
|
+
txSpinner.stop("Submitting transaction... confirmed");
|
|
268
|
+
// Fetch post-mine stats
|
|
269
|
+
const [tokenMineCount, earnings] = await Promise.all([
|
|
270
|
+
wallet_1.publicClient.readContract({
|
|
271
|
+
address: config_1.config.agentCoinAddress,
|
|
272
|
+
abi: agentCoinAbi,
|
|
273
|
+
functionName: "tokenMineCount",
|
|
274
|
+
args: [tokenId],
|
|
275
|
+
}),
|
|
276
|
+
wallet_1.publicClient.readContract({
|
|
277
|
+
address: config_1.config.agentCoinAddress,
|
|
278
|
+
abi: agentCoinAbi,
|
|
279
|
+
functionName: "tokenEarnings",
|
|
280
|
+
args: [tokenId],
|
|
281
|
+
}),
|
|
282
|
+
]);
|
|
283
|
+
const delta = earnings - runningTotal;
|
|
284
|
+
runningTotal = earnings;
|
|
285
|
+
console.log(` ${ui.green("+")} ${(0, viem_1.formatEther)(delta)} AGENT | Total: ${(0, viem_1.formatEther)(earnings)} AGENT | Tx: ${ui.dim((0, explorer_1.txUrl)(txHash))}`);
|
|
286
|
+
console.log("");
|
|
287
|
+
// Wait for block advancement before next iteration
|
|
288
|
+
const lastMineBlock = (await wallet_1.publicClient.readContract({
|
|
289
|
+
address: config_1.config.agentCoinAddress,
|
|
290
|
+
abi: agentCoinAbi,
|
|
291
|
+
functionName: "lastMineBlockNumber",
|
|
292
|
+
}));
|
|
293
|
+
await waitForNextBlock(lastMineBlock);
|
|
294
|
+
consecutiveFailures = 0;
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const classified = (0, errors_1.classifyError)(error);
|
|
298
|
+
if (classified.category === "fatal") {
|
|
299
|
+
ui.error(classified.userMessage);
|
|
300
|
+
if (classified.recovery)
|
|
301
|
+
ui.hint(classified.recovery);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (classified.userMessage.includes("One mine per block")) {
|
|
305
|
+
console.log(` ${ui.dim("Waiting for next block...")}`);
|
|
306
|
+
const lastMineBlock = (await wallet_1.publicClient.readContract({
|
|
307
|
+
address: config_1.config.agentCoinAddress,
|
|
308
|
+
abi: agentCoinAbi,
|
|
309
|
+
functionName: "lastMineBlockNumber",
|
|
310
|
+
}));
|
|
311
|
+
await waitForNextBlock(lastMineBlock);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
consecutiveFailures += 1;
|
|
315
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
316
|
+
ui.error(`${MAX_CONSECUTIVE_FAILURES} consecutive failures. Last: ${classified.userMessage}`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const delay = backoffMs(consecutiveFailures);
|
|
320
|
+
ui.error(`${classified.userMessage} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
|
|
321
|
+
if (classified.recovery)
|
|
322
|
+
ui.hint(classified.recovery);
|
|
323
|
+
console.log(` ${ui.dim(`Retrying in ${(delay / 1000).toFixed(1)}s...`)}`);
|
|
324
|
+
await sleep(delay);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
package/dist/mint.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runMintFlow = runMintFlow;
|
|
40
|
+
const viem_1 = require("viem");
|
|
41
|
+
const MiningAgent_json_1 = __importDefault(require("./abi/MiningAgent.json"));
|
|
42
|
+
const config_1 = require("./config");
|
|
43
|
+
const explorer_1 = require("./explorer");
|
|
44
|
+
const smhl_1 = require("./smhl");
|
|
45
|
+
const detect_1 = require("./detect");
|
|
46
|
+
const miner_1 = require("./miner");
|
|
47
|
+
const ui = __importStar(require("./ui"));
|
|
48
|
+
const wallet_1 = require("./wallet");
|
|
49
|
+
const miningAgentAbi = MiningAgent_json_1.default;
|
|
50
|
+
const ZERO_SEED = `0x${"0".repeat(64)}`;
|
|
51
|
+
function deriveChallengeFromSeed(seed) {
|
|
52
|
+
const bytes = (0, viem_1.hexToBytes)(seed);
|
|
53
|
+
const firstNChars = 5 + (bytes[0] % 6);
|
|
54
|
+
const wordCount = 3 + (bytes[2] % 5);
|
|
55
|
+
const totalLength = 20 + (bytes[5] % 31);
|
|
56
|
+
const charPosition = bytes[3] % totalLength;
|
|
57
|
+
const charValue = 97 + (bytes[4] % 26);
|
|
58
|
+
let targetAsciiSum = 400 + (bytes[1] * 3);
|
|
59
|
+
let maxAsciiSum = firstNChars * 126;
|
|
60
|
+
if (charPosition < firstNChars) {
|
|
61
|
+
maxAsciiSum = maxAsciiSum - 126 + charValue;
|
|
62
|
+
}
|
|
63
|
+
if (targetAsciiSum > maxAsciiSum) {
|
|
64
|
+
targetAsciiSum = 400 + ((targetAsciiSum - 400) % (maxAsciiSum - 399));
|
|
65
|
+
}
|
|
66
|
+
return (0, smhl_1.normalizeSmhlChallenge)([
|
|
67
|
+
targetAsciiSum,
|
|
68
|
+
firstNChars,
|
|
69
|
+
wordCount,
|
|
70
|
+
charPosition,
|
|
71
|
+
charValue,
|
|
72
|
+
totalLength,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
async function findMintedTokenId(startTokenId, endTokenIdExclusive, owner, blockNumber) {
|
|
76
|
+
for (let tokenId = startTokenId; tokenId < endTokenIdExclusive; tokenId += 1n) {
|
|
77
|
+
try {
|
|
78
|
+
const [tokenOwner, mintBlock] = await Promise.all([
|
|
79
|
+
wallet_1.publicClient.readContract({
|
|
80
|
+
address: config_1.config.miningAgentAddress,
|
|
81
|
+
abi: miningAgentAbi,
|
|
82
|
+
functionName: "ownerOf",
|
|
83
|
+
args: [tokenId],
|
|
84
|
+
}),
|
|
85
|
+
wallet_1.publicClient.readContract({
|
|
86
|
+
address: config_1.config.miningAgentAddress,
|
|
87
|
+
abi: miningAgentAbi,
|
|
88
|
+
functionName: "mintBlock",
|
|
89
|
+
args: [tokenId],
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
if (tokenOwner.toLowerCase() === owner.toLowerCase() && mintBlock === blockNumber) {
|
|
93
|
+
return tokenId;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore missing token ids while scanning the minted window.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error("Unable to determine minted token ID from post-mint contract state.");
|
|
101
|
+
}
|
|
102
|
+
async function runMintFlow() {
|
|
103
|
+
const { account, walletClient } = (0, wallet_1.requireWallet)();
|
|
104
|
+
console.log("");
|
|
105
|
+
// 1-rig-per-wallet enforcement
|
|
106
|
+
const existingBalance = (await wallet_1.publicClient.readContract({
|
|
107
|
+
address: config_1.config.miningAgentAddress,
|
|
108
|
+
abi: miningAgentAbi,
|
|
109
|
+
functionName: "balanceOf",
|
|
110
|
+
args: [account.address],
|
|
111
|
+
}));
|
|
112
|
+
if (existingBalance > 0n) {
|
|
113
|
+
const tokenId = (await wallet_1.publicClient.readContract({
|
|
114
|
+
address: config_1.config.miningAgentAddress,
|
|
115
|
+
abi: miningAgentAbi,
|
|
116
|
+
functionName: "tokenOfOwnerByIndex",
|
|
117
|
+
args: [account.address, 0n],
|
|
118
|
+
}));
|
|
119
|
+
const [rarityRaw, hashpowerRaw] = await Promise.all([
|
|
120
|
+
wallet_1.publicClient.readContract({
|
|
121
|
+
address: config_1.config.miningAgentAddress,
|
|
122
|
+
abi: miningAgentAbi,
|
|
123
|
+
functionName: "rarity",
|
|
124
|
+
args: [tokenId],
|
|
125
|
+
}),
|
|
126
|
+
wallet_1.publicClient.readContract({
|
|
127
|
+
address: config_1.config.miningAgentAddress,
|
|
128
|
+
abi: miningAgentAbi,
|
|
129
|
+
functionName: "hashpower",
|
|
130
|
+
args: [tokenId],
|
|
131
|
+
}),
|
|
132
|
+
]);
|
|
133
|
+
const rarity = Number(rarityRaw);
|
|
134
|
+
const hashpower = Number(hashpowerRaw);
|
|
135
|
+
ui.error("This wallet already owns a mining rig.");
|
|
136
|
+
console.log(` Rig #${tokenId} — ${detect_1.rarityLabels[rarity] ?? `Tier ${rarity}`} (${(0, detect_1.formatHashpower)(hashpower)})`);
|
|
137
|
+
console.log("");
|
|
138
|
+
ui.hint("One rig per wallet. Only one mine can succeed per block,");
|
|
139
|
+
ui.hint("so extra rigs in the same wallet waste ETH.");
|
|
140
|
+
ui.hint("To scale: apow wallet new → fund → apow mint");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Fetch mint price and balance FIRST
|
|
144
|
+
const priceSpinner = ui.spinner("Fetching mint price...");
|
|
145
|
+
const [mintPrice, ethBalance] = await Promise.all([
|
|
146
|
+
wallet_1.publicClient.readContract({
|
|
147
|
+
address: config_1.config.miningAgentAddress,
|
|
148
|
+
abi: miningAgentAbi,
|
|
149
|
+
functionName: "getMintPrice",
|
|
150
|
+
}),
|
|
151
|
+
(0, wallet_1.getEthBalance)(),
|
|
152
|
+
]);
|
|
153
|
+
priceSpinner.stop("Fetching mint price... done");
|
|
154
|
+
// Show price preview
|
|
155
|
+
console.log("");
|
|
156
|
+
ui.table([
|
|
157
|
+
["Mint price", `${(0, viem_1.formatEther)(mintPrice)} ETH`],
|
|
158
|
+
["Balance", `${Number((0, viem_1.formatEther)(ethBalance)).toFixed(6)} ETH`],
|
|
159
|
+
]);
|
|
160
|
+
console.log("");
|
|
161
|
+
if (ethBalance < mintPrice) {
|
|
162
|
+
ui.error("Insufficient ETH for mint.");
|
|
163
|
+
ui.hint(`Send at least ${(0, viem_1.formatEther)(mintPrice)} ETH to ${account.address} on Base`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Confirm before spending ETH
|
|
167
|
+
const proceed = await ui.confirm("Proceed with mint?");
|
|
168
|
+
if (!proceed) {
|
|
169
|
+
console.log(" Mint cancelled.");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log("");
|
|
173
|
+
// Request challenge
|
|
174
|
+
const challengeSpinner = ui.spinner("Requesting challenge...");
|
|
175
|
+
const challengeTx = await walletClient.writeContract({
|
|
176
|
+
address: config_1.config.miningAgentAddress,
|
|
177
|
+
abi: miningAgentAbi,
|
|
178
|
+
account,
|
|
179
|
+
functionName: "getChallenge",
|
|
180
|
+
args: [account.address],
|
|
181
|
+
});
|
|
182
|
+
const challengeReceipt = await wallet_1.publicClient.waitForTransactionReceipt({ hash: challengeTx });
|
|
183
|
+
if (challengeReceipt.status === "reverted") {
|
|
184
|
+
throw new Error("Challenge request reverted on-chain");
|
|
185
|
+
}
|
|
186
|
+
challengeSpinner.stop("Requesting challenge... done");
|
|
187
|
+
const challengeSeed = (await wallet_1.publicClient.readContract({
|
|
188
|
+
address: config_1.config.miningAgentAddress,
|
|
189
|
+
abi: miningAgentAbi,
|
|
190
|
+
functionName: "challengeSeeds",
|
|
191
|
+
args: [account.address],
|
|
192
|
+
}));
|
|
193
|
+
if (challengeSeed.toLowerCase() === ZERO_SEED.toLowerCase()) {
|
|
194
|
+
throw new Error("Challenge seed was not stored on-chain.");
|
|
195
|
+
}
|
|
196
|
+
// Solve SMHL
|
|
197
|
+
const challenge = deriveChallengeFromSeed(challengeSeed);
|
|
198
|
+
const smhlSpinner = ui.spinner("Solving SMHL...");
|
|
199
|
+
const solution = await (0, smhl_1.solveSmhlChallenge)(challenge, (attempt) => {
|
|
200
|
+
smhlSpinner.update(`Solving SMHL... attempt ${attempt}/5`);
|
|
201
|
+
});
|
|
202
|
+
smhlSpinner.stop("Solving SMHL... done");
|
|
203
|
+
const nextTokenIdBefore = (await wallet_1.publicClient.readContract({
|
|
204
|
+
address: config_1.config.miningAgentAddress,
|
|
205
|
+
abi: miningAgentAbi,
|
|
206
|
+
functionName: "nextTokenId",
|
|
207
|
+
}));
|
|
208
|
+
// Mint
|
|
209
|
+
const mintSpinner = ui.spinner("Minting...");
|
|
210
|
+
const mintTx = await walletClient.writeContract({
|
|
211
|
+
address: config_1.config.miningAgentAddress,
|
|
212
|
+
abi: miningAgentAbi,
|
|
213
|
+
account,
|
|
214
|
+
functionName: "mint",
|
|
215
|
+
args: [solution],
|
|
216
|
+
value: mintPrice,
|
|
217
|
+
});
|
|
218
|
+
mintSpinner.update("Waiting for confirmation...");
|
|
219
|
+
const receipt = await wallet_1.publicClient.waitForTransactionReceipt({ hash: mintTx });
|
|
220
|
+
if (receipt.status === "reverted") {
|
|
221
|
+
throw new Error("Mint transaction reverted on-chain");
|
|
222
|
+
}
|
|
223
|
+
mintSpinner.stop("Minting... confirmed");
|
|
224
|
+
const nextTokenIdAfter = (await wallet_1.publicClient.readContract({
|
|
225
|
+
address: config_1.config.miningAgentAddress,
|
|
226
|
+
abi: miningAgentAbi,
|
|
227
|
+
functionName: "nextTokenId",
|
|
228
|
+
}));
|
|
229
|
+
const tokenId = await findMintedTokenId(nextTokenIdBefore, nextTokenIdAfter, account.address, receipt.blockNumber);
|
|
230
|
+
const [rarityRaw, hashpowerRaw] = await Promise.all([
|
|
231
|
+
wallet_1.publicClient.readContract({
|
|
232
|
+
address: config_1.config.miningAgentAddress,
|
|
233
|
+
abi: miningAgentAbi,
|
|
234
|
+
functionName: "rarity",
|
|
235
|
+
args: [tokenId],
|
|
236
|
+
}),
|
|
237
|
+
wallet_1.publicClient.readContract({
|
|
238
|
+
address: config_1.config.miningAgentAddress,
|
|
239
|
+
abi: miningAgentAbi,
|
|
240
|
+
functionName: "hashpower",
|
|
241
|
+
args: [tokenId],
|
|
242
|
+
}),
|
|
243
|
+
]);
|
|
244
|
+
const rarity = Number(rarityRaw);
|
|
245
|
+
const hashpower = Number(hashpowerRaw);
|
|
246
|
+
console.log("");
|
|
247
|
+
console.log(` ${ui.green("Miner #" + tokenId.toString())} — ${detect_1.rarityLabels[rarity] ?? `Tier ${rarity}`} (${(0, detect_1.formatHashpower)(hashpower)})`);
|
|
248
|
+
console.log(` Tx: ${ui.dim((0, explorer_1.txUrl)(receipt.transactionHash))}`);
|
|
249
|
+
console.log(` NFT: ${ui.dim((0, explorer_1.tokenUrl)(config_1.config.miningAgentAddress, tokenId))}`);
|
|
250
|
+
console.log("");
|
|
251
|
+
// Offer to start mining
|
|
252
|
+
const startMine = await ui.confirm("Start mining?");
|
|
253
|
+
if (startMine) {
|
|
254
|
+
await (0, miner_1.startMining)(tokenId);
|
|
255
|
+
}
|
|
256
|
+
}
|