apow-cli 0.6.2 → 0.7.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 +0 -4
- package/README.md +7 -12
- package/dist/bridge/constants.js +2 -8
- package/dist/bridge/solana.js +1 -93
- package/dist/bridge/squid.js +7 -24
- package/dist/errors.js +2 -10
- package/dist/fund.js +8 -243
- package/dist/index.js +3 -4
- package/package.json +1 -1
- package/skill.md +6 -22
- package/dist/bridge/debridge.js +0 -251
package/.env.example
CHANGED
|
@@ -25,10 +25,6 @@ LLM_API_KEY=
|
|
|
25
25
|
# SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
|
26
26
|
# SQUID_INTEGRATOR_ID=
|
|
27
27
|
|
|
28
|
-
# Ethereum mainnet RPC (only for `apow fund --chain ethereum`)
|
|
29
|
-
# Default: https://ethereum-rpc.publicnode.com (free)
|
|
30
|
-
# ETHEREUM_RPC_URL=
|
|
31
|
-
|
|
32
28
|
# Contract addresses (defaults built-in, override only if needed)
|
|
33
29
|
# MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
|
|
34
30
|
# AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
|
package/README.md
CHANGED
|
@@ -45,7 +45,6 @@ EOF
|
|
|
45
45
|
|
|
46
46
|
# 3. Fund the wallet (bridge from any chain, auto-splits into ETH + USDC)
|
|
47
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
48
|
# Or send ETH/USDC on Base directly
|
|
50
49
|
|
|
51
50
|
# 4. Mint a mining rig NFT (proves agent identity via LLM, one-time)
|
|
@@ -92,7 +91,7 @@ npx apow-cli mine
|
|
|
92
91
|
| Command | Description |
|
|
93
92
|
|---------|-------------|
|
|
94
93
|
| `apow setup` | Interactive setup wizard: configure wallet, RPC, and LLM |
|
|
95
|
-
| `apow fund` | Fund your wallet: bridge from Solana
|
|
94
|
+
| `apow fund` | Fund your wallet: bridge from Solana or send on Base, auto-split ETH+USDC |
|
|
96
95
|
| `apow wallet new` | Generate a new mining wallet |
|
|
97
96
|
| `apow wallet show` | Show configured wallet address |
|
|
98
97
|
| `apow wallet export` | Export your wallet's private key |
|
|
@@ -116,8 +115,7 @@ LLM_MODEL=gpt-4o-mini # Required for minting only; mining uses optimized
|
|
|
116
115
|
LLM_API_KEY=sk-... # Required for minting only
|
|
117
116
|
# Bridging (only for `apow fund`)
|
|
118
117
|
# SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
|
119
|
-
#
|
|
120
|
-
# SQUID_INTEGRATOR_ID= # free, get at squidrouter.com (deposit address flow only)
|
|
118
|
+
# SQUID_INTEGRATOR_ID= # free, get at squidrouter.com
|
|
121
119
|
# Contract addresses (defaults built-in, override only if needed)
|
|
122
120
|
# MINING_AGENT_ADDRESS=0xB7caD3ca5F2BD8aEC2Eb67d6E8D448099B3bC03D
|
|
123
121
|
# AGENT_COIN_ADDRESS=0x12577CF0D8a07363224D6909c54C056A183e13b3
|
|
@@ -138,19 +136,14 @@ An LLM is required to mint your Mining Rig NFT (one-time identity verification).
|
|
|
138
136
|
| Anthropic | `claude-sonnet-4-5-20250929` | ~$0.005 | Works but slower |
|
|
139
137
|
| Ollama | `llama3.1` | Free | Local GPU required |
|
|
140
138
|
|
|
141
|
-
## Funding (v0.
|
|
139
|
+
## Funding (v0.7.0+)
|
|
142
140
|
|
|
143
|
-
Mining requires two assets on Base: **ETH** (gas) and **USDC** (x402 RPC). The `fund` command
|
|
141
|
+
Mining requires two assets on Base: **ETH** (gas) and **USDC** (x402 RPC). The `fund` command bridges from Solana or accepts deposits on Base, and auto-splits into both:
|
|
144
142
|
|
|
145
143
|
```bash
|
|
146
|
-
# From Solana
|
|
144
|
+
# From Solana (deposit address — send from any wallet, QR code included)
|
|
147
145
|
apow fund --chain solana --token sol # bridge SOL → ETH, auto-swap portion to USDC
|
|
148
146
|
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
147
|
|
|
155
148
|
# Already on Base
|
|
156
149
|
apow fund --chain base --token eth # show address, wait for deposit, auto-split
|
|
@@ -160,6 +153,8 @@ apow fund --chain base --token usdc # show address, wait for depos
|
|
|
160
153
|
apow fund --chain base --no-swap
|
|
161
154
|
```
|
|
162
155
|
|
|
156
|
+
**Solana bridging:** Uses [Squid Router](https://squidrouter.com/) (Chainflip). Generates a one-time deposit address with QR code — send from Phantom, Backpack, or any Solana wallet. Requires `SQUID_INTEGRATOR_ID` in `.env` (free at [squidrouter.com](https://app.squidrouter.com/)).
|
|
157
|
+
|
|
163
158
|
**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.
|
|
164
159
|
|
|
165
160
|
## Speed Mining (v0.4.0+)
|
package/dist/bridge/constants.js
CHANGED
|
@@ -4,9 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.SLIPPAGE_BPS = exports.MIN_USDC = exports.MIN_ETH = exports.TOKENS = exports.CHAIN_IDS = void 0;
|
|
5
5
|
exports.bridgeOutputAsset = bridgeOutputAsset;
|
|
6
6
|
exports.CHAIN_IDS = {
|
|
7
|
-
solana:
|
|
8
|
-
|
|
9
|
-
base: { debridge: "8453", squid: "8453" },
|
|
7
|
+
solana: "solana",
|
|
8
|
+
base: "8453",
|
|
10
9
|
};
|
|
11
10
|
exports.TOKENS = {
|
|
12
11
|
solana: {
|
|
@@ -14,11 +13,6 @@ exports.TOKENS = {
|
|
|
14
13
|
nativeWrapped: "So11111111111111111111111111111111111111112",
|
|
15
14
|
usdc: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
16
15
|
},
|
|
17
|
-
ethereum: {
|
|
18
|
-
native: "0x0000000000000000000000000000000000000000",
|
|
19
|
-
nativeSquid: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
|
20
|
-
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
21
|
-
},
|
|
22
16
|
base: {
|
|
23
17
|
native: "0x0000000000000000000000000000000000000000",
|
|
24
18
|
nativeSquid: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
package/dist/bridge/solana.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Solana
|
|
2
|
+
// Solana balance utilities for deposit detection.
|
|
3
3
|
// Uses dynamic import() for @solana/web3.js to avoid bloating startup.
|
|
4
4
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
5
|
if (k2 === undefined) k2 = k;
|
|
@@ -36,76 +36,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
})();
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.getSolanaRpcUrl = getSolanaRpcUrl;
|
|
39
|
-
exports.parseSolanaKey = parseSolanaKey;
|
|
40
|
-
exports.getSolanaBalance = getSolanaBalance;
|
|
41
39
|
exports.getAddressBalance = getAddressBalance;
|
|
42
40
|
exports.getSplTokenBalance = getSplTokenBalance;
|
|
43
|
-
exports.signAndSendTransaction = signAndSendTransaction;
|
|
44
41
|
const DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
45
|
-
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
46
|
-
function base58Decode(str) {
|
|
47
|
-
const bytes = [];
|
|
48
|
-
for (const char of str) {
|
|
49
|
-
const idx = BASE58_ALPHABET.indexOf(char);
|
|
50
|
-
if (idx === -1)
|
|
51
|
-
throw new Error(`Invalid base58 character: ${char}`);
|
|
52
|
-
let carry = idx;
|
|
53
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
54
|
-
carry += bytes[j] * 58;
|
|
55
|
-
bytes[j] = carry & 0xff;
|
|
56
|
-
carry >>= 8;
|
|
57
|
-
}
|
|
58
|
-
while (carry > 0) {
|
|
59
|
-
bytes.push(carry & 0xff);
|
|
60
|
-
carry >>= 8;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// Preserve leading zeros
|
|
64
|
-
for (const char of str) {
|
|
65
|
-
if (char !== "1")
|
|
66
|
-
break;
|
|
67
|
-
bytes.push(0);
|
|
68
|
-
}
|
|
69
|
-
return new Uint8Array(bytes.reverse());
|
|
70
|
-
}
|
|
71
42
|
function getSolanaRpcUrl() {
|
|
72
43
|
return process.env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC;
|
|
73
44
|
}
|
|
74
|
-
/** Parse a base58-encoded Solana secret key (64 bytes) or seed (32 bytes). */
|
|
75
|
-
async function parseSolanaKey(input) {
|
|
76
|
-
const { Keypair } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
77
|
-
const trimmed = input.trim();
|
|
78
|
-
// Try JSON array format first (Solana CLI keygen output)
|
|
79
|
-
if (trimmed.startsWith("[")) {
|
|
80
|
-
try {
|
|
81
|
-
const arr = JSON.parse(trimmed);
|
|
82
|
-
const keypair = Keypair.fromSecretKey(new Uint8Array(arr));
|
|
83
|
-
return { keypair, publicKey: keypair.publicKey.toBase58() };
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
throw new Error("Invalid Solana key: looks like JSON but could not parse.");
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Base58 secret key (Phantom, Backpack export format)
|
|
90
|
-
const decoded = base58Decode(trimmed);
|
|
91
|
-
if (decoded.length === 64) {
|
|
92
|
-
const keypair = Keypair.fromSecretKey(decoded);
|
|
93
|
-
return { keypair, publicKey: keypair.publicKey.toBase58() };
|
|
94
|
-
}
|
|
95
|
-
if (decoded.length === 32) {
|
|
96
|
-
// 32-byte seed
|
|
97
|
-
const keypair = Keypair.fromSeed(decoded);
|
|
98
|
-
return { keypair, publicKey: keypair.publicKey.toBase58() };
|
|
99
|
-
}
|
|
100
|
-
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.`);
|
|
101
|
-
}
|
|
102
|
-
/** Get SOL balance for a public key (in SOL, not lamports). */
|
|
103
|
-
async function getSolanaBalance(publicKeyBase58) {
|
|
104
|
-
const { Connection, PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
105
|
-
const connection = new Connection(getSolanaRpcUrl(), "confirmed");
|
|
106
|
-
const lamports = await connection.getBalance(new PublicKey(publicKeyBase58));
|
|
107
|
-
return lamports / 1e9;
|
|
108
|
-
}
|
|
109
45
|
/** Get SOL balance for any address (used to detect deposits). */
|
|
110
46
|
async function getAddressBalance(address) {
|
|
111
47
|
const { Connection, PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
@@ -125,31 +61,3 @@ async function getSplTokenBalance(publicKeyBase58, mintAddress) {
|
|
|
125
61
|
const parsed = accounts.value[0].account.data.parsed;
|
|
126
62
|
return parsed?.info?.tokenAmount?.uiAmount ?? 0;
|
|
127
63
|
}
|
|
128
|
-
/** Deserialize, sign, and submit a serialized Solana transaction (hex or base64). */
|
|
129
|
-
async function signAndSendTransaction(serializedTx, keypair) {
|
|
130
|
-
const { Connection, VersionedTransaction, Transaction } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
131
|
-
const connection = new Connection(getSolanaRpcUrl(), "confirmed");
|
|
132
|
-
// deBridge returns hex (0x...), other bridges may return base64
|
|
133
|
-
const txBuffer = serializedTx.startsWith("0x")
|
|
134
|
-
? Buffer.from(serializedTx.slice(2), "hex")
|
|
135
|
-
: Buffer.from(serializedTx, "base64");
|
|
136
|
-
// Get fresh blockhash (deBridge-generated txs may have stale ones)
|
|
137
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
|
|
138
|
-
// Try versioned transaction first, fall back to legacy.
|
|
139
|
-
// Skip preflight: deBridge Jupiter routes may fail simulation but succeed on-chain.
|
|
140
|
-
let signature;
|
|
141
|
-
try {
|
|
142
|
-
const tx = VersionedTransaction.deserialize(txBuffer);
|
|
143
|
-
tx.message.recentBlockhash = blockhash;
|
|
144
|
-
tx.sign([keypair]);
|
|
145
|
-
signature = await connection.sendTransaction(tx, { skipPreflight: true });
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
const tx = Transaction.from(txBuffer);
|
|
149
|
-
tx.recentBlockhash = blockhash;
|
|
150
|
-
tx.sign(keypair);
|
|
151
|
-
signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: true });
|
|
152
|
-
}
|
|
153
|
-
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
|
|
154
|
-
return signature;
|
|
155
|
-
}
|
package/dist/bridge/squid.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Squid Router bridge — deposit address flow.
|
|
3
|
-
// Supports SOL→Base
|
|
3
|
+
// Supports SOL→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
6
|
exports.SQUID_ROUTES = void 0;
|
|
@@ -10,33 +10,17 @@ const constants_1 = require("./constants");
|
|
|
10
10
|
const SQUID_API = "https://v2.api.squidrouter.com/v2";
|
|
11
11
|
exports.SQUID_ROUTES = {
|
|
12
12
|
sol_to_eth: {
|
|
13
|
-
fromChain: constants_1.CHAIN_IDS.solana
|
|
13
|
+
fromChain: constants_1.CHAIN_IDS.solana,
|
|
14
14
|
fromToken: constants_1.TOKENS.solana.nativeWrapped,
|
|
15
|
-
toChain: constants_1.CHAIN_IDS.base
|
|
15
|
+
toChain: constants_1.CHAIN_IDS.base,
|
|
16
16
|
toToken: constants_1.TOKENS.base.nativeSquid,
|
|
17
17
|
srcDecimals: 9,
|
|
18
18
|
dstDecimals: 18,
|
|
19
19
|
},
|
|
20
20
|
sol_usdc_to_base_usdc: {
|
|
21
|
-
fromChain: constants_1.CHAIN_IDS.solana
|
|
21
|
+
fromChain: constants_1.CHAIN_IDS.solana,
|
|
22
22
|
fromToken: constants_1.TOKENS.solana.usdc,
|
|
23
|
-
toChain: constants_1.CHAIN_IDS.base
|
|
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,
|
|
23
|
+
toChain: constants_1.CHAIN_IDS.base,
|
|
40
24
|
toToken: constants_1.TOKENS.base.usdc,
|
|
41
25
|
srcDecimals: 6,
|
|
42
26
|
dstDecimals: 6,
|
|
@@ -45,9 +29,8 @@ exports.SQUID_ROUTES = {
|
|
|
45
29
|
function getIntegratorId() {
|
|
46
30
|
const id = process.env.SQUID_INTEGRATOR_ID;
|
|
47
31
|
if (!id) {
|
|
48
|
-
throw new Error("SQUID_INTEGRATOR_ID is required for
|
|
49
|
-
"Get one free at https://app.squidrouter.com
|
|
50
|
-
"Or use direct signing instead: apow fund --chain solana --key <base58>");
|
|
32
|
+
throw new Error("SQUID_INTEGRATOR_ID is required for bridging from Solana.\n" +
|
|
33
|
+
"Get one free at https://app.squidrouter.com/");
|
|
51
34
|
}
|
|
52
35
|
return id;
|
|
53
36
|
}
|
package/dist/errors.js
CHANGED
|
@@ -110,19 +110,11 @@ const patterns = [
|
|
|
110
110
|
}),
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
|
-
test: (m) => m.includes("
|
|
113
|
+
test: (m) => m.includes("Squid") && (m.includes("route") || m.includes("deposit")),
|
|
114
114
|
classify: () => ({
|
|
115
115
|
category: "transient",
|
|
116
116
|
userMessage: "Bridge route temporarily unavailable",
|
|
117
|
-
recovery: "
|
|
118
|
-
}),
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
test: (m) => m.includes("ethereum-rpc.publicnode") || (m.includes("ETHEREUM_RPC") && m.includes("unreachable")),
|
|
122
|
-
classify: () => ({
|
|
123
|
-
category: "setup",
|
|
124
|
-
userMessage: "Ethereum mainnet RPC unreachable",
|
|
125
|
-
recovery: "Set ETHEREUM_RPC_URL in .env (default: https://ethereum-rpc.publicnode.com)",
|
|
117
|
+
recovery: "Wait a few minutes and try again",
|
|
126
118
|
}),
|
|
127
119
|
},
|
|
128
120
|
];
|
package/dist/fund.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Fund command — unified funding for mining on Base.
|
|
3
|
-
// Accepts deposits
|
|
3
|
+
// Accepts deposits from Solana (via Squid Router bridge) or Base (direct send),
|
|
4
4
|
// auto-splits into ETH (gas) + USDC (x402 RPC).
|
|
5
5
|
//
|
|
6
6
|
// Deposit types:
|
|
7
7
|
// 1. Solana SOL → bridge → ETH on Base → swap portion to USDC
|
|
8
8
|
// 2. Solana USDC → bridge → USDC on Base → swap portion to ETH
|
|
9
|
-
// 3.
|
|
10
|
-
// 4.
|
|
11
|
-
// 5. Base ETH → (already there) → swap portion to USDC
|
|
12
|
-
// 6. Base USDC → (already there) → swap portion to ETH
|
|
9
|
+
// 3. Base ETH → (already there) → swap portion to USDC
|
|
10
|
+
// 4. Base USDC → (already there) → swap portion to ETH
|
|
13
11
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
12
|
if (k2 === undefined) k2 = k;
|
|
15
13
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -47,11 +45,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
47
45
|
exports.runFundFlow = runFundFlow;
|
|
48
46
|
const viem_1 = require("viem");
|
|
49
47
|
const constants_1 = require("./bridge/constants");
|
|
50
|
-
const debridge_1 = require("./bridge/debridge");
|
|
51
48
|
const squid_1 = require("./bridge/squid");
|
|
52
49
|
const uniswap_1 = require("./bridge/uniswap");
|
|
53
50
|
const wallet_1 = require("./wallet");
|
|
54
|
-
const config_1 = require("./config");
|
|
55
51
|
const ui = __importStar(require("./ui"));
|
|
56
52
|
async function fetchPrices() {
|
|
57
53
|
const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=solana,ethereum&vs_currencies=usd");
|
|
@@ -189,94 +185,9 @@ async function showFinalBalances() {
|
|
|
189
185
|
console.log("");
|
|
190
186
|
}
|
|
191
187
|
// ---------------------------------------------------------------------------
|
|
192
|
-
// Solana
|
|
188
|
+
// Solana fund (Squid Router deposit address)
|
|
193
189
|
// ---------------------------------------------------------------------------
|
|
194
|
-
async function
|
|
195
|
-
const solanaSpinner = ui.spinner("Checking Solana balance...");
|
|
196
|
-
const solana = await Promise.resolve().then(() => __importStar(require("./bridge/solana")));
|
|
197
|
-
let kp;
|
|
198
|
-
try {
|
|
199
|
-
kp = await solana.parseSolanaKey(solanaKeyInput);
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
solanaSpinner.fail("Invalid Solana key");
|
|
203
|
-
throw err;
|
|
204
|
-
}
|
|
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) {
|
|
190
|
+
async function runSolanaFund(baseAddress, sourceToken, targetEth) {
|
|
280
191
|
const priceSpinner = ui.spinner("Fetching prices...");
|
|
281
192
|
const prices = await fetchPrices();
|
|
282
193
|
priceSpinner.stop(`ETH price: $${prices.ethPriceUsd.toFixed(0)}`);
|
|
@@ -377,109 +288,6 @@ async function runSolanaDepositBridge(baseAddress, sourceToken, targetEth) {
|
|
|
377
288
|
await showFinalBalances();
|
|
378
289
|
}
|
|
379
290
|
// ---------------------------------------------------------------------------
|
|
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.");
|
|
385
|
-
return;
|
|
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";
|
|
398
|
-
console.log("");
|
|
399
|
-
ui.table([
|
|
400
|
-
["Bridging", `${amount.toFixed(sourceToken === "usdc" ? 2 : 6)} ${tokenLabel} → ${receivedLabel} on Base`],
|
|
401
|
-
["Via", "deBridge DLN (~20 seconds)"],
|
|
402
|
-
["From", `Ethereum mainnet (${baseAddress.slice(0, 6)}...${baseAddress.slice(-4)})`],
|
|
403
|
-
["To", `Base (same address)`],
|
|
404
|
-
]);
|
|
405
|
-
console.log("");
|
|
406
|
-
const proceed = await ui.confirm("Confirm bridge?");
|
|
407
|
-
if (!proceed) {
|
|
408
|
-
console.log(" Cancelled.");
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
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
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// ---------------------------------------------------------------------------
|
|
428
|
-
// Ethereum deposit address bridge (Squid Router)
|
|
429
|
-
// ---------------------------------------------------------------------------
|
|
430
|
-
async function runEthereumDepositBridge(baseAddress, sourceToken, targetEth) {
|
|
431
|
-
const priceSpinner = ui.spinner("Fetching prices...");
|
|
432
|
-
const prices = await fetchPrices();
|
|
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";
|
|
441
|
-
const addrSpinner = ui.spinner("Generating deposit address...");
|
|
442
|
-
const squid = await Promise.resolve().then(() => __importStar(require("./bridge/squid")));
|
|
443
|
-
let deposit;
|
|
444
|
-
try {
|
|
445
|
-
deposit = await squid.getDepositAddress(baseAddress, amount, route);
|
|
446
|
-
}
|
|
447
|
-
catch (err) {
|
|
448
|
-
addrSpinner.fail("Failed to get deposit address");
|
|
449
|
-
throw err;
|
|
450
|
-
}
|
|
451
|
-
addrSpinner.stop("Deposit address ready");
|
|
452
|
-
console.log("");
|
|
453
|
-
console.log(` ${ui.bold(`Send ${tokenLabel} on Ethereum mainnet to:`)}`);
|
|
454
|
-
console.log("");
|
|
455
|
-
console.log(` ${ui.cyan(deposit.depositAddress)}`);
|
|
456
|
-
console.log("");
|
|
457
|
-
await showQrCode(deposit.depositAddress);
|
|
458
|
-
console.log("");
|
|
459
|
-
ui.table([
|
|
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`],
|
|
463
|
-
["Bridge", "Squid Router (Chainflip)"],
|
|
464
|
-
["Time", "~1-3 minutes"],
|
|
465
|
-
]);
|
|
466
|
-
console.log("");
|
|
467
|
-
if (deposit.expiresAt) {
|
|
468
|
-
ui.warn(`Deposit address expires: ${deposit.expiresAt}`);
|
|
469
|
-
console.log("");
|
|
470
|
-
}
|
|
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
291
|
// Base manual send (already on the right chain)
|
|
484
292
|
// ---------------------------------------------------------------------------
|
|
485
293
|
async function runBaseFund(baseAddress, sourceToken, noSwap) {
|
|
@@ -548,15 +356,10 @@ async function runBaseFund(baseAddress, sourceToken, noSwap) {
|
|
|
548
356
|
async function selectSourceChain() {
|
|
549
357
|
console.log(" Where are your funds?");
|
|
550
358
|
console.log(` ${ui.cyan("1.")} Solana (SOL or USDC)`);
|
|
551
|
-
console.log(` ${ui.cyan("2.")}
|
|
552
|
-
console.log(` ${ui.cyan("3.")} Base (send ETH or USDC manually)`);
|
|
359
|
+
console.log(` ${ui.cyan("2.")} Base (send ETH or USDC directly)`);
|
|
553
360
|
console.log("");
|
|
554
361
|
const choice = await ui.prompt("Choice", "1");
|
|
555
|
-
|
|
556
|
-
case "2": return "ethereum";
|
|
557
|
-
case "3": return "base";
|
|
558
|
-
default: return "solana";
|
|
559
|
-
}
|
|
362
|
+
return choice === "2" ? "base" : "solana";
|
|
560
363
|
}
|
|
561
364
|
async function selectSourceToken(chain) {
|
|
562
365
|
const nativeLabel = chain === "solana" ? "SOL" : "ETH";
|
|
@@ -574,8 +377,6 @@ function parseSourceChain(value) {
|
|
|
574
377
|
const v = value.toLowerCase();
|
|
575
378
|
if (v === "solana" || v === "sol")
|
|
576
379
|
return "solana";
|
|
577
|
-
if (v === "ethereum" || v === "eth")
|
|
578
|
-
return "ethereum";
|
|
579
380
|
if (v === "base")
|
|
580
381
|
return "base";
|
|
581
382
|
return undefined;
|
|
@@ -639,43 +440,7 @@ async function runFundFlow(options) {
|
|
|
639
440
|
// Route to the appropriate flow
|
|
640
441
|
switch (chain) {
|
|
641
442
|
case "solana": {
|
|
642
|
-
|
|
643
|
-
await runSolanaDirectBridge(options.key, baseAddress, token, targetEth);
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
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
|
-
}
|
|
663
|
-
}
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
case "ethereum": {
|
|
667
|
-
console.log("");
|
|
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)`);
|
|
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
|
-
}
|
|
443
|
+
await runSolanaFund(baseAddress, token, targetEth);
|
|
679
444
|
break;
|
|
680
445
|
}
|
|
681
446
|
case "base": {
|
package/dist/index.js
CHANGED
|
@@ -260,10 +260,9 @@ async function main() {
|
|
|
260
260
|
});
|
|
261
261
|
program
|
|
262
262
|
.command("fund")
|
|
263
|
-
.description("Fund your wallet — bridge from Solana
|
|
264
|
-
.option("--chain <chain>", "Source chain: solana,
|
|
265
|
-
.option("--token <token>", "Source token: sol,
|
|
266
|
-
.option("--key <base58>", "Solana private key for direct signing")
|
|
263
|
+
.description("Fund your wallet — bridge from Solana or send on Base")
|
|
264
|
+
.option("--chain <chain>", "Source chain: solana, base")
|
|
265
|
+
.option("--token <token>", "Source token: sol, usdc, eth")
|
|
267
266
|
.option("--amount <eth>", "Target ETH amount (default: 0.005)")
|
|
268
267
|
.option("--no-swap", "Skip auto-split after bridging")
|
|
269
268
|
.hook("preAction", async () => {
|
package/package.json
CHANGED
package/skill.md
CHANGED
|
@@ -73,8 +73,7 @@ EOF
|
|
|
73
73
|
|
|
74
74
|
# 3. Fund the wallet (bridge from any chain, auto-splits into ETH + USDC)
|
|
75
75
|
npx apow-cli fund --chain solana --token sol # bridge SOL → ETH+USDC on Base
|
|
76
|
-
# Or: npx apow-cli fund --chain
|
|
77
|
-
# Or: npx apow-cli fund --chain ethereum --token eth # bridge from Ethereum mainnet
|
|
76
|
+
# Or: npx apow-cli fund --chain base # send ETH or USDC on Base directly
|
|
78
77
|
# Or: ask your user to send ETH + USDC on Base directly
|
|
79
78
|
|
|
80
79
|
# 4. Mint + mine (fully autonomous from here)
|
|
@@ -152,33 +151,20 @@ Your mining wallet needs ETH on Base for gas and the mint fee.
|
|
|
152
151
|
|
|
153
152
|
### Built-in Bridge: `apow fund` (Recommended)
|
|
154
153
|
|
|
155
|
-
The CLI
|
|
154
|
+
The CLI bridges from Solana via [Squid Router](https://squidrouter.com/) (Chainflip), or accepts deposits directly on Base. Auto-splits into ETH (gas) + USDC (x402 RPC):
|
|
156
155
|
|
|
157
156
|
```bash
|
|
158
157
|
npx apow-cli fund # Interactive: choose chain + token
|
|
159
158
|
npx apow-cli fund --chain solana --token sol # Bridge SOL → ETH+USDC on Base
|
|
160
|
-
npx apow-cli fund --chain solana --token sol --key <b58> # Direct Solana signing (~20s)
|
|
161
159
|
npx apow-cli fund --chain solana --token usdc # Bridge Solana USDC → Base
|
|
162
|
-
npx apow-cli fund --chain ethereum --token eth # Bridge ETH from mainnet
|
|
163
|
-
npx apow-cli fund --chain ethereum --token usdc # Bridge USDC from mainnet
|
|
164
160
|
npx apow-cli fund --chain base # Show address, wait for deposit
|
|
165
161
|
npx apow-cli fund --chain base --no-swap # Skip auto-split
|
|
166
162
|
```
|
|
167
163
|
|
|
168
|
-
**Bridge
|
|
169
|
-
|
|
170
|
-
| Chain | Direct signing | Deposit address |
|
|
171
|
-
|-------|---------------|-----------------|
|
|
172
|
-
| Solana | deBridge DLN (~20s, `--key`) | Squid Router (~1-3 min) |
|
|
173
|
-
| Ethereum | deBridge DLN (~20s, uses PRIVATE_KEY on mainnet) | Squid Router (~1-3 min) |
|
|
174
|
-
| Base | N/A (already on Base) | Show address + QR code |
|
|
164
|
+
**Solana bridging:** Generates a one-time deposit address with QR code. Send tokens from any Solana wallet (Phantom, Backpack, etc.). Requires `SQUID_INTEGRATOR_ID` in `.env` (free at [squidrouter.com](https://app.squidrouter.com/)). Bridge time: ~1-3 minutes via Chainflip.
|
|
175
165
|
|
|
176
166
|
**Auto-split:** After bridging, the CLI checks ETH and USDC balances. If either is below the minimum (0.003 ETH for gas, 2.00 USDC for x402 RPC), it swaps the needed amount via Uniswap V3 on Base. Use `--no-swap` to skip.
|
|
177
167
|
|
|
178
|
-
**Direct signing (Solana `--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 funds arrive on Base. No API key needed.
|
|
179
|
-
|
|
180
|
-
**Deposit address (no `--key`):** Requires `SQUID_INTEGRATOR_ID` in `.env` (free, apply at [squidrouter.com](https://app.squidrouter.com/)). Generates a one-time deposit address with a QR code. Send tokens from any wallet and the bridge handles the rest.
|
|
181
|
-
|
|
182
168
|
### Manual Funding Options
|
|
183
169
|
|
|
184
170
|
If you prefer not to use the built-in bridge:
|
|
@@ -280,7 +266,6 @@ CHAIN=base
|
|
|
280
266
|
| `RPC_URL` | No | Alchemy x402 | Base JSON-RPC endpoint. Default uses Alchemy x402 (premium, paid via USDC). Set to override with a custom endpoint. |
|
|
281
267
|
| `CHAIN` | No | `base` | Network selector; auto-detects `baseSepolia` if RPC URL contains "sepolia" |
|
|
282
268
|
| `SOLANA_RPC_URL` | No | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint (only for `apow fund --chain solana`) |
|
|
283
|
-
| `ETHEREUM_RPC_URL` | No | `https://ethereum-rpc.publicnode.com` | Ethereum mainnet RPC (only for `apow fund --chain ethereum`) |
|
|
284
269
|
| `SQUID_INTEGRATOR_ID` | No | - | Squid Router integrator ID for deposit address flow (free at [squidrouter.com](https://app.squidrouter.com/)) |
|
|
285
270
|
|
|
286
271
|
### LLM Provider Recommendations (for Minting)
|
|
@@ -638,12 +623,11 @@ The CLI makes only these network calls:
|
|
|
638
623
|
2. **LLM API** (to user-configured provider): sends only word-puzzle prompts for SMHL solving, never wallet data
|
|
639
624
|
3. **Bridge APIs** (only when using `apow fund`):
|
|
640
625
|
- **CoinGecko** (`api.coingecko.com`): SOL/ETH price quotes
|
|
641
|
-
- **
|
|
642
|
-
- **Squid Router** (`v2.api.squidrouter.com`): deposit address generation (deposit address flow)
|
|
626
|
+
- **Squid Router** (`v2.api.squidrouter.com`): deposit address generation and bridge status
|
|
643
627
|
- **Uniswap V3** (on-chain, Base): ETH/USDC swaps for auto-split
|
|
644
|
-
- **Solana RPC** (`api.mainnet-beta.solana.com` or custom): balance checks
|
|
628
|
+
- **Solana RPC** (`api.mainnet-beta.solana.com` or custom): balance checks
|
|
645
629
|
|
|
646
|
-
No private keys are transmitted to bridge providers.
|
|
630
|
+
No private keys are transmitted to bridge providers. Squid generates a deposit address, and the user sends tokens from their own wallet.
|
|
647
631
|
|
|
648
632
|
### LLM Calls Are Data-Isolated
|
|
649
633
|
|
package/dist/bridge/debridge.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// deBridge DLN bridge — direct signing flow.
|
|
3
|
-
// Supports SOL→Base and Ethereum→Base bridging.
|
|
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.ROUTES = void 0;
|
|
40
|
-
exports.bridgeFromSolana = bridgeFromSolana;
|
|
41
|
-
exports.bridgeFromEvm = bridgeFromEvm;
|
|
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");
|
|
47
|
-
const solana = __importStar(require("./solana"));
|
|
48
|
-
const DLN_API = "https://dln.debridge.finance/v1.0";
|
|
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
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* Create DLN order params for any route.
|
|
81
|
-
*/
|
|
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,
|
|
90
|
-
dstChainTokenOutRecipient: baseAddress,
|
|
91
|
-
senderAddress,
|
|
92
|
-
srcChainOrderAuthorityAddress: senderAddress,
|
|
93
|
-
dstChainOrderAuthorityAddress: baseAddress,
|
|
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);
|
|
104
|
-
const response = await fetch(`${DLN_API}/dln/order/create-tx?${params}`);
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
const body = await response.text();
|
|
107
|
-
throw new Error(`deBridge API error (${response.status}): ${body}`);
|
|
108
|
-
}
|
|
109
|
-
const data = (await response.json());
|
|
110
|
-
if (data.errorCode || data.error) {
|
|
111
|
-
throw new Error(`deBridge error: ${data.error || data.message || JSON.stringify(data)}`);
|
|
112
|
-
}
|
|
113
|
-
const orderId = data.orderId;
|
|
114
|
-
const txData = data.tx?.data;
|
|
115
|
-
if (!txData) {
|
|
116
|
-
throw new Error("deBridge API returned no transaction data");
|
|
117
|
-
}
|
|
118
|
-
const txSignature = await solana.signAndSendTransaction(txData, solanaKeypair);
|
|
119
|
-
return {
|
|
120
|
-
orderId,
|
|
121
|
-
txSignature,
|
|
122
|
-
status: "submitted",
|
|
123
|
-
timeMs: Date.now() - startTime,
|
|
124
|
-
};
|
|
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://ethereum-rpc.publicnode.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
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Poll deBridge order status until fulfilled, cancelled, or timeout.
|
|
215
|
-
* Works for all deBridge orders regardless of source chain.
|
|
216
|
-
*/
|
|
217
|
-
async function pollOrderStatus(orderId, onUpdate, timeoutMs = 300_000) {
|
|
218
|
-
const deadline = Date.now() + timeoutMs;
|
|
219
|
-
while (Date.now() < deadline) {
|
|
220
|
-
try {
|
|
221
|
-
const response = await fetch(`${DLN_API}/dln/order/${orderId}/status`);
|
|
222
|
-
if (response.ok) {
|
|
223
|
-
const data = (await response.json());
|
|
224
|
-
const status = data.status || data.orderStatus || "unknown";
|
|
225
|
-
if (onUpdate)
|
|
226
|
-
onUpdate(status);
|
|
227
|
-
if (status === "Fulfilled" ||
|
|
228
|
-
status === "ClaimedUnlock" ||
|
|
229
|
-
status === "SentUnlock") {
|
|
230
|
-
return {
|
|
231
|
-
status: "fulfilled",
|
|
232
|
-
received: data.fulfilledDstAmount
|
|
233
|
-
? data.fulfilledDstAmount.toString()
|
|
234
|
-
: undefined,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
if (status === "Cancelled" || status === "CancelledByMaker") {
|
|
238
|
-
throw new Error(`Bridge order was cancelled: ${status}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
if (err instanceof Error && err.message.includes("cancelled"))
|
|
244
|
-
throw err;
|
|
245
|
-
// Transient fetch error — keep polling
|
|
246
|
-
}
|
|
247
|
-
await new Promise((r) => setTimeout(r, 5000));
|
|
248
|
-
}
|
|
249
|
-
throw new Error("Bridge order timed out after 5 minutes. Check deBridge explorer for order: " +
|
|
250
|
-
orderId);
|
|
251
|
-
}
|