@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.
- package/dist/chains-R754DQM5.js +13 -0
- package/dist/chains-R754DQM5.js.map +1 -0
- package/dist/chunk-AGDVU2O5.js +168 -0
- package/dist/chunk-AGDVU2O5.js.map +1 -0
- package/dist/chunk-EEBB5MQP.js +12 -0
- package/dist/chunk-EEBB5MQP.js.map +1 -0
- package/dist/{chunk-ZJ3XMP4N.js → chunk-KBDSYU2O.js} +697 -283
- package/dist/chunk-KBDSYU2O.js.map +1 -0
- package/dist/chunk-Z33PSOPD.js +128 -0
- package/dist/chunk-Z33PSOPD.js.map +1 -0
- package/dist/index.js +28 -6
- package/dist/index.js.map +1 -1
- package/dist/{mcp-NSLQO3QO.js → mcp-XTLPOD2S.js} +25 -11
- package/dist/mcp-XTLPOD2S.js.map +1 -0
- package/dist/server-IUOCZFT7.js +21 -0
- package/dist/server-IUOCZFT7.js.map +1 -0
- package/package.json +7 -1
- package/dist/chunk-ZJ3XMP4N.js.map +0 -1
- package/dist/mcp-NSLQO3QO.js.map +0 -1
|
@@ -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
|
-
|
|
11
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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:
|
|
216
|
+
redirect_uri: callbackUrl
|
|
231
217
|
};
|
|
232
218
|
if (config.clientSecret) {
|
|
233
219
|
tokenParams.client_secret = config.clientSecret;
|
|
234
220
|
}
|
|
235
|
-
if (
|
|
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 {
|
|
1696
|
-
import
|
|
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/
|
|
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
|
|
1698
|
+
import { join as join2 } from "path";
|
|
1719
1699
|
import { homedir as homedir2 } from "os";
|
|
1720
|
-
import {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
|
1736
|
-
function
|
|
1737
|
-
|
|
1738
|
-
|
|
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
|
|
1741
|
-
if (!
|
|
1742
|
-
throw new Error(
|
|
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
|
|
1746
|
-
|
|
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
|
|
1749
|
-
|
|
1750
|
-
|
|
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
|
|
1753
|
-
|
|
1761
|
+
function removeVaultEntry(id) {
|
|
1762
|
+
const vault = loadVault();
|
|
1763
|
+
delete vault.entries[id];
|
|
1764
|
+
saveVault(vault);
|
|
1754
1765
|
}
|
|
1755
|
-
function
|
|
1756
|
-
|
|
1757
|
-
const
|
|
1758
|
-
if (
|
|
1759
|
-
throw new Error(
|
|
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
|
|
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
|
-
|
|
1764
|
-
|
|
1832
|
+
CONFIG_DIR2,
|
|
1833
|
+
`.vault.${randomBytes2(4).toString("hex")}.tmp`
|
|
1765
1834
|
);
|
|
1766
|
-
writeFileSync2(tmpPath, JSON.stringify(
|
|
1767
|
-
renameSync2(tmpPath,
|
|
1835
|
+
writeFileSync2(tmpPath, JSON.stringify(locked, null, 2), { mode: 384 });
|
|
1836
|
+
renameSync2(tmpPath, VAULT_PATH);
|
|
1768
1837
|
}
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
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
|
-
|
|
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
|
|
1869
|
+
import { z } from "zod";
|
|
1785
1870
|
var walletCreateSchema = defineToolSchema({
|
|
1786
1871
|
name: "wallet_create",
|
|
1787
|
-
description: "
|
|
1788
|
-
input:
|
|
1789
|
-
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:
|
|
1792
|
-
name:
|
|
1793
|
-
|
|
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
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
1808
|
-
|
|
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 {
|
|
1813
|
-
import
|
|
1814
|
-
import {
|
|
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
|
|
1948
|
+
import { z as z2 } from "zod";
|
|
1818
1949
|
var walletImportSchema = defineToolSchema({
|
|
1819
1950
|
name: "wallet_import",
|
|
1820
|
-
description: "Import a
|
|
1821
|
-
input:
|
|
1822
|
-
name:
|
|
1823
|
-
|
|
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:
|
|
1826
|
-
name:
|
|
1827
|
-
|
|
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 =
|
|
1834
|
-
return new Promise((
|
|
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
|
-
|
|
1975
|
+
resolve(answer.trim());
|
|
1838
1976
|
});
|
|
1839
1977
|
});
|
|
1840
1978
|
}
|
|
1841
1979
|
var walletImport = createTool(walletImportSchema, async (params) => {
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
|
-
|
|
1860
|
-
return { name:
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
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
|
|
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:
|
|
1873
|
-
output:
|
|
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
|
-
|
|
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
|
|
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:
|
|
1900
|
-
wallet:
|
|
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
|
-
|
|
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
|
|
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:
|
|
1928
|
-
wallet:
|
|
1929
|
-
confirm:
|
|
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:
|
|
1932
|
-
name:
|
|
1933
|
-
deleted:
|
|
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 =
|
|
1945
|
-
|
|
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
|
|
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
|
|
2135
|
+
import { z as z6 } from "zod";
|
|
1955
2136
|
var transactionSignSchema = defineToolSchema({
|
|
1956
2137
|
name: "transaction_sign",
|
|
1957
|
-
description: "Sign a
|
|
1958
|
-
input:
|
|
1959
|
-
wallet:
|
|
1960
|
-
|
|
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:
|
|
1963
|
-
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
|
|
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
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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 {
|
|
1985
|
-
import
|
|
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
|
|
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.
|
|
1993
|
-
input:
|
|
1994
|
-
wallet:
|
|
1995
|
-
|
|
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:
|
|
1998
|
-
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
|
|
2005
|
-
const
|
|
2006
|
-
const
|
|
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:
|
|
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
|
-
|
|
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-
|
|
2444
|
+
//# sourceMappingURL=chunk-KBDSYU2O.js.map
|