perp-cli 0.3.3

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.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,14 @@
1
+ import { Command } from "commander";
2
+ /** Exported for smart landing page in index.ts */
3
+ export declare function getWalletSetupStatus(): {
4
+ hasWallets: boolean;
5
+ active: Record<string, string>;
6
+ wallets: Record<string, {
7
+ name: string;
8
+ type: string;
9
+ address: string;
10
+ }>;
11
+ };
12
+ /** Exported so config.ts can resolve the active wallet key */
13
+ export declare function getActiveWalletKey(exchange: string): string | null;
14
+ export declare function registerWalletCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,602 @@
1
+ import chalk from "chalk";
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
5
+ import { EXCHANGE_ENV_MAP, validateKey, loadEnvFile, setEnvVar, ENV_FILE } from "./init.js";
6
+ import { loadSettings, saveSettings } from "../settings.js";
7
+ const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
8
+ const WALLETS_FILE = resolve(PERP_DIR, "wallets.json");
9
+ function ensurePerpDir() {
10
+ if (!existsSync(PERP_DIR))
11
+ mkdirSync(PERP_DIR, { recursive: true, mode: 0o700 });
12
+ }
13
+ function loadStore() {
14
+ ensurePerpDir();
15
+ if (!existsSync(WALLETS_FILE))
16
+ return { wallets: {}, active: {} };
17
+ try {
18
+ return JSON.parse(readFileSync(WALLETS_FILE, "utf-8"));
19
+ }
20
+ catch {
21
+ return { wallets: {}, active: {} };
22
+ }
23
+ }
24
+ function saveStore(store) {
25
+ ensurePerpDir();
26
+ writeFileSync(WALLETS_FILE, JSON.stringify(store, null, 2), { mode: 0o600 });
27
+ }
28
+ /** Exported for smart landing page in index.ts */
29
+ export function getWalletSetupStatus() {
30
+ const store = loadStore();
31
+ const wallets = {};
32
+ for (const [k, v] of Object.entries(store.wallets)) {
33
+ wallets[k] = { name: v.name, type: v.type, address: v.address };
34
+ }
35
+ return { hasWallets: Object.keys(store.wallets).length > 0, active: store.active, wallets };
36
+ }
37
+ /** Exported so config.ts can resolve the active wallet key */
38
+ export function getActiveWalletKey(exchange) {
39
+ const store = loadStore();
40
+ const name = store.active[exchange];
41
+ if (!name)
42
+ return null;
43
+ return store.wallets[name]?.privateKey ?? null;
44
+ }
45
+ async function getSolanaBalances(address, isTestnet) {
46
+ const { Connection, PublicKey } = await import("@solana/web3.js");
47
+ const rpcUrl = isTestnet
48
+ ? "https://api.devnet.solana.com"
49
+ : "https://api.mainnet-beta.solana.com";
50
+ const connection = new Connection(rpcUrl);
51
+ const pubkey = new PublicKey(address);
52
+ const balances = [];
53
+ const solLamports = await connection.getBalance(pubkey);
54
+ const solBalance = solLamports / 1e9;
55
+ balances.push({ token: "SOL", balance: solBalance.toFixed(6), usdValue: "" });
56
+ const usdcMint = isTestnet
57
+ ? "USDPqRbLidFGufty2s3oizmDEKdqx7ePTqzDMbf5ZKM"
58
+ : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
59
+ try {
60
+ const tokenAccounts = await connection.getParsedTokenAccountsByOwner(pubkey, {
61
+ mint: new PublicKey(usdcMint),
62
+ });
63
+ let usdcBalance = 0;
64
+ for (const { account } of tokenAccounts.value) {
65
+ const parsed = account.data.parsed?.info;
66
+ if (parsed)
67
+ usdcBalance += Number(parsed.tokenAmount?.uiAmount ?? 0);
68
+ }
69
+ balances.push({
70
+ token: isTestnet ? "USDP" : "USDC",
71
+ balance: usdcBalance.toFixed(2),
72
+ usdValue: `$${formatUsd(usdcBalance)}`,
73
+ });
74
+ }
75
+ catch {
76
+ balances.push({ token: isTestnet ? "USDP" : "USDC", balance: "0.00", usdValue: "$0.00" });
77
+ }
78
+ try {
79
+ const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd");
80
+ const json = (await res.json());
81
+ balances[0].usdValue = `$${formatUsd(solBalance * (json.solana?.usd ?? 0))}`;
82
+ }
83
+ catch {
84
+ balances[0].usdValue = "-";
85
+ }
86
+ return balances;
87
+ }
88
+ async function getEvmBalances(address, isTestnet) {
89
+ const { ethers } = await import("ethers");
90
+ const rpcUrl = isTestnet
91
+ ? "https://sepolia-rollup.arbitrum.io/rpc"
92
+ : "https://arb1.arbitrum.io/rpc";
93
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
94
+ const balances = [];
95
+ const ethBal = await provider.getBalance(address);
96
+ const ethBalance = Number(ethers.formatEther(ethBal));
97
+ balances.push({ token: "ETH", balance: ethBalance.toFixed(6), usdValue: "" });
98
+ const usdcAddr = isTestnet
99
+ ? "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"
100
+ : "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
101
+ try {
102
+ const usdc = new ethers.Contract(usdcAddr, ["function balanceOf(address) view returns (uint256)"], provider);
103
+ const rawBal = await usdc.balanceOf(address);
104
+ const usdcBalance = Number(ethers.formatUnits(rawBal, 6));
105
+ balances.push({ token: "USDC", balance: usdcBalance.toFixed(2), usdValue: `$${formatUsd(usdcBalance)}` });
106
+ }
107
+ catch {
108
+ balances.push({ token: "USDC", balance: "0.00", usdValue: "$0.00" });
109
+ }
110
+ try {
111
+ const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd");
112
+ const json = (await res.json());
113
+ balances[0].usdValue = `$${formatUsd(ethBalance * (json.ethereum?.usd ?? 0))}`;
114
+ }
115
+ catch {
116
+ balances[0].usdValue = "-";
117
+ }
118
+ return balances;
119
+ }
120
+ // ── Derive address from private key ──────────────────────────
121
+ async function deriveSolanaAddress(key) {
122
+ const { Keypair } = await import("@solana/web3.js");
123
+ const bs58 = (await import("bs58")).default;
124
+ try {
125
+ return Keypair.fromSecretKey(bs58.decode(key)).publicKey.toBase58();
126
+ }
127
+ catch {
128
+ const arr = JSON.parse(key);
129
+ return Keypair.fromSecretKey(Uint8Array.from(arr)).publicKey.toBase58();
130
+ }
131
+ }
132
+ async function deriveEvmAddress(key) {
133
+ const { ethers } = await import("ethers");
134
+ const pk = key.startsWith("0x") ? key : `0x${key}`;
135
+ return new ethers.Wallet(pk).address;
136
+ }
137
+ // ── Commands ──────────────────────────────────────────────────
138
+ export function registerWalletCommands(program, isJson) {
139
+ const wallet = program.command("wallet").description("Wallet management & on-chain balances");
140
+ // ── generate ──
141
+ const generate = wallet.command("generate").description("Generate a new wallet keypair");
142
+ generate
143
+ .command("solana")
144
+ .description("Generate a new Solana keypair")
145
+ .option("-n, --name <name>", "Wallet alias name", "default-sol")
146
+ .action(async (opts) => {
147
+ const { Keypair } = await import("@solana/web3.js");
148
+ const bs58 = (await import("bs58")).default;
149
+ const keypair = Keypair.generate();
150
+ const address = keypair.publicKey.toBase58();
151
+ const privkey = bs58.encode(keypair.secretKey);
152
+ const store = loadStore();
153
+ if (store.wallets[opts.name]) {
154
+ console.error(chalk.red(`\n Wallet "${opts.name}" already exists. Use a different name or 'wallet remove' first.\n`));
155
+ process.exit(1);
156
+ }
157
+ store.wallets[opts.name] = {
158
+ name: opts.name,
159
+ type: "solana",
160
+ address,
161
+ privateKey: privkey,
162
+ createdAt: new Date().toISOString(),
163
+ };
164
+ if (!store.active.pacifica)
165
+ store.active.pacifica = opts.name;
166
+ saveStore(store);
167
+ // Also write to .env so loadPrivateKey finds it immediately
168
+ setEnvVar("PACIFICA_PRIVATE_KEY", privkey);
169
+ if (isJson())
170
+ return printJson(jsonOk({ name: opts.name, type: "solana", address }));
171
+ console.log(chalk.cyan.bold("\n New Solana Wallet\n"));
172
+ console.log(` Name: ${chalk.white.bold(opts.name)}`);
173
+ console.log(` Address: ${chalk.green(address)}`);
174
+ console.log(` Saved: ${chalk.gray("~/.perp/.env + wallets.json")}`);
175
+ if (store.active.pacifica === opts.name) {
176
+ console.log(chalk.cyan(`\n Active for: pacifica`));
177
+ }
178
+ console.log(chalk.red.bold("\n Fund this wallet before trading!\n"));
179
+ });
180
+ generate
181
+ .command("evm")
182
+ .description("Generate a new EVM wallet")
183
+ .option("-n, --name <name>", "Wallet alias name", "default-evm")
184
+ .action(async (opts) => {
185
+ const { ethers } = await import("ethers");
186
+ const w = ethers.Wallet.createRandom();
187
+ const store = loadStore();
188
+ if (store.wallets[opts.name]) {
189
+ console.error(chalk.red(`\n Wallet "${opts.name}" already exists. Use a different name or 'wallet remove' first.\n`));
190
+ process.exit(1);
191
+ }
192
+ store.wallets[opts.name] = {
193
+ name: opts.name,
194
+ type: "evm",
195
+ address: w.address,
196
+ privateKey: w.privateKey,
197
+ createdAt: new Date().toISOString(),
198
+ };
199
+ if (!store.active.hyperliquid)
200
+ store.active.hyperliquid = opts.name;
201
+ if (!store.active.lighter)
202
+ store.active.lighter = opts.name;
203
+ saveStore(store);
204
+ // Also write to .env so loadPrivateKey finds it immediately
205
+ setEnvVar("HL_PRIVATE_KEY", w.privateKey);
206
+ setEnvVar("LIGHTER_PRIVATE_KEY", w.privateKey);
207
+ if (isJson())
208
+ return printJson(jsonOk({ name: opts.name, type: "evm", address: w.address }));
209
+ console.log(chalk.cyan.bold("\n New EVM Wallet\n"));
210
+ console.log(` Name: ${chalk.white.bold(opts.name)}`);
211
+ console.log(` Address: ${chalk.green(w.address)}`);
212
+ console.log(` Saved: ${chalk.gray("~/.perp/.env + wallets.json")}`);
213
+ const activeFor = Object.entries(store.active)
214
+ .filter(([, v]) => v === opts.name)
215
+ .map(([k]) => k);
216
+ if (activeFor.length)
217
+ console.log(chalk.cyan(`\n Active for: ${activeFor.join(", ")}`));
218
+ console.log(chalk.red.bold("\n Fund this wallet before trading!\n"));
219
+ });
220
+ // ── import ──
221
+ const importCmd = wallet.command("import").description("Import an existing private key");
222
+ importCmd
223
+ .command("solana <privateKey>")
224
+ .description("Import a Solana private key (base58 or JSON array)")
225
+ .option("-n, --name <name>", "Wallet alias name", "imported-sol")
226
+ .action(async (privateKey, opts) => {
227
+ let address;
228
+ let normalizedKey = privateKey;
229
+ try {
230
+ const { Keypair } = await import("@solana/web3.js");
231
+ const bs58 = (await import("bs58")).default;
232
+ try {
233
+ const bytes = bs58.decode(privateKey);
234
+ const kp = Keypair.fromSecretKey(bytes);
235
+ address = kp.publicKey.toBase58();
236
+ normalizedKey = bs58.encode(kp.secretKey);
237
+ }
238
+ catch {
239
+ const arr = JSON.parse(privateKey);
240
+ const kp = Keypair.fromSecretKey(Uint8Array.from(arr));
241
+ address = kp.publicKey.toBase58();
242
+ normalizedKey = bs58.encode(kp.secretKey);
243
+ }
244
+ }
245
+ catch {
246
+ console.error(chalk.red("\n Invalid Solana private key.\n"));
247
+ process.exit(1);
248
+ }
249
+ const store = loadStore();
250
+ if (store.wallets[opts.name]) {
251
+ console.error(chalk.red(`\n Wallet "${opts.name}" already exists.\n`));
252
+ process.exit(1);
253
+ }
254
+ store.wallets[opts.name] = {
255
+ name: opts.name, type: "solana", address, privateKey: normalizedKey,
256
+ createdAt: new Date().toISOString(),
257
+ };
258
+ if (!store.active.pacifica)
259
+ store.active.pacifica = opts.name;
260
+ saveStore(store);
261
+ // Also write to .env so loadPrivateKey finds it immediately
262
+ setEnvVar("PACIFICA_PRIVATE_KEY", normalizedKey);
263
+ if (isJson())
264
+ return printJson(jsonOk({ name: opts.name, type: "solana", address }));
265
+ console.log(chalk.cyan.bold("\n Solana Wallet Imported\n"));
266
+ console.log(` Name: ${chalk.white.bold(opts.name)}`);
267
+ console.log(` Address: ${chalk.green(address)}\n`);
268
+ });
269
+ importCmd
270
+ .command("evm <privateKey>")
271
+ .description("Import an EVM private key (0x hex)")
272
+ .option("-n, --name <name>", "Wallet alias name", "imported-evm")
273
+ .action(async (privateKey, opts) => {
274
+ const { ethers } = await import("ethers");
275
+ const pk = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
276
+ let address;
277
+ try {
278
+ address = new ethers.Wallet(pk).address;
279
+ }
280
+ catch {
281
+ console.error(chalk.red("\n Invalid EVM private key.\n"));
282
+ process.exit(1);
283
+ }
284
+ const store = loadStore();
285
+ if (store.wallets[opts.name]) {
286
+ console.error(chalk.red(`\n Wallet "${opts.name}" already exists.\n`));
287
+ process.exit(1);
288
+ }
289
+ store.wallets[opts.name] = {
290
+ name: opts.name, type: "evm", address, privateKey: pk,
291
+ createdAt: new Date().toISOString(),
292
+ };
293
+ if (!store.active.hyperliquid)
294
+ store.active.hyperliquid = opts.name;
295
+ if (!store.active.lighter)
296
+ store.active.lighter = opts.name;
297
+ saveStore(store);
298
+ // Also write to .env so loadPrivateKey finds it immediately
299
+ setEnvVar("HL_PRIVATE_KEY", pk);
300
+ setEnvVar("LIGHTER_PRIVATE_KEY", pk);
301
+ if (isJson())
302
+ return printJson(jsonOk({ name: opts.name, type: "evm", address }));
303
+ console.log(chalk.cyan.bold("\n EVM Wallet Imported\n"));
304
+ console.log(` Name: ${chalk.white.bold(opts.name)}`);
305
+ console.log(` Address: ${chalk.green(address)}\n`);
306
+ });
307
+ // ── use (set active wallet for exchange) ──
308
+ wallet
309
+ .command("use <name> [exchange]")
310
+ .description("Set active wallet for exchange (auto-detects if omitted)")
311
+ .option("--for <exchange>", "Exchange to bind (legacy alias)")
312
+ .action(async (name, exchangeArg, opts) => {
313
+ const store = loadStore();
314
+ const entry = store.wallets[name];
315
+ if (!entry) {
316
+ console.error(chalk.red(`\n Wallet "${name}" not found. Run 'perp wallet list' to see available wallets.\n`));
317
+ process.exit(1);
318
+ }
319
+ // Resolve exchange: positional arg > --for flag > auto-detect from wallet type
320
+ const rawExchange = exchangeArg || opts.for;
321
+ let exchanges;
322
+ if (rawExchange) {
323
+ const ex = rawExchange.toLowerCase();
324
+ const needsSolana = ex === "pacifica";
325
+ if (needsSolana && entry.type !== "solana") {
326
+ console.error(chalk.red(`\n Pacifica requires a Solana wallet. "${name}" is EVM.\n`));
327
+ process.exit(1);
328
+ }
329
+ if (!needsSolana && entry.type !== "evm") {
330
+ console.error(chalk.red(`\n ${ex} requires an EVM wallet. "${name}" is Solana.\n`));
331
+ process.exit(1);
332
+ }
333
+ exchanges = [ex];
334
+ }
335
+ else {
336
+ // Auto-detect from wallet type
337
+ exchanges = entry.type === "solana" ? ["pacifica"] : ["hyperliquid", "lighter"];
338
+ }
339
+ for (const exchange of exchanges) {
340
+ store.active[exchange] = name;
341
+ }
342
+ saveStore(store);
343
+ if (isJson())
344
+ return printJson(jsonOk({ exchanges, wallet: name, address: entry.address }));
345
+ console.log(chalk.green(`\n ${chalk.white.bold(name)} is now active for ${chalk.cyan(exchanges.join(", "))}`));
346
+ console.log(chalk.gray(` Address: ${entry.address}\n`));
347
+ });
348
+ // ── set (configure exchange key → ~/.perp/.env) ──
349
+ wallet
350
+ .command("set <exchange> <key>")
351
+ .description("Set private key for an exchange")
352
+ .option("--default", "Also set as default exchange")
353
+ .action(async (exchange, key, opts) => {
354
+ // Resolve alias (hl → hyperliquid, pac → pacifica, lt → lighter)
355
+ const aliases = { hl: "hyperliquid", pac: "pacifica", lt: "lighter" };
356
+ const resolved = aliases[exchange.toLowerCase()] || exchange.toLowerCase();
357
+ const info = EXCHANGE_ENV_MAP[resolved];
358
+ if (!info) {
359
+ if (isJson()) {
360
+ const { jsonError } = await import("../utils.js");
361
+ return printJson(jsonError("INVALID_PARAMS", `Unknown exchange: ${exchange}. Use: pacifica, hyperliquid, lighter (or hl, pac, lt)`));
362
+ }
363
+ console.error(chalk.red(`\n Unknown exchange: ${exchange}`));
364
+ console.error(chalk.gray(` Use: pacifica, hyperliquid, lighter (or hl, pac, lt)\n`));
365
+ process.exit(1);
366
+ }
367
+ const { valid, address } = await validateKey(info.chain, key);
368
+ if (!valid) {
369
+ if (isJson()) {
370
+ const { jsonError } = await import("../utils.js");
371
+ return printJson(jsonError("INVALID_PARAMS", `Invalid ${info.chain} private key`));
372
+ }
373
+ console.error(chalk.red(`\n Invalid ${info.chain} private key.\n`));
374
+ process.exit(1);
375
+ }
376
+ const normalized = info.chain === "evm"
377
+ ? (key.startsWith("0x") ? key : `0x${key}`)
378
+ : key;
379
+ setEnvVar(info.envKey, normalized);
380
+ if (opts.default) {
381
+ const settings = loadSettings();
382
+ settings.defaultExchange = resolved;
383
+ saveSettings(settings);
384
+ }
385
+ if (isJson())
386
+ return printJson(jsonOk({ exchange: resolved, address, envFile: ENV_FILE, default: !!opts.default }));
387
+ console.log(chalk.green(`\n ${resolved} configured.`));
388
+ console.log(` Address: ${chalk.green(address)}`);
389
+ console.log(` Saved to: ${chalk.gray("~/.perp/.env")}`);
390
+ if (opts.default)
391
+ console.log(` Default: ${chalk.cyan(resolved)}`);
392
+ console.log();
393
+ });
394
+ // ── show (show configured exchanges with public addresses) ──
395
+ wallet
396
+ .command("show")
397
+ .description("Show configured wallets with public addresses")
398
+ .action(async () => {
399
+ const stored = loadEnvFile();
400
+ const entries = [];
401
+ for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
402
+ const fromFile = stored[info.envKey];
403
+ const fromEnv = process.env[info.envKey];
404
+ if (fromFile) {
405
+ entries.push({ name: exchange, chain: info.chain, key: fromFile, source: "~/.perp/.env" });
406
+ }
407
+ else if (fromEnv) {
408
+ entries.push({ name: exchange, chain: info.chain, key: fromEnv, source: "environment" });
409
+ }
410
+ }
411
+ const results = [];
412
+ for (const entry of entries) {
413
+ const { valid, address } = await validateKey(entry.chain, entry.key);
414
+ results.push({ name: entry.name, address: valid ? address : "(invalid key)", source: entry.source });
415
+ }
416
+ if (isJson()) {
417
+ const data = results.map((r) => ({ exchange: r.name, address: r.address, source: r.source }));
418
+ return printJson(jsonOk({ envFile: ENV_FILE, exchanges: data }));
419
+ }
420
+ console.log(chalk.cyan.bold("\n Configured Wallets\n"));
421
+ if (results.length === 0) {
422
+ console.log(chalk.gray(" No keys configured."));
423
+ console.log(chalk.gray(` Run ${chalk.cyan("perp init")} or ${chalk.cyan("perp wallet set <exchange> <key>")}\n`));
424
+ return;
425
+ }
426
+ for (const { name, address, source } of results) {
427
+ console.log(` ${chalk.cyan(name.padEnd(14))} ${chalk.green(address)} ${chalk.gray(source)}`);
428
+ }
429
+ console.log();
430
+ });
431
+ // ── list ──
432
+ wallet
433
+ .command("list")
434
+ .description("List all saved wallets (legacy wallets.json)")
435
+ .action(async () => {
436
+ const store = loadStore();
437
+ const entries = Object.values(store.wallets);
438
+ if (isJson())
439
+ return printJson(jsonOk({ wallets: store.wallets, active: store.active }));
440
+ if (entries.length === 0) {
441
+ console.log(chalk.gray("\n No wallets found."));
442
+ console.log(chalk.gray(" Use 'perp wallet generate' or 'perp wallet import' to add one.\n"));
443
+ return;
444
+ }
445
+ // Build reverse map: wallet name -> which exchanges it's active for
446
+ const activeMap = new Map();
447
+ for (const [exchange, walletName] of Object.entries(store.active)) {
448
+ if (!activeMap.has(walletName))
449
+ activeMap.set(walletName, []);
450
+ activeMap.get(walletName).push(exchange);
451
+ }
452
+ console.log(chalk.cyan.bold("\n Saved Wallets\n"));
453
+ const rows = entries.map((w) => {
454
+ const activeFor = activeMap.get(w.name) ?? [];
455
+ const activeStr = activeFor.length
456
+ ? chalk.cyan(activeFor.join(", "))
457
+ : chalk.gray("-");
458
+ return [
459
+ chalk.white.bold(w.name),
460
+ w.type,
461
+ chalk.green(w.address.slice(0, 10) + "..." + w.address.slice(-6)),
462
+ activeStr,
463
+ chalk.gray(w.createdAt.split("T")[0]),
464
+ ];
465
+ });
466
+ console.log(makeTable(["Name", "Type", "Address", "Active For", "Created"], rows));
467
+ console.log();
468
+ });
469
+ // ── remove ──
470
+ wallet
471
+ .command("remove <name>")
472
+ .description("Remove a saved wallet")
473
+ .action(async (name) => {
474
+ const store = loadStore();
475
+ if (!store.wallets[name]) {
476
+ console.error(chalk.red(`\n Wallet "${name}" not found.\n`));
477
+ process.exit(1);
478
+ }
479
+ const address = store.wallets[name].address;
480
+ delete store.wallets[name];
481
+ // Clear active references
482
+ for (const [exchange, walletName] of Object.entries(store.active)) {
483
+ if (walletName === name)
484
+ delete store.active[exchange];
485
+ }
486
+ saveStore(store);
487
+ if (isJson())
488
+ return printJson(jsonOk({ removed: name, address }));
489
+ console.log(chalk.yellow(`\n Wallet "${name}" removed.`));
490
+ console.log(chalk.gray(` Address was: ${address}\n`));
491
+ });
492
+ // ── rename ──
493
+ wallet
494
+ .command("rename <oldName> <newName>")
495
+ .description("Rename a wallet")
496
+ .action(async (oldName, newName) => {
497
+ const store = loadStore();
498
+ if (!store.wallets[oldName]) {
499
+ console.error(chalk.red(`\n Wallet "${oldName}" not found.\n`));
500
+ process.exit(1);
501
+ }
502
+ if (store.wallets[newName]) {
503
+ console.error(chalk.red(`\n Wallet "${newName}" already exists.\n`));
504
+ process.exit(1);
505
+ }
506
+ store.wallets[newName] = { ...store.wallets[oldName], name: newName };
507
+ delete store.wallets[oldName];
508
+ // Update active references
509
+ for (const [exchange, walletName] of Object.entries(store.active)) {
510
+ if (walletName === oldName)
511
+ store.active[exchange] = newName;
512
+ }
513
+ saveStore(store);
514
+ if (isJson())
515
+ return printJson(jsonOk({ renamed: { from: oldName, to: newName } }));
516
+ console.log(chalk.green(`\n Renamed "${oldName}" -> "${newName}"\n`));
517
+ });
518
+ // ── balance (by wallet name) ──
519
+ wallet
520
+ .command("balance [name]")
521
+ .description("Check on-chain balance for a saved wallet (or active wallet)")
522
+ .option("--testnet", "Use testnet", false)
523
+ .action(async (name, opts) => {
524
+ const store = loadStore();
525
+ let entry;
526
+ if (name) {
527
+ entry = store.wallets[name];
528
+ if (!entry) {
529
+ console.error(chalk.red(`\n Wallet "${name}" not found.\n`));
530
+ process.exit(1);
531
+ }
532
+ }
533
+ else {
534
+ // Show all active wallets
535
+ const activeEntries = Object.entries(store.active)
536
+ .map(([ex, wn]) => ({ exchange: ex, ...store.wallets[wn] }))
537
+ .filter((e) => e.address);
538
+ if (activeEntries.length === 0) {
539
+ console.error(chalk.gray("\n No active wallets. Use 'perp wallet use <name>' to set one.\n"));
540
+ process.exit(1);
541
+ }
542
+ for (const ae of activeEntries) {
543
+ if (!isJson())
544
+ console.log(chalk.cyan.bold(`\n ${ae.name} (${ae.exchange})`));
545
+ const balances = ae.type === "solana"
546
+ ? await getSolanaBalances(ae.address, opts.testnet)
547
+ : await getEvmBalances(ae.address, opts.testnet);
548
+ if (isJson()) {
549
+ printJson(jsonOk({ wallet: ae.name, exchange: ae.exchange, balances }));
550
+ continue;
551
+ }
552
+ const rows = balances.map((b) => [
553
+ chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-"),
554
+ ]);
555
+ console.log(makeTable(["Token", "Balance", "USD Value"], rows));
556
+ }
557
+ console.log();
558
+ return;
559
+ }
560
+ if (!isJson())
561
+ console.log(chalk.cyan(`\n Fetching balance for "${entry.name}" (${entry.address.slice(0, 8)}...)\n`));
562
+ const balances = entry.type === "solana"
563
+ ? await getSolanaBalances(entry.address, opts.testnet)
564
+ : await getEvmBalances(entry.address, opts.testnet);
565
+ if (isJson())
566
+ return printJson(jsonOk({ wallet: entry.name, balances }));
567
+ const rows = balances.map((b) => [
568
+ chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-"),
569
+ ]);
570
+ console.log(makeTable(["Token", "Balance", "USD Value"], rows));
571
+ console.log();
572
+ });
573
+ // ── direct address balance (kept for convenience) ──
574
+ wallet
575
+ .command("solana <address>")
576
+ .description("Check Solana wallet balances by address")
577
+ .option("--testnet", "Use devnet", false)
578
+ .action(async (address, opts) => {
579
+ if (!isJson())
580
+ console.log(chalk.cyan(`\n Fetching Solana balances for ${address.slice(0, 8)}...${address.slice(-4)}\n`));
581
+ const balances = await getSolanaBalances(address, opts.testnet);
582
+ if (isJson())
583
+ return printJson(jsonOk(balances));
584
+ const rows = balances.map((b) => [chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-")]);
585
+ console.log(makeTable(["Token", "Balance", "USD Value"], rows));
586
+ console.log(chalk.gray(`\n Network: ${opts.testnet ? "Devnet" : "Mainnet"}\n`));
587
+ });
588
+ wallet
589
+ .command("arbitrum <address>")
590
+ .description("Check Arbitrum wallet balances by address")
591
+ .option("--testnet", "Use Sepolia testnet", false)
592
+ .action(async (address, opts) => {
593
+ if (!isJson())
594
+ console.log(chalk.cyan(`\n Fetching Arbitrum balances for ${address.slice(0, 8)}...${address.slice(-4)}\n`));
595
+ const balances = await getEvmBalances(address, opts.testnet);
596
+ if (isJson())
597
+ return printJson(jsonOk(balances));
598
+ const rows = balances.map((b) => [chalk.white.bold(b.token), b.balance, b.usdValue || chalk.gray("-")]);
599
+ console.log(makeTable(["Token", "Balance", "USD Value"], rows));
600
+ console.log(chalk.gray(`\n Network: Arbitrum ${opts.testnet ? "Sepolia" : "One"}\n`));
601
+ });
602
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ export declare function registerWithdrawCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean): void;