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/detect.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.rarityLabels = void 0;
|
|
7
|
+
exports.detectMiners = detectMiners;
|
|
8
|
+
exports.selectBestMiner = selectBestMiner;
|
|
9
|
+
exports.formatHashpower = formatHashpower;
|
|
10
|
+
const MiningAgent_json_1 = __importDefault(require("./abi/MiningAgent.json"));
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
const wallet_1 = require("./wallet");
|
|
13
|
+
const miningAgentAbi = MiningAgent_json_1.default;
|
|
14
|
+
exports.rarityLabels = ["Common", "Uncommon", "Rare", "Epic", "Mythic"];
|
|
15
|
+
async function detectMiners(owner) {
|
|
16
|
+
const balance = (await wallet_1.publicClient.readContract({
|
|
17
|
+
address: config_1.config.miningAgentAddress,
|
|
18
|
+
abi: miningAgentAbi,
|
|
19
|
+
functionName: "balanceOf",
|
|
20
|
+
args: [owner],
|
|
21
|
+
}));
|
|
22
|
+
if (balance === 0n) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const miners = [];
|
|
26
|
+
for (let i = 0n; i < balance; i++) {
|
|
27
|
+
const tokenId = (await wallet_1.publicClient.readContract({
|
|
28
|
+
address: config_1.config.miningAgentAddress,
|
|
29
|
+
abi: miningAgentAbi,
|
|
30
|
+
functionName: "tokenOfOwnerByIndex",
|
|
31
|
+
args: [owner, i],
|
|
32
|
+
}));
|
|
33
|
+
const [rarityRaw, hashpowerRaw] = await Promise.all([
|
|
34
|
+
wallet_1.publicClient.readContract({
|
|
35
|
+
address: config_1.config.miningAgentAddress,
|
|
36
|
+
abi: miningAgentAbi,
|
|
37
|
+
functionName: "rarity",
|
|
38
|
+
args: [tokenId],
|
|
39
|
+
}),
|
|
40
|
+
wallet_1.publicClient.readContract({
|
|
41
|
+
address: config_1.config.miningAgentAddress,
|
|
42
|
+
abi: miningAgentAbi,
|
|
43
|
+
functionName: "hashpower",
|
|
44
|
+
args: [tokenId],
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
const rarity = Number(rarityRaw);
|
|
48
|
+
miners.push({
|
|
49
|
+
tokenId,
|
|
50
|
+
rarity,
|
|
51
|
+
rarityLabel: exports.rarityLabels[rarity] ?? `Tier ${rarity}`,
|
|
52
|
+
hashpower: Number(hashpowerRaw),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return miners;
|
|
56
|
+
}
|
|
57
|
+
function selectBestMiner(miners) {
|
|
58
|
+
return miners.reduce((best, m) => (m.hashpower > best.hashpower ? m : best));
|
|
59
|
+
}
|
|
60
|
+
function formatHashpower(hashpower) {
|
|
61
|
+
return `${(hashpower / 100).toFixed(2)}x`;
|
|
62
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyError = classifyError;
|
|
4
|
+
const patterns = [
|
|
5
|
+
{
|
|
6
|
+
test: (m) => m.includes("Not your miner"),
|
|
7
|
+
classify: () => ({
|
|
8
|
+
category: "fatal",
|
|
9
|
+
userMessage: "Miner NFT is not owned by your wallet",
|
|
10
|
+
recovery: "Check token ID or verify ownership on Basescan",
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
test: (m) => m.includes("Supply exhausted"),
|
|
15
|
+
classify: () => ({
|
|
16
|
+
category: "fatal",
|
|
17
|
+
userMessage: "All 18.9M mineable AGENT have been mined",
|
|
18
|
+
recovery: "Mining is complete. Trade AGENT on Uniswap.",
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
test: (m) => m.includes("No contracts"),
|
|
23
|
+
classify: () => ({
|
|
24
|
+
category: "fatal",
|
|
25
|
+
userMessage: "Smart contract wallets cannot mine",
|
|
26
|
+
recovery: "Use a regular (EOA) wallet",
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
test: (m) => m.includes("Sold out") || m.includes("Max supply"),
|
|
31
|
+
classify: () => ({
|
|
32
|
+
category: "fatal",
|
|
33
|
+
userMessage: "All 10,000 mining rigs have been minted",
|
|
34
|
+
recovery: "Buy a miner NFT on a secondary marketplace",
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
test: (m) => m.includes("One mine per block"),
|
|
39
|
+
classify: () => ({
|
|
40
|
+
category: "transient",
|
|
41
|
+
userMessage: "One mine per block — waiting for next block",
|
|
42
|
+
recovery: "",
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
test: (m) => m.includes("Expired"),
|
|
47
|
+
classify: () => ({
|
|
48
|
+
category: "transient",
|
|
49
|
+
userMessage: "SMHL challenge expired (20s window)",
|
|
50
|
+
recovery: "Retrying with a fresh challenge...",
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
test: (m) => m.includes("fetch failed") || m.includes("ECONNREFUSED") || m.includes("ENOTFOUND"),
|
|
55
|
+
classify: (msg) => {
|
|
56
|
+
const target = msg.includes("11434") ? "Ollama" : msg.includes("anthropic") ? "Anthropic API" : msg.includes("openai") ? "OpenAI API" : "RPC";
|
|
57
|
+
return {
|
|
58
|
+
category: "transient",
|
|
59
|
+
userMessage: `Could not reach ${target}`,
|
|
60
|
+
recovery: "Check internet connection and RPC_URL in .env",
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
test: (m) => /\b40[13]\b/.test(m) && (m.includes("anthropic") || m.includes("openai") || m.toLowerCase().includes("unauthorized") || m.toLowerCase().includes("forbidden")),
|
|
66
|
+
classify: () => ({
|
|
67
|
+
category: "setup",
|
|
68
|
+
userMessage: "LLM API key is invalid or expired",
|
|
69
|
+
recovery: "Run `apow setup` to configure a valid key",
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
test: (m) => m.toLowerCase().includes("insufficient funds"),
|
|
74
|
+
classify: () => ({
|
|
75
|
+
category: "setup",
|
|
76
|
+
userMessage: "Not enough ETH for gas",
|
|
77
|
+
recovery: "Send ETH to your wallet on Base",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
test: (m) => m.includes("SMHL solve failed"),
|
|
82
|
+
classify: () => ({
|
|
83
|
+
category: "llm",
|
|
84
|
+
userMessage: "LLM failed to solve the SMHL challenge after 5 attempts",
|
|
85
|
+
recovery: "Try a different model (gpt-4o-mini recommended) or check your LLM API key",
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
function classifyError(error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
for (const pattern of patterns) {
|
|
92
|
+
if (pattern.test(message)) {
|
|
93
|
+
return pattern.classify(message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
category: "transient",
|
|
98
|
+
userMessage: message.length > 200 ? message.slice(0, 200) + "..." : message,
|
|
99
|
+
recovery: "",
|
|
100
|
+
};
|
|
101
|
+
}
|
package/dist/explorer.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.txUrl = txUrl;
|
|
4
|
+
exports.addressUrl = addressUrl;
|
|
5
|
+
exports.tokenUrl = tokenUrl;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
function baseUrl() {
|
|
8
|
+
return config_1.config.chainName === "baseSepolia"
|
|
9
|
+
? "https://sepolia.basescan.org"
|
|
10
|
+
: "https://basescan.org";
|
|
11
|
+
}
|
|
12
|
+
function txUrl(hash) {
|
|
13
|
+
return `${baseUrl()}/tx/${hash}`;
|
|
14
|
+
}
|
|
15
|
+
function addressUrl(addr) {
|
|
16
|
+
return `${baseUrl()}/address/${addr}`;
|
|
17
|
+
}
|
|
18
|
+
function tokenUrl(contract, tokenId) {
|
|
19
|
+
return `${baseUrl()}/nft/${contract}/${tokenId.toString()}`;
|
|
20
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const node_fs_1 = require("node:fs");
|
|
41
|
+
const node_path_1 = require("node:path");
|
|
42
|
+
const commander_1 = require("commander");
|
|
43
|
+
const viem_1 = require("viem");
|
|
44
|
+
const MiningAgent_json_1 = __importDefault(require("./abi/MiningAgent.json"));
|
|
45
|
+
const config_1 = require("./config");
|
|
46
|
+
const detect_1 = require("./detect");
|
|
47
|
+
const explorer_1 = require("./explorer");
|
|
48
|
+
const mint_1 = require("./mint");
|
|
49
|
+
const miner_1 = require("./miner");
|
|
50
|
+
const preflight_1 = require("./preflight");
|
|
51
|
+
const stats_1 = require("./stats");
|
|
52
|
+
const ui = __importStar(require("./ui"));
|
|
53
|
+
const wallet_1 = require("./wallet");
|
|
54
|
+
const miningAgentAbi = MiningAgent_json_1.default;
|
|
55
|
+
function saveKeyFile(address, privateKey) {
|
|
56
|
+
const filename = `wallet-${address}.txt`;
|
|
57
|
+
const filepath = (0, node_path_1.join)(process.cwd(), filename);
|
|
58
|
+
const content = [
|
|
59
|
+
`Address: ${address}`,
|
|
60
|
+
`Private Key: ${privateKey}`,
|
|
61
|
+
``,
|
|
62
|
+
`Generated: ${new Date().toISOString()}`,
|
|
63
|
+
``,
|
|
64
|
+
`Import this key into MetaMask, Phantom, or any EVM wallet.`,
|
|
65
|
+
`Keep this file safe — anyone with the private key controls your funds.`,
|
|
66
|
+
"",
|
|
67
|
+
].join("\n");
|
|
68
|
+
(0, node_fs_1.writeFileSync)(filepath, content, { encoding: "utf8", mode: 0o600 });
|
|
69
|
+
return filepath;
|
|
70
|
+
}
|
|
71
|
+
function parseTokenId(value) {
|
|
72
|
+
try {
|
|
73
|
+
return BigInt(value);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new Error(`Invalid token ID: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function readVersion() {
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf8"));
|
|
82
|
+
return pkg.version ?? "0.1.0";
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return "0.1.0";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function resolveTokenId(tokenIdArg) {
|
|
89
|
+
if (tokenIdArg) {
|
|
90
|
+
return parseTokenId(tokenIdArg);
|
|
91
|
+
}
|
|
92
|
+
if (!wallet_1.account) {
|
|
93
|
+
ui.error("No token ID provided and no wallet configured.");
|
|
94
|
+
ui.hint("Usage: apow mine <tokenId> or configure PRIVATE_KEY in .env");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const miners = await (0, detect_1.detectMiners)(wallet_1.account.address);
|
|
98
|
+
if (miners.length === 0) {
|
|
99
|
+
ui.error("No mining rigs found for this wallet.");
|
|
100
|
+
ui.hint("Run `apow mint` to mint a miner NFT first.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const best = (0, detect_1.selectBestMiner)(miners);
|
|
104
|
+
if (miners.length === 1) {
|
|
105
|
+
console.log(` Using miner #${best.tokenId} (${best.rarityLabel}, ${(0, detect_1.formatHashpower)(best.hashpower)})`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(` Found ${miners.length} miners — using #${best.tokenId} (${best.rarityLabel}, ${(0, detect_1.formatHashpower)(best.hashpower)})`);
|
|
109
|
+
for (const m of miners) {
|
|
110
|
+
const marker = m.tokenId === best.tokenId ? ui.green(" *") : " ";
|
|
111
|
+
console.log(` ${marker} #${m.tokenId} — ${m.rarityLabel} (${(0, detect_1.formatHashpower)(m.hashpower)})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return best.tokenId;
|
|
115
|
+
}
|
|
116
|
+
async function setupWizard() {
|
|
117
|
+
console.log("");
|
|
118
|
+
ui.banner(["AgentCoin Miner Setup"]);
|
|
119
|
+
console.log("");
|
|
120
|
+
const values = {};
|
|
121
|
+
// Step 1: Wallet
|
|
122
|
+
console.log(` ${ui.bold("Step 1/3: Wallet")}`);
|
|
123
|
+
const hasWallet = await ui.confirm("Do you have a Base wallet?");
|
|
124
|
+
let privateKey;
|
|
125
|
+
let addr;
|
|
126
|
+
if (hasWallet) {
|
|
127
|
+
const inputKey = await ui.promptSecret("Private key (0x-prefixed)");
|
|
128
|
+
if (!inputKey) {
|
|
129
|
+
ui.error("Private key is required.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(inputKey)) {
|
|
133
|
+
ui.error("Invalid private key format. Must be 0x + 64 hex characters.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
privateKey = inputKey;
|
|
137
|
+
const { privateKeyToAccount } = await Promise.resolve().then(() => __importStar(require("viem/accounts")));
|
|
138
|
+
const walletAccount = privateKeyToAccount(privateKey);
|
|
139
|
+
addr = walletAccount.address;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const { generatePrivateKey, privateKeyToAccount } = await Promise.resolve().then(() => __importStar(require("viem/accounts")));
|
|
143
|
+
privateKey = generatePrivateKey();
|
|
144
|
+
const walletAccount = privateKeyToAccount(privateKey);
|
|
145
|
+
addr = walletAccount.address;
|
|
146
|
+
console.log("");
|
|
147
|
+
console.log(` ${ui.bold("NEW WALLET GENERATED")}`);
|
|
148
|
+
console.log("");
|
|
149
|
+
console.log(` Address: ${addr}`);
|
|
150
|
+
console.log(` Private Key: ${privateKey}`);
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log(` ${ui.yellow("⚠ SAVE YOUR PRIVATE KEY — this is the only time")}`);
|
|
153
|
+
console.log(` ${ui.yellow(" it will be displayed. Anyone with this key")}`);
|
|
154
|
+
console.log(` ${ui.yellow(" controls your funds.")}`);
|
|
155
|
+
console.log("");
|
|
156
|
+
console.log(` ${ui.dim("Import into Phantom, MetaMask, or any EVM wallet")}`);
|
|
157
|
+
console.log(` ${ui.dim("to view your AGENT tokens and Mining Rig NFT.")}`);
|
|
158
|
+
console.log("");
|
|
159
|
+
console.log(` ${ui.dim("Fund this address with ≥0.005 ETH on Base to start.")}`);
|
|
160
|
+
console.log("");
|
|
161
|
+
const keyPath = saveKeyFile(addr, privateKey);
|
|
162
|
+
console.log(` ${ui.dim(`Key saved to: ${keyPath}`)}`);
|
|
163
|
+
console.log(` ${ui.yellow("Back up this file securely, then delete it.")}`);
|
|
164
|
+
console.log("");
|
|
165
|
+
}
|
|
166
|
+
values.PRIVATE_KEY = privateKey;
|
|
167
|
+
ui.ok(`Wallet: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
|
168
|
+
console.log("");
|
|
169
|
+
// Step 2: RPC
|
|
170
|
+
console.log(` ${ui.bold("Step 2/3: RPC")}`);
|
|
171
|
+
const rpcUrl = await ui.prompt("Base RPC URL", "https://mainnet.base.org");
|
|
172
|
+
values.RPC_URL = rpcUrl;
|
|
173
|
+
// Validate RPC connectivity
|
|
174
|
+
try {
|
|
175
|
+
const { createPublicClient, http } = await Promise.resolve().then(() => __importStar(require("viem")));
|
|
176
|
+
const { base, baseSepolia } = await Promise.resolve().then(() => __importStar(require("viem/chains")));
|
|
177
|
+
const isSepolia = rpcUrl.toLowerCase().includes("sepolia");
|
|
178
|
+
const testClient = createPublicClient({
|
|
179
|
+
chain: isSepolia ? baseSepolia : base,
|
|
180
|
+
transport: http(rpcUrl),
|
|
181
|
+
});
|
|
182
|
+
const blockNumber = await testClient.getBlockNumber();
|
|
183
|
+
const networkName = isSepolia ? "Base Sepolia" : "Base mainnet";
|
|
184
|
+
ui.ok(`Connected — ${networkName}, block #${blockNumber.toLocaleString()}`);
|
|
185
|
+
if (isSepolia)
|
|
186
|
+
values.CHAIN = "baseSepolia";
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
ui.fail("Could not connect to RPC");
|
|
190
|
+
ui.hint("Continuing anyway — you can fix RPC_URL in .env later");
|
|
191
|
+
}
|
|
192
|
+
console.log("");
|
|
193
|
+
// Step 3: LLM
|
|
194
|
+
console.log(` ${ui.bold("Step 3/3: LLM Provider")}`);
|
|
195
|
+
const providerInput = await ui.prompt("Provider (openai/anthropic/gemini/ollama/claude-code/codex)", "openai");
|
|
196
|
+
const provider = (["openai", "anthropic", "gemini", "ollama", "claude-code", "codex"].includes(providerInput) ? providerInput : "openai");
|
|
197
|
+
values.LLM_PROVIDER = provider;
|
|
198
|
+
if (provider === "ollama") {
|
|
199
|
+
const ollamaUrl = await ui.prompt("Ollama URL", "http://127.0.0.1:11434");
|
|
200
|
+
values.OLLAMA_URL = ollamaUrl;
|
|
201
|
+
ui.ok(`Ollama at ${ollamaUrl}`);
|
|
202
|
+
}
|
|
203
|
+
else if (provider === "claude-code" || provider === "codex") {
|
|
204
|
+
ui.ok(`Using local ${provider} CLI — no API key needed`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const apiKey = await ui.promptSecret("API key");
|
|
208
|
+
if (apiKey) {
|
|
209
|
+
values.LLM_API_KEY = apiKey;
|
|
210
|
+
ui.ok(`${provider} key set`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
ui.fail("No API key provided");
|
|
214
|
+
ui.hint(`Set LLM_API_KEY in .env later`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const defaultModel = provider === "gemini" ? "gemini-2.5-flash" : provider === "anthropic" ? "claude-sonnet-4-5-20250929" : provider === "claude-code" || provider === "codex" ? "default" : "gpt-4o-mini";
|
|
218
|
+
const model = await ui.prompt("Model", defaultModel);
|
|
219
|
+
values.LLM_MODEL = model;
|
|
220
|
+
if ((0, config_1.isExpensiveModel)(model)) {
|
|
221
|
+
ui.warn(`${model} is expensive. Consider gpt-4o-mini for lower cost.`);
|
|
222
|
+
}
|
|
223
|
+
// Contract addresses
|
|
224
|
+
values.MINING_AGENT_ADDRESS = config_1.config.miningAgentAddress ?? "";
|
|
225
|
+
values.AGENT_COIN_ADDRESS = config_1.config.agentCoinAddress ?? "";
|
|
226
|
+
console.log("");
|
|
227
|
+
// Check for existing .env
|
|
228
|
+
const envPath = (0, node_path_1.join)(process.cwd(), ".env");
|
|
229
|
+
if ((0, node_fs_1.existsSync)(envPath)) {
|
|
230
|
+
const overwrite = await ui.confirm("Overwrite existing .env?");
|
|
231
|
+
if (!overwrite) {
|
|
232
|
+
console.log(" Setup cancelled.");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
await (0, config_1.writeEnvFile)(values);
|
|
237
|
+
ui.ok("Config saved to .env");
|
|
238
|
+
// Check .gitignore
|
|
239
|
+
const gitignorePath = (0, node_path_1.join)(process.cwd(), ".gitignore");
|
|
240
|
+
if ((0, node_fs_1.existsSync)(gitignorePath)) {
|
|
241
|
+
const gitignore = (0, node_fs_1.readFileSync)(gitignorePath, "utf8");
|
|
242
|
+
if (!gitignore.includes(".env")) {
|
|
243
|
+
ui.warn(".gitignore does not include .env — your secrets may be committed!");
|
|
244
|
+
ui.hint("Add .env and wallet-*.txt to .gitignore");
|
|
245
|
+
}
|
|
246
|
+
if (!gitignore.includes("wallet-")) {
|
|
247
|
+
ui.hint("Consider adding wallet-*.txt to .gitignore");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
console.log("");
|
|
251
|
+
console.log(` Next: ${ui.cyan("apow mint")}`);
|
|
252
|
+
console.log("");
|
|
253
|
+
}
|
|
254
|
+
async function main() {
|
|
255
|
+
const version = readVersion();
|
|
256
|
+
const program = new commander_1.Command();
|
|
257
|
+
// SIGINT handler
|
|
258
|
+
process.on("SIGINT", () => {
|
|
259
|
+
ui.stopAll();
|
|
260
|
+
console.log("");
|
|
261
|
+
console.log(ui.dim(" Interrupted. Bye!"));
|
|
262
|
+
process.exit(0);
|
|
263
|
+
});
|
|
264
|
+
program
|
|
265
|
+
.name("apow")
|
|
266
|
+
.description("Mine AGENT tokens on Base L2 with AI-powered proof of work")
|
|
267
|
+
.version(version);
|
|
268
|
+
program
|
|
269
|
+
.command("setup")
|
|
270
|
+
.description("Interactive setup wizard — configure wallet, RPC, and LLM")
|
|
271
|
+
.action(async () => {
|
|
272
|
+
await setupWizard();
|
|
273
|
+
});
|
|
274
|
+
program
|
|
275
|
+
.command("mint")
|
|
276
|
+
.description("Mint a new miner NFT")
|
|
277
|
+
.hook("preAction", async () => {
|
|
278
|
+
await (0, preflight_1.runPreflight)("wallet");
|
|
279
|
+
})
|
|
280
|
+
.action(async () => {
|
|
281
|
+
await (0, mint_1.runMintFlow)();
|
|
282
|
+
});
|
|
283
|
+
program
|
|
284
|
+
.command("mine")
|
|
285
|
+
.description("Start the mining loop")
|
|
286
|
+
.argument("[tokenId]", "Miner token ID (auto-detects if omitted)")
|
|
287
|
+
.hook("preAction", async () => {
|
|
288
|
+
await (0, preflight_1.runPreflight)("mining");
|
|
289
|
+
})
|
|
290
|
+
.action(async (tokenIdArg) => {
|
|
291
|
+
const tokenId = await resolveTokenId(tokenIdArg);
|
|
292
|
+
await (0, miner_1.startMining)(tokenId);
|
|
293
|
+
});
|
|
294
|
+
program
|
|
295
|
+
.command("stats")
|
|
296
|
+
.description("Show network and miner statistics")
|
|
297
|
+
.argument("[tokenId]", "Miner token ID (auto-detects if omitted)")
|
|
298
|
+
.hook("preAction", async () => {
|
|
299
|
+
await (0, preflight_1.runPreflight)("readonly");
|
|
300
|
+
})
|
|
301
|
+
.action(async (tokenIdArg) => {
|
|
302
|
+
let tokenId;
|
|
303
|
+
if (tokenIdArg) {
|
|
304
|
+
tokenId = parseTokenId(tokenIdArg);
|
|
305
|
+
}
|
|
306
|
+
else if (wallet_1.account) {
|
|
307
|
+
try {
|
|
308
|
+
const miners = await (0, detect_1.detectMiners)(wallet_1.account.address);
|
|
309
|
+
if (miners.length > 0) {
|
|
310
|
+
tokenId = (0, detect_1.selectBestMiner)(miners).tokenId;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// No miners — show network stats only
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await (0, stats_1.displayStats)(tokenId);
|
|
318
|
+
});
|
|
319
|
+
const walletCmd = program
|
|
320
|
+
.command("wallet")
|
|
321
|
+
.description("Wallet generation and management");
|
|
322
|
+
walletCmd
|
|
323
|
+
.command("new")
|
|
324
|
+
.description("Generate a new Base wallet (prints key and saves to file)")
|
|
325
|
+
.action(async () => {
|
|
326
|
+
const { generatePrivateKey, privateKeyToAccount } = await Promise.resolve().then(() => __importStar(require("viem/accounts")));
|
|
327
|
+
const key = generatePrivateKey();
|
|
328
|
+
const acct = privateKeyToAccount(key);
|
|
329
|
+
console.log("");
|
|
330
|
+
console.log(` ${ui.bold("NEW WALLET GENERATED")}`);
|
|
331
|
+
console.log("");
|
|
332
|
+
console.log(` Address: ${acct.address}`);
|
|
333
|
+
console.log(` Private Key: ${key}`);
|
|
334
|
+
console.log("");
|
|
335
|
+
console.log(` ${ui.yellow("⚠ SAVE YOUR PRIVATE KEY — this is the only time")}`);
|
|
336
|
+
console.log(` ${ui.yellow(" it will be displayed. Anyone with this key")}`);
|
|
337
|
+
console.log(` ${ui.yellow(" controls your funds.")}`);
|
|
338
|
+
console.log("");
|
|
339
|
+
const keyPath = saveKeyFile(acct.address, key);
|
|
340
|
+
console.log(` ${ui.dim(`Key saved to: ${keyPath}`)}`);
|
|
341
|
+
console.log(` ${ui.yellow("Back up this file securely, then delete it.")}`);
|
|
342
|
+
console.log("");
|
|
343
|
+
console.log(` ${ui.dim("Import into Phantom, MetaMask, or any EVM wallet")}`);
|
|
344
|
+
console.log(` ${ui.dim("to view your AGENT tokens and Mining Rig NFT.")}`);
|
|
345
|
+
console.log("");
|
|
346
|
+
});
|
|
347
|
+
walletCmd
|
|
348
|
+
.command("show")
|
|
349
|
+
.description("Show wallet address from current .env PRIVATE_KEY")
|
|
350
|
+
.action(async () => {
|
|
351
|
+
if (!wallet_1.account) {
|
|
352
|
+
ui.error("No wallet configured. Set PRIVATE_KEY in .env or run: apow wallet new");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
console.log("");
|
|
356
|
+
console.log(` Address: ${wallet_1.account.address}`);
|
|
357
|
+
console.log("");
|
|
358
|
+
});
|
|
359
|
+
walletCmd
|
|
360
|
+
.command("export")
|
|
361
|
+
.description("Export wallet private key (with confirmation)")
|
|
362
|
+
.action(async () => {
|
|
363
|
+
if (!wallet_1.account || !config_1.config.privateKey) {
|
|
364
|
+
ui.error("No wallet configured. Set PRIVATE_KEY in .env or run: apow wallet new");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const proceed = await ui.confirm("This will display your private key. Continue?");
|
|
368
|
+
if (!proceed) {
|
|
369
|
+
console.log(" Cancelled.");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.log("");
|
|
373
|
+
console.log(` Address: ${wallet_1.account.address}`);
|
|
374
|
+
console.log(` Private Key: ${config_1.config.privateKey}`);
|
|
375
|
+
console.log("");
|
|
376
|
+
const filename = `wallet-${wallet_1.account.address}.txt`;
|
|
377
|
+
const filepath = (0, node_path_1.join)(process.cwd(), filename);
|
|
378
|
+
if (!(0, node_fs_1.existsSync)(filepath)) {
|
|
379
|
+
const save = await ui.confirm("Save to file?");
|
|
380
|
+
if (save) {
|
|
381
|
+
const keyPath = saveKeyFile(wallet_1.account.address, config_1.config.privateKey);
|
|
382
|
+
console.log(` ${ui.dim(`Saved to: ${keyPath}`)}`);
|
|
383
|
+
console.log(` ${ui.yellow("Back up this file securely, then delete it.")}`);
|
|
384
|
+
console.log("");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
walletCmd
|
|
389
|
+
.command("fund")
|
|
390
|
+
.description("Send ETH from your wallet to another address")
|
|
391
|
+
.argument("<address>", "Destination address (0x-prefixed)")
|
|
392
|
+
.argument("[amount]", "ETH amount to send (default: mint price + 0.003 ETH gas buffer)")
|
|
393
|
+
.hook("preAction", async () => {
|
|
394
|
+
await (0, preflight_1.runPreflight)("wallet");
|
|
395
|
+
})
|
|
396
|
+
.action(async (address, amountArg) => {
|
|
397
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
398
|
+
ui.error("Invalid address format. Must be 0x + 40 hex characters.");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const { account: senderAccount, walletClient } = (0, wallet_1.requireWallet)();
|
|
402
|
+
const destAddress = address;
|
|
403
|
+
// Determine amount
|
|
404
|
+
let amount;
|
|
405
|
+
if (amountArg) {
|
|
406
|
+
try {
|
|
407
|
+
amount = (0, viem_1.parseEther)(amountArg);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
ui.error(`Invalid amount: ${amountArg}. Use decimal ETH (e.g., 0.005).`);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// Default: current mint price + 0.003 ETH gas buffer
|
|
416
|
+
const mintPrice = (await wallet_1.publicClient.readContract({
|
|
417
|
+
address: config_1.config.miningAgentAddress,
|
|
418
|
+
abi: miningAgentAbi,
|
|
419
|
+
functionName: "getMintPrice",
|
|
420
|
+
}));
|
|
421
|
+
const gasBuffer = (0, viem_1.parseEther)("0.003");
|
|
422
|
+
amount = mintPrice + gasBuffer;
|
|
423
|
+
}
|
|
424
|
+
const senderBalance = await (0, wallet_1.getEthBalance)();
|
|
425
|
+
console.log("");
|
|
426
|
+
ui.table([
|
|
427
|
+
["From", `${senderAccount.address.slice(0, 6)}...${senderAccount.address.slice(-4)}`],
|
|
428
|
+
["To", `${destAddress.slice(0, 6)}...${destAddress.slice(-4)}`],
|
|
429
|
+
["Amount", `${(0, viem_1.formatEther)(amount)} ETH`],
|
|
430
|
+
["Balance", `${Number((0, viem_1.formatEther)(senderBalance)).toFixed(6)} ETH`],
|
|
431
|
+
]);
|
|
432
|
+
console.log("");
|
|
433
|
+
if (senderBalance < amount) {
|
|
434
|
+
ui.error("Insufficient ETH balance.");
|
|
435
|
+
ui.hint(`Need ${(0, viem_1.formatEther)(amount)} ETH, have ${Number((0, viem_1.formatEther)(senderBalance)).toFixed(6)} ETH`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const proceed = await ui.confirm("Send ETH?");
|
|
439
|
+
if (!proceed) {
|
|
440
|
+
console.log(" Cancelled.");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const sendSpinner = ui.spinner("Sending ETH...");
|
|
444
|
+
const txHash = await walletClient.sendTransaction({
|
|
445
|
+
account: senderAccount,
|
|
446
|
+
to: destAddress,
|
|
447
|
+
value: amount,
|
|
448
|
+
});
|
|
449
|
+
sendSpinner.update("Waiting for confirmation...");
|
|
450
|
+
const receipt = await wallet_1.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
451
|
+
if (receipt.status === "reverted") {
|
|
452
|
+
sendSpinner.fail("Transaction reverted");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
sendSpinner.stop("Sending ETH... confirmed");
|
|
456
|
+
console.log(` ${ui.green("Sent")} ${(0, viem_1.formatEther)(amount)} ETH to ${destAddress.slice(0, 6)}...${destAddress.slice(-4)}`);
|
|
457
|
+
console.log(` Tx: ${ui.dim((0, explorer_1.txUrl)(receipt.transactionHash))}`);
|
|
458
|
+
console.log("");
|
|
459
|
+
});
|
|
460
|
+
await program.parseAsync(process.argv);
|
|
461
|
+
}
|
|
462
|
+
main().catch((error) => {
|
|
463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
464
|
+
ui.error(message);
|
|
465
|
+
process.exitCode = 1;
|
|
466
|
+
});
|