@moonpay/cli 0.3.11 → 0.4.1

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.
@@ -1,15 +1,31 @@
1
1
  import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ KEY_CHAIN_MAP,
4
+ chainSchema,
5
+ deleteWalletFile,
6
+ keyChainSchema,
7
+ loadAllWallets,
8
+ loadWallet,
9
+ lockedVaultDataSchema,
10
+ saveWalletMetadata,
11
+ vaultDataSchema,
12
+ walletInfoSchema
13
+ } from "./chunk-AGDVU2O5.js";
14
+ import {
15
+ deriveAllAddresses,
16
+ deriveKeyForChain
17
+ } from "./chunk-Z33PSOPD.js";
18
+ import {
19
+ __require
20
+ } from "./chunk-EEBB5MQP.js";
2
21
 
3
22
  // src/auth.ts
4
23
  import { spawn } from "child_process";
5
24
  import * as crypto from "crypto";
6
25
  import * as fs from "fs";
7
- import * as http from "http";
8
26
  import * as os from "os";
9
27
  import * as path from "path";
10
- function escapeHtml(str) {
11
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
12
- }
28
+ import { createInterface } from "readline";
13
29
  function generateCodeVerifier() {
14
30
  return crypto.randomBytes(32).toString("base64url");
15
31
  }
@@ -20,8 +36,6 @@ var CONFIG_DIR = path.join(os.homedir(), ".config", "moonpay");
20
36
  var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
21
37
  var CREDENTIALS_PATH = path.join(CONFIG_DIR, "credentials.json");
22
38
  var LOCK_PATH = path.join(CONFIG_DIR, ".credentials.lock");
23
- var CALLBACK_PORT = 3847;
24
- var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
25
39
  var DEFAULT_CONFIG = {
26
40
  baseUrl: "https://agents.moonpay.com",
27
41
  clientId: "mooniq_zin3s5jz3olzkdfxpmbeaogv"
@@ -72,14 +86,10 @@ function acquireLock() {
72
86
  };
73
87
  }
74
88
  function getConfig() {
75
- if (!fs.existsSync(CONFIG_PATH)) {
76
- return null;
77
- }
89
+ if (!fs.existsSync(CONFIG_PATH)) return null;
78
90
  try {
79
91
  const data = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
80
- if (!data.baseUrl || !data.clientId) {
81
- return null;
82
- }
92
+ if (!data.baseUrl || !data.clientId) return null;
83
93
  return data;
84
94
  } catch {
85
95
  return null;
@@ -92,14 +102,10 @@ function getConfigOrDefault() {
92
102
  return DEFAULT_CONFIG;
93
103
  }
94
104
  function getCredentials() {
95
- if (!fs.existsSync(CREDENTIALS_PATH)) {
96
- return null;
97
- }
105
+ if (!fs.existsSync(CREDENTIALS_PATH)) return null;
98
106
  try {
99
107
  const data = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf-8"));
100
- if (!data.accessToken || !data.baseUrl) {
101
- return null;
102
- }
108
+ if (!data.accessToken || !data.baseUrl) return null;
103
109
  return data;
104
110
  } catch {
105
111
  return null;
@@ -127,86 +133,41 @@ function openBrowser(url) {
127
133
  const platform = process.platform;
128
134
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
129
135
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
130
- spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
136
+ try {
137
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
138
+ return true;
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+ function promptLine(prompt) {
144
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
145
+ return new Promise((resolve) => {
146
+ rl.question(prompt, (answer) => {
147
+ rl.close();
148
+ resolve(answer.trim());
149
+ });
150
+ });
131
151
  }
132
- async function login(config) {
152
+ var LOGIN_STATE_PATH = path.join(CONFIG_DIR, ".login-state.json");
153
+ async function login(config, options = {}) {
154
+ const callbackUrl = `${config.baseUrl}/callback`;
155
+ if (options.code) {
156
+ const stateData = loadLoginState();
157
+ if (!stateData) {
158
+ throw new Error("No pending login found. Run `mp login --no-browser` first.");
159
+ }
160
+ return exchangeCode(config, options.code, callbackUrl, stateData.codeVerifier);
161
+ }
133
162
  const state = crypto.randomUUID();
134
163
  const usePkce = !config.clientSecret;
135
164
  let codeVerifier;
136
165
  if (usePkce) {
137
166
  codeVerifier = generateCodeVerifier();
138
167
  }
139
- let resolveCallback;
140
- const codePromise = new Promise((resolve2) => {
141
- resolveCallback = resolve2;
142
- });
143
- const sockets = /* @__PURE__ */ new Set();
144
- const server = http.createServer((req, res) => {
145
- const url = new URL(req.url ?? "/", `http://localhost:${CALLBACK_PORT}`);
146
- if (url.pathname === "/callback") {
147
- const code2 = url.searchParams.get("code");
148
- const returnedState = url.searchParams.get("state");
149
- const error = url.searchParams.get("error");
150
- const errorDesc = url.searchParams.get("error_description");
151
- if (returnedState !== state) {
152
- res.writeHead(200, { "Content-Type": "text/html" });
153
- res.end(
154
- `<!DOCTYPE html><html><head><meta charset="utf-8"><title>OAuth Error</title></head><body style="font-family:system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:2rem;text-align:center;background:#fef2f2"><h1 style="color:#b91c1c;margin:0 0 1rem">OAuth Error</h1><p style="color:#991b1b;margin:0">State mismatch \u2014 possible CSRF attack. Please try logging in again.</p></body></html>`
155
- );
156
- resolveCallback("");
157
- setTimeout(() => server.close(), 2e3);
158
- } else if (error) {
159
- const safeError = escapeHtml(error);
160
- const safeDesc = escapeHtml(errorDesc ?? "Unknown error");
161
- res.writeHead(200, { "Content-Type": "text/html" });
162
- res.end(
163
- `<!DOCTYPE html><html><head><meta charset="utf-8"><title>OAuth Error</title></head><body style="font-family:system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:2rem;text-align:center;background:#fef2f2"><h1 style="color:#b91c1c;margin:0 0 1rem">OAuth Error</h1><p style="color:#991b1b;margin:0">${safeError}: ${safeDesc}</p></body></html>`
164
- );
165
- resolveCallback("");
166
- setTimeout(() => server.close(), 2e3);
167
- } else if (code2) {
168
- res.writeHead(200, { "Content-Type": "text/html" });
169
- res.end(
170
- `<!DOCTYPE html><html><head><meta charset="utf-8"><title>MoonPay Agents</title></head><body style="font-family:system-ui,-apple-system,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:2rem;text-align:center;background:#0a0a0a;color:#fafafa"><img src="https://agents.moonpay.com/logos/moonpay.jpg" alt="MoonPay" style="width:64px;height:64px;border-radius:16px;margin-bottom:1.5rem" /><h1 style="margin:0 0 0.5rem;font-size:1.5rem;font-weight:600">Your agent has money now.</h1><p style="color:#a1a1aa;margin:0;font-size:1rem">You can close this tab and return to the terminal.</p></body></html>`
171
- );
172
- resolveCallback(code2);
173
- } else {
174
- res.writeHead(200, { "Content-Type": "text/html" });
175
- res.end(
176
- `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Error</title></head><body style="font-family:system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:2rem;text-align:center;background:#fef2f2"><h1 style="color:#b91c1c;margin:0 0 1rem">Error</h1><p style="color:#991b1b;margin:0">No authorization code received.</p></body></html>`
177
- );
178
- resolveCallback("");
179
- setTimeout(() => server.close(), 2e3);
180
- }
181
- } else {
182
- res.writeHead(204);
183
- res.end();
184
- }
185
- });
186
- await new Promise((resolve2, reject) => {
187
- server.on("error", (err) => {
188
- if (err.code === "EADDRINUSE") {
189
- reject(
190
- new Error(
191
- `Port ${CALLBACK_PORT} is in use. Close the other process or run: lsof -i :${CALLBACK_PORT}`
192
- )
193
- );
194
- } else {
195
- reject(err);
196
- }
197
- });
198
- server.on("connection", (socket) => {
199
- sockets.add(socket);
200
- socket.on("close", () => sockets.delete(socket));
201
- });
202
- server.listen(CALLBACK_PORT, "127.0.0.1", () => resolve2());
203
- });
204
- console.log(
205
- `Waiting for callback at http://localhost:${CALLBACK_PORT}/callback`
206
- );
207
168
  const authorizeParams = new URLSearchParams({
208
169
  client_id: config.clientId,
209
- redirect_uri: CALLBACK_URL,
170
+ redirect_uri: callbackUrl,
210
171
  response_type: "code",
211
172
  scope: "profile email",
212
173
  state
@@ -216,23 +177,48 @@ async function login(config) {
216
177
  authorizeParams.set("code_challenge_method", "S256");
217
178
  }
218
179
  const authorizeUrl = `${config.baseUrl}/authorize?${authorizeParams.toString()}`;
219
- console.log("Opening browser for authorization...");
220
- console.log("(Make sure you're logged in to MoonPay in your browser)\n");
221
- openBrowser(authorizeUrl);
222
- const code = await codePromise;
180
+ saveLoginState({ state, codeVerifier: codeVerifier ?? null });
181
+ const opened = options.noBrowser ? false : openBrowser(authorizeUrl);
182
+ if (!opened) {
183
+ console.log("Open this URL in your browser to log in:\n");
184
+ console.log(` ${authorizeUrl}
185
+ `);
186
+ }
187
+ if (options.noBrowser) {
188
+ console.log("Then run: mp login --code <paste-code-here>\n");
189
+ process.exit(0);
190
+ }
191
+ const code = await promptLine("Paste code: ");
223
192
  if (!code) {
224
- throw new Error("No authorization code received");
193
+ throw new Error("No authorization code provided.");
194
+ }
195
+ return exchangeCode(config, code, callbackUrl, codeVerifier ?? null);
196
+ }
197
+ function saveLoginState(state) {
198
+ ensureConfigDir();
199
+ atomicWriteFileSync(LOGIN_STATE_PATH, JSON.stringify(state), 384);
200
+ }
201
+ function loadLoginState() {
202
+ if (!fs.existsSync(LOGIN_STATE_PATH)) return null;
203
+ try {
204
+ const data = JSON.parse(fs.readFileSync(LOGIN_STATE_PATH, "utf-8"));
205
+ fs.unlinkSync(LOGIN_STATE_PATH);
206
+ return data;
207
+ } catch {
208
+ return null;
225
209
  }
210
+ }
211
+ async function exchangeCode(config, code, callbackUrl, codeVerifier) {
226
212
  const tokenParams = {
227
213
  grant_type: "authorization_code",
228
214
  code,
229
215
  client_id: config.clientId,
230
- redirect_uri: CALLBACK_URL
216
+ redirect_uri: callbackUrl
231
217
  };
232
218
  if (config.clientSecret) {
233
219
  tokenParams.client_secret = config.clientSecret;
234
220
  }
235
- if (usePkce && codeVerifier) {
221
+ if (codeVerifier) {
236
222
  tokenParams.code_verifier = codeVerifier;
237
223
  }
238
224
  const tokenResponse = await fetch(`${config.baseUrl}/api/oauth/token`, {
@@ -254,14 +240,6 @@ async function login(config) {
254
240
  baseUrl: config.baseUrl
255
241
  };
256
242
  saveCredentials(creds);
257
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
258
- server.close();
259
- for (const socket of sockets) {
260
- if ("destroy" in socket && typeof socket.destroy === "function") {
261
- socket.destroy();
262
- }
263
- }
264
- sockets.clear();
265
243
  return creds;
266
244
  }
267
245
  async function refreshCredentials(creds, config) {
@@ -1692,8 +1670,11 @@ var schemas_default = [
1692
1670
  ];
1693
1671
 
1694
1672
  // src/tools/wallet/create/tool.ts
1695
- import { Keypair } from "@solana/web3.js";
1696
- import bs58 from "bs58";
1673
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1674
+ import { join as join3 } from "path";
1675
+ import { homedir as homedir3 } from "os";
1676
+ import { generateMnemonic } from "@scure/bip39";
1677
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
1697
1678
 
1698
1679
  // src/tools/shared.ts
1699
1680
  var defineToolSchema = (config) => config;
@@ -1706,198 +1687,407 @@ var createTool = (schema, handler) => ({
1706
1687
  }
1707
1688
  });
1708
1689
 
1709
- // src/tools/wallet/server.ts
1690
+ // src/tools/wallet/vault.ts
1710
1691
  import {
1711
1692
  readFileSync as readFileSync2,
1712
- readdirSync,
1713
1693
  writeFileSync as writeFileSync2,
1714
- mkdirSync as mkdirSync2,
1715
1694
  existsSync as existsSync2,
1716
- renameSync as renameSync2
1695
+ renameSync as renameSync2,
1696
+ mkdirSync as mkdirSync2
1717
1697
  } from "fs";
1718
- import { join as join2, resolve } from "path";
1698
+ import { join as join2 } from "path";
1719
1699
  import { homedir as homedir2 } from "os";
1720
- import { randomBytes as randomBytes2 } from "crypto";
1721
-
1722
- // src/tools/wallet/models.ts
1723
- import { z } from "zod";
1724
- var walletSchema = z.object({
1725
- name: z.string(),
1726
- address: z.string(),
1727
- secretKey: z.string(),
1728
- chain: z.literal("solana"),
1729
- createdAt: z.string()
1730
- });
1731
- var walletInfoSchema = walletSchema.omit({ secretKey: true });
1732
-
1733
- // src/tools/wallet/server.ts
1700
+ import {
1701
+ randomBytes as randomBytes2,
1702
+ randomUUID as randomUUID2,
1703
+ scryptSync,
1704
+ createCipheriv,
1705
+ createDecipheriv
1706
+ } from "crypto";
1734
1707
  var CONFIG_DIR2 = join2(homedir2(), ".config", "moonpay");
1735
- var WALLETS_DIR = join2(CONFIG_DIR2, "wallets");
1736
- function validateWalletName(name) {
1737
- if (!name || /[/\\]/.test(name) || name.includes("..") || name.includes("\0")) {
1738
- throw new Error(`Invalid wallet name: "${name}"`);
1708
+ var VAULT_PATH = join2(CONFIG_DIR2, "vault.json");
1709
+ function ensureConfigDir2() {
1710
+ mkdirSync2(CONFIG_DIR2, { recursive: true, mode: 448 });
1711
+ }
1712
+ function loadVault() {
1713
+ if (!existsSync2(VAULT_PATH)) {
1714
+ const empty = { version: 1, entries: {} };
1715
+ saveVault(empty);
1716
+ return empty;
1717
+ }
1718
+ const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
1719
+ const locked = lockedVaultDataSchema.safeParse(raw);
1720
+ if (locked.success) {
1721
+ throw new Error(
1722
+ "Vault is locked. Run `mp wallet unlock` to decrypt it first."
1723
+ );
1739
1724
  }
1740
- const resolved = resolve(WALLETS_DIR, `${name}.json`);
1741
- if (!resolved.startsWith(WALLETS_DIR + "/")) {
1742
- throw new Error(`Invalid wallet name: "${name}"`);
1725
+ const result = vaultDataSchema.safeParse(raw);
1726
+ if (!result.success) {
1727
+ throw new Error("Vault file is corrupted or has an unknown format.");
1743
1728
  }
1729
+ return result.data;
1744
1730
  }
1745
- function ensureWalletsDir() {
1746
- mkdirSync2(WALLETS_DIR, { recursive: true, mode: 448 });
1731
+ function saveVault(vault) {
1732
+ ensureConfigDir2();
1733
+ const tmpPath = join2(
1734
+ CONFIG_DIR2,
1735
+ `.vault.${randomBytes2(4).toString("hex")}.tmp`
1736
+ );
1737
+ writeFileSync2(tmpPath, JSON.stringify(vault, null, 2), { mode: 384 });
1738
+ renameSync2(tmpPath, VAULT_PATH);
1739
+ }
1740
+ function isVaultLocked() {
1741
+ if (!existsSync2(VAULT_PATH)) return false;
1742
+ const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
1743
+ return lockedVaultDataSchema.safeParse(raw).success;
1744
+ }
1745
+ function addHdEntry(mnemonic) {
1746
+ const vault = loadVault();
1747
+ const id = randomUUID2();
1748
+ const entry = { id, type: "hd", mnemonic };
1749
+ vault.entries[id] = entry;
1750
+ saveVault(vault);
1751
+ return id;
1747
1752
  }
1748
- function getWalletPath(name) {
1749
- validateWalletName(name);
1750
- return join2(WALLETS_DIR, `${name}.json`);
1753
+ function addImportedEntry(chain, privateKey) {
1754
+ const vault = loadVault();
1755
+ const id = randomUUID2();
1756
+ const entry = { id, type: "imported", chain, privateKey };
1757
+ vault.entries[id] = entry;
1758
+ saveVault(vault);
1759
+ return id;
1751
1760
  }
1752
- function getWalletsDir() {
1753
- return WALLETS_DIR;
1761
+ function removeVaultEntry(id) {
1762
+ const vault = loadVault();
1763
+ delete vault.entries[id];
1764
+ saveVault(vault);
1754
1765
  }
1755
- function saveWallet(wallet) {
1756
- ensureWalletsDir();
1757
- const filePath = getWalletPath(wallet.name);
1758
- if (existsSync2(filePath)) {
1759
- throw new Error(`Wallet "${wallet.name}" already exists`);
1766
+ function resolveSigningKey(wallet, chain) {
1767
+ const vault = loadVault();
1768
+ const entry = vault.entries[wallet.vaultRef];
1769
+ if (!entry) {
1770
+ throw new Error(
1771
+ `Vault entry not found for wallet "${wallet.name}". The vault may be out of sync.`
1772
+ );
1760
1773
  }
1761
- const safeName = wallet.name.replace(/[^a-zA-Z0-9_-]/g, "_");
1774
+ const keyChain = KEY_CHAIN_MAP[chain];
1775
+ if (entry.type === "imported") {
1776
+ if (entry.chain !== keyChain) {
1777
+ throw new Error(
1778
+ `Wallet "${wallet.name}" was imported for ${entry.chain}, cannot sign for ${chain}.`
1779
+ );
1780
+ }
1781
+ return {
1782
+ privateKey: decodePrivateKey(entry.privateKey, keyChain),
1783
+ address: wallet.addresses[keyChain]
1784
+ };
1785
+ }
1786
+ const derived = deriveKeyForChain(entry.mnemonic, keyChain, wallet.account ?? 0);
1787
+ return {
1788
+ privateKey: derived.privateKey,
1789
+ address: derived.address
1790
+ };
1791
+ }
1792
+ function decodePrivateKey(key, chain) {
1793
+ if (chain === "solana") {
1794
+ const bs583 = __require("bs58");
1795
+ return bs583.default.decode(key);
1796
+ }
1797
+ return Uint8Array.from(Buffer.from(key, "hex"));
1798
+ }
1799
+ var SCRYPT_N = 2 ** 14;
1800
+ var SCRYPT_R = 8;
1801
+ var SCRYPT_P = 1;
1802
+ var KEY_LENGTH = 32;
1803
+ function deriveEncryptionKey(password, salt) {
1804
+ return scryptSync(password, salt, KEY_LENGTH, {
1805
+ N: SCRYPT_N,
1806
+ r: SCRYPT_R,
1807
+ p: SCRYPT_P
1808
+ });
1809
+ }
1810
+ function lockVault(password) {
1811
+ const vault = loadVault();
1812
+ const salt = randomBytes2(32);
1813
+ const key = deriveEncryptionKey(password, salt);
1814
+ const iv = randomBytes2(12);
1815
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
1816
+ const plaintext = JSON.stringify(vault);
1817
+ const encrypted = Buffer.concat([
1818
+ cipher.update(plaintext, "utf8"),
1819
+ cipher.final()
1820
+ ]);
1821
+ const tag = cipher.getAuthTag();
1822
+ const locked = {
1823
+ version: 1,
1824
+ locked: true,
1825
+ ciphertext: encrypted.toString("base64"),
1826
+ iv: iv.toString("base64"),
1827
+ salt: salt.toString("base64"),
1828
+ tag: tag.toString("base64")
1829
+ };
1830
+ ensureConfigDir2();
1762
1831
  const tmpPath = join2(
1763
- WALLETS_DIR,
1764
- `.${safeName}.${randomBytes2(4).toString("hex")}.tmp`
1832
+ CONFIG_DIR2,
1833
+ `.vault.${randomBytes2(4).toString("hex")}.tmp`
1765
1834
  );
1766
- writeFileSync2(tmpPath, JSON.stringify(wallet, null, 2), { mode: 384 });
1767
- renameSync2(tmpPath, filePath);
1835
+ writeFileSync2(tmpPath, JSON.stringify(locked, null, 2), { mode: 384 });
1836
+ renameSync2(tmpPath, VAULT_PATH);
1768
1837
  }
1769
- function loadWallet(nameOrAddress) {
1770
- ensureWalletsDir();
1771
- const files = readdirSync(WALLETS_DIR).filter((f) => f.endsWith(".json"));
1772
- for (const file of files) {
1773
- const raw = JSON.parse(readFileSync2(join2(WALLETS_DIR, file), "utf-8"));
1774
- const result = walletSchema.safeParse(raw);
1775
- if (!result.success) continue;
1776
- const wallet = result.data;
1777
- if (wallet.name === nameOrAddress || wallet.address === nameOrAddress)
1778
- return wallet;
1838
+ function unlockVault(password) {
1839
+ if (!existsSync2(VAULT_PATH)) {
1840
+ throw new Error("No vault found. Create a wallet first.");
1841
+ }
1842
+ const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
1843
+ const locked = lockedVaultDataSchema.safeParse(raw);
1844
+ if (!locked.success) {
1845
+ throw new Error("Vault is not locked.");
1846
+ }
1847
+ const { ciphertext, iv, salt, tag } = locked.data;
1848
+ const saltBuf = Buffer.from(salt, "base64");
1849
+ const key = deriveEncryptionKey(password, saltBuf);
1850
+ const ivBuf = Buffer.from(iv, "base64");
1851
+ const tagBuf = Buffer.from(tag, "base64");
1852
+ const ciphertextBuf = Buffer.from(ciphertext, "base64");
1853
+ const decipher = createDecipheriv("aes-256-gcm", key, ivBuf, { authTagLength: 16 });
1854
+ decipher.setAuthTag(tagBuf);
1855
+ let decrypted;
1856
+ try {
1857
+ decrypted = Buffer.concat([
1858
+ decipher.update(ciphertextBuf),
1859
+ decipher.final()
1860
+ ]);
1861
+ } catch {
1862
+ throw new Error("Wrong password.");
1779
1863
  }
1780
- throw new Error(`Wallet "${nameOrAddress}" not found`);
1864
+ const vault = vaultDataSchema.parse(JSON.parse(decrypted.toString("utf8")));
1865
+ saveVault(vault);
1781
1866
  }
1782
1867
 
1783
1868
  // src/tools/wallet/create/schema.ts
1784
- import { z as z2 } from "zod";
1869
+ import { z } from "zod";
1785
1870
  var walletCreateSchema = defineToolSchema({
1786
1871
  name: "wallet_create",
1787
- description: "Generate a new Solana keypair and store it locally",
1788
- input: z2.object({
1789
- name: z2.string().describe("Wallet name")
1872
+ description: "Create a new multi-chain HD wallet. Generates a BIP39 mnemonic seed phrase and derives addresses for Solana, Ethereum (shared by Base, Arbitrum, Polygon, Optimism, BNB, Avalanche), Bitcoin, and Tron. The mnemonic is saved to a local backup file (path returned in output) \u2014 it is never printed or returned in the response. To derive additional accounts from the same mnemonic, pass --from with an existing wallet name and --account with the desired index.",
1873
+ input: z.object({
1874
+ name: z.string().describe("Wallet name"),
1875
+ from: z.string().nullable().describe("Name of an existing HD wallet to derive from. Uses the same mnemonic but a different account index. Omit to generate a new mnemonic."),
1876
+ account: z.number().int().min(0).nullable().describe("BIP44 account index (0, 1, 2, ...). Required when using --from. Omit for new wallets (defaults to 0).")
1790
1877
  }),
1791
- output: z2.object({
1792
- name: z2.string().describe("Wallet name"),
1793
- address: z2.string().describe("Solana wallet address (base58)")
1878
+ output: z.object({
1879
+ name: z.string().describe("Wallet name"),
1880
+ account: z.number().describe("BIP44 account index used for derivation"),
1881
+ addresses: z.record(keyChainSchema, z.string()).describe("Derived addresses per chain (solana, ethereum, bitcoin, tron)"),
1882
+ backupPath: z.string().nullable().describe("Path to the mnemonic backup file (0o600 permissions). Null when derived from an existing wallet.")
1794
1883
  })
1795
1884
  });
1796
1885
 
1797
1886
  // src/tools/wallet/create/tool.ts
1798
1887
  var walletCreate = createTool(walletCreateSchema, async (params) => {
1799
- const keypair = Keypair.generate();
1800
- const wallet = {
1801
- name: params.name,
1802
- address: keypair.publicKey.toBase58(),
1803
- secretKey: bs58.encode(keypair.secretKey),
1804
- chain: "solana",
1888
+ if (params.from) {
1889
+ if (params.account === null) {
1890
+ throw new Error("--account is required when using --from.");
1891
+ }
1892
+ return deriveFromExisting(params.name, params.from, params.account);
1893
+ }
1894
+ return createNew(params.name);
1895
+ });
1896
+ function createNew(name) {
1897
+ const mnemonic = generateMnemonic(english, 128);
1898
+ const addresses = deriveAllAddresses(mnemonic);
1899
+ const vaultRef = addHdEntry(mnemonic);
1900
+ const metadata = {
1901
+ name,
1902
+ type: "hd",
1903
+ vaultRef,
1904
+ account: 0,
1905
+ addresses,
1805
1906
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1806
1907
  };
1807
- saveWallet(wallet);
1808
- return { name: wallet.name, address: wallet.address };
1809
- });
1908
+ saveWalletMetadata(metadata);
1909
+ const backupDir = join3(homedir3(), ".config", "moonpay", "backups");
1910
+ mkdirSync3(backupDir, { recursive: true, mode: 448 });
1911
+ const backupPath = join3(backupDir, `${name}.mnemonic`);
1912
+ writeFileSync3(backupPath, mnemonic + "\n", { mode: 384 });
1913
+ return { name, account: 0, addresses, backupPath };
1914
+ }
1915
+ function deriveFromExisting(name, from, account) {
1916
+ const source = loadWallet(from);
1917
+ if (source.type !== "hd") {
1918
+ throw new Error(
1919
+ `Wallet "${from}" is an imported single-key wallet. Only HD wallets support account derivation.`
1920
+ );
1921
+ }
1922
+ const vault = loadVault();
1923
+ const entry = vault.entries[source.vaultRef];
1924
+ if (!entry || entry.type !== "hd") {
1925
+ throw new Error(`Vault entry for wallet "${from}" not found or is not HD.`);
1926
+ }
1927
+ const addresses = deriveAllAddresses(entry.mnemonic, account);
1928
+ const metadata = {
1929
+ name,
1930
+ type: "hd",
1931
+ vaultRef: source.vaultRef,
1932
+ account,
1933
+ addresses,
1934
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1935
+ };
1936
+ saveWalletMetadata(metadata);
1937
+ return { name, account, addresses, backupPath: null };
1938
+ }
1810
1939
 
1811
1940
  // src/tools/wallet/import/tool.ts
1812
- import { Keypair as Keypair2 } from "@solana/web3.js";
1813
- import bs582 from "bs58";
1814
- import { createInterface } from "readline";
1941
+ import { validateMnemonic } from "@scure/bip39";
1942
+ import { wordlist as english2 } from "@scure/bip39/wordlists/english";
1943
+ import { Keypair } from "@solana/web3.js";
1944
+ import bs58 from "bs58";
1945
+ import { createInterface as createInterface2 } from "readline";
1815
1946
 
1816
1947
  // src/tools/wallet/import/schema.ts
1817
- import { z as z3 } from "zod";
1948
+ import { z as z2 } from "zod";
1818
1949
  var walletImportSchema = defineToolSchema({
1819
1950
  name: "wallet_import",
1820
- description: "Import a Solana wallet from a base58 private key",
1821
- input: z3.object({
1822
- name: z3.string().describe("Wallet name"),
1823
- key: z3.string().nullable().describe("Base58-encoded private key (prompted interactively if not provided)")
1951
+ description: "Import a wallet from a BIP39 mnemonic (HD, all chains) or a single private key (one chain). Provide either --mnemonic or --key, not both.",
1952
+ input: z2.object({
1953
+ name: z2.string().describe("Wallet name"),
1954
+ mnemonic: z2.string().nullable().describe("BIP39 mnemonic seed phrase (for HD wallet import)"),
1955
+ key: z2.string().nullable().describe(
1956
+ "Private key: base58 for Solana, hex for EVM/Bitcoin (for single-chain import)"
1957
+ ),
1958
+ chain: chainSchema.nullable().describe(
1959
+ "Chain for single-key import: solana, ethereum, base, arbitrum, or bitcoin (default solana)"
1960
+ )
1824
1961
  }),
1825
- output: z3.object({
1826
- name: z3.string().describe("Wallet name"),
1827
- address: z3.string().describe("Solana wallet address (base58)")
1962
+ output: z2.object({
1963
+ name: z2.string().describe("Wallet name"),
1964
+ type: z2.enum(["hd", "imported"]).describe("Wallet type"),
1965
+ addresses: z2.record(keyChainSchema, z2.string()).describe("Wallet addresses per chain")
1828
1966
  })
1829
1967
  });
1830
1968
 
1831
1969
  // src/tools/wallet/import/tool.ts
1832
1970
  async function promptSecret(prompt) {
1833
- const rl = createInterface({ input: process.stdin, output: process.stderr });
1834
- return new Promise((resolve2) => {
1971
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
1972
+ return new Promise((resolve) => {
1835
1973
  rl.question(prompt, (answer) => {
1836
1974
  rl.close();
1837
- resolve2(answer.trim());
1975
+ resolve(answer.trim());
1838
1976
  });
1839
1977
  });
1840
1978
  }
1841
1979
  var walletImport = createTool(walletImportSchema, async (params) => {
1842
- const secretKeyBase58 = params.key !== null ? params.key : await promptSecret("Enter private key (base58): ");
1843
- let keypair;
1844
- try {
1845
- const secretKeyBytes = bs582.decode(secretKeyBase58);
1846
- keypair = Keypair2.fromSecretKey(secretKeyBytes);
1847
- } catch {
1848
- throw new Error(
1849
- "Invalid private key. Expected a base58-encoded Solana secret key."
1850
- );
1980
+ if (params.mnemonic && params.key) {
1981
+ throw new Error("Provide either --mnemonic or --key, not both.");
1851
1982
  }
1852
- const wallet = {
1853
- name: params.name,
1854
- address: keypair.publicKey.toBase58(),
1855
- secretKey: secretKeyBase58,
1856
- chain: "solana",
1983
+ if (params.mnemonic) {
1984
+ return importMnemonic(params.name, params.mnemonic);
1985
+ }
1986
+ const chain = params.chain ? KEY_CHAIN_MAP[params.chain] : "solana";
1987
+ const key = params.key !== null ? params.key : await promptSecret("Enter private key: ");
1988
+ return importKey(params.name, chain, key);
1989
+ });
1990
+ async function importMnemonic(name, mnemonic) {
1991
+ const trimmed = mnemonic.trim().toLowerCase();
1992
+ if (!validateMnemonic(trimmed, english2)) {
1993
+ throw new Error("Invalid BIP39 mnemonic.");
1994
+ }
1995
+ const addresses = deriveAllAddresses(trimmed);
1996
+ const vaultRef = addHdEntry(trimmed);
1997
+ const metadata = {
1998
+ name,
1999
+ type: "hd",
2000
+ vaultRef,
2001
+ account: 0,
2002
+ addresses,
1857
2003
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1858
2004
  };
1859
- saveWallet(wallet);
1860
- return { name: wallet.name, address: wallet.address };
1861
- });
1862
-
1863
- // src/tools/wallet/list/tool.ts
1864
- import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
1865
- import { join as join3 } from "path";
2005
+ saveWalletMetadata(metadata);
2006
+ return { name, type: "hd", addresses };
2007
+ }
2008
+ async function importKey(name, chain, key) {
2009
+ let address;
2010
+ if (chain === "solana") {
2011
+ try {
2012
+ const secretKeyBytes = bs58.decode(key);
2013
+ const keypair = Keypair.fromSecretKey(secretKeyBytes);
2014
+ address = keypair.publicKey.toBase58();
2015
+ } catch {
2016
+ throw new Error(
2017
+ "Invalid private key. Expected a base58-encoded Solana secret key."
2018
+ );
2019
+ }
2020
+ } else if (chain === "ethereum") {
2021
+ const cleanKey = key.startsWith("0x") ? key.slice(2) : key;
2022
+ if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
2023
+ throw new Error(
2024
+ "Invalid private key. Expected a 64-character hex string for EVM."
2025
+ );
2026
+ }
2027
+ const { deriveKeyForChain: deriveKeyForChain2 } = await import("./chains-R754DQM5.js");
2028
+ const { HDKey } = await import("@scure/bip32");
2029
+ const ecc2 = await import("tiny-secp256k1");
2030
+ const { createHash: createHash3 } = await import("crypto");
2031
+ const privKeyBytes = Buffer.from(cleanKey, "hex");
2032
+ const pubKey = ecc2.pointFromScalar(privKeyBytes);
2033
+ if (!pubKey) throw new Error("Invalid private key.");
2034
+ const uncompressed = ecc2.pointCompress(pubKey, false);
2035
+ const hash = createHash3("sha3-256").update(uncompressed.slice(1)).digest();
2036
+ address = "0x" + hash.slice(-20).toString("hex");
2037
+ key = cleanKey;
2038
+ } else {
2039
+ const cleanKey = key.startsWith("0x") ? key.slice(2) : key;
2040
+ if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
2041
+ throw new Error(
2042
+ "Invalid private key. Expected a 64-character hex string for Bitcoin."
2043
+ );
2044
+ }
2045
+ const bitcoin = await import("bitcoinjs-lib");
2046
+ const ECPairFactory = (await import("ecpair")).default;
2047
+ const ecc2 = await import("tiny-secp256k1");
2048
+ const ECPair = ECPairFactory(ecc2);
2049
+ const keyPair = ECPair.fromPrivateKey(Buffer.from(cleanKey, "hex"));
2050
+ const { address: btcAddress } = bitcoin.payments.p2wpkh({
2051
+ pubkey: Buffer.from(keyPair.publicKey)
2052
+ });
2053
+ if (!btcAddress) throw new Error("Failed to derive Bitcoin address.");
2054
+ address = btcAddress;
2055
+ key = cleanKey;
2056
+ }
2057
+ const vaultRef = addImportedEntry(chain, key);
2058
+ const metadata = {
2059
+ name,
2060
+ type: "imported",
2061
+ vaultRef,
2062
+ account: 0,
2063
+ addresses: { [chain]: address },
2064
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2065
+ };
2066
+ saveWalletMetadata(metadata);
2067
+ return { name, type: "imported", addresses: { [chain]: address } };
2068
+ }
1866
2069
 
1867
2070
  // src/tools/wallet/list/schema.ts
1868
- import { z as z4 } from "zod";
2071
+ import { z as z3 } from "zod";
1869
2072
  var walletListSchema = defineToolSchema({
1870
2073
  name: "wallet_list",
1871
2074
  description: "List all local wallets",
1872
- input: z4.object({}),
1873
- output: z4.array(walletInfoSchema)
2075
+ input: z3.object({}),
2076
+ output: z3.array(walletInfoSchema)
1874
2077
  });
1875
2078
 
1876
2079
  // src/tools/wallet/list/tool.ts
1877
2080
  var walletList = createTool(walletListSchema, async () => {
1878
- ensureWalletsDir();
1879
- const dir = getWalletsDir();
1880
- const files = readdirSync2(dir).filter(
1881
- (f) => f.endsWith(".json") && !f.startsWith(".")
1882
- );
1883
- return files.map((f) => {
1884
- const wallet = JSON.parse(readFileSync3(join3(dir, f), "utf-8"));
1885
- return {
1886
- name: wallet.name,
1887
- address: wallet.address,
1888
- chain: wallet.chain,
1889
- createdAt: wallet.createdAt
1890
- };
1891
- });
2081
+ return loadAllWallets();
1892
2082
  });
1893
2083
 
1894
2084
  // src/tools/wallet/retrieve/schema.ts
1895
- import { z as z5 } from "zod";
2085
+ import { z as z4 } from "zod";
1896
2086
  var walletRetrieveSchema = defineToolSchema({
1897
2087
  name: "wallet_retrieve",
1898
2088
  description: "Get details of a specific wallet",
1899
- input: z5.object({
1900
- wallet: z5.string().describe("Wallet name or address")
2089
+ input: z4.object({
2090
+ wallet: z4.string().describe("Wallet name or address")
1901
2091
  }),
1902
2092
  output: walletInfoSchema
1903
2093
  });
@@ -1906,31 +2096,22 @@ var walletRetrieveSchema = defineToolSchema({
1906
2096
  var walletRetrieve = createTool(
1907
2097
  walletRetrieveSchema,
1908
2098
  async (params) => {
1909
- const wallet = loadWallet(params.wallet);
1910
- return {
1911
- name: wallet.name,
1912
- address: wallet.address,
1913
- chain: wallet.chain,
1914
- createdAt: wallet.createdAt
1915
- };
2099
+ return loadWallet(params.wallet);
1916
2100
  }
1917
2101
  );
1918
2102
 
1919
- // src/tools/wallet/delete/tool.ts
1920
- import { unlinkSync as unlinkSync2 } from "fs";
1921
-
1922
2103
  // src/tools/wallet/delete/schema.ts
1923
- import { z as z6 } from "zod";
2104
+ import { z as z5 } from "zod";
1924
2105
  var walletDeleteSchema = defineToolSchema({
1925
2106
  name: "wallet_delete",
1926
2107
  description: "Permanently delete a local wallet. This removes the private key file and cannot be undone.",
1927
- input: z6.object({
1928
- wallet: z6.string().describe("Name or address of the wallet to delete"),
1929
- confirm: z6.boolean().describe("Must be true to confirm deletion")
2108
+ input: z5.object({
2109
+ wallet: z5.string().describe("Name or address of the wallet to delete"),
2110
+ confirm: z5.boolean().describe("Must be true to confirm deletion")
1930
2111
  }),
1931
- output: z6.object({
1932
- name: z6.string().describe("Name of the deleted wallet"),
1933
- deleted: z6.literal(true)
2112
+ output: z5.object({
2113
+ name: z5.string().describe("Name of the deleted wallet"),
2114
+ deleted: z5.literal(true)
1934
2115
  })
1935
2116
  });
1936
2117
 
@@ -1941,26 +2122,29 @@ var walletDelete = createTool(walletDeleteSchema, async (params) => {
1941
2122
  "Deletion not confirmed. Pass --confirm to permanently delete this wallet."
1942
2123
  );
1943
2124
  }
1944
- const wallet = await walletRetrieve.handler({ wallet: params.wallet });
1945
- unlinkSync2(getWalletPath(wallet.name));
2125
+ const wallet = loadWallet(params.wallet);
2126
+ removeVaultEntry(wallet.vaultRef);
2127
+ deleteWalletFile(wallet.name);
1946
2128
  return { name: wallet.name, deleted: true };
1947
2129
  });
1948
2130
 
1949
2131
  // src/tools/transaction/sign/tool.ts
1950
- import { Keypair as Keypair3, VersionedTransaction } from "@solana/web3.js";
1951
- import bs583 from "bs58";
2132
+ import { Keypair as Keypair2, VersionedTransaction } from "@solana/web3.js";
1952
2133
 
1953
2134
  // src/tools/transaction/sign/schema.ts
1954
- import { z as z7 } from "zod";
2135
+ import { z as z6 } from "zod";
1955
2136
  var transactionSignSchema = defineToolSchema({
1956
2137
  name: "transaction_sign",
1957
- description: "Sign a base64-encoded Solana transaction with a local wallet",
1958
- input: z7.object({
1959
- wallet: z7.string().describe("Wallet address"),
1960
- transaction: z7.string().describe("Base64-encoded unsigned transaction")
2138
+ description: "Sign a transaction with a local wallet. Supports Solana (VersionedTransaction), EVM (RLP-encoded), and Bitcoin (PSBT) transactions.",
2139
+ input: z6.object({
2140
+ wallet: z6.string().describe("Wallet name or address"),
2141
+ chain: chainSchema.describe(
2142
+ "Chain: solana, ethereum, base, arbitrum, or bitcoin"
2143
+ ),
2144
+ transaction: z6.string().describe("Base64-encoded unsigned transaction")
1961
2145
  }),
1962
- output: z7.object({
1963
- transaction: z7.string().describe("Base64-encoded signed transaction")
2146
+ output: z6.object({
2147
+ transaction: z6.string().describe("Base64-encoded signed transaction")
1964
2148
  })
1965
2149
  });
1966
2150
 
@@ -1968,47 +2152,274 @@ var transactionSignSchema = defineToolSchema({
1968
2152
  var transactionSign = createTool(
1969
2153
  transactionSignSchema,
1970
2154
  async (params) => {
1971
- const storedWallet = loadWallet(params.wallet);
2155
+ const wallet = loadWallet(params.wallet);
2156
+ const chain = params.chain;
2157
+ const keyChain = KEY_CHAIN_MAP[chain];
2158
+ const { privateKey } = resolveSigningKey(wallet, chain);
1972
2159
  const txBytes = Buffer.from(params.transaction.trim(), "base64");
1973
- const tx = VersionedTransaction.deserialize(txBytes);
1974
- const secretKeyBytes = bs583.decode(storedWallet.secretKey);
1975
- const keypair = Keypair3.fromSecretKey(secretKeyBytes);
1976
- tx.sign([keypair]);
1977
- return {
1978
- transaction: Buffer.from(tx.serialize()).toString("base64")
1979
- };
2160
+ switch (keyChain) {
2161
+ case "solana":
2162
+ return signSolana(privateKey, txBytes);
2163
+ case "ethereum":
2164
+ return signEvm(privateKey, txBytes);
2165
+ case "bitcoin":
2166
+ return signBitcoin(privateKey, txBytes);
2167
+ case "tron":
2168
+ return signEvm(privateKey, txBytes);
2169
+ }
1980
2170
  }
1981
2171
  );
2172
+ function signSolana(privateKey, txBytes) {
2173
+ const tx = VersionedTransaction.deserialize(txBytes);
2174
+ const keypair = Keypair2.fromSecretKey(privateKey);
2175
+ tx.sign([keypair]);
2176
+ return { transaction: Buffer.from(tx.serialize()).toString("base64") };
2177
+ }
2178
+ async function signEvm(privateKey, txBytes) {
2179
+ const { createHash: createHash3 } = await import("crypto");
2180
+ const ecc2 = await import("tiny-secp256k1");
2181
+ const hash = createHash3("sha3-256").update(txBytes).digest();
2182
+ const sig = ecc2.sign(hash, privateKey);
2183
+ if (!sig) throw new Error("EVM signing failed");
2184
+ const signed = Buffer.concat([txBytes, Buffer.from(sig)]);
2185
+ return { transaction: signed.toString("base64") };
2186
+ }
2187
+ async function signBitcoin(privateKey, txBytes) {
2188
+ const bitcoin = await import("bitcoinjs-lib");
2189
+ const ECPairFactory = (await import("ecpair")).default;
2190
+ const ecc2 = await import("tiny-secp256k1");
2191
+ const ECPair = ECPairFactory(ecc2);
2192
+ const keyPair = ECPair.fromPrivateKey(Buffer.from(privateKey));
2193
+ const psbt = bitcoin.Psbt.fromBase64(txBytes.toString("base64"));
2194
+ psbt.signAllInputs(keyPair);
2195
+ return { transaction: psbt.toBase64() };
2196
+ }
1982
2197
 
1983
2198
  // src/tools/message/sign/tool.ts
1984
- import { Keypair as Keypair4 } from "@solana/web3.js";
1985
- import bs584 from "bs58";
2199
+ import { createHash as createHash2 } from "crypto";
2200
+ import { Keypair as Keypair3 } from "@solana/web3.js";
2201
+ import bs582 from "bs58";
1986
2202
  import nacl from "tweetnacl";
2203
+ import * as ecc from "tiny-secp256k1";
1987
2204
 
1988
2205
  // src/tools/message/sign/schema.ts
1989
- import { z as z8 } from "zod";
2206
+ import { z as z7 } from "zod";
1990
2207
  var messageSignSchema = defineToolSchema({
1991
2208
  name: "message_sign",
1992
- description: "Sign a message with a local wallet. Produces a base58-encoded ed25519 signature compatible with Solana's signMessage.",
1993
- input: z8.object({
1994
- wallet: z8.string().describe("Wallet address to sign with"),
1995
- message: z8.string().describe("Message text to sign")
2209
+ description: "Sign a message with a local wallet. Supports Solana (ed25519), EVM (EIP-191 personal sign), and Bitcoin (secp256k1 ECDSA).",
2210
+ input: z7.object({
2211
+ wallet: z7.string().describe("Wallet address or name to sign with"),
2212
+ chain: chainSchema.describe(
2213
+ "Chain: solana, ethereum, base, arbitrum, or bitcoin"
2214
+ ),
2215
+ message: z7.string().describe("Message text to sign")
1996
2216
  }),
1997
- output: z8.object({
1998
- signature: z8.string().describe("Base58-encoded ed25519 signature")
2217
+ output: z7.object({
2218
+ signature: z7.string().describe(
2219
+ "Signature: base58 for Solana, hex (0x-prefixed) for EVM/Bitcoin"
2220
+ )
1999
2221
  })
2000
2222
  });
2001
2223
 
2002
2224
  // src/tools/message/sign/tool.ts
2003
2225
  var messageSign = createTool(messageSignSchema, async (params) => {
2004
- const storedWallet = loadWallet(params.wallet);
2005
- const secretKeyBytes = bs584.decode(storedWallet.secretKey);
2006
- const keypair = Keypair4.fromSecretKey(secretKeyBytes);
2226
+ const wallet = loadWallet(params.wallet);
2227
+ const chain = params.chain;
2228
+ const keyChain = KEY_CHAIN_MAP[chain];
2229
+ const { privateKey } = resolveSigningKey(wallet, chain);
2007
2230
  const messageBytes = Buffer.from(params.message, "utf8");
2231
+ switch (keyChain) {
2232
+ case "solana":
2233
+ return signSolana2(privateKey, messageBytes);
2234
+ case "ethereum":
2235
+ return signEvm2(privateKey, messageBytes);
2236
+ case "bitcoin":
2237
+ return signBitcoin2(privateKey, messageBytes);
2238
+ case "tron":
2239
+ return signEvm2(privateKey, messageBytes);
2240
+ }
2241
+ });
2242
+ function signSolana2(privateKey, messageBytes) {
2243
+ const keypair = Keypair3.fromSecretKey(privateKey);
2008
2244
  const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
2009
- return { signature: bs584.encode(signatureBytes) };
2245
+ return { signature: bs582.encode(signatureBytes) };
2246
+ }
2247
+ function signEvm2(privateKey, messageBytes) {
2248
+ const prefix = Buffer.from(
2249
+ `Ethereum Signed Message:
2250
+ ${messageBytes.length}`,
2251
+ "utf8"
2252
+ );
2253
+ const prefixed = Buffer.concat([prefix, messageBytes]);
2254
+ const hash = createHash2("sha3-256").update(prefixed).digest();
2255
+ const sig = ecc.sign(hash, privateKey);
2256
+ if (!sig) throw new Error("EVM message signing failed");
2257
+ return { signature: "0x" + Buffer.from(sig).toString("hex") };
2258
+ }
2259
+ function signBitcoin2(privateKey, messageBytes) {
2260
+ const hash1 = createHash2("sha256").update(messageBytes).digest();
2261
+ const hash2 = createHash2("sha256").update(hash1).digest();
2262
+ const sig = ecc.sign(hash2, privateKey);
2263
+ if (!sig) throw new Error("Bitcoin message signing failed");
2264
+ return { signature: "0x" + Buffer.from(sig).toString("hex") };
2265
+ }
2266
+
2267
+ // src/tools/wallet/lock/tool.ts
2268
+ import { createInterface as createInterface3 } from "readline";
2269
+
2270
+ // src/tools/wallet/lock/schema.ts
2271
+ import { z as z8 } from "zod";
2272
+ var walletLockSchema = defineToolSchema({
2273
+ name: "wallet_lock",
2274
+ description: "Encrypt the vault with a password. All signing operations will fail until `wallet unlock` is called. Password is prompted interactively.",
2275
+ input: z8.object({}),
2276
+ output: z8.object({
2277
+ locked: z8.literal(true)
2278
+ })
2010
2279
  });
2011
2280
 
2281
+ // src/tools/wallet/lock/tool.ts
2282
+ async function promptPassword(prompt) {
2283
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
2284
+ return new Promise((resolve) => {
2285
+ rl.question(prompt, (answer) => {
2286
+ rl.close();
2287
+ resolve(answer);
2288
+ });
2289
+ });
2290
+ }
2291
+ var walletLock = createTool(walletLockSchema, async () => {
2292
+ if (isVaultLocked()) {
2293
+ throw new Error("Vault is already locked.");
2294
+ }
2295
+ const password = await promptPassword("Enter password to encrypt vault: ");
2296
+ if (!password) {
2297
+ throw new Error("Password cannot be empty.");
2298
+ }
2299
+ const confirm = await promptPassword("Confirm password: ");
2300
+ if (password !== confirm) {
2301
+ throw new Error("Passwords do not match.");
2302
+ }
2303
+ lockVault(password);
2304
+ return { locked: true };
2305
+ });
2306
+
2307
+ // src/tools/wallet/unlock/tool.ts
2308
+ import { createInterface as createInterface4 } from "readline";
2309
+
2310
+ // src/tools/wallet/unlock/schema.ts
2311
+ import { z as z9 } from "zod";
2312
+ var walletUnlockSchema = defineToolSchema({
2313
+ name: "wallet_unlock",
2314
+ description: "Decrypt the vault with a password. Restores access to all signing operations. Password is prompted interactively.",
2315
+ input: z9.object({}),
2316
+ output: z9.object({
2317
+ unlocked: z9.literal(true)
2318
+ })
2319
+ });
2320
+
2321
+ // src/tools/wallet/unlock/tool.ts
2322
+ async function promptPassword2(prompt) {
2323
+ const rl = createInterface4({ input: process.stdin, output: process.stderr });
2324
+ return new Promise((resolve) => {
2325
+ rl.question(prompt, (answer) => {
2326
+ rl.close();
2327
+ resolve(answer);
2328
+ });
2329
+ });
2330
+ }
2331
+ var walletUnlock = createTool(walletUnlockSchema, async () => {
2332
+ if (!isVaultLocked()) {
2333
+ throw new Error("Vault is not locked.");
2334
+ }
2335
+ const password = await promptPassword2("Enter password to decrypt vault: ");
2336
+ unlockVault(password);
2337
+ return { unlocked: true };
2338
+ });
2339
+
2340
+ // src/tools/bitcoin/balance/tool.ts
2341
+ import https from "https";
2342
+
2343
+ // src/tools/bitcoin/balance/schema.ts
2344
+ import { z as z10 } from "zod";
2345
+ var bitcoinBalanceRetrieveSchema = defineToolSchema({
2346
+ name: "bitcoin_balance_retrieve",
2347
+ description: "Get the BTC balance of a Bitcoin address. Returns confirmed and unconfirmed balances in BTC and satoshis.",
2348
+ input: z10.object({
2349
+ wallet: z10.string().describe("Bitcoin address (bc1...) or wallet name")
2350
+ }),
2351
+ output: z10.object({
2352
+ address: z10.string().describe("Bitcoin address"),
2353
+ confirmed: z10.object({
2354
+ btc: z10.string().describe("Confirmed balance in BTC"),
2355
+ sats: z10.number().describe("Confirmed balance in satoshis")
2356
+ }),
2357
+ unconfirmed: z10.object({
2358
+ btc: z10.string().describe("Unconfirmed (pending) balance in BTC"),
2359
+ sats: z10.number().describe("Unconfirmed (pending) balance in satoshis")
2360
+ }),
2361
+ total: z10.object({
2362
+ btc: z10.string().describe("Total balance in BTC"),
2363
+ sats: z10.number().describe("Total balance in satoshis")
2364
+ })
2365
+ })
2366
+ });
2367
+
2368
+ // src/tools/bitcoin/balance/tool.ts
2369
+ var SATS_PER_BTC = 1e8;
2370
+ function satsToBtc(sats) {
2371
+ return (sats / SATS_PER_BTC).toFixed(8);
2372
+ }
2373
+ function fetchJson(url) {
2374
+ return new Promise((resolve, reject) => {
2375
+ https.get(url, { headers: { "User-Agent": "moonpay-cli" } }, (res) => {
2376
+ if (res.statusCode !== 200) {
2377
+ reject(new Error(`HTTP ${res.statusCode} from ${url}`));
2378
+ res.resume();
2379
+ return;
2380
+ }
2381
+ let data = "";
2382
+ res.on("data", (chunk) => data += chunk);
2383
+ res.on("end", () => {
2384
+ try {
2385
+ resolve(JSON.parse(data));
2386
+ } catch {
2387
+ reject(new Error("Invalid JSON response"));
2388
+ }
2389
+ });
2390
+ }).on("error", reject);
2391
+ });
2392
+ }
2393
+ var bitcoinBalanceRetrieve = createTool(
2394
+ bitcoinBalanceRetrieveSchema,
2395
+ async (params) => {
2396
+ let address = params.wallet;
2397
+ if (!address.startsWith("bc1") && !address.startsWith("1") && !address.startsWith("3")) {
2398
+ const { loadWallet: loadWallet2 } = await import("./server-IUOCZFT7.js");
2399
+ const wallet = loadWallet2(address);
2400
+ const btcAddress = wallet.addresses.bitcoin;
2401
+ if (!btcAddress) {
2402
+ throw new Error(
2403
+ `Wallet "${address}" has no Bitcoin address. It may be an imported single-chain wallet.`
2404
+ );
2405
+ }
2406
+ address = btcAddress;
2407
+ }
2408
+ const data = await fetchJson(
2409
+ `https://mempool.space/api/address/${address}`
2410
+ );
2411
+ const confirmedSats = data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum;
2412
+ const unconfirmedSats = data.mempool_stats.funded_txo_sum - data.mempool_stats.spent_txo_sum;
2413
+ const totalSats = confirmedSats + unconfirmedSats;
2414
+ return {
2415
+ address,
2416
+ confirmed: { btc: satsToBtc(confirmedSats), sats: confirmedSats },
2417
+ unconfirmed: { btc: satsToBtc(unconfirmedSats), sats: unconfirmedSats },
2418
+ total: { btc: satsToBtc(totalSats), sats: totalSats }
2419
+ };
2420
+ }
2421
+ );
2422
+
2012
2423
  export {
2013
2424
  getConfigOrDefault,
2014
2425
  clearCredentials,
@@ -2018,13 +2429,16 @@ export {
2018
2429
  schemas_default,
2019
2430
  defineToolSchema,
2020
2431
  createTool,
2021
- loadWallet,
2432
+ resolveSigningKey,
2022
2433
  walletCreate,
2023
2434
  walletImport,
2024
2435
  walletList,
2025
2436
  walletRetrieve,
2026
2437
  walletDelete,
2027
2438
  transactionSign,
2028
- messageSign
2439
+ messageSign,
2440
+ walletLock,
2441
+ walletUnlock,
2442
+ bitcoinBalanceRetrieve
2029
2443
  };
2030
- //# sourceMappingURL=chunk-ZJ3XMP4N.js.map
2444
+ //# sourceMappingURL=chunk-KBDSYU2O.js.map