apow-cli 0.1.4 → 0.2.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 CHANGED
@@ -19,6 +19,10 @@ LLM_API_KEY=
19
19
  # Ollama (only if LLM_PROVIDER=ollama)
20
20
  # OLLAMA_URL=http://127.0.0.1:11434
21
21
 
22
+ # Solana bridging (only needed for `apow fund --solana`)
23
+ # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
24
+ # SQUID_INTEGRATOR_ID=
25
+
22
26
  # Contract addresses (defaults built-in, override only if needed)
23
27
  # MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
24
28
  # AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
package/README.md CHANGED
@@ -34,8 +34,9 @@ LLM_MODEL=gpt-4o-mini
34
34
  LLM_API_KEY=<your key>
35
35
  EOF
36
36
 
37
- # 3. Ask your user to fund the wallet with ≥0.005 ETH on Base
38
- # This is the ONLY step that requires human action
37
+ # 3. Fund the wallet (bridge from Solana or send ETH on Base)
38
+ npx apow-cli fund --solana # bridge SOL ETH on Base
39
+ # Or ask your user to send ≥0.005 ETH on Base directly
39
40
 
40
41
  # 4. Mint a mining rig NFT (solves SMHL challenge via LLM)
41
42
  npx apow-cli mint
@@ -66,7 +67,8 @@ If you prefer to do it yourself:
66
67
  # 1. Interactive setup — wallet, RPC, LLM config
67
68
  npx apow-cli setup
68
69
 
69
- # 2. Fund your wallet with ETH on Base (≥0.005 ETH)
70
+ # 2. Fund your wallet (bridge from Solana or send ETH directly)
71
+ npx apow-cli fund
70
72
 
71
73
  # 3. Mint a mining rig NFT
72
74
  npx apow-cli mint
@@ -80,6 +82,7 @@ npx apow-cli mine
80
82
  | Command | Description |
81
83
  |---------|-------------|
82
84
  | `apow setup` | Interactive setup wizard — configure wallet, RPC, and LLM |
85
+ | `apow fund` | Fund your wallet — bridge SOL → ETH on Base, or show deposit address |
83
86
  | `apow wallet new` | Generate a new mining wallet |
84
87
  | `apow wallet show` | Show configured wallet address |
85
88
  | `apow wallet export` | Export your wallet's private key |
@@ -98,6 +101,9 @@ RPC_URL=https://mainnet.base.org
98
101
  LLM_PROVIDER=openai # openai | anthropic | gemini | ollama | claude-code | codex
99
102
  LLM_MODEL=gpt-4o-mini
100
103
  LLM_API_KEY=sk-...
104
+ # Solana bridging (only for `apow fund --solana`)
105
+ # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
106
+ # SQUID_INTEGRATOR_ID= # free, get at squidrouter.com (deposit address flow only)
101
107
  # Contract addresses (defaults built-in, override only if needed)
102
108
  # MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
103
109
  # AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ // deBridge DLN bridge — direct signing flow (Option A).
3
+ // SOL → native ETH on Base in ~20 seconds via DLN market makers.
4
+ // No API key needed.
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.bridgeViaDeBridge = bridgeViaDeBridge;
40
+ exports.pollOrderStatus = pollOrderStatus;
41
+ const solana = __importStar(require("./solana"));
42
+ const DLN_API = "https://dln.debridge.finance/v1.0";
43
+ const SOLANA_CHAIN_ID = "7565164";
44
+ const BASE_CHAIN_ID = "8453";
45
+ const NATIVE_SOL = "11111111111111111111111111111111"; // System Program = native SOL
46
+ const NATIVE_ETH = "0x0000000000000000000000000000000000000000";
47
+ /**
48
+ * Create and submit a deBridge DLN order: SOL → ETH on Base.
49
+ * Returns after the Solana tx is confirmed. Call pollOrderStatus() to wait for fulfillment.
50
+ */
51
+ async function bridgeViaDeBridge(solanaKeypair, baseAddress, solAmount) {
52
+ const startTime = Date.now();
53
+ const lamports = Math.floor(solAmount * 1e9);
54
+ const srcPublicKey = solanaKeypair.publicKey.toBase58();
55
+ // Step 1: Get serialized bridge transaction from DLN
56
+ const params = new URLSearchParams({
57
+ srcChainId: SOLANA_CHAIN_ID,
58
+ srcChainTokenIn: NATIVE_SOL,
59
+ srcChainTokenInAmount: lamports.toString(),
60
+ dstChainId: BASE_CHAIN_ID,
61
+ dstChainTokenOut: NATIVE_ETH,
62
+ dstChainTokenOutRecipient: baseAddress,
63
+ senderAddress: srcPublicKey,
64
+ srcChainOrderAuthorityAddress: srcPublicKey,
65
+ dstChainOrderAuthorityAddress: baseAddress,
66
+ });
67
+ const response = await fetch(`${DLN_API}/dln/order/create-tx?${params}`);
68
+ if (!response.ok) {
69
+ const body = await response.text();
70
+ throw new Error(`deBridge API error (${response.status}): ${body}`);
71
+ }
72
+ const data = await response.json();
73
+ if (data.errorCode || data.error) {
74
+ throw new Error(`deBridge error: ${data.error || data.message || JSON.stringify(data)}`);
75
+ }
76
+ const orderId = data.orderId;
77
+ const txData = data.tx?.data;
78
+ if (!txData) {
79
+ throw new Error("deBridge API returned no transaction data");
80
+ }
81
+ // Step 2: Sign and submit on Solana
82
+ const txSignature = await solana.signAndSendTransaction(txData, solanaKeypair);
83
+ return {
84
+ orderId,
85
+ txSignature,
86
+ status: "submitted",
87
+ timeMs: Date.now() - startTime,
88
+ };
89
+ }
90
+ /**
91
+ * Poll deBridge order status until fulfilled, cancelled, or timeout.
92
+ * Default timeout: 5 minutes.
93
+ */
94
+ async function pollOrderStatus(orderId, onUpdate, timeoutMs = 300_000) {
95
+ const deadline = Date.now() + timeoutMs;
96
+ while (Date.now() < deadline) {
97
+ try {
98
+ const response = await fetch(`${DLN_API}/dln/order/${orderId}/status`);
99
+ if (response.ok) {
100
+ const data = await response.json();
101
+ const status = data.status || data.orderStatus || "unknown";
102
+ if (onUpdate)
103
+ onUpdate(status);
104
+ if (status === "Fulfilled" ||
105
+ status === "ClaimedUnlock" ||
106
+ status === "SentUnlock") {
107
+ return {
108
+ status: "fulfilled",
109
+ ethReceived: data.fulfilledDstAmount
110
+ ? (Number(data.fulfilledDstAmount) / 1e18).toFixed(6)
111
+ : undefined,
112
+ };
113
+ }
114
+ if (status === "Cancelled" || status === "CancelledByMaker") {
115
+ throw new Error(`Bridge order was cancelled: ${status}`);
116
+ }
117
+ }
118
+ }
119
+ catch (err) {
120
+ if (err instanceof Error && err.message.includes("cancelled"))
121
+ throw err;
122
+ // Transient fetch error — keep polling
123
+ }
124
+ await new Promise((r) => setTimeout(r, 5000));
125
+ }
126
+ throw new Error("Bridge order timed out after 5 minutes. Check deBridge explorer for order: " +
127
+ orderId);
128
+ }
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ // Solana wallet utilities — key parsing, balance checks, transaction signing.
3
+ // Uses dynamic import() for @solana/web3.js to avoid bloating startup.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.getSolanaRpcUrl = getSolanaRpcUrl;
39
+ exports.parseSolanaKey = parseSolanaKey;
40
+ exports.getSolanaBalance = getSolanaBalance;
41
+ exports.getAddressBalance = getAddressBalance;
42
+ exports.signAndSendTransaction = signAndSendTransaction;
43
+ const DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
44
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
45
+ function base58Decode(str) {
46
+ const bytes = [];
47
+ for (const char of str) {
48
+ const idx = BASE58_ALPHABET.indexOf(char);
49
+ if (idx === -1)
50
+ throw new Error(`Invalid base58 character: ${char}`);
51
+ let carry = idx;
52
+ for (let j = 0; j < bytes.length; j++) {
53
+ carry += bytes[j] * 58;
54
+ bytes[j] = carry & 0xff;
55
+ carry >>= 8;
56
+ }
57
+ while (carry > 0) {
58
+ bytes.push(carry & 0xff);
59
+ carry >>= 8;
60
+ }
61
+ }
62
+ // Preserve leading zeros
63
+ for (const char of str) {
64
+ if (char !== "1")
65
+ break;
66
+ bytes.push(0);
67
+ }
68
+ return new Uint8Array(bytes.reverse());
69
+ }
70
+ function getSolanaRpcUrl() {
71
+ return process.env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC;
72
+ }
73
+ /** Parse a base58-encoded Solana secret key (64 bytes) or seed (32 bytes). */
74
+ async function parseSolanaKey(input) {
75
+ const { Keypair } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
76
+ const trimmed = input.trim();
77
+ // Try JSON array format first (Solana CLI keygen output)
78
+ if (trimmed.startsWith("[")) {
79
+ try {
80
+ const arr = JSON.parse(trimmed);
81
+ const keypair = Keypair.fromSecretKey(new Uint8Array(arr));
82
+ return { keypair, publicKey: keypair.publicKey.toBase58() };
83
+ }
84
+ catch {
85
+ throw new Error("Invalid Solana key: looks like JSON but could not parse.");
86
+ }
87
+ }
88
+ // Base58 secret key (Phantom, Backpack export format)
89
+ const decoded = base58Decode(trimmed);
90
+ if (decoded.length === 64) {
91
+ const keypair = Keypair.fromSecretKey(decoded);
92
+ return { keypair, publicKey: keypair.publicKey.toBase58() };
93
+ }
94
+ if (decoded.length === 32) {
95
+ // 32-byte seed
96
+ const keypair = Keypair.fromSeed(decoded);
97
+ return { keypair, publicKey: keypair.publicKey.toBase58() };
98
+ }
99
+ throw new Error(`Invalid Solana key: expected 64 bytes (secret key) or 32 bytes (seed), got ${decoded.length}. Provide the full base58-encoded secret key from your wallet.`);
100
+ }
101
+ /** Get SOL balance for a public key (in SOL, not lamports). */
102
+ async function getSolanaBalance(publicKeyBase58) {
103
+ const { Connection, PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
104
+ const connection = new Connection(getSolanaRpcUrl(), "confirmed");
105
+ const lamports = await connection.getBalance(new PublicKey(publicKeyBase58));
106
+ return lamports / 1e9;
107
+ }
108
+ /** Get SOL balance for any address (used to detect deposits). */
109
+ async function getAddressBalance(address) {
110
+ const { Connection, PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
111
+ const connection = new Connection(getSolanaRpcUrl(), "confirmed");
112
+ const lamports = await connection.getBalance(new PublicKey(address));
113
+ return lamports / 1e9;
114
+ }
115
+ /** Deserialize, sign, and submit a base64-encoded Solana transaction. */
116
+ async function signAndSendTransaction(serializedTxBase64, keypair) {
117
+ const { Connection, VersionedTransaction } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
118
+ const connection = new Connection(getSolanaRpcUrl(), "confirmed");
119
+ const txBuffer = Buffer.from(serializedTxBase64, "base64");
120
+ const tx = VersionedTransaction.deserialize(txBuffer);
121
+ tx.sign([keypair]);
122
+ const signature = await connection.sendTransaction(tx, {
123
+ skipPreflight: false,
124
+ preflightCommitment: "confirmed",
125
+ });
126
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
127
+ await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
128
+ return signature;
129
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ // Squid Router bridge — deposit address flow (Options B+C).
3
+ // SOL → ETH on Base via Chainflip multi-hop (~1-3 minutes).
4
+ // Requires SQUID_INTEGRATOR_ID (free, apply at squidrouter.com).
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getDepositAddress = getDepositAddress;
7
+ exports.pollBridgeStatus = pollBridgeStatus;
8
+ const SQUID_API = "https://v2.api.squidrouter.com/v2";
9
+ const SOLANA_CHAIN_ID = "solana";
10
+ const BASE_CHAIN_ID = "8453";
11
+ const NATIVE_SOL_ADDRESS = "So11111111111111111111111111111111111111112"; // Wrapped SOL mint
12
+ const NATIVE_ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
13
+ function getIntegratorId() {
14
+ const id = process.env.SQUID_INTEGRATOR_ID;
15
+ if (!id) {
16
+ throw new Error("SQUID_INTEGRATOR_ID is required for the deposit address flow.\n" +
17
+ "Get one free at https://app.squidrouter.com/\n" +
18
+ "Or use direct signing instead: apow fund --solana --key <base58>");
19
+ }
20
+ return id;
21
+ }
22
+ /**
23
+ * Get a Squid deposit address for SOL → ETH on Base bridging.
24
+ * User sends SOL to this address from any wallet; Squid handles the rest.
25
+ */
26
+ async function getDepositAddress(baseAddress, solAmount) {
27
+ const integratorId = getIntegratorId();
28
+ const lamports = Math.floor(solAmount * 1e9).toString();
29
+ // Step 1: Get route quote
30
+ const routeResponse = await fetch(`${SQUID_API}/route`, {
31
+ method: "POST",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ "x-integrator-id": integratorId,
35
+ },
36
+ body: JSON.stringify({
37
+ fromChain: SOLANA_CHAIN_ID,
38
+ toChain: BASE_CHAIN_ID,
39
+ fromToken: NATIVE_SOL_ADDRESS,
40
+ toToken: NATIVE_ETH_ADDRESS,
41
+ fromAmount: lamports,
42
+ toAddress: baseAddress,
43
+ quoteOnly: false,
44
+ enableBoost: true,
45
+ prefer: ["CHAINFLIP_DEPOSIT_ADDRESS"],
46
+ }),
47
+ });
48
+ if (!routeResponse.ok) {
49
+ const body = await routeResponse.text();
50
+ throw new Error(`Squid route API error (${routeResponse.status}): ${body}`);
51
+ }
52
+ const routeData = (await routeResponse.json());
53
+ if (routeData.error) {
54
+ throw new Error(`Squid route error: ${routeData.error.message || JSON.stringify(routeData.error)}`);
55
+ }
56
+ // Step 2: Request deposit address from route
57
+ const depositResponse = await fetch(`${SQUID_API}/deposit-address`, {
58
+ method: "POST",
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ "x-integrator-id": integratorId,
62
+ },
63
+ body: JSON.stringify({
64
+ routeData: routeData.route,
65
+ }),
66
+ });
67
+ if (!depositResponse.ok) {
68
+ const body = await depositResponse.text();
69
+ throw new Error(`Squid deposit-address API error (${depositResponse.status}): ${body}`);
70
+ }
71
+ const depositData = (await depositResponse.json());
72
+ const estimatedReceive = routeData.route?.estimate?.toAmount
73
+ ? (Number(routeData.route.estimate.toAmount) / 1e18).toFixed(6)
74
+ : "unknown";
75
+ return {
76
+ depositAddress: depositData.depositAddress,
77
+ requestId: depositData.requestId || routeData.requestId,
78
+ expectedReceive: estimatedReceive,
79
+ expiresAt: depositData.expiresAt,
80
+ };
81
+ }
82
+ /**
83
+ * Poll Squid bridge status until complete, failed, or timeout.
84
+ * Default timeout: 10 minutes (Chainflip can be slow).
85
+ */
86
+ async function pollBridgeStatus(requestId, onUpdate, timeoutMs = 600_000) {
87
+ const integratorId = getIntegratorId();
88
+ const deadline = Date.now() + timeoutMs;
89
+ while (Date.now() < deadline) {
90
+ try {
91
+ const params = new URLSearchParams({
92
+ requestId,
93
+ bridgeType: "chainflipmultihop",
94
+ });
95
+ const response = await fetch(`${SQUID_API}/status?${params}`, {
96
+ headers: { "x-integrator-id": integratorId },
97
+ });
98
+ if (response.ok) {
99
+ const data = (await response.json());
100
+ const status = data.squidTransactionStatus || data.status || "unknown";
101
+ if (onUpdate)
102
+ onUpdate(status);
103
+ if (status === "success" ||
104
+ status === "completed" ||
105
+ status === "destination_executed") {
106
+ return {
107
+ status: "fulfilled",
108
+ ethReceived: data.toChain?.amount
109
+ ? (Number(data.toChain.amount) / 1e18).toFixed(6)
110
+ : undefined,
111
+ };
112
+ }
113
+ if (status === "failed" || status === "refunded") {
114
+ throw new Error(`Bridge failed with status: ${status}`);
115
+ }
116
+ }
117
+ }
118
+ catch (err) {
119
+ if (err instanceof Error &&
120
+ (err.message.includes("failed") || err.message.includes("refunded"))) {
121
+ throw err;
122
+ }
123
+ // Transient fetch error — keep polling
124
+ }
125
+ await new Promise((r) => setTimeout(r, 5000));
126
+ }
127
+ throw new Error("Bridge timed out after 10 minutes. Check Squid explorer: https://axelarscan.io/");
128
+ }
package/dist/fund.js ADDED
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ // Fund command — bridge SOL → ETH on Base so Solana users can start mining.
3
+ // Three paths:
4
+ // Option A: Direct Solana signing via deBridge DLN (~20s)
5
+ // Option B: Deposit address + QR via Squid Router (~1-3 min)
6
+ // Option C: Manual send (just show Base address)
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.runFundFlow = runFundFlow;
42
+ const viem_1 = require("viem");
43
+ const wallet_1 = require("./wallet");
44
+ const ui = __importStar(require("./ui"));
45
+ async function fetchPrices() {
46
+ const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=solana,ethereum&vs_currencies=usd");
47
+ if (!res.ok)
48
+ throw new Error("Failed to fetch prices from CoinGecko");
49
+ const data = (await res.json());
50
+ const ethUsd = data.ethereum.usd;
51
+ const solUsd = data.solana.usd;
52
+ return { solPerEth: ethUsd / solUsd, ethPriceUsd: ethUsd, solPriceUsd: solUsd };
53
+ }
54
+ /** SOL needed for target ETH, with 10% buffer for bridge fees + slippage. */
55
+ function solNeededForEth(targetEth, solPerEth) {
56
+ return targetEth * solPerEth * 1.1;
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // QR code helper
60
+ // ---------------------------------------------------------------------------
61
+ async function showQrCode(text) {
62
+ try {
63
+ const mod = await Promise.resolve().then(() => __importStar(require("qrcode-terminal")));
64
+ const qrcode = mod.default || mod;
65
+ await new Promise((resolve) => {
66
+ qrcode.generate(text, { small: true }, (qr) => {
67
+ for (const line of qr.split("\n")) {
68
+ if (line)
69
+ console.log(` ${line}`);
70
+ }
71
+ resolve();
72
+ });
73
+ });
74
+ }
75
+ catch {
76
+ // QR is nice-to-have, not critical
77
+ }
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Option A — Direct signing via deBridge
81
+ // ---------------------------------------------------------------------------
82
+ async function runDirectBridge(solanaKeyInput, baseAddress, targetEth) {
83
+ const solanaSpinner = ui.spinner("Checking Solana balance...");
84
+ const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
85
+ const debridge = await Promise.resolve().then(() => __importStar(require("./bridge/debridge")));
86
+ let kp;
87
+ try {
88
+ kp = await solana.parseSolanaKey(solanaKeyInput);
89
+ }
90
+ catch (err) {
91
+ solanaSpinner.fail("Invalid Solana key");
92
+ throw err;
93
+ }
94
+ const balance = await solana.getSolanaBalance(kp.publicKey);
95
+ solanaSpinner.stop(`Solana balance: ${balance.toFixed(4)} SOL`);
96
+ // Prices
97
+ const priceSpinner = ui.spinner("Fetching prices...");
98
+ const prices = await fetchPrices();
99
+ const solAmount = solNeededForEth(targetEth, prices.solPerEth);
100
+ priceSpinner.stop(`SOL/ETH rate: ${prices.solPerEth.toFixed(1)} SOL = 1 ETH`);
101
+ if (balance < solAmount) {
102
+ ui.error(`Insufficient SOL. Need ~${solAmount.toFixed(4)} SOL, have ${balance.toFixed(4)} SOL.`);
103
+ return;
104
+ }
105
+ console.log("");
106
+ ui.table([
107
+ ["Bridging", `${solAmount.toFixed(4)} SOL → ~${targetEth.toFixed(4)} ETH on Base`],
108
+ ["Via", "deBridge DLN (~20 seconds)"],
109
+ [
110
+ "From",
111
+ `${kp.publicKey.slice(0, 4)}...${kp.publicKey.slice(-4)}`,
112
+ ],
113
+ ["To", `${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)}`],
114
+ ]);
115
+ console.log("");
116
+ const proceed = await ui.confirm("Confirm bridge?");
117
+ if (!proceed) {
118
+ console.log(" Cancelled.");
119
+ return;
120
+ }
121
+ const bridgeSpinner = ui.spinner("Signing bridge transaction...");
122
+ const result = await debridge.bridgeViaDeBridge(kp.keypair, baseAddress, solAmount);
123
+ bridgeSpinner.stop(`Submitted! Order: ${result.orderId.slice(0, 12)}...`);
124
+ const pollSpinner = ui.spinner("Waiting for bridge fulfillment... (~20s)");
125
+ const fulfillment = await debridge.pollOrderStatus(result.orderId, (status) => pollSpinner.update(`Bridge status: ${status}`));
126
+ const received = fulfillment.ethReceived || `~${targetEth.toFixed(4)}`;
127
+ pollSpinner.stop(`Bridge complete! ${received} ETH arrived on Base`);
128
+ console.log("");
129
+ console.log(` ${ui.green("Your wallet is funded!")} Next: ${ui.cyan("apow mint")}`);
130
+ console.log("");
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // Options B+C — Deposit address via Squid
134
+ // ---------------------------------------------------------------------------
135
+ async function runDepositBridge(baseAddress, targetEth) {
136
+ const priceSpinner = ui.spinner("Fetching prices...");
137
+ const prices = await fetchPrices();
138
+ const solAmount = solNeededForEth(targetEth, prices.solPerEth);
139
+ priceSpinner.stop(`SOL/ETH rate: ${prices.solPerEth.toFixed(1)} SOL = 1 ETH`);
140
+ const addrSpinner = ui.spinner("Generating deposit address...");
141
+ const squid = await Promise.resolve().then(() => __importStar(require("./bridge/squid")));
142
+ const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
143
+ let deposit;
144
+ try {
145
+ deposit = await squid.getDepositAddress(baseAddress, solAmount);
146
+ }
147
+ catch (err) {
148
+ addrSpinner.fail("Failed to get deposit address");
149
+ throw err;
150
+ }
151
+ addrSpinner.stop("Deposit address ready");
152
+ // Display deposit info
153
+ console.log("");
154
+ console.log(` ${ui.bold("Send SOL to this address:")}`);
155
+ console.log("");
156
+ console.log(` ${ui.cyan(deposit.depositAddress)}`);
157
+ console.log("");
158
+ await showQrCode(deposit.depositAddress);
159
+ console.log("");
160
+ ui.table([
161
+ [
162
+ "Amount",
163
+ `~${solAmount.toFixed(4)} SOL (~$${(solAmount * prices.solPriceUsd).toFixed(2)})`,
164
+ ],
165
+ ["You'll receive", `~${deposit.expectedReceive} ETH on Base`],
166
+ ["Bridge", "Squid Router (Chainflip)"],
167
+ ["Time", "~1-3 minutes"],
168
+ ]);
169
+ console.log("");
170
+ if (deposit.expiresAt) {
171
+ ui.warn(`Deposit address expires: ${deposit.expiresAt}`);
172
+ console.log("");
173
+ }
174
+ // Poll for SOL deposit
175
+ const depositSpinner = ui.spinner("Waiting for SOL deposit... (Ctrl+C to cancel)");
176
+ const initialBalance = await solana.getAddressBalance(deposit.depositAddress);
177
+ let depositDetected = false;
178
+ const depositDeadline = Date.now() + 600_000; // 10 min
179
+ while (!depositDetected && Date.now() < depositDeadline) {
180
+ await new Promise((r) => setTimeout(r, 3000));
181
+ try {
182
+ const currentBalance = await solana.getAddressBalance(deposit.depositAddress);
183
+ if (currentBalance > initialBalance + 0.001) {
184
+ depositDetected = true;
185
+ depositSpinner.stop(`Deposit received! ${(currentBalance - initialBalance).toFixed(4)} SOL`);
186
+ }
187
+ }
188
+ catch {
189
+ // Transient RPC error — keep polling
190
+ }
191
+ }
192
+ if (!depositDetected) {
193
+ depositSpinner.fail("No deposit detected after 10 minutes");
194
+ ui.hint("If you sent SOL, check: https://explorer.squidrouter.com");
195
+ return;
196
+ }
197
+ // Poll for bridge completion
198
+ const bridgeSpinner = ui.spinner("Bridging SOL → ETH on Base... (~1-3 min)");
199
+ const result = await squid.pollBridgeStatus(deposit.requestId, (status) => bridgeSpinner.update(`Bridge status: ${status}`));
200
+ const received = result.ethReceived || deposit.expectedReceive;
201
+ bridgeSpinner.stop(`Bridge complete! ${received} ETH arrived`);
202
+ console.log("");
203
+ console.log(` ${ui.green("Your wallet is funded!")} Next: ${ui.cyan("apow mint")}`);
204
+ console.log("");
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // Option C — Manual Base address
208
+ // ---------------------------------------------------------------------------
209
+ async function runManualFund(baseAddress) {
210
+ console.log("");
211
+ console.log(` ${ui.bold("Send ETH on Base to this address:")}`);
212
+ console.log("");
213
+ console.log(` ${ui.cyan(baseAddress)}`);
214
+ console.log("");
215
+ await showQrCode(baseAddress);
216
+ console.log("");
217
+ console.log(` ${ui.dim("Send from any wallet — Coinbase, MetaMask, Phantom, etc.")}`);
218
+ console.log(` ${ui.dim("Need ~0.005 ETH to start mining.")}`);
219
+ console.log(` ${ui.dim("After sending, run:")} ${ui.cyan("apow mint")}`);
220
+ console.log("");
221
+ }
222
+ // ---------------------------------------------------------------------------
223
+ // Main entry point
224
+ // ---------------------------------------------------------------------------
225
+ async function runFundFlow(options) {
226
+ if (!wallet_1.account) {
227
+ ui.error("No wallet configured. Run `apow setup` first.");
228
+ process.exit(1);
229
+ }
230
+ const baseAddress = wallet_1.account.address;
231
+ const balance = await (0, wallet_1.getEthBalance)();
232
+ const ethBalance = Number((0, viem_1.formatEther)(balance));
233
+ console.log("");
234
+ ui.banner(["Fund Your Mining Wallet"]);
235
+ console.log("");
236
+ ui.table([
237
+ [
238
+ "Your Base wallet",
239
+ `${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)}`,
240
+ ],
241
+ [
242
+ "Balance",
243
+ `${ethBalance.toFixed(6)} ETH${ethBalance < 0.005 ? " (need ~0.005 ETH to start)" : ""}`,
244
+ ],
245
+ ]);
246
+ console.log("");
247
+ // Parse target ETH
248
+ const targetEth = options.amount ? parseFloat(options.amount) : 0.005;
249
+ if (isNaN(targetEth) || targetEth <= 0) {
250
+ ui.error("Invalid amount. Specify ETH target (e.g., --amount 0.005).");
251
+ return;
252
+ }
253
+ // --key flag: direct bridge immediately
254
+ if (options.key) {
255
+ await runDirectBridge(options.key, baseAddress, targetEth);
256
+ return;
257
+ }
258
+ // --solana flag: ask about key, then bridge
259
+ if (options.solana) {
260
+ const hasKey = await ui.confirm("Do you have your Solana private key?");
261
+ if (hasKey) {
262
+ const key = await ui.promptSecret("Solana private key (base58)");
263
+ if (!key) {
264
+ ui.error("No key provided.");
265
+ return;
266
+ }
267
+ await runDirectBridge(key, baseAddress, targetEth);
268
+ }
269
+ else {
270
+ await runDepositBridge(baseAddress, targetEth);
271
+ }
272
+ return;
273
+ }
274
+ // Interactive menu
275
+ console.log(" How do you want to fund?");
276
+ console.log(` ${ui.cyan("1.")} Bridge from Solana (SOL → ETH on Base)`);
277
+ console.log(` ${ui.cyan("2.")} Send ETH on Base directly (from another wallet)`);
278
+ console.log(` ${ui.cyan("3.")} Copy address and fund manually`);
279
+ console.log("");
280
+ const choice = await ui.prompt("Choice", "1");
281
+ switch (choice) {
282
+ case "1": {
283
+ const hasKey = await ui.confirm("Do you have your Solana private key?");
284
+ if (hasKey) {
285
+ const key = await ui.promptSecret("Solana private key (base58)");
286
+ if (!key) {
287
+ ui.error("No key provided.");
288
+ return;
289
+ }
290
+ await runDirectBridge(key, baseAddress, targetEth);
291
+ }
292
+ else {
293
+ await runDepositBridge(baseAddress, targetEth);
294
+ }
295
+ break;
296
+ }
297
+ case "2": {
298
+ console.log("");
299
+ console.log(` ${ui.bold("Send ETH on Base to:")}`);
300
+ console.log(` ${ui.cyan(baseAddress)}`);
301
+ console.log("");
302
+ console.log(` ${ui.dim("Need ~0.005 ETH to start mining.")}`);
303
+ console.log(` ${ui.dim("After sending, run:")} ${ui.cyan("apow mint")}`);
304
+ console.log("");
305
+ break;
306
+ }
307
+ case "3":
308
+ default: {
309
+ await runManualFund(baseAddress);
310
+ break;
311
+ }
312
+ }
313
+ }
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ const MiningAgent_json_1 = __importDefault(require("./abi/MiningAgent.json"));
45
45
  const config_1 = require("./config");
46
46
  const detect_1 = require("./detect");
47
47
  const explorer_1 = require("./explorer");
48
+ const fund_1 = require("./fund");
48
49
  const mint_1 = require("./mint");
49
50
  const miner_1 = require("./miner");
50
51
  const preflight_1 = require("./preflight");
@@ -271,6 +272,18 @@ async function main() {
271
272
  .action(async () => {
272
273
  await setupWizard();
273
274
  });
275
+ program
276
+ .command("fund")
277
+ .description("Fund your wallet — bridge SOL → ETH on Base, or show deposit address")
278
+ .option("--solana", "Bridge from Solana")
279
+ .option("--key <base58>", "Solana private key for direct signing")
280
+ .option("--amount <eth>", "Target ETH amount (default: 0.005)")
281
+ .hook("preAction", async () => {
282
+ await (0, preflight_1.runPreflight)("readonly");
283
+ })
284
+ .action(async (opts) => {
285
+ await (0, fund_1.runFundFlow)(opts);
286
+ });
274
287
  program
275
288
  .command("mint")
276
289
  .description("Mint a new miner NFT")
package/dist/smhl.js CHANGED
@@ -234,7 +234,7 @@ async function requestGeminiSolution(prompt) {
234
234
  parts: [{ text: "You generate short lowercase word sequences that match exact constraints. Return only the words separated by spaces. Nothing else." }],
235
235
  },
236
236
  contents: [{ parts: [{ text: prompt }] }],
237
- generationConfig: { temperature: 0.7 },
237
+ generationConfig: { temperature: 0.7, thinkingConfig: { thinkingBudget: 0 } },
238
238
  }),
239
239
  });
240
240
  if (response.status === 429) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apow-cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Mine AGENT tokens on Base L2 with AI-powered proof of work",
5
5
  "keywords": [
6
6
  "apow",
@@ -39,9 +39,11 @@
39
39
  "prepublishOnly": "npm run build"
40
40
  },
41
41
  "dependencies": {
42
+ "@solana/web3.js": "^1.98.0",
42
43
  "commander": "^14.0.0",
43
44
  "dotenv": "^17.2.3",
44
45
  "openai": "^6.6.0",
46
+ "qrcode-terminal": "^0.12.0",
45
47
  "viem": "^2.38.2"
46
48
  },
47
49
  "devDependencies": {
package/skill.md CHANGED
@@ -30,6 +30,7 @@ with ETH on Base. Everything else you handle yourself.
30
30
  **Via npm (recommended):**
31
31
  ```bash
32
32
  npx apow-cli setup # interactive wizard — wallet, RPC, LLM config
33
+ npx apow-cli fund # fund wallet — bridge from Solana or show deposit address
33
34
  npx apow-cli mint # mint a mining rig NFT
34
35
  npx apow-cli mine # start mining (auto-detects your best rig)
35
36
  ```
@@ -65,8 +66,10 @@ MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
65
66
  AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
66
67
  EOF
67
68
 
68
- # 3. Ask your user to fund the wallet with ≥0.005 ETH on Base
69
- # This is the ONLY step that requires human action
69
+ # 3. Fund the wallet (bridge from Solana or send ETH on Base)
70
+ npx apow-cli fund --solana # interactive bridge SOL ETH on Base
71
+ # Or: npx apow-cli fund --solana --key <base58> # direct signing (~20s)
72
+ # Or: ask your user to send ≥0.005 ETH on Base directly
70
73
 
71
74
  # 4. Mint + mine (fully autonomous from here)
72
75
  npx apow-cli mint
@@ -138,9 +141,34 @@ This prompts for confirmation, then displays your address and private key. It al
138
141
  Your mining wallet needs ETH on Base for gas and the mint fee.
139
142
  **Minimum:** 0.005 ETH (~$15) covers minting + several mining cycles.
140
143
 
141
- Ask your user how they want to fund their wallet, then give them the appropriate instructions:
144
+ ### Built-in Bridge: `apow fund` (Recommended)
142
145
 
143
- ### From Solana (Phantom Wallet)
146
+ The CLI has a built-in cross-chain bridge for Solana users:
147
+
148
+ ```bash
149
+ npx apow-cli fund # Interactive: choose funding method
150
+ npx apow-cli fund --solana # Deposit address + QR code
151
+ npx apow-cli fund --solana --key <base58> # Direct Solana signing (~20s)
152
+ npx apow-cli fund --solana --amount 0.01 # Custom ETH target
153
+ ```
154
+
155
+ **Three funding paths:**
156
+
157
+ | Path | How it works | Speed | Requires Solana key? |
158
+ |------|-------------|-------|---------------------|
159
+ | Direct signing | deBridge DLN signs + submits via your Solana keypair | ~20 seconds | Yes (`--key`) |
160
+ | Deposit address | Squid Router generates a Solana address; send from any wallet | ~1-3 minutes | No |
161
+ | Manual | Shows your Base address + QR code | Varies | No |
162
+
163
+ **Direct signing (`--key`):** Provide your base58 Solana secret key. The CLI calls deBridge DLN to create a bridge order, signs the Solana transaction locally, submits it, and polls until ETH arrives on Base. No API key needed.
164
+
165
+ **Deposit address (`--solana` without `--key`):** Requires `SQUID_INTEGRATOR_ID` in `.env` (free, apply at [squidrouter.com](https://app.squidrouter.com/)). Generates a one-time Solana deposit address with a QR code. Send SOL from any wallet (Phantom, Backpack, etc.) and the bridge handles the rest.
166
+
167
+ ### Manual Funding Options
168
+
169
+ If you prefer not to use the built-in bridge:
170
+
171
+ #### From Solana (Phantom Wallet)
144
172
  Phantom natively supports Base. Tell your user:
145
173
  1. Open Phantom → tap the **Swap** icon
146
174
  2. Set **From:** SOL (Solana) → **To:** ETH (Base)
@@ -149,19 +177,17 @@ Phantom natively supports Base. Tell your user:
149
177
  5. Once ETH arrives on Base, tap **Send** → paste the mining wallet address
150
178
  6. Confirm the transfer
151
179
 
152
- Alternatively, use [relay.link/bridge/solana](https://relay.link/bridge/solana) to bridge SOL → ETH on Base directly to the mining wallet address.
153
-
154
- ### From an Exchange (Coinbase, Binance, etc.)
180
+ #### From an Exchange (Coinbase, Binance, etc.)
155
181
  1. Buy ETH on Base (Coinbase supports Base withdrawals natively)
156
182
  2. Withdraw to the mining wallet address
157
183
  3. Select **Base** as the network — do NOT send on Ethereum mainnet
158
184
 
159
- ### From Ethereum Mainnet
185
+ #### From Ethereum Mainnet
160
186
  Bridge ETH to Base via [bridge.base.org](https://bridge.base.org):
161
187
  1. Connect source wallet → enter mining wallet address as recipient
162
188
  2. Bridge ≥0.005 ETH → arrives on Base in ~10 minutes
163
189
 
164
- ### From Another Base Wallet
190
+ #### From Another Base Wallet
165
191
  Send ETH directly to the mining wallet address on Base.
166
192
 
167
193
  ### Verifying Funds
@@ -236,6 +262,8 @@ CHAIN=base
236
262
  | `LLM_MODEL` | No | `gpt-4o-mini` | Model identifier passed to the provider |
237
263
  | `RPC_URL` | No | `https://mainnet.base.org` | Base JSON-RPC endpoint |
238
264
  | `CHAIN` | No | `base` | Network selector; auto-detects `baseSepolia` if RPC URL contains "sepolia" |
265
+ | `SOLANA_RPC_URL` | No | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint (only for `apow fund --solana`) |
266
+ | `SQUID_INTEGRATOR_ID` | No | -- | Squid Router integrator ID for deposit address flow (free at [squidrouter.com](https://app.squidrouter.com/)) |
239
267
 
240
268
  ### LLM Provider Recommendations
241
269
 
@@ -253,10 +281,54 @@ CHAIN=base
253
281
 
254
282
  ### RPC Recommendations
255
283
 
256
- The default `https://mainnet.base.org` is rate-limited. For production mining, use a dedicated RPC:
257
- - [Alchemy](https://www.alchemy.com/) -- `https://base-mainnet.g.alchemy.com/v2/YOUR_KEY`
258
- - [Infura](https://infura.io/) -- `https://base-mainnet.infura.io/v3/YOUR_KEY`
259
- - [QuickNode](https://www.quicknode.com/) -- custom endpoint
284
+ The default `https://mainnet.base.org` is a public RPC with aggressive rate limits. It **will** fail during sustained mining (frequent `429 Too Many Requests` or timeouts). You need a dedicated RPC endpoint. All providers below offer a **free tier** that is more than sufficient for mining.
285
+
286
+ #### Option 1: Alchemy (Recommended)
287
+
288
+ 1. Go to [alchemy.com](https://www.alchemy.com/) and sign up (free, no credit card)
289
+ 2. Click **Create new app** → Name: `apow-miner` → Chain: **Base** → Network: **Base Mainnet**
290
+ 3. On the app dashboard, copy the **HTTPS** URL. It looks like:
291
+ ```
292
+ https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY
293
+ ```
294
+ 4. Set in your `.env`:
295
+ ```
296
+ RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY
297
+ ```
298
+
299
+ **Free tier:** 300M compute units/month (~millions of RPC calls). More than enough for mining.
300
+
301
+ #### Option 2: QuickNode
302
+
303
+ 1. Go to [quicknode.com](https://www.quicknode.com/) and sign up (free, no credit card)
304
+ 2. Click **Create Endpoint** → Chain: **Base** → Network: **Mainnet**
305
+ 3. Copy the **HTTP Provider** URL. It looks like:
306
+ ```
307
+ https://something-something.base-mainnet.quiknode.pro/YOUR_TOKEN/
308
+ ```
309
+ 4. Set in your `.env`:
310
+ ```
311
+ RPC_URL=https://something-something.base-mainnet.quiknode.pro/YOUR_TOKEN/
312
+ ```
313
+
314
+ **Free tier:** 10M API credits/month. Sufficient for a few miners.
315
+
316
+ #### Option 3: Other Free RPCs
317
+
318
+ | Provider | Free Tier | URL Pattern |
319
+ |---|---|---|
320
+ | [Infura](https://infura.io/) | 100K req/day | `https://base-mainnet.infura.io/v3/KEY` |
321
+ | [Ankr](https://www.ankr.com/) | 30 req/s | `https://rpc.ankr.com/base` (no key needed) |
322
+ | [Blast](https://blastapi.io/) | 40 req/s | `https://base-mainnet.blastapi.io/KEY` |
323
+
324
+ #### Troubleshooting RPC Issues
325
+
326
+ | Symptom | Cause | Fix |
327
+ |---|---|---|
328
+ | `429 Too Many Requests` | Public RPC rate limit | Switch to a dedicated RPC (Alchemy/QuickNode) |
329
+ | `Timed out waiting for next block (60s)` | RPC not responding | Check endpoint URL; try a different provider |
330
+ | `fetch failed` / `ECONNREFUSED` | RPC URL is wrong or down | Verify URL; test with `curl YOUR_RPC_URL -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'` |
331
+ | Stale data / missed mines | RPC caching or slow sync | Alchemy and QuickNode are fastest; avoid free community RPCs |
260
332
 
261
333
  ---
262
334
 
@@ -565,12 +637,17 @@ The CLI contains no analytics, no error reporting, and no phone-home behavior of
565
637
  - No error reporting services (no Sentry, no Bugsnag)
566
638
  - No tracking pixels, no usage metrics, no telemetry endpoints
567
639
 
568
- The CLI makes exactly two types of network calls:
640
+ The CLI makes only these network calls:
569
641
 
570
642
  1. **Blockchain RPC** (to user-configured RPC URL, default: `mainnet.base.org`) -- standard `eth_call`, `eth_sendRawTransaction`, etc.
571
643
  2. **LLM API** (to user-configured provider) -- sends only word-puzzle prompts for SMHL solving, never wallet data
644
+ 3. **Bridge APIs** (only when using `apow fund --solana`):
645
+ - **CoinGecko** (`api.coingecko.com`) -- SOL/ETH price quotes
646
+ - **deBridge DLN** (`dln.debridge.finance`) -- bridge order creation and status (direct signing flow)
647
+ - **Squid Router** (`v2.api.squidrouter.com`) -- deposit address generation (deposit address flow)
648
+ - **Solana RPC** (`api.mainnet-beta.solana.com` or custom) -- balance checks and tx submission
572
649
 
573
- Nothing else. No other outbound connections.
650
+ No private keys are transmitted to bridge providers. deBridge returns a serialized Solana transaction that is signed locally. Squid generates a deposit address -- the user sends SOL themselves.
574
651
 
575
652
  ### LLM Calls Are Data-Isolated
576
653
 
@@ -601,7 +678,7 @@ The SMHL solver sends only generic word-generation prompts to the LLM (e.g., "Wr
601
678
  cd apow-cli && npm install && npm run build
602
679
  node dist/index.js setup
603
680
  ```
604
- 5. **Review dependencies.** The dependency tree is minimal and standard: `viem` (Ethereum library), `commander` (CLI framework), `dotenv` (env loading), `chalk`/`ora` (terminal UI), and an LLM client. No exotic or suspicious packages.
681
+ 5. **Review dependencies.** The dependency tree is minimal and standard: `viem` (Ethereum library), `commander` (CLI framework), `dotenv` (env loading), `@solana/web3.js` (Solana signing, lazy-loaded only for bridging), `qrcode-terminal` (QR codes for fund command), and an LLM client. No exotic or suspicious packages.
605
682
 
606
683
  ### How to Verify These Claims Yourself
607
684