apow-cli 0.4.0 → 0.6.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
@@ -1,9 +1,10 @@
1
1
  # Wallet
2
2
  PRIVATE_KEY=
3
3
 
4
- # RPC — The public default works but is unreliable for sustained mining.
5
- # We strongly recommend a free Alchemy endpoint: https://www.alchemy.com/ (no credit card)
6
- RPC_URL=https://mainnet.base.org
4
+ # RPC — Default: Alchemy x402 (premium RPC, paid automatically via USDC in your wallet).
5
+ # No API key needed. If you prefer a different RPC, uncomment and set RPC_URL below.
6
+ # Setting RPC_URL disables x402 and uses the specified endpoint instead.
7
+ # RPC_URL=https://mainnet.base.org
7
8
 
8
9
  # LLM Provider (openai / anthropic / gemini / ollama / claude-code / codex)
9
10
  LLM_PROVIDER=openai
@@ -20,10 +21,14 @@ LLM_API_KEY=
20
21
  # Ollama (only if LLM_PROVIDER=ollama)
21
22
  # OLLAMA_URL=http://127.0.0.1:11434
22
23
 
23
- # Solana bridging (only needed for `apow fund --solana`)
24
+ # Bridging (only needed for `apow fund`)
24
25
  # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
25
26
  # SQUID_INTEGRATOR_ID=
26
27
 
28
+ # Ethereum mainnet RPC (only for `apow fund --chain ethereum`)
29
+ # Default: https://eth.llamarpc.com (free)
30
+ # ETHEREUM_RPC_URL=
31
+
27
32
  # Contract addresses (defaults built-in, override only if needed)
28
33
  # MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
29
34
  # AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Mining client for the [APoW (Agentic Proof of Work)](https://github.com/Agentoshi/apow-core) protocol on Base. Prove your agent identity once by minting an ERC-8004 Mining Rig, then compete on hash power to mine $AGENT tokens.
4
4
 
5
- **Your agent does all the work you just fund a wallet.**
5
+ **Your agent does all the work. You just fund a wallet.**
6
6
 
7
7
  ## Install
8
8
 
@@ -16,19 +16,17 @@ Or run directly:
16
16
  npx apow-cli
17
17
  ```
18
18
 
19
- ## Important: RPC Endpoint
19
+ ## RPC: Alchemy x402 (Default)
20
20
 
21
- > **The default public RPC (`mainnet.base.org`) is unreliable for mining.** It rate-limits aggressively and will cause frequent transaction failures. **We strongly recommend getting a free Alchemy endpoint** before you start:
21
+ > **v0.5.0+: Premium RPC with zero setup.** The CLI uses [Alchemy x402](https://x402.alchemy.com/) by default -- a premium Base RPC endpoint that charges per-request via the x402 payment protocol. Your mining wallet pays automatically with USDC on Base. No API key, no account, no rate limits.
22
22
  >
23
- > 1. Sign up at [alchemy.com](https://www.alchemy.com/) (free, no credit card)
24
- > 2. Create an app → Chain: **Base** → Network: **Base Mainnet**
25
- > 3. Copy the HTTPS URL → set `RPC_URL` in your `.env`
23
+ > **Requirements:** USDC balance on Base in your mining wallet. Even a small amount (1-2 USDC) covers thousands of RPC calls.
26
24
  >
27
- > Alchemy's free tier gives you 300M compute units/month more than enough for mining. See [RPC Recommendations](skill.md#rpc-recommendations) for other free options.
25
+ > **Fallback:** If no USDC is available, the CLI automatically falls back to the public RPC (`mainnet.base.org`). To use a custom RPC instead, set `RPC_URL` in your `.env` (this disables x402).
28
26
 
29
27
  ## For AI Agents
30
28
 
31
- Any AI agent can go from zero to actively mining in 5 steps no human interaction except funding the wallet.
29
+ Any AI agent can go from zero to actively mining in 5 steps with no human interaction except funding the wallet.
32
30
 
33
31
  ```bash
34
32
  # 1. Generate a wallet
@@ -36,20 +34,21 @@ npx apow-cli wallet new
36
34
  # → Captures address + private key from stdout
37
35
 
38
36
  # 2. Write .env (no interactive prompts)
39
- # LLM config is only needed for minting mining uses optimized SMHL solving
37
+ # LLM config is only needed for minting; mining uses optimized SMHL solving
38
+ # RPC defaults to Alchemy x402 (paid via USDC) — no RPC_URL needed
40
39
  cat > .env << 'EOF'
41
40
  PRIVATE_KEY=0x<from step 1>
42
- RPC_URL=https://mainnet.base.org # UNRELIABLE — get a free Alchemy URL (see above)
43
41
  LLM_PROVIDER=openai # Required for minting only
44
42
  LLM_MODEL=gpt-4o-mini # Required for minting only
45
43
  LLM_API_KEY=<your key> # Required for minting only
46
44
  EOF
47
45
 
48
- # 3. Fund the wallet (bridge from Solana or send ETH on Base)
49
- npx apow-cli fund --solana # bridge SOL → ETH on Base
50
- # Or ask your user to send ≥0.005 ETH on Base directly
46
+ # 3. Fund the wallet (bridge from any chain, auto-splits into ETH + USDC)
47
+ npx apow-cli fund --chain solana --token sol # bridge SOL → ETH+USDC on Base
48
+ npx apow-cli fund --chain ethereum --token eth # bridge ETH → ETH+USDC on Base
49
+ # Or send ETH/USDC on Base directly
51
50
 
52
- # 4. Mint a mining rig NFT (proves agent identity via LLM one-time)
51
+ # 4. Mint a mining rig NFT (proves agent identity via LLM, one-time)
53
52
  npx apow-cli mint
54
53
 
55
54
  # 5. Start mining (runs forever, no LLM needed, multi-threaded)
@@ -75,7 +74,7 @@ Each wallet gets one rig, each rig mines independently. More wallets = more chan
75
74
  If you prefer to do it yourself:
76
75
 
77
76
  ```bash
78
- # 1. Interactive setup wallet, RPC, LLM config
77
+ # 1. Interactive setup: wallet, RPC, LLM config
79
78
  npx apow-cli setup
80
79
 
81
80
  # 2. Fund your wallet (bridge from Solana or send ETH directly)
@@ -92,8 +91,8 @@ npx apow-cli mine
92
91
 
93
92
  | Command | Description |
94
93
  |---------|-------------|
95
- | `apow setup` | Interactive setup wizard configure wallet, RPC, and LLM |
96
- | `apow fund` | Fund your wallet bridge SOL ETH on Base, or show deposit address |
94
+ | `apow setup` | Interactive setup wizard: configure wallet, RPC, and LLM |
95
+ | `apow fund` | Fund your wallet: bridge from Solana/Ethereum, swap on Base, auto-split ETH+USDC |
97
96
  | `apow wallet new` | Generate a new mining wallet |
98
97
  | `apow wallet show` | Show configured wallet address |
99
98
  | `apow wallet export` | Export your wallet's private key |
@@ -101,6 +100,9 @@ npx apow-cli mine
101
100
  | `apow mint` | Mint a MiningAgent NFT (one per wallet) |
102
101
  | `apow mine [tokenId]` | Mine $AGENT with your NFT (auto-detects best rig) |
103
102
  | `apow stats [tokenId]` | View mining stats, earnings, difficulty |
103
+ | `apow dashboard start` | Launch multi-wallet mining dashboard |
104
+ | `apow dashboard add <addr>` | Add a wallet to the dashboard |
105
+ | `apow dashboard scan [dir]` | Auto-detect wallet files in a directory |
104
106
 
105
107
  ## Configuration
106
108
 
@@ -108,12 +110,13 @@ Create a `.env` file or use `apow setup`:
108
110
 
109
111
  ```bash
110
112
  PRIVATE_KEY=0x... # Your wallet private key
111
- RPC_URL=https://mainnet.base.org # UNRELIABLE strongly recommend a free Alchemy URL instead (see above)
112
- LLM_PROVIDER=openai # openai | anthropic | gemini | ollama | claude-code | codex (for minting)
113
- LLM_MODEL=gpt-4o-mini # Required for minting only mining uses optimized SMHL solving
113
+ # RPC_URL=https://mainnet.base.org # Optional: set to override default Alchemy x402
114
+ LLM_PROVIDER=openai # openai | gemini | deepseek | qwen | anthropic | ollama (for minting)
115
+ LLM_MODEL=gpt-4o-mini # Required for minting only; mining uses optimized SMHL solving
114
116
  LLM_API_KEY=sk-... # Required for minting only
115
- # Solana bridging (only for `apow fund --solana`)
117
+ # Bridging (only for `apow fund`)
116
118
  # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
119
+ # ETHEREUM_RPC_URL=https://eth.llamarpc.com # free, for `--chain ethereum` only
117
120
  # SQUID_INTEGRATOR_ID= # free, get at squidrouter.com (deposit address flow only)
118
121
  # Contract addresses (defaults built-in, override only if needed)
119
122
  # MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
@@ -124,16 +127,40 @@ See [.env.example](.env.example) for all options.
124
127
 
125
128
  ## LLM Providers (for Minting)
126
129
 
127
- An LLM is required to mint your Mining Rig NFT (one-time identity verification). Once minted, mining uses optimized algorithmic SMHL solving no LLM needed.
130
+ An LLM is required to mint your Mining Rig NFT (one-time identity verification). Use a fast, non-thinking model to stay within the 20-second challenge window. Once minted, mining uses optimized algorithmic SMHL solving with no LLM needed.
128
131
 
129
132
  | Provider | Model | Cost/call | Notes |
130
133
  |----------|-------|-----------|-------|
131
- | OpenAI | `gpt-4o-mini` | ~$0.001 | Cheapest cloud option |
132
- | Anthropic | `claude-sonnet-4-5-20250929` | ~$0.005 | High accuracy |
133
- | Gemini | `gemini-2.5-flash` | ~$0.001 | Fast |
134
+ | OpenAI | `gpt-4o-mini` | ~$0.001 | Recommended. Cheapest, fastest |
135
+ | Gemini | `gemini-2.5-flash` | ~$0.001 | Fast, good accuracy |
136
+ | DeepSeek | `deepseek-chat` | ~$0.001 | Fast, accessible in China |
137
+ | Qwen | `qwen-plus` | ~$0.002 | Alibaba Cloud |
138
+ | Anthropic | `claude-sonnet-4-5-20250929` | ~$0.005 | Works but slower |
134
139
  | Ollama | `llama3.1` | Free | Local GPU required |
135
- | Claude Code | `default` | Subscription | No API key needed |
136
- | Codex | `default` | Subscription | No API key needed |
140
+
141
+ ## Funding (v0.6.0+)
142
+
143
+ Mining requires two assets on Base: **ETH** (gas) and **USDC** (x402 RPC). The `fund` command accepts deposits in 6 forms across 3 chains, auto-bridges to Base, and auto-splits into both:
144
+
145
+ ```bash
146
+ # From Solana
147
+ apow fund --chain solana --token sol # bridge SOL → ETH, auto-swap portion to USDC
148
+ apow fund --chain solana --token usdc # bridge USDC, auto-swap portion to ETH
149
+ apow fund --chain solana --token sol --key <b58> # direct signing (fast, ~20s)
150
+
151
+ # From Ethereum mainnet
152
+ apow fund --chain ethereum --token eth # bridge ETH → Base, auto-swap portion to USDC
153
+ apow fund --chain ethereum --token usdc # bridge USDC → Base, auto-swap portion to ETH
154
+
155
+ # Already on Base
156
+ apow fund --chain base --token eth # show address, wait for deposit, auto-split
157
+ apow fund --chain base --token usdc # show address, wait for deposit, auto-split
158
+
159
+ # Skip auto-split (keep single asset)
160
+ apow fund --chain base --no-swap
161
+ ```
162
+
163
+ **Auto-split targets:** 0.003 ETH (gas for ~100 mine txns) + 2.00 USDC (~100K x402 RPC calls). If both are already met, the CLI skips the swap.
137
164
 
138
165
  ## Speed Mining (v0.4.0+)
139
166
 
@@ -142,6 +169,42 @@ Mining in v0.4.0 uses two key optimizations:
142
169
  - **Algorithmic SMHL**: Mining SMHL challenges are solved algorithmically in microseconds (no LLM call). Your agent identity was already proven when you minted your ERC-8004 Mining Rig.
143
170
  - **Multi-threaded nonce grinding**: Hash computation is parallelized across all CPU cores via `worker_threads`. Set `MINER_THREADS` in `.env` to override the default (all cores).
144
171
 
172
+ > **Want more hash power?** Rent a high-core-count machine on [vast.ai](https://vast.ai/) to increase your nonce grinding throughput. Not required, but scales linearly with core count.
173
+
174
+ ## Dashboard
175
+
176
+ Monitor your entire mining fleet from a single web UI. Zero external dependencies -- vanilla HTML/JS served by the CLI.
177
+
178
+ ```bash
179
+ # Quick start: scan wallet files and launch
180
+ apow dashboard scan . # detect wallet-0x*.txt files in current directory
181
+ apow dashboard start # open dashboard at http://localhost:3847
182
+ ```
183
+
184
+ ### Commands
185
+
186
+ | Command | Description |
187
+ |---------|-------------|
188
+ | `apow dashboard start` | Launch dashboard web UI (default port 3847) |
189
+ | `apow dashboard add <addr>` | Add a wallet address to monitor |
190
+ | `apow dashboard remove <addr>` | Remove a wallet from monitoring |
191
+ | `apow dashboard scan [dir]` | Auto-detect wallets from `wallet-0x*.txt` files |
192
+ | `apow dashboard wallets` | List all monitored wallets |
193
+
194
+ ### Fleet Configuration
195
+
196
+ Wallets are stored in `~/.apow/wallets.json` (plain JSON array of addresses). For advanced fleet management, create `~/.apow/fleets.json`:
197
+
198
+ ```json
199
+ [
200
+ { "name": "Local", "type": "array", "path": "/home/user/.apow/wallets.json" },
201
+ { "name": "Vast.ai", "type": "rigdirs", "path": "/mnt/mining/rigs" },
202
+ { "name": "Pool", "type": "walletfiles", "path": "/mnt/mining/wallets" }
203
+ ]
204
+ ```
205
+
206
+ Fleet types: `array` (JSON array of addresses), `solkek` (master/miners JSON), `rigdirs` (scan `rig*/wallet-0x*.txt`), `walletfiles` (scan `wallet-0x*.txt`).
207
+
145
208
  ## Protocol
146
209
 
147
210
  The APoW protocol contracts and documentation live in [apow-core](https://github.com/Agentoshi/apow-core).
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ // Shared chain IDs, token addresses, and types for cross-chain bridging.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SLIPPAGE_BPS = exports.MIN_USDC = exports.MIN_ETH = exports.TOKENS = exports.CHAIN_IDS = void 0;
5
+ exports.bridgeOutputAsset = bridgeOutputAsset;
6
+ exports.CHAIN_IDS = {
7
+ solana: { debridge: "7565164", squid: "solana" },
8
+ ethereum: { debridge: "1", squid: "1" },
9
+ base: { debridge: "8453", squid: "8453" },
10
+ };
11
+ exports.TOKENS = {
12
+ solana: {
13
+ native: "11111111111111111111111111111111",
14
+ nativeWrapped: "So11111111111111111111111111111111111111112",
15
+ usdc: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
16
+ },
17
+ ethereum: {
18
+ native: "0x0000000000000000000000000000000000000000",
19
+ nativeSquid: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
20
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
21
+ },
22
+ base: {
23
+ native: "0x0000000000000000000000000000000000000000",
24
+ nativeSquid: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
25
+ weth: "0x4200000000000000000000000000000000000006",
26
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
27
+ },
28
+ };
29
+ function bridgeOutputAsset(token) {
30
+ return token === "usdc" ? "usdc" : "eth";
31
+ }
32
+ exports.MIN_ETH = 0.003;
33
+ exports.MIN_USDC = 2.0;
34
+ exports.SLIPPAGE_BPS = 200; // 2%
@@ -1,6 +1,6 @@
1
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.
2
+ // deBridge DLN bridge — direct signing flow.
3
+ // Supports SOL→Base and Ethereum→Base bridging.
4
4
  // No API key needed.
5
5
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
6
  if (k2 === undefined) k2 = k;
@@ -36,40 +36,77 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.bridgeViaDeBridge = bridgeViaDeBridge;
39
+ exports.ROUTES = void 0;
40
+ exports.bridgeFromSolana = bridgeFromSolana;
41
+ exports.bridgeFromEvm = bridgeFromEvm;
40
42
  exports.pollOrderStatus = pollOrderStatus;
43
+ const viem_1 = require("viem");
44
+ const chains_1 = require("viem/chains");
45
+ const accounts_1 = require("viem/accounts");
46
+ const constants_1 = require("./constants");
41
47
  const solana = __importStar(require("./solana"));
42
48
  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";
49
+ exports.ROUTES = {
50
+ sol_to_eth: {
51
+ srcChainId: constants_1.CHAIN_IDS.solana.debridge,
52
+ srcToken: constants_1.TOKENS.solana.native,
53
+ srcDecimals: 9,
54
+ dstChainId: constants_1.CHAIN_IDS.base.debridge,
55
+ dstToken: constants_1.TOKENS.base.native,
56
+ },
57
+ sol_usdc_to_base_usdc: {
58
+ srcChainId: constants_1.CHAIN_IDS.solana.debridge,
59
+ srcToken: constants_1.TOKENS.solana.usdc,
60
+ srcDecimals: 6,
61
+ dstChainId: constants_1.CHAIN_IDS.base.debridge,
62
+ dstToken: constants_1.TOKENS.base.usdc,
63
+ },
64
+ eth_to_base_eth: {
65
+ srcChainId: constants_1.CHAIN_IDS.ethereum.debridge,
66
+ srcToken: constants_1.TOKENS.ethereum.native,
67
+ srcDecimals: 18,
68
+ dstChainId: constants_1.CHAIN_IDS.base.debridge,
69
+ dstToken: constants_1.TOKENS.base.native,
70
+ },
71
+ eth_usdc_to_base_usdc: {
72
+ srcChainId: constants_1.CHAIN_IDS.ethereum.debridge,
73
+ srcToken: constants_1.TOKENS.ethereum.usdc,
74
+ srcDecimals: 6,
75
+ dstChainId: constants_1.CHAIN_IDS.base.debridge,
76
+ dstToken: constants_1.TOKENS.base.usdc,
77
+ },
78
+ };
47
79
  /**
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.
80
+ * Create DLN order params for any route.
50
81
  */
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,
82
+ function buildOrderParams(route, amount, senderAddress, baseAddress) {
83
+ const rawAmount = Math.floor(amount * 10 ** route.srcDecimals);
84
+ return new URLSearchParams({
85
+ srcChainId: route.srcChainId,
86
+ srcChainTokenIn: route.srcToken,
87
+ srcChainTokenInAmount: rawAmount.toString(),
88
+ dstChainId: route.dstChainId,
89
+ dstChainTokenOut: route.dstToken,
62
90
  dstChainTokenOutRecipient: baseAddress,
63
- senderAddress: srcPublicKey,
64
- srcChainOrderAuthorityAddress: srcPublicKey,
91
+ senderAddress,
92
+ srcChainOrderAuthorityAddress: senderAddress,
65
93
  dstChainOrderAuthorityAddress: baseAddress,
66
94
  });
95
+ }
96
+ /**
97
+ * Bridge from Solana to Base via deBridge DLN (direct signing).
98
+ * Works for SOL→ETH and USDC→USDC routes.
99
+ */
100
+ async function bridgeFromSolana(solanaKeypair, baseAddress, amount, route = exports.ROUTES.sol_to_eth) {
101
+ const startTime = Date.now();
102
+ const srcPublicKey = solanaKeypair.publicKey.toBase58();
103
+ const params = buildOrderParams(route, amount, srcPublicKey, baseAddress);
67
104
  const response = await fetch(`${DLN_API}/dln/order/create-tx?${params}`);
68
105
  if (!response.ok) {
69
106
  const body = await response.text();
70
107
  throw new Error(`deBridge API error (${response.status}): ${body}`);
71
108
  }
72
- const data = await response.json();
109
+ const data = (await response.json());
73
110
  if (data.errorCode || data.error) {
74
111
  throw new Error(`deBridge error: ${data.error || data.message || JSON.stringify(data)}`);
75
112
  }
@@ -78,7 +115,6 @@ async function bridgeViaDeBridge(solanaKeypair, baseAddress, solAmount) {
78
115
  if (!txData) {
79
116
  throw new Error("deBridge API returned no transaction data");
80
117
  }
81
- // Step 2: Sign and submit on Solana
82
118
  const txSignature = await solana.signAndSendTransaction(txData, solanaKeypair);
83
119
  return {
84
120
  orderId,
@@ -87,9 +123,96 @@ async function bridgeViaDeBridge(solanaKeypair, baseAddress, solAmount) {
87
123
  timeMs: Date.now() - startTime,
88
124
  };
89
125
  }
126
+ /**
127
+ * Bridge from Ethereum mainnet to Base via deBridge DLN (direct signing).
128
+ * Uses the same PRIVATE_KEY — same address on all EVM chains.
129
+ * Works for ETH→ETH and USDC→USDC routes.
130
+ */
131
+ async function bridgeFromEvm(privateKey, baseAddress, amount, route = exports.ROUTES.eth_to_base_eth) {
132
+ const startTime = Date.now();
133
+ const evmAccount = (0, accounts_1.privateKeyToAccount)(privateKey);
134
+ const rpcUrl = process.env.ETHEREUM_RPC_URL ?? "https://eth.llamarpc.com";
135
+ const ethWalletClient = (0, viem_1.createWalletClient)({
136
+ account: evmAccount,
137
+ chain: chains_1.mainnet,
138
+ transport: (0, viem_1.http)(rpcUrl),
139
+ });
140
+ const params = buildOrderParams(route, amount, evmAccount.address, baseAddress);
141
+ const response = await fetch(`${DLN_API}/dln/order/create-tx?${params}`);
142
+ if (!response.ok) {
143
+ const body = await response.text();
144
+ throw new Error(`deBridge API error (${response.status}): ${body}`);
145
+ }
146
+ const data = (await response.json());
147
+ if (data.errorCode || data.error) {
148
+ throw new Error(`deBridge error: ${data.error || data.message || JSON.stringify(data)}`);
149
+ }
150
+ const orderId = data.orderId;
151
+ // For EVM, the API returns tx params instead of serialized tx
152
+ const tx = data.tx;
153
+ if (!tx || !tx.to) {
154
+ throw new Error("deBridge API returned no EVM transaction data");
155
+ }
156
+ // For USDC routes, handle token approval if needed
157
+ if (tx.allowanceTarget && tx.allowanceValue) {
158
+ const { createPublicClient } = await Promise.resolve().then(() => __importStar(require("viem")));
159
+ const ethPublicClient = createPublicClient({
160
+ chain: chains_1.mainnet,
161
+ transport: (0, viem_1.http)(rpcUrl),
162
+ });
163
+ const erc20Abi = [
164
+ {
165
+ type: "function",
166
+ name: "allowance",
167
+ inputs: [
168
+ { name: "owner", type: "address" },
169
+ { name: "spender", type: "address" },
170
+ ],
171
+ outputs: [{ name: "", type: "uint256" }],
172
+ stateMutability: "view",
173
+ },
174
+ {
175
+ type: "function",
176
+ name: "approve",
177
+ inputs: [
178
+ { name: "spender", type: "address" },
179
+ { name: "amount", type: "uint256" },
180
+ ],
181
+ outputs: [{ name: "", type: "bool" }],
182
+ stateMutability: "nonpayable",
183
+ },
184
+ ];
185
+ const currentAllowance = (await ethPublicClient.readContract({
186
+ address: route.srcToken,
187
+ abi: erc20Abi,
188
+ functionName: "allowance",
189
+ args: [evmAccount.address, tx.allowanceTarget],
190
+ }));
191
+ if (currentAllowance < BigInt(tx.allowanceValue)) {
192
+ const approveTx = await ethWalletClient.writeContract({
193
+ address: route.srcToken,
194
+ abi: erc20Abi,
195
+ functionName: "approve",
196
+ args: [tx.allowanceTarget, BigInt(tx.allowanceValue)],
197
+ });
198
+ await ethPublicClient.waitForTransactionReceipt({ hash: approveTx });
199
+ }
200
+ }
201
+ const txHash = await ethWalletClient.sendTransaction({
202
+ to: tx.to,
203
+ data: tx.data,
204
+ value: tx.value ? BigInt(tx.value) : 0n,
205
+ });
206
+ return {
207
+ orderId,
208
+ txSignature: txHash,
209
+ status: "submitted",
210
+ timeMs: Date.now() - startTime,
211
+ };
212
+ }
90
213
  /**
91
214
  * Poll deBridge order status until fulfilled, cancelled, or timeout.
92
- * Default timeout: 5 minutes.
215
+ * Works for all deBridge orders regardless of source chain.
93
216
  */
94
217
  async function pollOrderStatus(orderId, onUpdate, timeoutMs = 300_000) {
95
218
  const deadline = Date.now() + timeoutMs;
@@ -97,7 +220,7 @@ async function pollOrderStatus(orderId, onUpdate, timeoutMs = 300_000) {
97
220
  try {
98
221
  const response = await fetch(`${DLN_API}/dln/order/${orderId}/status`);
99
222
  if (response.ok) {
100
- const data = await response.json();
223
+ const data = (await response.json());
101
224
  const status = data.status || data.orderStatus || "unknown";
102
225
  if (onUpdate)
103
226
  onUpdate(status);
@@ -106,8 +229,8 @@ async function pollOrderStatus(orderId, onUpdate, timeoutMs = 300_000) {
106
229
  status === "SentUnlock") {
107
230
  return {
108
231
  status: "fulfilled",
109
- ethReceived: data.fulfilledDstAmount
110
- ? (Number(data.fulfilledDstAmount) / 1e18).toFixed(6)
232
+ received: data.fulfilledDstAmount
233
+ ? data.fulfilledDstAmount.toString()
111
234
  : undefined,
112
235
  };
113
236
  }
@@ -39,6 +39,7 @@ exports.getSolanaRpcUrl = getSolanaRpcUrl;
39
39
  exports.parseSolanaKey = parseSolanaKey;
40
40
  exports.getSolanaBalance = getSolanaBalance;
41
41
  exports.getAddressBalance = getAddressBalance;
42
+ exports.getSplTokenBalance = getSplTokenBalance;
42
43
  exports.signAndSendTransaction = signAndSendTransaction;
43
44
  const DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
44
45
  const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
@@ -112,6 +113,18 @@ async function getAddressBalance(address) {
112
113
  const lamports = await connection.getBalance(new PublicKey(address));
113
114
  return lamports / 1e9;
114
115
  }
116
+ /** Get SPL token balance (e.g., USDC) for a Solana public key. Returns UI amount (not raw). */
117
+ async function getSplTokenBalance(publicKeyBase58, mintAddress) {
118
+ const { Connection, PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
119
+ const connection = new Connection(getSolanaRpcUrl(), "confirmed");
120
+ const owner = new PublicKey(publicKeyBase58);
121
+ const mint = new PublicKey(mintAddress);
122
+ const accounts = await connection.getParsedTokenAccountsByOwner(owner, { mint });
123
+ if (accounts.value.length === 0)
124
+ return 0;
125
+ const parsed = accounts.value[0].account.data.parsed;
126
+ return parsed?.info?.tokenAmount?.uiAmount ?? 0;
127
+ }
115
128
  /** Deserialize, sign, and submit a base64-encoded Solana transaction. */
116
129
  async function signAndSendTransaction(serializedTxBase64, keypair) {
117
130
  const { Connection, VersionedTransaction } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
@@ -1,31 +1,63 @@
1
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).
2
+ // Squid Router bridge — deposit address flow.
3
+ // Supports SOL→Base and Ethereum→Base via Chainflip multi-hop (~1-3 minutes).
4
4
  // Requires SQUID_INTEGRATOR_ID (free, apply at squidrouter.com).
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SQUID_ROUTES = void 0;
6
7
  exports.getDepositAddress = getDepositAddress;
7
8
  exports.pollBridgeStatus = pollBridgeStatus;
9
+ const constants_1 = require("./constants");
8
10
  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";
11
+ exports.SQUID_ROUTES = {
12
+ sol_to_eth: {
13
+ fromChain: constants_1.CHAIN_IDS.solana.squid,
14
+ fromToken: constants_1.TOKENS.solana.nativeWrapped,
15
+ toChain: constants_1.CHAIN_IDS.base.squid,
16
+ toToken: constants_1.TOKENS.base.nativeSquid,
17
+ srcDecimals: 9,
18
+ dstDecimals: 18,
19
+ },
20
+ sol_usdc_to_base_usdc: {
21
+ fromChain: constants_1.CHAIN_IDS.solana.squid,
22
+ fromToken: constants_1.TOKENS.solana.usdc,
23
+ toChain: constants_1.CHAIN_IDS.base.squid,
24
+ toToken: constants_1.TOKENS.base.usdc,
25
+ srcDecimals: 6,
26
+ dstDecimals: 6,
27
+ },
28
+ eth_to_base_eth: {
29
+ fromChain: constants_1.CHAIN_IDS.ethereum.squid,
30
+ fromToken: constants_1.TOKENS.ethereum.nativeSquid,
31
+ toChain: constants_1.CHAIN_IDS.base.squid,
32
+ toToken: constants_1.TOKENS.base.nativeSquid,
33
+ srcDecimals: 18,
34
+ dstDecimals: 18,
35
+ },
36
+ eth_usdc_to_base_usdc: {
37
+ fromChain: constants_1.CHAIN_IDS.ethereum.squid,
38
+ fromToken: constants_1.TOKENS.ethereum.usdc,
39
+ toChain: constants_1.CHAIN_IDS.base.squid,
40
+ toToken: constants_1.TOKENS.base.usdc,
41
+ srcDecimals: 6,
42
+ dstDecimals: 6,
43
+ },
44
+ };
13
45
  function getIntegratorId() {
14
46
  const id = process.env.SQUID_INTEGRATOR_ID;
15
47
  if (!id) {
16
48
  throw new Error("SQUID_INTEGRATOR_ID is required for the deposit address flow.\n" +
17
49
  "Get one free at https://app.squidrouter.com/\n" +
18
- "Or use direct signing instead: apow fund --solana --key <base58>");
50
+ "Or use direct signing instead: apow fund --chain solana --key <base58>");
19
51
  }
20
52
  return id;
21
53
  }
22
54
  /**
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.
55
+ * Get a Squid deposit address for bridging to Base.
56
+ * User sends tokens to this address from any wallet; Squid handles the rest.
25
57
  */
26
- async function getDepositAddress(baseAddress, solAmount) {
58
+ async function getDepositAddress(baseAddress, amount, route = exports.SQUID_ROUTES.sol_to_eth) {
27
59
  const integratorId = getIntegratorId();
28
- const lamports = Math.floor(solAmount * 1e9).toString();
60
+ const rawAmount = Math.floor(amount * 10 ** route.srcDecimals).toString();
29
61
  // Step 1: Get route quote
30
62
  const routeResponse = await fetch(`${SQUID_API}/route`, {
31
63
  method: "POST",
@@ -34,11 +66,11 @@ async function getDepositAddress(baseAddress, solAmount) {
34
66
  "x-integrator-id": integratorId,
35
67
  },
36
68
  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,
69
+ fromChain: route.fromChain,
70
+ toChain: route.toChain,
71
+ fromToken: route.fromToken,
72
+ toToken: route.toToken,
73
+ fromAmount: rawAmount,
42
74
  toAddress: baseAddress,
43
75
  quoteOnly: false,
44
76
  enableBoost: true,
@@ -69,8 +101,9 @@ async function getDepositAddress(baseAddress, solAmount) {
69
101
  throw new Error(`Squid deposit-address API error (${depositResponse.status}): ${body}`);
70
102
  }
71
103
  const depositData = (await depositResponse.json());
72
- const estimatedReceive = routeData.route?.estimate?.toAmount
73
- ? (Number(routeData.route.estimate.toAmount) / 1e18).toFixed(6)
104
+ const toAmount = routeData.route?.estimate?.toAmount;
105
+ const estimatedReceive = toAmount
106
+ ? (Number(toAmount) / 10 ** route.dstDecimals).toFixed(route.dstDecimals === 6 ? 2 : 6)
74
107
  : "unknown";
75
108
  return {
76
109
  depositAddress: depositData.depositAddress,
@@ -83,7 +116,7 @@ async function getDepositAddress(baseAddress, solAmount) {
83
116
  * Poll Squid bridge status until complete, failed, or timeout.
84
117
  * Default timeout: 10 minutes (Chainflip can be slow).
85
118
  */
86
- async function pollBridgeStatus(requestId, onUpdate, timeoutMs = 600_000) {
119
+ async function pollBridgeStatus(requestId, dstDecimals = 18, onUpdate, timeoutMs = 600_000) {
87
120
  const integratorId = getIntegratorId();
88
121
  const deadline = Date.now() + timeoutMs;
89
122
  while (Date.now() < deadline) {
@@ -105,8 +138,8 @@ async function pollBridgeStatus(requestId, onUpdate, timeoutMs = 600_000) {
105
138
  status === "destination_executed") {
106
139
  return {
107
140
  status: "fulfilled",
108
- ethReceived: data.toChain?.amount
109
- ? (Number(data.toChain.amount) / 1e18).toFixed(6)
141
+ received: data.toChain?.amount
142
+ ? (Number(data.toChain.amount) / 10 ** dstDecimals).toFixed(dstDecimals === 6 ? 2 : 6)
110
143
  : undefined,
111
144
  };
112
145
  }