apow-cli 0.4.1 → 0.6.1

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/dist/fund.js CHANGED
@@ -1,9 +1,15 @@
1
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)
2
+ // Fund command — unified funding for mining on Base.
3
+ // Accepts deposits in 6 forms across 3 chains, auto-bridges to Base,
4
+ // auto-splits into ETH (gas) + USDC (x402 RPC).
5
+ //
6
+ // Deposit types:
7
+ // 1. Solana SOL → bridge → ETH on Base → swap portion to USDC
8
+ // 2. Solana USDC → bridge → USDC on Base → swap portion to ETH
9
+ // 3. Ethereum ETH → bridge → ETH on Base → swap portion to USDC
10
+ // 4. Ethereum USDC → bridge → USDC on Base → swap portion to ETH
11
+ // 5. Base ETH → (already there) → swap portion to USDC
12
+ // 6. Base USDC → (already there) → swap portion to ETH
7
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
14
  if (k2 === undefined) k2 = k;
9
15
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -40,7 +46,12 @@ var __importStar = (this && this.__importStar) || (function () {
40
46
  Object.defineProperty(exports, "__esModule", { value: true });
41
47
  exports.runFundFlow = runFundFlow;
42
48
  const viem_1 = require("viem");
49
+ const constants_1 = require("./bridge/constants");
50
+ const debridge_1 = require("./bridge/debridge");
51
+ const squid_1 = require("./bridge/squid");
52
+ const uniswap_1 = require("./bridge/uniswap");
43
53
  const wallet_1 = require("./wallet");
54
+ const config_1 = require("./config");
44
55
  const ui = __importStar(require("./ui"));
45
56
  async function fetchPrices() {
46
57
  const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=solana,ethereum&vs_currencies=usd");
@@ -51,9 +62,9 @@ async function fetchPrices() {
51
62
  const solUsd = data.solana.usd;
52
63
  return { solPerEth: ethUsd / solUsd, ethPriceUsd: ethUsd, solPriceUsd: solUsd };
53
64
  }
54
- /** SOL needed for target ETH, with 10% buffer for bridge fees + slippage. */
55
- function solNeededForEth(targetEth, solPerEth) {
56
- return targetEth * solPerEth * 1.1;
65
+ /** Source token amount needed for target ETH worth, with 10% buffer. */
66
+ function amountNeededForEth(targetEth, tokenPriceUsd, ethPriceUsd) {
67
+ return (targetEth * ethPriceUsd / tokenPriceUsd) * 1.1;
57
68
  }
58
69
  // ---------------------------------------------------------------------------
59
70
  // QR code helper
@@ -77,12 +88,112 @@ async function showQrCode(text) {
77
88
  }
78
89
  }
79
90
  // ---------------------------------------------------------------------------
80
- // Option A Direct signing via deBridge
91
+ // Auto-split: ensure both ETH and USDC minimums are met
81
92
  // ---------------------------------------------------------------------------
82
- async function runDirectBridge(solanaKeyInput, baseAddress, targetEth) {
93
+ async function autoSplit(depositedAsset, prices, noSwap) {
94
+ if (noSwap)
95
+ return;
96
+ if (!wallet_1.account)
97
+ return;
98
+ const ethBal = Number((0, viem_1.formatEther)(await (0, wallet_1.getEthBalance)()));
99
+ const usdcBal = Number((0, viem_1.formatUnits)(await (0, uniswap_1.getUsdcBalance)(wallet_1.account.address), 6));
100
+ // Already good
101
+ if (ethBal >= constants_1.MIN_ETH && usdcBal >= constants_1.MIN_USDC) {
102
+ return;
103
+ }
104
+ // Not enough to cover anything useful
105
+ const totalUsd = ethBal * prices.ethPriceUsd + usdcBal;
106
+ const minTotalUsd = constants_1.MIN_ETH * prices.ethPriceUsd + constants_1.MIN_USDC;
107
+ if (totalUsd < minTotalUsd * 0.5) {
108
+ ui.warn("Balance too low for auto-split. Fund more to cover both ETH (gas) and USDC (x402 RPC).");
109
+ return;
110
+ }
111
+ if (depositedAsset === "eth" && usdcBal < constants_1.MIN_USDC) {
112
+ const usdcNeeded = constants_1.MIN_USDC - usdcBal;
113
+ const ethToSwap = (usdcNeeded / prices.ethPriceUsd) * 1.02; // 2% buffer
114
+ if (ethBal - ethToSwap < constants_1.MIN_ETH * 0.5) {
115
+ ui.warn("Not enough ETH to swap for USDC and keep enough for gas. Skipping auto-split.");
116
+ return;
117
+ }
118
+ console.log("");
119
+ ui.table([
120
+ ["Auto-split", `Swap ~${ethToSwap.toFixed(6)} ETH → ~${usdcNeeded.toFixed(2)} USDC`],
121
+ ["Purpose", "USDC for x402 RPC calls"],
122
+ ["Remaining ETH", `~${(ethBal - ethToSwap).toFixed(6)} ETH (gas)`],
123
+ ]);
124
+ console.log("");
125
+ const proceed = await ui.confirm("Confirm swap?");
126
+ if (!proceed) {
127
+ console.log(" Skipped auto-split.");
128
+ return;
129
+ }
130
+ const swapSpinner = ui.spinner("Swapping ETH → USDC on Uniswap V3...");
131
+ const ethWei = (0, viem_1.parseEther)(ethToSwap.toFixed(18));
132
+ const minUsdc = (0, viem_1.parseUnits)((usdcNeeded * (1 - constants_1.SLIPPAGE_BPS / 10000)).toFixed(6), 6);
133
+ try {
134
+ const result = await (0, uniswap_1.swapEthToUsdc)(ethWei, minUsdc);
135
+ swapSpinner.stop(`Swap complete: ${result.usdcReceived} USDC`);
136
+ }
137
+ catch (err) {
138
+ swapSpinner.fail("Swap failed");
139
+ throw err;
140
+ }
141
+ }
142
+ if (depositedAsset === "usdc" && ethBal < constants_1.MIN_ETH) {
143
+ const ethNeeded = constants_1.MIN_ETH - ethBal;
144
+ const usdcToSwap = ethNeeded * prices.ethPriceUsd * 1.02; // 2% buffer
145
+ if (usdcBal - usdcToSwap < constants_1.MIN_USDC * 0.5) {
146
+ ui.warn("Not enough USDC to swap for ETH and keep enough for RPC. Skipping auto-split.");
147
+ return;
148
+ }
149
+ console.log("");
150
+ ui.table([
151
+ ["Auto-split", `Swap ~${usdcToSwap.toFixed(2)} USDC → ~${ethNeeded.toFixed(6)} ETH`],
152
+ ["Purpose", "ETH for gas"],
153
+ ["Remaining USDC", `~${(usdcBal - usdcToSwap).toFixed(2)} USDC (x402 RPC)`],
154
+ ]);
155
+ console.log("");
156
+ const proceed = await ui.confirm("Confirm swap?");
157
+ if (!proceed) {
158
+ console.log(" Skipped auto-split.");
159
+ return;
160
+ }
161
+ const swapSpinner = ui.spinner("Swapping USDC → ETH on Uniswap V3...");
162
+ const usdcRaw = (0, viem_1.parseUnits)(usdcToSwap.toFixed(6), 6);
163
+ const minEth = (0, viem_1.parseEther)((ethNeeded * (1 - constants_1.SLIPPAGE_BPS / 10000)).toFixed(18));
164
+ try {
165
+ const result = await (0, uniswap_1.swapUsdcToEth)(usdcRaw, minEth);
166
+ swapSpinner.stop(`Swap complete: ${result.ethReceived} ETH`);
167
+ }
168
+ catch (err) {
169
+ swapSpinner.fail("Swap failed");
170
+ throw err;
171
+ }
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Show final balances
176
+ // ---------------------------------------------------------------------------
177
+ async function showFinalBalances() {
178
+ if (!wallet_1.account)
179
+ return;
180
+ const ethBal = Number((0, viem_1.formatEther)(await (0, wallet_1.getEthBalance)()));
181
+ const usdcBal = Number((0, viem_1.formatUnits)(await (0, uniswap_1.getUsdcBalance)(wallet_1.account.address), 6));
182
+ console.log("");
183
+ console.log(` ${ui.green("Ready to mine!")}`);
184
+ ui.table([
185
+ ["ETH", `${ethBal.toFixed(6)} ETH (gas)`],
186
+ ["USDC", `${usdcBal.toFixed(2)} USDC (x402 RPC)`],
187
+ ]);
188
+ console.log(` Next: ${ui.cyan("apow mint")}`);
189
+ console.log("");
190
+ }
191
+ // ---------------------------------------------------------------------------
192
+ // Solana direct bridge (deBridge DLN)
193
+ // ---------------------------------------------------------------------------
194
+ async function runSolanaDirectBridge(solanaKeyInput, baseAddress, sourceToken, targetEth) {
83
195
  const solanaSpinner = ui.spinner("Checking Solana balance...");
84
196
  const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
85
- const debridge = await Promise.resolve().then(() => __importStar(require("./bridge/debridge")));
86
197
  let kp;
87
198
  try {
88
199
  kp = await solana.parseSolanaKey(solanaKeyInput);
@@ -91,26 +202,205 @@ async function runDirectBridge(solanaKeyInput, baseAddress, targetEth) {
91
202
  solanaSpinner.fail("Invalid Solana key");
92
203
  throw err;
93
204
  }
94
- const balance = await solana.getSolanaBalance(kp.publicKey);
95
- solanaSpinner.stop(`Solana balance: ${balance.toFixed(4)} SOL`);
96
- // Prices
205
+ if (sourceToken === "usdc") {
206
+ const usdcBal = await solana.getSplTokenBalance(kp.publicKey, constants_1.TOKENS.solana.usdc);
207
+ solanaSpinner.stop(`Solana USDC balance: ${usdcBal.toFixed(2)} USDC`);
208
+ const priceSpinner = ui.spinner("Fetching prices...");
209
+ const prices = await fetchPrices();
210
+ priceSpinner.stop(`ETH price: $${prices.ethPriceUsd.toFixed(0)}`);
211
+ const usdcAmount = targetEth * prices.ethPriceUsd * 1.1; // 10% buffer
212
+ if (usdcBal < usdcAmount) {
213
+ ui.error(`Insufficient USDC. Need ~${usdcAmount.toFixed(2)} USDC, have ${usdcBal.toFixed(2)} USDC.`);
214
+ return;
215
+ }
216
+ console.log("");
217
+ ui.table([
218
+ ["Bridging", `${usdcAmount.toFixed(2)} USDC → USDC on Base`],
219
+ ["Via", "deBridge DLN (~20 seconds)"],
220
+ ["From", `${kp.publicKey.slice(0, 4)}...${kp.publicKey.slice(-4)}`],
221
+ ["To", `${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)}`],
222
+ ]);
223
+ console.log("");
224
+ const proceed = await ui.confirm("Confirm bridge?");
225
+ if (!proceed) {
226
+ console.log(" Cancelled.");
227
+ return;
228
+ }
229
+ const bridgeSpinner = ui.spinner("Signing bridge transaction...");
230
+ const result = await (0, debridge_1.bridgeFromSolana)(kp.keypair, baseAddress, usdcAmount, debridge_1.ROUTES.sol_usdc_to_base_usdc);
231
+ bridgeSpinner.stop(`Submitted! Order: ${result.orderId.slice(0, 12)}...`);
232
+ const pollSpinner = ui.spinner("Waiting for bridge fulfillment... (~20s)");
233
+ const fulfillment = await (0, debridge_1.pollOrderStatus)(result.orderId, (s) => pollSpinner.update(`Bridge status: ${s}`));
234
+ pollSpinner.stop("Bridge complete! USDC arrived on Base");
235
+ await autoSplit("usdc", prices, false);
236
+ await showFinalBalances();
237
+ }
238
+ else {
239
+ // SOL → ETH
240
+ const balance = await solana.getSolanaBalance(kp.publicKey);
241
+ solanaSpinner.stop(`Solana balance: ${balance.toFixed(4)} SOL`);
242
+ const priceSpinner = ui.spinner("Fetching prices...");
243
+ const prices = await fetchPrices();
244
+ const solAmount = amountNeededForEth(targetEth, prices.solPriceUsd, prices.ethPriceUsd);
245
+ priceSpinner.stop(`SOL/ETH rate: ${prices.solPerEth.toFixed(1)} SOL = 1 ETH`);
246
+ if (balance < solAmount) {
247
+ ui.error(`Insufficient SOL. Need ~${solAmount.toFixed(4)} SOL, have ${balance.toFixed(4)} SOL.`);
248
+ return;
249
+ }
250
+ console.log("");
251
+ ui.table([
252
+ ["Bridging", `${solAmount.toFixed(4)} SOL → ~${targetEth.toFixed(4)} ETH on Base`],
253
+ ["Via", "deBridge DLN (~20 seconds)"],
254
+ ["From", `${kp.publicKey.slice(0, 4)}...${kp.publicKey.slice(-4)}`],
255
+ ["To", `${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)}`],
256
+ ]);
257
+ console.log("");
258
+ const proceed = await ui.confirm("Confirm bridge?");
259
+ if (!proceed) {
260
+ console.log(" Cancelled.");
261
+ return;
262
+ }
263
+ const bridgeSpinner = ui.spinner("Signing bridge transaction...");
264
+ const result = await (0, debridge_1.bridgeFromSolana)(kp.keypair, baseAddress, solAmount, debridge_1.ROUTES.sol_to_eth);
265
+ bridgeSpinner.stop(`Submitted! Order: ${result.orderId.slice(0, 12)}...`);
266
+ const pollSpinner = ui.spinner("Waiting for bridge fulfillment... (~20s)");
267
+ const fulfillment = await (0, debridge_1.pollOrderStatus)(result.orderId, (s) => pollSpinner.update(`Bridge status: ${s}`));
268
+ const received = fulfillment.received
269
+ ? (Number(fulfillment.received) / 1e18).toFixed(6)
270
+ : `~${targetEth.toFixed(4)}`;
271
+ pollSpinner.stop(`Bridge complete! ${received} ETH arrived on Base`);
272
+ await autoSplit("eth", prices, false);
273
+ await showFinalBalances();
274
+ }
275
+ }
276
+ // ---------------------------------------------------------------------------
277
+ // Solana deposit address bridge (Squid Router)
278
+ // ---------------------------------------------------------------------------
279
+ async function runSolanaDepositBridge(baseAddress, sourceToken, targetEth) {
97
280
  const priceSpinner = ui.spinner("Fetching prices...");
98
281
  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.`);
282
+ priceSpinner.stop(`ETH price: $${prices.ethPriceUsd.toFixed(0)}`);
283
+ const route = sourceToken === "usdc"
284
+ ? squid_1.SQUID_ROUTES.sol_usdc_to_base_usdc
285
+ : squid_1.SQUID_ROUTES.sol_to_eth;
286
+ const amount = sourceToken === "usdc"
287
+ ? targetEth * prices.ethPriceUsd * 1.1
288
+ : amountNeededForEth(targetEth, prices.solPriceUsd, prices.ethPriceUsd);
289
+ const tokenLabel = sourceToken === "usdc" ? "USDC" : "SOL";
290
+ const addrSpinner = ui.spinner("Generating deposit address...");
291
+ const squid = await Promise.resolve().then(() => __importStar(require("./bridge/squid")));
292
+ const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
293
+ let deposit;
294
+ try {
295
+ deposit = await squid.getDepositAddress(baseAddress, amount, route);
296
+ }
297
+ catch (err) {
298
+ addrSpinner.fail("Failed to get deposit address");
299
+ throw err;
300
+ }
301
+ addrSpinner.stop("Deposit address ready");
302
+ console.log("");
303
+ console.log(` ${ui.bold(`Send ${tokenLabel} to this address:`)}`);
304
+ console.log("");
305
+ console.log(` ${ui.cyan(deposit.depositAddress)}`);
306
+ console.log("");
307
+ await showQrCode(deposit.depositAddress);
308
+ console.log("");
309
+ ui.table([
310
+ ["Amount", `~${amount.toFixed(sourceToken === "usdc" ? 2 : 4)} ${tokenLabel}`],
311
+ ["You'll receive", `~${deposit.expectedReceive} ${sourceToken === "usdc" ? "USDC" : "ETH"} on Base`],
312
+ ["Bridge", "Squid Router (Chainflip)"],
313
+ ["Time", "~1-3 minutes"],
314
+ ]);
315
+ console.log("");
316
+ if (deposit.expiresAt) {
317
+ ui.warn(`Deposit address expires: ${deposit.expiresAt}`);
318
+ console.log("");
319
+ }
320
+ // Poll for deposit
321
+ const depositSpinner = ui.spinner(`Waiting for ${tokenLabel} deposit... (Ctrl+C to cancel)`);
322
+ if (sourceToken === "native") {
323
+ // SOL deposit: poll Solana balance
324
+ const initialBalance = await solana.getAddressBalance(deposit.depositAddress);
325
+ let depositDetected = false;
326
+ const depositDeadline = Date.now() + 600_000;
327
+ while (!depositDetected && Date.now() < depositDeadline) {
328
+ await new Promise((r) => setTimeout(r, 3000));
329
+ try {
330
+ const currentBalance = await solana.getAddressBalance(deposit.depositAddress);
331
+ if (currentBalance > initialBalance + 0.001) {
332
+ depositDetected = true;
333
+ depositSpinner.stop(`Deposit received! ${(currentBalance - initialBalance).toFixed(4)} SOL`);
334
+ }
335
+ }
336
+ catch {
337
+ // Transient RPC error
338
+ }
339
+ }
340
+ if (!depositDetected) {
341
+ depositSpinner.fail(`No ${tokenLabel} deposit detected after 10 minutes`);
342
+ ui.hint("If you sent tokens, check: https://explorer.squidrouter.com");
343
+ return;
344
+ }
345
+ }
346
+ else {
347
+ // USDC deposit: poll SPL token balance at the deposit address
348
+ let depositDetected = false;
349
+ const depositDeadline = Date.now() + 600_000;
350
+ while (!depositDetected && Date.now() < depositDeadline) {
351
+ await new Promise((r) => setTimeout(r, 3000));
352
+ try {
353
+ const bal = await solana.getSplTokenBalance(deposit.depositAddress, constants_1.TOKENS.solana.usdc);
354
+ if (bal > 0.01) {
355
+ depositDetected = true;
356
+ depositSpinner.stop(`Deposit received! ${bal.toFixed(2)} USDC`);
357
+ }
358
+ }
359
+ catch {
360
+ // Transient RPC error
361
+ }
362
+ }
363
+ if (!depositDetected) {
364
+ depositSpinner.fail("No USDC deposit detected after 10 minutes");
365
+ ui.hint("If you sent USDC, check: https://explorer.squidrouter.com");
366
+ return;
367
+ }
368
+ }
369
+ // Poll for bridge completion
370
+ const bridgeSpinner = ui.spinner("Bridging to Base... (~1-3 min)");
371
+ const result = await (0, squid_1.pollBridgeStatus)(deposit.requestId, route.dstDecimals, (status) => bridgeSpinner.update(`Bridge status: ${status}`));
372
+ const received = result.received || deposit.expectedReceive;
373
+ const receivedAsset = sourceToken === "usdc" ? "USDC" : "ETH";
374
+ bridgeSpinner.stop(`Bridge complete! ${received} ${receivedAsset} arrived`);
375
+ const outputAsset = (0, constants_1.bridgeOutputAsset)(sourceToken);
376
+ await autoSplit(outputAsset, prices, false);
377
+ await showFinalBalances();
378
+ }
379
+ // ---------------------------------------------------------------------------
380
+ // Ethereum direct bridge (deBridge DLN)
381
+ // ---------------------------------------------------------------------------
382
+ async function runEthereumDirectBridge(baseAddress, sourceToken, targetEth) {
383
+ if (!config_1.config.privateKey) {
384
+ ui.error("No PRIVATE_KEY configured. Your wallet key is used on Ethereum mainnet too.");
103
385
  return;
104
386
  }
387
+ const priceSpinner = ui.spinner("Fetching prices...");
388
+ const prices = await fetchPrices();
389
+ priceSpinner.stop(`ETH price: $${prices.ethPriceUsd.toFixed(0)}`);
390
+ const route = sourceToken === "usdc"
391
+ ? debridge_1.ROUTES.eth_usdc_to_base_usdc
392
+ : debridge_1.ROUTES.eth_to_base_eth;
393
+ const amount = sourceToken === "usdc"
394
+ ? targetEth * prices.ethPriceUsd * 1.1
395
+ : targetEth * 1.1;
396
+ const tokenLabel = sourceToken === "usdc" ? "USDC" : "ETH";
397
+ const receivedLabel = sourceToken === "usdc" ? "USDC" : "ETH";
105
398
  console.log("");
106
399
  ui.table([
107
- ["Bridging", `${solAmount.toFixed(4)} SOL~${targetEth.toFixed(4)} ETH on Base`],
400
+ ["Bridging", `${amount.toFixed(sourceToken === "usdc" ? 2 : 6)} ${tokenLabel}${receivedLabel} on Base`],
108
401
  ["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)}`],
402
+ ["From", `Ethereum mainnet (${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)})`],
403
+ ["To", `Base (same address)`],
114
404
  ]);
115
405
  console.log("");
116
406
  const proceed = await ui.confirm("Confirm bridge?");
@@ -118,51 +408,58 @@ async function runDirectBridge(solanaKeyInput, baseAddress, targetEth) {
118
408
  console.log(" Cancelled.");
119
409
  return;
120
410
  }
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("");
411
+ const bridgeSpinner = ui.spinner("Signing bridge transaction on Ethereum...");
412
+ try {
413
+ const result = await (0, debridge_1.bridgeFromEvm)(config_1.config.privateKey, baseAddress, amount, route);
414
+ bridgeSpinner.stop(`Submitted! Order: ${result.orderId.slice(0, 12)}...`);
415
+ const pollSpinner = ui.spinner("Waiting for bridge fulfillment... (~20s)");
416
+ const fulfillment = await (0, debridge_1.pollOrderStatus)(result.orderId, (s) => pollSpinner.update(`Bridge status: ${s}`));
417
+ pollSpinner.stop(`Bridge complete! ${receivedLabel} arrived on Base`);
418
+ const outputAsset = (0, constants_1.bridgeOutputAsset)(sourceToken);
419
+ await autoSplit(outputAsset, prices, false);
420
+ await showFinalBalances();
421
+ }
422
+ catch (err) {
423
+ bridgeSpinner.fail("Bridge failed");
424
+ throw err;
425
+ }
131
426
  }
132
427
  // ---------------------------------------------------------------------------
133
- // Options B+C — Deposit address via Squid
428
+ // Ethereum deposit address bridge (Squid Router)
134
429
  // ---------------------------------------------------------------------------
135
- async function runDepositBridge(baseAddress, targetEth) {
430
+ async function runEthereumDepositBridge(baseAddress, sourceToken, targetEth) {
136
431
  const priceSpinner = ui.spinner("Fetching prices...");
137
432
  const prices = await fetchPrices();
138
- const solAmount = solNeededForEth(targetEth, prices.solPerEth);
139
- priceSpinner.stop(`SOL/ETH rate: ${prices.solPerEth.toFixed(1)} SOL = 1 ETH`);
433
+ priceSpinner.stop(`ETH price: $${prices.ethPriceUsd.toFixed(0)}`);
434
+ const route = sourceToken === "usdc"
435
+ ? squid_1.SQUID_ROUTES.eth_usdc_to_base_usdc
436
+ : squid_1.SQUID_ROUTES.eth_to_base_eth;
437
+ const amount = sourceToken === "usdc"
438
+ ? targetEth * prices.ethPriceUsd * 1.1
439
+ : targetEth * 1.1;
440
+ const tokenLabel = sourceToken === "usdc" ? "USDC" : "ETH";
140
441
  const addrSpinner = ui.spinner("Generating deposit address...");
141
442
  const squid = await Promise.resolve().then(() => __importStar(require("./bridge/squid")));
142
- const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
143
443
  let deposit;
144
444
  try {
145
- deposit = await squid.getDepositAddress(baseAddress, solAmount);
445
+ deposit = await squid.getDepositAddress(baseAddress, amount, route);
146
446
  }
147
447
  catch (err) {
148
448
  addrSpinner.fail("Failed to get deposit address");
149
449
  throw err;
150
450
  }
151
451
  addrSpinner.stop("Deposit address ready");
152
- // Display deposit info
153
452
  console.log("");
154
- console.log(` ${ui.bold("Send SOL to this address:")}`);
453
+ console.log(` ${ui.bold(`Send ${tokenLabel} on Ethereum mainnet to:`)}`);
155
454
  console.log("");
156
455
  console.log(` ${ui.cyan(deposit.depositAddress)}`);
157
456
  console.log("");
158
457
  await showQrCode(deposit.depositAddress);
159
458
  console.log("");
160
459
  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`],
460
+ ["Amount", `~${amount.toFixed(sourceToken === "usdc" ? 2 : 6)} ${tokenLabel}`],
461
+ ["Network", "Ethereum mainnet"],
462
+ ["You'll receive", `~${deposit.expectedReceive} ${sourceToken === "usdc" ? "USDC" : "ETH"} on Base`],
166
463
  ["Bridge", "Squid Router (Chainflip)"],
167
464
  ["Time", "~1-3 minutes"],
168
465
  ]);
@@ -171,53 +468,127 @@ async function runDepositBridge(baseAddress, targetEth) {
171
468
  ui.warn(`Deposit address expires: ${deposit.expiresAt}`);
172
469
  console.log("");
173
470
  }
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);
471
+ // Wait for user to send manually — poll Squid status directly
472
+ const bridgeSpinner = ui.spinner(`Send ${tokenLabel} and waiting for bridge... (Ctrl+C to cancel)`);
473
+ // For Ethereum deposits, we rely on Squid status API rather than polling an RPC
474
+ const result = await (0, squid_1.pollBridgeStatus)(deposit.requestId, route.dstDecimals, (status) => bridgeSpinner.update(`Bridge status: ${status}`), 900_000);
475
+ const received = result.received || deposit.expectedReceive;
476
+ const receivedAsset = sourceToken === "usdc" ? "USDC" : "ETH";
477
+ bridgeSpinner.stop(`Bridge complete! ${received} ${receivedAsset} arrived`);
478
+ const outputAsset = (0, constants_1.bridgeOutputAsset)(sourceToken);
479
+ await autoSplit(outputAsset, prices, false);
480
+ await showFinalBalances();
481
+ }
482
+ // ---------------------------------------------------------------------------
483
+ // Base manual send (already on the right chain)
484
+ // ---------------------------------------------------------------------------
485
+ async function runBaseFund(baseAddress, sourceToken, noSwap) {
486
+ const tokenLabel = sourceToken === "usdc" ? "USDC" : "ETH";
487
+ console.log("");
488
+ console.log(` ${ui.bold(`Send ${tokenLabel} on Base to this address:`)}`);
489
+ console.log("");
490
+ console.log(` ${ui.cyan(baseAddress)}`);
491
+ console.log("");
492
+ await showQrCode(baseAddress);
493
+ console.log("");
494
+ console.log(` ${ui.dim("Send from any wallet — Coinbase, MetaMask, Phantom, etc.")}`);
495
+ if (sourceToken === "usdc") {
496
+ console.log(` ${ui.dim("Need at least 2 USDC for x402 RPC + some for ETH swap.")}`);
497
+ }
498
+ else {
499
+ console.log(` ${ui.dim("Need ~0.005 ETH to cover gas + USDC swap.")}`);
500
+ }
501
+ console.log("");
502
+ const waitForDeposit = await ui.confirm("Wait for deposit and auto-split?");
503
+ if (!waitForDeposit) {
504
+ console.log(` ${ui.dim("After sending, run:")} ${ui.cyan("apow fund --chain base")} to auto-split.`);
505
+ console.log("");
506
+ return;
507
+ }
508
+ const prices = await fetchPrices();
509
+ // Poll for balance change
510
+ const depositSpinner = ui.spinner(`Waiting for ${tokenLabel} deposit... (Ctrl+C to cancel)`);
511
+ const initialEth = await (0, wallet_1.getEthBalance)();
512
+ const initialUsdc = wallet_1.account ? await (0, uniswap_1.getUsdcBalance)(wallet_1.account.address) : 0n;
513
+ const depositDeadline = Date.now() + 600_000;
177
514
  let depositDetected = false;
178
- const depositDeadline = Date.now() + 600_000; // 10 min
179
515
  while (!depositDetected && Date.now() < depositDeadline) {
180
516
  await new Promise((r) => setTimeout(r, 3000));
181
517
  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`);
518
+ if (sourceToken === "usdc" && wallet_1.account) {
519
+ const currentUsdc = await (0, uniswap_1.getUsdcBalance)(wallet_1.account.address);
520
+ if (currentUsdc > initialUsdc + 100000n) { // > 0.1 USDC
521
+ depositDetected = true;
522
+ depositSpinner.stop(`Deposit received! ${(0, viem_1.formatUnits)(currentUsdc - initialUsdc, 6)} USDC`);
523
+ }
524
+ }
525
+ else {
526
+ const currentEth = await (0, wallet_1.getEthBalance)();
527
+ if (currentEth > initialEth + (0, viem_1.parseEther)("0.0001")) {
528
+ depositDetected = true;
529
+ depositSpinner.stop(`Deposit received! ${(0, viem_1.formatEther)(currentEth - initialEth)} ETH`);
530
+ }
186
531
  }
187
532
  }
188
533
  catch {
189
- // Transient RPC error — keep polling
534
+ // Transient RPC error
190
535
  }
191
536
  }
192
537
  if (!depositDetected) {
193
- depositSpinner.fail("No deposit detected after 10 minutes");
194
- ui.hint("If you sent SOL, check: https://explorer.squidrouter.com");
538
+ depositSpinner.fail(`No ${tokenLabel} deposit detected after 10 minutes`);
195
539
  return;
196
540
  }
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("");
541
+ const outputAsset = (0, constants_1.bridgeOutputAsset)(sourceToken);
542
+ await autoSplit(outputAsset, prices, noSwap);
543
+ await showFinalBalances();
205
544
  }
206
545
  // ---------------------------------------------------------------------------
207
- // Option C — Manual Base address
546
+ // Interactive menus
208
547
  // ---------------------------------------------------------------------------
209
- async function runManualFund(baseAddress) {
548
+ async function selectSourceChain() {
549
+ console.log(" Where are your funds?");
550
+ console.log(` ${ui.cyan("1.")} Solana (SOL or USDC)`);
551
+ console.log(` ${ui.cyan("2.")} Ethereum mainnet (ETH or USDC)`);
552
+ console.log(` ${ui.cyan("3.")} Base (send ETH or USDC manually)`);
210
553
  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);
554
+ const choice = await ui.prompt("Choice", "1");
555
+ switch (choice) {
556
+ case "2": return "ethereum";
557
+ case "3": return "base";
558
+ default: return "solana";
559
+ }
560
+ }
561
+ async function selectSourceToken(chain) {
562
+ const nativeLabel = chain === "solana" ? "SOL" : "ETH";
216
563
  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")}`);
564
+ console.log(" What token?");
565
+ console.log(` ${ui.cyan("1.")} ${nativeLabel}`);
566
+ console.log(` ${ui.cyan("2.")} USDC`);
220
567
  console.log("");
568
+ const choice = await ui.prompt("Choice", "1");
569
+ return choice === "2" ? "usdc" : "native";
570
+ }
571
+ function parseSourceChain(value) {
572
+ if (!value)
573
+ return undefined;
574
+ const v = value.toLowerCase();
575
+ if (v === "solana" || v === "sol")
576
+ return "solana";
577
+ if (v === "ethereum" || v === "eth")
578
+ return "ethereum";
579
+ if (v === "base")
580
+ return "base";
581
+ return undefined;
582
+ }
583
+ function parseSourceToken(value) {
584
+ if (!value)
585
+ return undefined;
586
+ const v = value.toLowerCase();
587
+ if (v === "sol" || v === "eth" || v === "native")
588
+ return "native";
589
+ if (v === "usdc")
590
+ return "usdc";
591
+ return undefined;
221
592
  }
222
593
  // ---------------------------------------------------------------------------
223
594
  // Main entry point
@@ -228,85 +599,87 @@ async function runFundFlow(options) {
228
599
  process.exit(1);
229
600
  }
230
601
  const baseAddress = wallet_1.account.address;
231
- const balance = await (0, wallet_1.getEthBalance)();
232
- const ethBalance = Number((0, viem_1.formatEther)(balance));
602
+ const ethBalance = Number((0, viem_1.formatEther)(await (0, wallet_1.getEthBalance)()));
603
+ const usdcBalance = Number((0, viem_1.formatUnits)(await (0, uniswap_1.getUsdcBalance)(baseAddress), 6));
604
+ const noSwap = options.swap === false;
233
605
  console.log("");
234
606
  ui.banner(["Fund Your Mining Wallet"]);
235
607
  console.log("");
236
608
  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
- ],
609
+ ["Wallet", `${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)}`],
610
+ ["ETH", `${ethBalance.toFixed(6)} ETH${ethBalance < constants_1.MIN_ETH ? ` (need ≥${constants_1.MIN_ETH} for gas)` : ""}`],
611
+ ["USDC", `${usdcBalance.toFixed(2)} USDC${usdcBalance < constants_1.MIN_USDC ? ` (need ≥${constants_1.MIN_USDC} for x402 RPC)` : ""}`],
245
612
  ]);
246
613
  console.log("");
247
- // Parse target ETH
614
+ // Already funded?
615
+ if (ethBalance >= constants_1.MIN_ETH && usdcBalance >= constants_1.MIN_USDC) {
616
+ console.log(` ${ui.green("Already funded! Ready to mine.")}`);
617
+ console.log(` Next: ${ui.cyan("apow mint")}`);
618
+ console.log("");
619
+ // Allow explicit re-run if user wants to add more
620
+ const addMore = await ui.confirm("Add more funds?");
621
+ if (!addMore)
622
+ return;
623
+ }
624
+ // Parse target ETH amount
248
625
  const targetEth = options.amount ? parseFloat(options.amount) : 0.005;
249
626
  if (isNaN(targetEth) || targetEth <= 0) {
250
627
  ui.error("Invalid amount. Specify ETH target (e.g., --amount 0.005).");
251
628
  return;
252
629
  }
253
- // --key flag: direct bridge immediately
254
- if (options.key) {
255
- await runDirectBridge(options.key, baseAddress, targetEth);
256
- return;
630
+ // Resolve source chain and token (from flags or interactive)
631
+ let chain = parseSourceChain(options.chain);
632
+ let token = parseSourceToken(options.token);
633
+ if (!chain) {
634
+ chain = await selectSourceChain();
257
635
  }
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;
636
+ if (!token) {
637
+ token = await selectSourceToken(chain);
273
638
  }
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);
639
+ // Route to the appropriate flow
640
+ switch (chain) {
641
+ case "solana": {
642
+ if (options.key) {
643
+ await runSolanaDirectBridge(options.key, baseAddress, token, targetEth);
291
644
  }
292
645
  else {
293
- await runDepositBridge(baseAddress, targetEth);
646
+ console.log("");
647
+ console.log(" Bridge method:");
648
+ console.log(` ${ui.cyan("A.")} Direct signing (~20s, need Solana private key)`);
649
+ console.log(` ${ui.cyan("B.")} Deposit address (~1-3 min, send from any wallet)`);
650
+ console.log("");
651
+ const method = await ui.prompt("Choice", "A");
652
+ if (method.toUpperCase() === "A") {
653
+ const key = await ui.promptSecret("Solana private key (base58)");
654
+ if (!key) {
655
+ ui.error("No key provided.");
656
+ return;
657
+ }
658
+ await runSolanaDirectBridge(key, baseAddress, token, targetEth);
659
+ }
660
+ else {
661
+ await runSolanaDepositBridge(baseAddress, token, targetEth);
662
+ }
294
663
  }
295
664
  break;
296
665
  }
297
- case "2": {
666
+ case "ethereum": {
298
667
  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")}`);
668
+ console.log(" Bridge method:");
669
+ console.log(` ${ui.cyan("A.")} Direct signing (~20s, uses your PRIVATE_KEY on mainnet)`);
670
+ console.log(` ${ui.cyan("B.")} Deposit address (~1-3 min, send from any wallet)`);
304
671
  console.log("");
672
+ const method = await ui.prompt("Choice", "A");
673
+ if (method.toUpperCase() === "A") {
674
+ await runEthereumDirectBridge(baseAddress, token, targetEth);
675
+ }
676
+ else {
677
+ await runEthereumDepositBridge(baseAddress, token, targetEth);
678
+ }
305
679
  break;
306
680
  }
307
- case "3":
308
- default: {
309
- await runManualFund(baseAddress);
681
+ case "base": {
682
+ await runBaseFund(baseAddress, token, noSwap);
310
683
  break;
311
684
  }
312
685
  }