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 +9 -4
- package/README.md +90 -27
- package/dist/bridge/constants.js +34 -0
- package/dist/bridge/debridge.js +151 -28
- package/dist/bridge/solana.js +13 -0
- package/dist/bridge/squid.js +54 -21
- package/dist/bridge/uniswap.js +184 -0
- package/dist/config.js +1 -0
- package/dist/dashboard.js +6 -2
- package/dist/errors.js +40 -0
- package/dist/fund.js +506 -133
- package/dist/index.js +10 -23
- package/dist/preflight.js +52 -0
- package/dist/wallet.js +24 -2
- package/dist/x402.js +45 -0
- package/package.json +3 -1
- package/skill.md +150 -101
package/.env.example
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Wallet
|
|
2
2
|
PRIVATE_KEY=
|
|
3
3
|
|
|
4
|
-
# RPC —
|
|
5
|
-
#
|
|
6
|
-
RPC_URL
|
|
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
|
-
#
|
|
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
|
|
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
|
-
##
|
|
19
|
+
## RPC: Alchemy x402 (Default)
|
|
20
20
|
|
|
21
|
-
> **The
|
|
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
|
-
>
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
|
49
|
-
npx apow-cli fund --solana
|
|
50
|
-
|
|
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
|
|
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
|
|
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
|
|
96
|
-
| `apow fund` | Fund your wallet
|
|
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 #
|
|
112
|
-
LLM_PROVIDER=openai # openai |
|
|
113
|
-
LLM_MODEL=gpt-4o-mini # Required for minting only
|
|
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
|
-
#
|
|
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
|
|
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
|
|
132
|
-
|
|
|
133
|
-
|
|
|
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
|
-
|
|
136
|
-
|
|
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%
|
package/dist/bridge/debridge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// deBridge DLN bridge — direct signing flow
|
|
3
|
-
// SOL
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
64
|
-
srcChainOrderAuthorityAddress:
|
|
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
|
-
*
|
|
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
|
-
|
|
110
|
-
?
|
|
232
|
+
received: data.fulfilledDstAmount
|
|
233
|
+
? data.fulfilledDstAmount.toString()
|
|
111
234
|
: undefined,
|
|
112
235
|
};
|
|
113
236
|
}
|
package/dist/bridge/solana.js
CHANGED
|
@@ -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")));
|
package/dist/bridge/squid.js
CHANGED
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Squid Router bridge — deposit address flow
|
|
3
|
-
// SOL
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
24
|
-
* User sends
|
|
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,
|
|
58
|
+
async function getDepositAddress(baseAddress, amount, route = exports.SQUID_ROUTES.sol_to_eth) {
|
|
27
59
|
const integratorId = getIntegratorId();
|
|
28
|
-
const
|
|
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:
|
|
38
|
-
toChain:
|
|
39
|
-
fromToken:
|
|
40
|
-
toToken:
|
|
41
|
-
fromAmount:
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
109
|
-
? (Number(data.toChain.amount) /
|
|
141
|
+
received: data.toChain?.amount
|
|
142
|
+
? (Number(data.toChain.amount) / 10 ** dstDecimals).toFixed(dstDecimals === 6 ? 2 : 6)
|
|
110
143
|
: undefined,
|
|
111
144
|
};
|
|
112
145
|
}
|