@solana-compass/cli 0.1.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/README.md +327 -0
- package/bin/sol.mjs +6 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/lend.d.ts +2 -0
- package/dist/commands/lend.js +262 -0
- package/dist/commands/lend.js.map +1 -0
- package/dist/commands/lp.d.ts +2 -0
- package/dist/commands/lp.js +158 -0
- package/dist/commands/lp.js.map +1 -0
- package/dist/commands/network.d.ts +2 -0
- package/dist/commands/network.js +126 -0
- package/dist/commands/network.js.map +1 -0
- package/dist/commands/portfolio.d.ts +2 -0
- package/dist/commands/portfolio.js +187 -0
- package/dist/commands/portfolio.js.map +1 -0
- package/dist/commands/stake.d.ts +2 -0
- package/dist/commands/stake.js +143 -0
- package/dist/commands/stake.js.map +1 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +551 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/tx.d.ts +2 -0
- package/dist/commands/tx.js +131 -0
- package/dist/commands/tx.js.map +1 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +403 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/core/config-manager.d.ts +31 -0
- package/dist/core/config-manager.js +79 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/kamino-compat.d.ts +17 -0
- package/dist/core/kamino-compat.js +38 -0
- package/dist/core/kamino-compat.js.map +1 -0
- package/dist/core/lend-service.d.ts +41 -0
- package/dist/core/lend-service.js +331 -0
- package/dist/core/lend-service.js.map +1 -0
- package/dist/core/lp-service.d.ts +30 -0
- package/dist/core/lp-service.js +21 -0
- package/dist/core/lp-service.js.map +1 -0
- package/dist/core/onramp-service.d.ts +15 -0
- package/dist/core/onramp-service.js +57 -0
- package/dist/core/onramp-service.js.map +1 -0
- package/dist/core/portfolio-service.d.ts +55 -0
- package/dist/core/portfolio-service.js +272 -0
- package/dist/core/portfolio-service.js.map +1 -0
- package/dist/core/price-service.d.ts +8 -0
- package/dist/core/price-service.js +116 -0
- package/dist/core/price-service.js.map +1 -0
- package/dist/core/rpc.d.ts +5 -0
- package/dist/core/rpc.js +69 -0
- package/dist/core/rpc.js.map +1 -0
- package/dist/core/stake-service.d.ts +42 -0
- package/dist/core/stake-service.js +319 -0
- package/dist/core/stake-service.js.map +1 -0
- package/dist/core/swap-service.d.ts +31 -0
- package/dist/core/swap-service.js +142 -0
- package/dist/core/swap-service.js.map +1 -0
- package/dist/core/token-registry.d.ts +23 -0
- package/dist/core/token-registry.js +174 -0
- package/dist/core/token-registry.js.map +1 -0
- package/dist/core/token-service.d.ts +20 -0
- package/dist/core/token-service.js +92 -0
- package/dist/core/token-service.js.map +1 -0
- package/dist/core/transaction.d.ts +55 -0
- package/dist/core/transaction.js +196 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/wallet-manager.d.ts +20 -0
- package/dist/core/wallet-manager.js +142 -0
- package/dist/core/wallet-manager.js.map +1 -0
- package/dist/db/database.d.ts +3 -0
- package/dist/db/database.js +44 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrations/001_initial.d.ts +1 -0
- package/dist/db/migrations/001_initial.js +116 -0
- package/dist/db/migrations/001_initial.js.map +1 -0
- package/dist/db/migrations/002_tx_prices.d.ts +1 -0
- package/dist/db/migrations/002_tx_prices.js +5 -0
- package/dist/db/migrations/002_tx_prices.js.map +1 -0
- package/dist/db/repos/price-repo.d.ts +11 -0
- package/dist/db/repos/price-repo.js +14 -0
- package/dist/db/repos/price-repo.js.map +1 -0
- package/dist/db/repos/snapshot-repo.d.ts +27 -0
- package/dist/db/repos/snapshot-repo.js +32 -0
- package/dist/db/repos/snapshot-repo.js.map +1 -0
- package/dist/db/repos/token-repo.d.ts +17 -0
- package/dist/db/repos/token-repo.js +53 -0
- package/dist/db/repos/token-repo.js.map +1 -0
- package/dist/db/repos/transaction-repo.d.ts +22 -0
- package/dist/db/repos/transaction-repo.js +28 -0
- package/dist/db/repos/transaction-repo.js.map +1 -0
- package/dist/db/repos/wallet-repo.d.ts +18 -0
- package/dist/db/repos/wallet-repo.js +44 -0
- package/dist/db/repos/wallet-repo.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatter.d.ts +27 -0
- package/dist/output/formatter.js +76 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/portfolio-renderer.d.ts +3 -0
- package/dist/output/portfolio-renderer.js +205 -0
- package/dist/output/portfolio-renderer.js.map +1 -0
- package/dist/output/table.d.ts +8 -0
- package/dist/output/table.js +22 -0
- package/dist/output/table.js.map +1 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.js +39 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/retry.d.ts +17 -0
- package/dist/utils/retry.js +55 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/solana.d.ts +9 -0
- package/dist/utils/solana.js +26 -0
- package/dist/utils/solana.js.map +1 -0
- package/dist/utils/token-list.d.ts +9 -0
- package/dist/utils/token-list.js +31 -0
- package/dist/utils/token-list.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# sol — Solana for Humans and LLM Agents
|
|
2
|
+
|
|
3
|
+
A Solana CLI that reads like English. Every command has structured `--json` output so LLM agents like Claude Code can drive it programmatically — managing wallets, executing swaps, staking, lending, and tracking portfolios without ever touching raw transactions.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
sol token swap 50 usdc bonk # Swap via Jupiter
|
|
7
|
+
sol stake new 10 # Stake SOL in one command
|
|
8
|
+
sol lend deposit 100 usdc # Earn yield on Kamino
|
|
9
|
+
sol portfolio # See everything you hold
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why This Exists
|
|
15
|
+
|
|
16
|
+
Solana has great infrastructure but terrible UX for automation. An LLM agent that wants to "swap 50 USDC for BONK" shouldn't need to construct transaction messages, manage blockhashes, or parse binary account data. **sol** wraps all of that behind natural-language commands with structured JSON responses:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
$ sol token swap 50 usdc bonk --json
|
|
20
|
+
{
|
|
21
|
+
"ok": true,
|
|
22
|
+
"data": {
|
|
23
|
+
"inputToken": "USDC",
|
|
24
|
+
"outputToken": "BONK",
|
|
25
|
+
"inputAmount": 50,
|
|
26
|
+
"outputAmount": 3842519.52,
|
|
27
|
+
"signature": "4xK9...",
|
|
28
|
+
"explorerUrl": "https://solscan.io/tx/4xK9..."
|
|
29
|
+
},
|
|
30
|
+
"meta": { "elapsed_ms": 1823 }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No API keys required. No SDK integration. Just commands and JSON.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @solana-compass/cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or run without installing:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @solana-compass/cli wallet list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Node.js >= 20.
|
|
51
|
+
|
|
52
|
+
### First-time setup
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sol config set rpc.url https://api.mainnet-beta.solana.com
|
|
56
|
+
sol wallet create
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use a dedicated RPC endpoint for production — the public one rate-limits aggressively. Helius, Triton, or QuickNode all offer free tiers.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
### wallet — Create, import, and manage Solana keypairs
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
sol wallet create # New wallet, auto-named
|
|
69
|
+
sol wallet create --name trading --count 3 # Batch-create 3 wallets
|
|
70
|
+
sol wallet list # List all wallets with SOL balances
|
|
71
|
+
sol wallet balance # Full token balances + USD values
|
|
72
|
+
sol wallet balance trading # Balance for a specific wallet
|
|
73
|
+
|
|
74
|
+
sol wallet import --solana-cli # Import from ~/.config/solana/id.json
|
|
75
|
+
sol wallet import ./keypair.json --name cold # Import from file
|
|
76
|
+
sol wallet export main # Show key file path
|
|
77
|
+
sol wallet remove old-wallet # Remove from registry
|
|
78
|
+
|
|
79
|
+
sol wallet set-default trading # Change the active wallet
|
|
80
|
+
sol wallet label main --add trading # Tag wallets for organization
|
|
81
|
+
sol wallet history # Recent transaction activity
|
|
82
|
+
sol wallet history --type swap --limit 5 # Filtered
|
|
83
|
+
sol wallet fund --amount 100 # Generate fiat onramp URL (Transak)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Wallets are stored locally as key files. The first wallet created becomes the default for all commands. Change it with `sol wallet set-default <name>`, or override per-command with `--wallet <name-or-address>`.
|
|
87
|
+
|
|
88
|
+
### token — Prices, swaps, transfers, and account management
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
sol token price sol # Current SOL price
|
|
92
|
+
sol token price sol usdc bonk # Multiple prices at once
|
|
93
|
+
sol token info bonk # Token metadata (mint, decimals)
|
|
94
|
+
sol token list # All tokens in your wallet
|
|
95
|
+
sol token sync # Refresh token metadata cache
|
|
96
|
+
|
|
97
|
+
sol token swap 50 usdc bonk # Swap via Jupiter
|
|
98
|
+
sol token swap 1.5 sol usdc --slippage 100 # 1% slippage tolerance
|
|
99
|
+
sol token swap 50 usdc bonk --quote-only # Preview without executing
|
|
100
|
+
|
|
101
|
+
sol token swap 5 usdc sol --wallet backup # Swap from a specific wallet
|
|
102
|
+
sol token send 2 sol GkX...abc # Send SOL to an address
|
|
103
|
+
sol token burn bonk --all # Burn all of a token
|
|
104
|
+
sol token close --all --yes # Close empty accounts, reclaim rent
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Swaps use Jupiter's aggregator — best price across all Solana DEXes, no API key needed. Every swap is logged with cost-basis prices for portfolio tracking.
|
|
108
|
+
|
|
109
|
+
### Token resolution
|
|
110
|
+
|
|
111
|
+
Anywhere a command takes a token, you can use a **symbol** (`sol`, `usdc`, `bonk`) or a **mint address** (`EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`). Resolution works in three steps:
|
|
112
|
+
|
|
113
|
+
1. **Hardcoded well-known list** — 15 major tokens (SOL, USDC, USDT, JUP, BONK, mSOL, jitoSOL, bSOL, ETH, wBTC, PYTH, JTO, WEN, RNDR, JLP) resolve instantly offline. These are immune to spoofing.
|
|
114
|
+
|
|
115
|
+
2. **Local cache** — Previously resolved tokens are cached in SQLite with a 24-hour TTL. Populated by prior lookups and `sol token sync`.
|
|
116
|
+
|
|
117
|
+
3. **Jupiter Token API** — If not cached, the CLI searches Jupiter's token database. Results are ranked by liquidity and trading volume, so `usdc` returns the real USDC — not a scam token with the same symbol. The result is cached for next time.
|
|
118
|
+
|
|
119
|
+
**For unfamiliar tokens, verify before transacting:**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
sol token info peng # Check the resolved mint address
|
|
123
|
+
sol token swap 50 usdc EPjFW...1v --quote-only # Use the mint address directly
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Using a mint address bypasses symbol search entirely — useful when you know exactly which token you want, or when dealing with tokens that share a ticker. Agents should prefer mint addresses for safety when operating autonomously.
|
|
127
|
+
|
|
128
|
+
### stake — Native SOL staking with MEV compounding
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
sol stake new 10 # Stake 10 SOL (default: Solana Compass)
|
|
132
|
+
sol stake new 5 --validator DPm...xyz # Stake with a specific validator
|
|
133
|
+
sol stake list # All stake accounts + claimable MEV
|
|
134
|
+
sol stake claim-mev # Compound MEV tips (re-stake)
|
|
135
|
+
sol stake claim-mev --withdraw # Withdraw MEV to wallet instead
|
|
136
|
+
sol stake withdraw 7gK...abc # Smart withdraw (handles deactivation)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Creates a stake account, funds it, and delegates — all in a single transaction. The CLI handles the multi-step Solana staking process so you don't have to.
|
|
140
|
+
|
|
141
|
+
### lend — Lending and borrowing on Kamino Finance
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
sol lend rates sol # Deposit/borrow APY for SOL
|
|
145
|
+
sol lend rates usdc # USDC rates
|
|
146
|
+
|
|
147
|
+
sol lend deposit 100 usdc # Deposit USDC to earn yield
|
|
148
|
+
sol lend deposit 5 sol # Deposit SOL as collateral
|
|
149
|
+
sol lend withdraw 50 usdc # Partial withdrawal
|
|
150
|
+
sol lend withdraw max sol # Withdraw entire deposit
|
|
151
|
+
|
|
152
|
+
sol lend borrow 500 usdc --collateral sol # Borrow against collateral
|
|
153
|
+
sol lend repay 250 usdc # Partial repay
|
|
154
|
+
sol lend repay max usdc # Repay full outstanding debt
|
|
155
|
+
|
|
156
|
+
sol lend positions # All deposits, borrows, health factor
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Positions include real-time APY, USD values, and health factor monitoring. The CLI warns when health factor drops below 1.1.
|
|
160
|
+
|
|
161
|
+
### portfolio — Unified view across all positions
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
sol portfolio # Everything: tokens, stakes, lending
|
|
165
|
+
sol portfolio --wallet trading # Single wallet view
|
|
166
|
+
|
|
167
|
+
sol portfolio snapshot # Save current state
|
|
168
|
+
sol portfolio snapshot --label "pre-trade" # With a label
|
|
169
|
+
sol portfolio history # List all snapshots
|
|
170
|
+
|
|
171
|
+
sol portfolio compare # Diff vs latest snapshot
|
|
172
|
+
sol portfolio compare 3 # Diff vs snapshot #3
|
|
173
|
+
sol portfolio pnl # P&L since first snapshot
|
|
174
|
+
sol portfolio pnl --since 5 # P&L since snapshot #5
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The portfolio aggregates tokens, staked SOL, and Kamino lending positions across all wallets. Snapshots enable tracking changes over time — useful for agents that need to measure the impact of their actions.
|
|
178
|
+
|
|
179
|
+
### config — Persistent settings
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
sol config set rpc.url https://my-rpc.com # Set RPC endpoint
|
|
183
|
+
sol config get rpc.url # Read a value
|
|
184
|
+
sol config list # Show all settings
|
|
185
|
+
sol config path # Config file location
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Other commands
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
sol network # Epoch, TPS, inflation, staking APY
|
|
192
|
+
sol tx 4xK9...abc # Look up a transaction by signature
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## For LLM Agents
|
|
198
|
+
|
|
199
|
+
Every command supports `--json` for structured output. Responses follow a consistent envelope:
|
|
200
|
+
|
|
201
|
+
**Success:**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"ok": true,
|
|
205
|
+
"data": { ... },
|
|
206
|
+
"meta": { "elapsed_ms": 450 }
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Error:**
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"ok": false,
|
|
214
|
+
"error": "SWAP_FAILED",
|
|
215
|
+
"message": "Insufficient SOL balance"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Error codes are predictable `UPPER_SNAKE_CASE` identifiers (`WALLET_CREATE_FAILED`, `LEND_DEPOSIT_FAILED`, etc.). An agent can branch on `ok`, read `data` for results, and use `error` codes for programmatic error handling without parsing human text.
|
|
220
|
+
|
|
221
|
+
### Agent workflow example
|
|
222
|
+
|
|
223
|
+
A Claude Code agent managing a DeFi portfolio might run:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# 1. Check what we're working with
|
|
227
|
+
sol wallet balance --json
|
|
228
|
+
sol portfolio --json
|
|
229
|
+
|
|
230
|
+
# 2. Sell some BONK for USDC
|
|
231
|
+
sol token swap 1000000 bonk usdc --json
|
|
232
|
+
|
|
233
|
+
# 3. Put idle USDC to work earning yield
|
|
234
|
+
sol lend deposit 100 usdc --json
|
|
235
|
+
|
|
236
|
+
# 4. Stake idle SOL
|
|
237
|
+
sol stake new 5 --json
|
|
238
|
+
|
|
239
|
+
# 5. Verify final state and snapshot for tracking
|
|
240
|
+
sol portfolio --json
|
|
241
|
+
sol portfolio snapshot --label "rebalanced" --json
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Each step returns structured data the agent can parse and act on. No web scraping, no API key management, no transaction construction.
|
|
245
|
+
|
|
246
|
+
### What agents get for free
|
|
247
|
+
|
|
248
|
+
- **Wallet management** — Create and fund wallets without touching key files
|
|
249
|
+
- **Token resolution** — Say `sol` or `usdc` instead of mint addresses
|
|
250
|
+
- **Transaction handling** — Automatic retries, blockhash management, confirmation polling
|
|
251
|
+
- **Cost-basis tracking** — Every swap is logged with USD prices at execution time
|
|
252
|
+
- **Portfolio snapshots** — Track P&L across sessions without external databases
|
|
253
|
+
- **Error classification** — Structured errors distinguish transient failures (retry) from terminal ones (stop)
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Global Flags
|
|
258
|
+
|
|
259
|
+
| Flag | Description |
|
|
260
|
+
|------|-------------|
|
|
261
|
+
| `--json` | Structured JSON output |
|
|
262
|
+
| `--wallet <name>` | Override default wallet |
|
|
263
|
+
| `--rpc <url>` | Override RPC endpoint |
|
|
264
|
+
| `--verbose` | Show debug logging |
|
|
265
|
+
|
|
266
|
+
These work on every command: `sol --json --wallet trading token swap 50 usdc bonk`
|
|
267
|
+
|
|
268
|
+
## RPC Resolution
|
|
269
|
+
|
|
270
|
+
The CLI resolves an RPC endpoint in this order:
|
|
271
|
+
|
|
272
|
+
1. `--rpc` flag
|
|
273
|
+
2. `SOL_RPC_URL` environment variable
|
|
274
|
+
3. `~/.sol/config.toml` → `rpc.url`
|
|
275
|
+
4. Solana CLI config (`solana config get`)
|
|
276
|
+
5. Public mainnet RPC (with warning)
|
|
277
|
+
|
|
278
|
+
## Data Storage
|
|
279
|
+
|
|
280
|
+
All data lives in `~/.sol/`:
|
|
281
|
+
|
|
282
|
+
| Path | Contents |
|
|
283
|
+
|------|----------|
|
|
284
|
+
| `config.toml` | Configuration (TOML) |
|
|
285
|
+
| `data.db` | SQLite — wallets, token cache, transaction log, snapshots |
|
|
286
|
+
| `wallets/*.json` | Keypair files (Solana CLI format, chmod 600) |
|
|
287
|
+
|
|
288
|
+
The transaction log is the source of truth for cost basis and P&L — every swap, transfer, deposit, and withdrawal is recorded with USD prices at the time of execution.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Architecture
|
|
293
|
+
|
|
294
|
+
- **Runtime**: Node.js via `tsx` — no build step needed
|
|
295
|
+
- **CLI framework**: commander.js
|
|
296
|
+
- **Solana SDK**: `@solana/kit` v2
|
|
297
|
+
- **Swaps**: Jupiter REST API (no SDK, no API key)
|
|
298
|
+
- **Lending**: Kamino Finance via `@kamino-finance/klend-sdk`
|
|
299
|
+
- **Prices**: Jupiter Price API with CoinGecko fallback
|
|
300
|
+
- **Database**: SQLite via `better-sqlite3` (WAL mode)
|
|
301
|
+
- **Config**: TOML via `smol-toml`
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
src/
|
|
305
|
+
index.ts # CLI entry point, global flags
|
|
306
|
+
commands/ # One file per command group
|
|
307
|
+
core/ # Business logic (wallet-manager, swap-service, etc.)
|
|
308
|
+
db/ # SQLite with migration runner
|
|
309
|
+
output/ # JSON envelope + table/portfolio renderers
|
|
310
|
+
utils/ # Solana helpers, token list, retry logic
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Security
|
|
314
|
+
|
|
315
|
+
- Key files are plain JSON (Solana CLI compatible), stored with `chmod 600`
|
|
316
|
+
- Private keys are never logged or printed to stdout
|
|
317
|
+
- `wallet export` returns the file path, not the key contents
|
|
318
|
+
- All transactions are logged to SQLite for audit
|
|
319
|
+
- Swap commands show quote details before executing
|
|
320
|
+
|
|
321
|
+
## Disclaimer
|
|
322
|
+
|
|
323
|
+
This software interacts with the Solana blockchain and can execute irreversible transactions involving real funds. You are solely responsible for your own transactions, wallet security, and any financial outcomes. The authors are not liable for any losses. Use at your own risk.
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
MIT
|
package/bin/sol.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getConfigValue, setConfigValue, listConfig, getConfigPath } from '../core/config-manager.js';
|
|
2
|
+
import { output, success, failure, isJsonMode } from '../output/formatter.js';
|
|
3
|
+
import { table } from '../output/table.js';
|
|
4
|
+
export function registerConfigCommand(program) {
|
|
5
|
+
const config = program.command('config').description('Manage CLI configuration');
|
|
6
|
+
config
|
|
7
|
+
.command('set <key> <value>')
|
|
8
|
+
.description('Set a config value (e.g., sol config set rpc.url https://my-rpc.com)')
|
|
9
|
+
.action((key, value) => {
|
|
10
|
+
try {
|
|
11
|
+
setConfigValue(key, value);
|
|
12
|
+
if (isJsonMode()) {
|
|
13
|
+
output(success({ key, value: getConfigValue(key) }));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(`Set ${key} = ${value}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
output(failure('CONFIG_SET_FAILED', err.message));
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
config
|
|
25
|
+
.command('get <key>')
|
|
26
|
+
.description('Get a config value')
|
|
27
|
+
.action((key) => {
|
|
28
|
+
const value = getConfigValue(key);
|
|
29
|
+
if (isJsonMode()) {
|
|
30
|
+
output(success({ key, value }));
|
|
31
|
+
}
|
|
32
|
+
else if (value === undefined) {
|
|
33
|
+
console.log(`(not set)`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.log(String(value));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
config
|
|
40
|
+
.command('list')
|
|
41
|
+
.description('List all config values')
|
|
42
|
+
.action(() => {
|
|
43
|
+
const values = listConfig();
|
|
44
|
+
if (isJsonMode()) {
|
|
45
|
+
output(success(values));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const entries = Object.entries(values);
|
|
49
|
+
if (entries.length === 0) {
|
|
50
|
+
console.log('No configuration set. Use: sol config set <key> <value>');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(table(entries.map(([key, value]) => ({ key, value: String(value) })), [
|
|
54
|
+
{ key: 'key', header: 'Key' },
|
|
55
|
+
{ key: 'value', header: 'Value' },
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
config
|
|
61
|
+
.command('path')
|
|
62
|
+
.description('Show config file path')
|
|
63
|
+
.action(() => {
|
|
64
|
+
const path = getConfigPath();
|
|
65
|
+
if (isJsonMode()) {
|
|
66
|
+
output(success({ path }));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(path);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as lendService from '../core/lend-service.js';
|
|
2
|
+
import { getDefaultWalletName, resolveWalletName } from '../core/wallet-manager.js';
|
|
3
|
+
import { output, success, failure, isJsonMode, timed } from '../output/formatter.js';
|
|
4
|
+
import { table } from '../output/table.js';
|
|
5
|
+
import * as walletRepo from '../db/repos/wallet-repo.js';
|
|
6
|
+
export function registerLendCommand(program) {
|
|
7
|
+
const lend = program.command('lend').description('Lending and borrowing (Kamino Finance)');
|
|
8
|
+
// ── rates ───────────────────────────────────────────────
|
|
9
|
+
lend
|
|
10
|
+
.command('rates <token>')
|
|
11
|
+
.description('Show Kamino deposit/borrow APY for a token')
|
|
12
|
+
.action(async (token) => {
|
|
13
|
+
try {
|
|
14
|
+
const { result: rates, elapsed_ms } = await timed(() => lendService.getRates(token));
|
|
15
|
+
if (isJsonMode()) {
|
|
16
|
+
output(success({ token, rates }, { elapsed_ms }));
|
|
17
|
+
}
|
|
18
|
+
else if (rates.length === 0) {
|
|
19
|
+
console.log(`No Kamino lending reserve found for "${token}".`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const r = rates[0];
|
|
23
|
+
console.log(`Kamino Lending — ${r.token}\n`);
|
|
24
|
+
console.log(table([{
|
|
25
|
+
depositApy: `${(r.depositApy * 100).toFixed(2)}%`,
|
|
26
|
+
borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
|
|
27
|
+
utilization: `${r.utilizationPct.toFixed(1)}%`,
|
|
28
|
+
totalDeposited: fmtLargeAmount(r.totalDeposited),
|
|
29
|
+
totalBorrowed: fmtLargeAmount(r.totalBorrowed),
|
|
30
|
+
}], [
|
|
31
|
+
{ key: 'depositApy', header: 'Deposit APY', align: 'right' },
|
|
32
|
+
{ key: 'borrowApy', header: 'Borrow APY', align: 'right' },
|
|
33
|
+
{ key: 'utilization', header: 'Utilization', align: 'right' },
|
|
34
|
+
{ key: 'totalDeposited', header: 'Total Deposited', align: 'right' },
|
|
35
|
+
{ key: 'totalBorrowed', header: 'Total Borrowed', align: 'right' },
|
|
36
|
+
]));
|
|
37
|
+
console.log(`\nRun \`sol lend deposit <amount> ${token}\` to start earning.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
output(failure('LEND_RATES_FAILED', err.message));
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// ── positions ───────────────────────────────────────────
|
|
46
|
+
lend
|
|
47
|
+
.command('positions')
|
|
48
|
+
.description('List all Kamino lending/borrowing positions')
|
|
49
|
+
.option('--wallet <name>', 'Wallet to check')
|
|
50
|
+
.action(async (opts) => {
|
|
51
|
+
try {
|
|
52
|
+
const walletName = opts.wallet ? resolveWalletName(opts.wallet) : getDefaultWalletName();
|
|
53
|
+
const wallet = walletRepo.getWallet(walletName);
|
|
54
|
+
if (!wallet)
|
|
55
|
+
throw new Error(`Wallet "${walletName}" not found`);
|
|
56
|
+
const { result: positions, elapsed_ms } = await timed(() => lendService.getPositions(wallet.address));
|
|
57
|
+
if (isJsonMode()) {
|
|
58
|
+
output(success({ wallet: walletName, positions }, { elapsed_ms }));
|
|
59
|
+
}
|
|
60
|
+
else if (positions.length === 0) {
|
|
61
|
+
console.log('No Kamino lending positions found.');
|
|
62
|
+
console.log('Run `sol lend rates <token>` to see available rates.');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const deposits = positions.filter(p => p.type === 'deposit');
|
|
66
|
+
const borrows = positions.filter(p => p.type === 'borrow');
|
|
67
|
+
if (deposits.length > 0) {
|
|
68
|
+
console.log('Deposits');
|
|
69
|
+
console.log(table(deposits.map(p => ({
|
|
70
|
+
token: p.token,
|
|
71
|
+
amount: fmtAmount(p.amount),
|
|
72
|
+
value: `$${p.valueUsd.toFixed(2)}`,
|
|
73
|
+
apy: `${(p.apy * 100).toFixed(2)}%`,
|
|
74
|
+
})), [
|
|
75
|
+
{ key: 'token', header: 'Token' },
|
|
76
|
+
{ key: 'amount', header: 'Amount', align: 'right' },
|
|
77
|
+
{ key: 'value', header: 'Value', align: 'right' },
|
|
78
|
+
{ key: 'apy', header: 'APY', align: 'right' },
|
|
79
|
+
]));
|
|
80
|
+
console.log('');
|
|
81
|
+
}
|
|
82
|
+
if (borrows.length > 0) {
|
|
83
|
+
console.log('Borrows');
|
|
84
|
+
console.log(table(borrows.map(p => ({
|
|
85
|
+
token: p.token,
|
|
86
|
+
amount: fmtAmount(p.amount),
|
|
87
|
+
value: `$${p.valueUsd.toFixed(2)}`,
|
|
88
|
+
apy: `${(p.apy * 100).toFixed(2)}%`,
|
|
89
|
+
health: p.healthFactor != null ? p.healthFactor.toFixed(2) : '—',
|
|
90
|
+
})), [
|
|
91
|
+
{ key: 'token', header: 'Token' },
|
|
92
|
+
{ key: 'amount', header: 'Amount', align: 'right' },
|
|
93
|
+
{ key: 'value', header: 'Value', align: 'right' },
|
|
94
|
+
{ key: 'apy', header: 'APY', align: 'right' },
|
|
95
|
+
{ key: 'health', header: 'Health', align: 'right' },
|
|
96
|
+
]));
|
|
97
|
+
// Health factor warning
|
|
98
|
+
const minHealth = Math.min(...borrows.map(p => p.healthFactor ?? Infinity));
|
|
99
|
+
if (minHealth < 1.1) {
|
|
100
|
+
console.log('\nWarning: health factor below 1.1. Consider repaying or adding collateral.');
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
// Net value
|
|
105
|
+
const totalDeposits = deposits.reduce((s, p) => s + p.valueUsd, 0);
|
|
106
|
+
const totalBorrows = borrows.reduce((s, p) => s + p.valueUsd, 0);
|
|
107
|
+
console.log(`Net lending value: $${(totalDeposits - totalBorrows).toFixed(2)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
output(failure('LEND_POSITIONS_FAILED', err.message));
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// ── deposit ─────────────────────────────────────────────
|
|
116
|
+
lend
|
|
117
|
+
.command('deposit <amount> <token>')
|
|
118
|
+
.description('Deposit into Kamino lending vault')
|
|
119
|
+
.option('--wallet <name>', 'Wallet to use')
|
|
120
|
+
.action(async (amountStr, token, opts) => {
|
|
121
|
+
try {
|
|
122
|
+
const amount = parseFloat(amountStr);
|
|
123
|
+
if (isNaN(amount) || amount <= 0)
|
|
124
|
+
throw new Error('Invalid amount');
|
|
125
|
+
const walletName = opts.wallet ? resolveWalletName(opts.wallet) : getDefaultWalletName();
|
|
126
|
+
const { result, elapsed_ms } = await timed(() => lendService.deposit(walletName, token, amount));
|
|
127
|
+
if (isJsonMode()) {
|
|
128
|
+
output(success(result, { elapsed_ms }));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(`Deposited ${amount} ${token.toUpperCase()} into Kamino`);
|
|
132
|
+
console.log(` Tx: ${result.explorerUrl}`);
|
|
133
|
+
console.log(`\nRun \`sol lend positions\` to see your deposits.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
output(failure('LEND_DEPOSIT_FAILED', err.message));
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// ── withdraw ────────────────────────────────────────────
|
|
142
|
+
lend
|
|
143
|
+
.command('withdraw <amount> <token>')
|
|
144
|
+
.description('Withdraw from Kamino lending position (use "max" for full withdrawal)')
|
|
145
|
+
.option('--wallet <name>', 'Wallet to use')
|
|
146
|
+
.action(async (amountStr, token, opts) => {
|
|
147
|
+
try {
|
|
148
|
+
const amount = parseMaxAmount(amountStr);
|
|
149
|
+
const walletName = opts.wallet ? resolveWalletName(opts.wallet) : getDefaultWalletName();
|
|
150
|
+
const { result, elapsed_ms } = await timed(() => lendService.withdraw(walletName, token, amount));
|
|
151
|
+
if (isJsonMode()) {
|
|
152
|
+
output(success(result, { elapsed_ms }));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const label = isFinite(amount) ? `${amount} ${token.toUpperCase()}` : `all ${token.toUpperCase()}`;
|
|
156
|
+
console.log(`Withdrew ${label} from Kamino`);
|
|
157
|
+
console.log(` Tx: ${result.explorerUrl}`);
|
|
158
|
+
console.log(`\nRun \`sol lend positions\` to check remaining positions.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
output(failure('LEND_WITHDRAW_FAILED', err.message));
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
// ── borrow ──────────────────────────────────────────────
|
|
167
|
+
lend
|
|
168
|
+
.command('borrow <amount> <token>')
|
|
169
|
+
.description('Borrow against collateral on Kamino')
|
|
170
|
+
.option('--collateral <token>', 'Collateral token (required)')
|
|
171
|
+
.option('--wallet <name>', 'Wallet to use')
|
|
172
|
+
.action(async (amountStr, token, opts) => {
|
|
173
|
+
try {
|
|
174
|
+
const amount = parseFloat(amountStr);
|
|
175
|
+
if (isNaN(amount) || amount <= 0)
|
|
176
|
+
throw new Error('Invalid amount');
|
|
177
|
+
if (!opts.collateral)
|
|
178
|
+
throw new Error('--collateral is required');
|
|
179
|
+
const walletName = opts.wallet ? resolveWalletName(opts.wallet) : getDefaultWalletName();
|
|
180
|
+
const { result, elapsed_ms } = await timed(() => lendService.borrow(walletName, token, amount, opts.collateral));
|
|
181
|
+
if (isJsonMode()) {
|
|
182
|
+
output(success(result, { elapsed_ms }));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(`Borrowed ${amount} ${token.toUpperCase()} from Kamino`);
|
|
186
|
+
console.log(` Tx: ${result.explorerUrl}`);
|
|
187
|
+
if (result.healthFactor != null) {
|
|
188
|
+
console.log(` Health factor: ${result.healthFactor.toFixed(2)}`);
|
|
189
|
+
if (result.healthFactor < 1.1) {
|
|
190
|
+
console.log(' Warning: health factor is low. Monitor closely.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
console.log(`\nRun \`sol lend positions\` to view all positions.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
output(failure('LEND_BORROW_FAILED', err.message));
|
|
198
|
+
process.exitCode = 1;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
// ── repay ───────────────────────────────────────────────
|
|
202
|
+
lend
|
|
203
|
+
.command('repay <amount> <token>')
|
|
204
|
+
.description('Repay a Kamino loan (use "max" to repay full debt)')
|
|
205
|
+
.option('--wallet <name>', 'Wallet to use')
|
|
206
|
+
.action(async (amountStr, token, opts) => {
|
|
207
|
+
try {
|
|
208
|
+
const amount = parseMaxAmount(amountStr);
|
|
209
|
+
const walletName = opts.wallet ? resolveWalletName(opts.wallet) : getDefaultWalletName();
|
|
210
|
+
const { result, elapsed_ms } = await timed(() => lendService.repay(walletName, token, amount));
|
|
211
|
+
if (isJsonMode()) {
|
|
212
|
+
output(success(result, { elapsed_ms }));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const label = isFinite(amount) ? `${amount} ${token.toUpperCase()}` : `all ${token.toUpperCase()}`;
|
|
216
|
+
console.log(`Repaid ${label} on Kamino`);
|
|
217
|
+
console.log(` Tx: ${result.explorerUrl}`);
|
|
218
|
+
if (result.remainingDebt != null) {
|
|
219
|
+
if (result.remainingDebt === 0) {
|
|
220
|
+
console.log(' Loan fully repaid!');
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(` Remaining debt: ${fmtAmount(result.remainingDebt)} ${token.toUpperCase()}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
console.log(`\nRun \`sol lend positions\` to check remaining positions.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
output(failure('LEND_REPAY_FAILED', err.message));
|
|
231
|
+
process.exitCode = 1;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
236
|
+
function fmtAmount(n) {
|
|
237
|
+
if (n >= 1_000_000)
|
|
238
|
+
return n.toFixed(0);
|
|
239
|
+
if (n >= 1)
|
|
240
|
+
return n.toFixed(4);
|
|
241
|
+
if (n >= 0.0001)
|
|
242
|
+
return n.toFixed(6);
|
|
243
|
+
return n.toFixed(9);
|
|
244
|
+
}
|
|
245
|
+
function parseMaxAmount(str) {
|
|
246
|
+
if (str === 'max' || str === 'all')
|
|
247
|
+
return Infinity;
|
|
248
|
+
const n = parseFloat(str);
|
|
249
|
+
if (isNaN(n) || n <= 0)
|
|
250
|
+
throw new Error('Invalid amount (use a number or "max")');
|
|
251
|
+
return n;
|
|
252
|
+
}
|
|
253
|
+
function fmtLargeAmount(n) {
|
|
254
|
+
if (n >= 1_000_000_000)
|
|
255
|
+
return `${(n / 1_000_000_000).toFixed(2)}B`;
|
|
256
|
+
if (n >= 1_000_000)
|
|
257
|
+
return `${(n / 1_000_000).toFixed(2)}M`;
|
|
258
|
+
if (n >= 1_000)
|
|
259
|
+
return `${(n / 1_000).toFixed(2)}K`;
|
|
260
|
+
return fmtAmount(n);
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=lend.js.map
|