@moonpay/cli 0.3.10 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-HKQ2DNOD.js} +667 -281
- package/dist/chunk-HKQ2DNOD.js.map +1 -0
- package/dist/chunk-Z33PSOPD.js +128 -0
- package/dist/chunk-Z33PSOPD.js.map +1 -0
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/dist/{mcp-J2ARFIOB.js → mcp-TKTJOQA5.js} +101 -56
- package/dist/mcp-TKTJOQA5.js.map +1 -0
- package/dist/server-IUOCZFT7.js +21 -0
- package/dist/server-IUOCZFT7.js.map +1 -0
- package/package.json +9 -2
- package/dist/chunk-ZJ3XMP4N.js.map +0 -1
- package/dist/mcp-J2ARFIOB.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,33 @@ 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
|
async function login(config) {
|
|
133
153
|
const state = crypto.randomUUID();
|
|
134
154
|
const usePkce = !config.clientSecret;
|
|
155
|
+
const callbackUrl = `${config.baseUrl}/callback`;
|
|
135
156
|
let codeVerifier;
|
|
136
157
|
if (usePkce) {
|
|
137
158
|
codeVerifier = generateCodeVerifier();
|
|
138
159
|
}
|
|
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
160
|
const authorizeParams = new URLSearchParams({
|
|
208
161
|
client_id: config.clientId,
|
|
209
|
-
redirect_uri:
|
|
162
|
+
redirect_uri: callbackUrl,
|
|
210
163
|
response_type: "code",
|
|
211
164
|
scope: "profile email",
|
|
212
165
|
state
|
|
@@ -216,18 +169,23 @@ async function login(config) {
|
|
|
216
169
|
authorizeParams.set("code_challenge_method", "S256");
|
|
217
170
|
}
|
|
218
171
|
const authorizeUrl = `${config.baseUrl}/authorize?${authorizeParams.toString()}`;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
172
|
+
const opened = openBrowser(authorizeUrl);
|
|
173
|
+
if (opened) {
|
|
174
|
+
console.log("Opened browser for authorization.\n");
|
|
175
|
+
} else {
|
|
176
|
+
console.log("Open this URL in your browser to log in:\n");
|
|
177
|
+
console.log(` ${authorizeUrl}
|
|
178
|
+
`);
|
|
179
|
+
}
|
|
180
|
+
const code = await promptLine("Paste code: ");
|
|
223
181
|
if (!code) {
|
|
224
|
-
throw new Error("No authorization code
|
|
182
|
+
throw new Error("No authorization code provided.");
|
|
225
183
|
}
|
|
226
184
|
const tokenParams = {
|
|
227
185
|
grant_type: "authorization_code",
|
|
228
186
|
code,
|
|
229
187
|
client_id: config.clientId,
|
|
230
|
-
redirect_uri:
|
|
188
|
+
redirect_uri: callbackUrl
|
|
231
189
|
};
|
|
232
190
|
if (config.clientSecret) {
|
|
233
191
|
tokenParams.client_secret = config.clientSecret;
|
|
@@ -254,14 +212,6 @@ async function login(config) {
|
|
|
254
212
|
baseUrl: config.baseUrl
|
|
255
213
|
};
|
|
256
214
|
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
215
|
return creds;
|
|
266
216
|
}
|
|
267
217
|
async function refreshCredentials(creds, config) {
|
|
@@ -1692,8 +1642,11 @@ var schemas_default = [
|
|
|
1692
1642
|
];
|
|
1693
1643
|
|
|
1694
1644
|
// src/tools/wallet/create/tool.ts
|
|
1695
|
-
import {
|
|
1696
|
-
import
|
|
1645
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
1646
|
+
import { join as join3 } from "path";
|
|
1647
|
+
import { homedir as homedir3 } from "os";
|
|
1648
|
+
import { generateMnemonic } from "@scure/bip39";
|
|
1649
|
+
import { wordlist as english } from "@scure/bip39/wordlists/english";
|
|
1697
1650
|
|
|
1698
1651
|
// src/tools/shared.ts
|
|
1699
1652
|
var defineToolSchema = (config) => config;
|
|
@@ -1706,198 +1659,407 @@ var createTool = (schema, handler) => ({
|
|
|
1706
1659
|
}
|
|
1707
1660
|
});
|
|
1708
1661
|
|
|
1709
|
-
// src/tools/wallet/
|
|
1662
|
+
// src/tools/wallet/vault.ts
|
|
1710
1663
|
import {
|
|
1711
1664
|
readFileSync as readFileSync2,
|
|
1712
|
-
readdirSync,
|
|
1713
1665
|
writeFileSync as writeFileSync2,
|
|
1714
|
-
mkdirSync as mkdirSync2,
|
|
1715
1666
|
existsSync as existsSync2,
|
|
1716
|
-
renameSync as renameSync2
|
|
1667
|
+
renameSync as renameSync2,
|
|
1668
|
+
mkdirSync as mkdirSync2
|
|
1717
1669
|
} from "fs";
|
|
1718
|
-
import { join as join2
|
|
1670
|
+
import { join as join2 } from "path";
|
|
1719
1671
|
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
|
|
1672
|
+
import {
|
|
1673
|
+
randomBytes as randomBytes2,
|
|
1674
|
+
randomUUID as randomUUID2,
|
|
1675
|
+
scryptSync,
|
|
1676
|
+
createCipheriv,
|
|
1677
|
+
createDecipheriv
|
|
1678
|
+
} from "crypto";
|
|
1734
1679
|
var CONFIG_DIR2 = join2(homedir2(), ".config", "moonpay");
|
|
1735
|
-
var
|
|
1736
|
-
function
|
|
1737
|
-
|
|
1738
|
-
|
|
1680
|
+
var VAULT_PATH = join2(CONFIG_DIR2, "vault.json");
|
|
1681
|
+
function ensureConfigDir2() {
|
|
1682
|
+
mkdirSync2(CONFIG_DIR2, { recursive: true, mode: 448 });
|
|
1683
|
+
}
|
|
1684
|
+
function loadVault() {
|
|
1685
|
+
if (!existsSync2(VAULT_PATH)) {
|
|
1686
|
+
const empty = { version: 1, entries: {} };
|
|
1687
|
+
saveVault(empty);
|
|
1688
|
+
return empty;
|
|
1689
|
+
}
|
|
1690
|
+
const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
|
|
1691
|
+
const locked = lockedVaultDataSchema.safeParse(raw);
|
|
1692
|
+
if (locked.success) {
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
"Vault is locked. Run `mp wallet unlock` to decrypt it first."
|
|
1695
|
+
);
|
|
1739
1696
|
}
|
|
1740
|
-
const
|
|
1741
|
-
if (!
|
|
1742
|
-
throw new Error(
|
|
1697
|
+
const result = vaultDataSchema.safeParse(raw);
|
|
1698
|
+
if (!result.success) {
|
|
1699
|
+
throw new Error("Vault file is corrupted or has an unknown format.");
|
|
1743
1700
|
}
|
|
1701
|
+
return result.data;
|
|
1702
|
+
}
|
|
1703
|
+
function saveVault(vault) {
|
|
1704
|
+
ensureConfigDir2();
|
|
1705
|
+
const tmpPath = join2(
|
|
1706
|
+
CONFIG_DIR2,
|
|
1707
|
+
`.vault.${randomBytes2(4).toString("hex")}.tmp`
|
|
1708
|
+
);
|
|
1709
|
+
writeFileSync2(tmpPath, JSON.stringify(vault, null, 2), { mode: 384 });
|
|
1710
|
+
renameSync2(tmpPath, VAULT_PATH);
|
|
1711
|
+
}
|
|
1712
|
+
function isVaultLocked() {
|
|
1713
|
+
if (!existsSync2(VAULT_PATH)) return false;
|
|
1714
|
+
const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
|
|
1715
|
+
return lockedVaultDataSchema.safeParse(raw).success;
|
|
1744
1716
|
}
|
|
1745
|
-
function
|
|
1746
|
-
|
|
1717
|
+
function addHdEntry(mnemonic) {
|
|
1718
|
+
const vault = loadVault();
|
|
1719
|
+
const id = randomUUID2();
|
|
1720
|
+
const entry = { id, type: "hd", mnemonic };
|
|
1721
|
+
vault.entries[id] = entry;
|
|
1722
|
+
saveVault(vault);
|
|
1723
|
+
return id;
|
|
1747
1724
|
}
|
|
1748
|
-
function
|
|
1749
|
-
|
|
1750
|
-
|
|
1725
|
+
function addImportedEntry(chain, privateKey) {
|
|
1726
|
+
const vault = loadVault();
|
|
1727
|
+
const id = randomUUID2();
|
|
1728
|
+
const entry = { id, type: "imported", chain, privateKey };
|
|
1729
|
+
vault.entries[id] = entry;
|
|
1730
|
+
saveVault(vault);
|
|
1731
|
+
return id;
|
|
1751
1732
|
}
|
|
1752
|
-
function
|
|
1753
|
-
|
|
1733
|
+
function removeVaultEntry(id) {
|
|
1734
|
+
const vault = loadVault();
|
|
1735
|
+
delete vault.entries[id];
|
|
1736
|
+
saveVault(vault);
|
|
1737
|
+
}
|
|
1738
|
+
function resolveSigningKey(wallet, chain) {
|
|
1739
|
+
const vault = loadVault();
|
|
1740
|
+
const entry = vault.entries[wallet.vaultRef];
|
|
1741
|
+
if (!entry) {
|
|
1742
|
+
throw new Error(
|
|
1743
|
+
`Vault entry not found for wallet "${wallet.name}". The vault may be out of sync.`
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
const keyChain = KEY_CHAIN_MAP[chain];
|
|
1747
|
+
if (entry.type === "imported") {
|
|
1748
|
+
if (entry.chain !== keyChain) {
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`Wallet "${wallet.name}" was imported for ${entry.chain}, cannot sign for ${chain}.`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
privateKey: decodePrivateKey(entry.privateKey, keyChain),
|
|
1755
|
+
address: wallet.addresses[keyChain]
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
const derived = deriveKeyForChain(entry.mnemonic, keyChain, wallet.account ?? 0);
|
|
1759
|
+
return {
|
|
1760
|
+
privateKey: derived.privateKey,
|
|
1761
|
+
address: derived.address
|
|
1762
|
+
};
|
|
1754
1763
|
}
|
|
1755
|
-
function
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
throw new Error(`Wallet "${wallet.name}" already exists`);
|
|
1764
|
+
function decodePrivateKey(key, chain) {
|
|
1765
|
+
if (chain === "solana") {
|
|
1766
|
+
const bs583 = __require("bs58");
|
|
1767
|
+
return bs583.default.decode(key);
|
|
1760
1768
|
}
|
|
1761
|
-
|
|
1769
|
+
return Uint8Array.from(Buffer.from(key, "hex"));
|
|
1770
|
+
}
|
|
1771
|
+
var SCRYPT_N = 2 ** 14;
|
|
1772
|
+
var SCRYPT_R = 8;
|
|
1773
|
+
var SCRYPT_P = 1;
|
|
1774
|
+
var KEY_LENGTH = 32;
|
|
1775
|
+
function deriveEncryptionKey(password, salt) {
|
|
1776
|
+
return scryptSync(password, salt, KEY_LENGTH, {
|
|
1777
|
+
N: SCRYPT_N,
|
|
1778
|
+
r: SCRYPT_R,
|
|
1779
|
+
p: SCRYPT_P
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
function lockVault(password) {
|
|
1783
|
+
const vault = loadVault();
|
|
1784
|
+
const salt = randomBytes2(32);
|
|
1785
|
+
const key = deriveEncryptionKey(password, salt);
|
|
1786
|
+
const iv = randomBytes2(12);
|
|
1787
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
1788
|
+
const plaintext = JSON.stringify(vault);
|
|
1789
|
+
const encrypted = Buffer.concat([
|
|
1790
|
+
cipher.update(plaintext, "utf8"),
|
|
1791
|
+
cipher.final()
|
|
1792
|
+
]);
|
|
1793
|
+
const tag = cipher.getAuthTag();
|
|
1794
|
+
const locked = {
|
|
1795
|
+
version: 1,
|
|
1796
|
+
locked: true,
|
|
1797
|
+
ciphertext: encrypted.toString("base64"),
|
|
1798
|
+
iv: iv.toString("base64"),
|
|
1799
|
+
salt: salt.toString("base64"),
|
|
1800
|
+
tag: tag.toString("base64")
|
|
1801
|
+
};
|
|
1802
|
+
ensureConfigDir2();
|
|
1762
1803
|
const tmpPath = join2(
|
|
1763
|
-
|
|
1764
|
-
|
|
1804
|
+
CONFIG_DIR2,
|
|
1805
|
+
`.vault.${randomBytes2(4).toString("hex")}.tmp`
|
|
1765
1806
|
);
|
|
1766
|
-
writeFileSync2(tmpPath, JSON.stringify(
|
|
1767
|
-
renameSync2(tmpPath,
|
|
1807
|
+
writeFileSync2(tmpPath, JSON.stringify(locked, null, 2), { mode: 384 });
|
|
1808
|
+
renameSync2(tmpPath, VAULT_PATH);
|
|
1768
1809
|
}
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
if (wallet.name === nameOrAddress || wallet.address === nameOrAddress)
|
|
1778
|
-
return wallet;
|
|
1810
|
+
function unlockVault(password) {
|
|
1811
|
+
if (!existsSync2(VAULT_PATH)) {
|
|
1812
|
+
throw new Error("No vault found. Create a wallet first.");
|
|
1813
|
+
}
|
|
1814
|
+
const raw = JSON.parse(readFileSync2(VAULT_PATH, "utf-8"));
|
|
1815
|
+
const locked = lockedVaultDataSchema.safeParse(raw);
|
|
1816
|
+
if (!locked.success) {
|
|
1817
|
+
throw new Error("Vault is not locked.");
|
|
1779
1818
|
}
|
|
1780
|
-
|
|
1819
|
+
const { ciphertext, iv, salt, tag } = locked.data;
|
|
1820
|
+
const saltBuf = Buffer.from(salt, "base64");
|
|
1821
|
+
const key = deriveEncryptionKey(password, saltBuf);
|
|
1822
|
+
const ivBuf = Buffer.from(iv, "base64");
|
|
1823
|
+
const tagBuf = Buffer.from(tag, "base64");
|
|
1824
|
+
const ciphertextBuf = Buffer.from(ciphertext, "base64");
|
|
1825
|
+
const decipher = createDecipheriv("aes-256-gcm", key, ivBuf, { authTagLength: 16 });
|
|
1826
|
+
decipher.setAuthTag(tagBuf);
|
|
1827
|
+
let decrypted;
|
|
1828
|
+
try {
|
|
1829
|
+
decrypted = Buffer.concat([
|
|
1830
|
+
decipher.update(ciphertextBuf),
|
|
1831
|
+
decipher.final()
|
|
1832
|
+
]);
|
|
1833
|
+
} catch {
|
|
1834
|
+
throw new Error("Wrong password.");
|
|
1835
|
+
}
|
|
1836
|
+
const vault = vaultDataSchema.parse(JSON.parse(decrypted.toString("utf8")));
|
|
1837
|
+
saveVault(vault);
|
|
1781
1838
|
}
|
|
1782
1839
|
|
|
1783
1840
|
// src/tools/wallet/create/schema.ts
|
|
1784
|
-
import { z
|
|
1841
|
+
import { z } from "zod";
|
|
1785
1842
|
var walletCreateSchema = defineToolSchema({
|
|
1786
1843
|
name: "wallet_create",
|
|
1787
|
-
description: "
|
|
1788
|
-
input:
|
|
1789
|
-
name:
|
|
1844
|
+
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.",
|
|
1845
|
+
input: z.object({
|
|
1846
|
+
name: z.string().describe("Wallet name"),
|
|
1847
|
+
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."),
|
|
1848
|
+
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
1849
|
}),
|
|
1791
|
-
output:
|
|
1792
|
-
name:
|
|
1793
|
-
|
|
1850
|
+
output: z.object({
|
|
1851
|
+
name: z.string().describe("Wallet name"),
|
|
1852
|
+
account: z.number().describe("BIP44 account index used for derivation"),
|
|
1853
|
+
addresses: z.record(keyChainSchema, z.string()).describe("Derived addresses per chain (solana, ethereum, bitcoin, tron)"),
|
|
1854
|
+
backupPath: z.string().nullable().describe("Path to the mnemonic backup file (0o600 permissions). Null when derived from an existing wallet.")
|
|
1794
1855
|
})
|
|
1795
1856
|
});
|
|
1796
1857
|
|
|
1797
1858
|
// src/tools/wallet/create/tool.ts
|
|
1798
1859
|
var walletCreate = createTool(walletCreateSchema, async (params) => {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1860
|
+
if (params.from) {
|
|
1861
|
+
if (params.account === null) {
|
|
1862
|
+
throw new Error("--account is required when using --from.");
|
|
1863
|
+
}
|
|
1864
|
+
return deriveFromExisting(params.name, params.from, params.account);
|
|
1865
|
+
}
|
|
1866
|
+
return createNew(params.name);
|
|
1867
|
+
});
|
|
1868
|
+
function createNew(name) {
|
|
1869
|
+
const mnemonic = generateMnemonic(english, 128);
|
|
1870
|
+
const addresses = deriveAllAddresses(mnemonic);
|
|
1871
|
+
const vaultRef = addHdEntry(mnemonic);
|
|
1872
|
+
const metadata = {
|
|
1873
|
+
name,
|
|
1874
|
+
type: "hd",
|
|
1875
|
+
vaultRef,
|
|
1876
|
+
account: 0,
|
|
1877
|
+
addresses,
|
|
1805
1878
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1806
1879
|
};
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
});
|
|
1880
|
+
saveWalletMetadata(metadata);
|
|
1881
|
+
const backupDir = join3(homedir3(), ".config", "moonpay", "backups");
|
|
1882
|
+
mkdirSync3(backupDir, { recursive: true, mode: 448 });
|
|
1883
|
+
const backupPath = join3(backupDir, `${name}.mnemonic`);
|
|
1884
|
+
writeFileSync3(backupPath, mnemonic + "\n", { mode: 384 });
|
|
1885
|
+
return { name, account: 0, addresses, backupPath };
|
|
1886
|
+
}
|
|
1887
|
+
function deriveFromExisting(name, from, account) {
|
|
1888
|
+
const source = loadWallet(from);
|
|
1889
|
+
if (source.type !== "hd") {
|
|
1890
|
+
throw new Error(
|
|
1891
|
+
`Wallet "${from}" is an imported single-key wallet. Only HD wallets support account derivation.`
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
const vault = loadVault();
|
|
1895
|
+
const entry = vault.entries[source.vaultRef];
|
|
1896
|
+
if (!entry || entry.type !== "hd") {
|
|
1897
|
+
throw new Error(`Vault entry for wallet "${from}" not found or is not HD.`);
|
|
1898
|
+
}
|
|
1899
|
+
const addresses = deriveAllAddresses(entry.mnemonic, account);
|
|
1900
|
+
const metadata = {
|
|
1901
|
+
name,
|
|
1902
|
+
type: "hd",
|
|
1903
|
+
vaultRef: source.vaultRef,
|
|
1904
|
+
account,
|
|
1905
|
+
addresses,
|
|
1906
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1907
|
+
};
|
|
1908
|
+
saveWalletMetadata(metadata);
|
|
1909
|
+
return { name, account, addresses, backupPath: null };
|
|
1910
|
+
}
|
|
1810
1911
|
|
|
1811
1912
|
// src/tools/wallet/import/tool.ts
|
|
1812
|
-
import {
|
|
1813
|
-
import
|
|
1814
|
-
import {
|
|
1913
|
+
import { validateMnemonic } from "@scure/bip39";
|
|
1914
|
+
import { wordlist as english2 } from "@scure/bip39/wordlists/english";
|
|
1915
|
+
import { Keypair } from "@solana/web3.js";
|
|
1916
|
+
import bs58 from "bs58";
|
|
1917
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1815
1918
|
|
|
1816
1919
|
// src/tools/wallet/import/schema.ts
|
|
1817
|
-
import { z as
|
|
1920
|
+
import { z as z2 } from "zod";
|
|
1818
1921
|
var walletImportSchema = defineToolSchema({
|
|
1819
1922
|
name: "wallet_import",
|
|
1820
|
-
description: "Import a
|
|
1821
|
-
input:
|
|
1822
|
-
name:
|
|
1823
|
-
|
|
1923
|
+
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.",
|
|
1924
|
+
input: z2.object({
|
|
1925
|
+
name: z2.string().describe("Wallet name"),
|
|
1926
|
+
mnemonic: z2.string().nullable().describe("BIP39 mnemonic seed phrase (for HD wallet import)"),
|
|
1927
|
+
key: z2.string().nullable().describe(
|
|
1928
|
+
"Private key: base58 for Solana, hex for EVM/Bitcoin (for single-chain import)"
|
|
1929
|
+
),
|
|
1930
|
+
chain: chainSchema.nullable().describe(
|
|
1931
|
+
"Chain for single-key import: solana, ethereum, base, arbitrum, or bitcoin (default solana)"
|
|
1932
|
+
)
|
|
1824
1933
|
}),
|
|
1825
|
-
output:
|
|
1826
|
-
name:
|
|
1827
|
-
|
|
1934
|
+
output: z2.object({
|
|
1935
|
+
name: z2.string().describe("Wallet name"),
|
|
1936
|
+
type: z2.enum(["hd", "imported"]).describe("Wallet type"),
|
|
1937
|
+
addresses: z2.record(keyChainSchema, z2.string()).describe("Wallet addresses per chain")
|
|
1828
1938
|
})
|
|
1829
1939
|
});
|
|
1830
1940
|
|
|
1831
1941
|
// src/tools/wallet/import/tool.ts
|
|
1832
1942
|
async function promptSecret(prompt) {
|
|
1833
|
-
const rl =
|
|
1834
|
-
return new Promise((
|
|
1943
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
1944
|
+
return new Promise((resolve) => {
|
|
1835
1945
|
rl.question(prompt, (answer) => {
|
|
1836
1946
|
rl.close();
|
|
1837
|
-
|
|
1947
|
+
resolve(answer.trim());
|
|
1838
1948
|
});
|
|
1839
1949
|
});
|
|
1840
1950
|
}
|
|
1841
1951
|
var walletImport = createTool(walletImportSchema, async (params) => {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1952
|
+
if (params.mnemonic && params.key) {
|
|
1953
|
+
throw new Error("Provide either --mnemonic or --key, not both.");
|
|
1954
|
+
}
|
|
1955
|
+
if (params.mnemonic) {
|
|
1956
|
+
return importMnemonic(params.name, params.mnemonic);
|
|
1957
|
+
}
|
|
1958
|
+
const chain = params.chain ? KEY_CHAIN_MAP[params.chain] : "solana";
|
|
1959
|
+
const key = params.key !== null ? params.key : await promptSecret("Enter private key: ");
|
|
1960
|
+
return importKey(params.name, chain, key);
|
|
1961
|
+
});
|
|
1962
|
+
async function importMnemonic(name, mnemonic) {
|
|
1963
|
+
const trimmed = mnemonic.trim().toLowerCase();
|
|
1964
|
+
if (!validateMnemonic(trimmed, english2)) {
|
|
1965
|
+
throw new Error("Invalid BIP39 mnemonic.");
|
|
1851
1966
|
}
|
|
1852
|
-
const
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1967
|
+
const addresses = deriveAllAddresses(trimmed);
|
|
1968
|
+
const vaultRef = addHdEntry(trimmed);
|
|
1969
|
+
const metadata = {
|
|
1970
|
+
name,
|
|
1971
|
+
type: "hd",
|
|
1972
|
+
vaultRef,
|
|
1973
|
+
account: 0,
|
|
1974
|
+
addresses,
|
|
1857
1975
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1858
1976
|
};
|
|
1859
|
-
|
|
1860
|
-
return { name:
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1977
|
+
saveWalletMetadata(metadata);
|
|
1978
|
+
return { name, type: "hd", addresses };
|
|
1979
|
+
}
|
|
1980
|
+
async function importKey(name, chain, key) {
|
|
1981
|
+
let address;
|
|
1982
|
+
if (chain === "solana") {
|
|
1983
|
+
try {
|
|
1984
|
+
const secretKeyBytes = bs58.decode(key);
|
|
1985
|
+
const keypair = Keypair.fromSecretKey(secretKeyBytes);
|
|
1986
|
+
address = keypair.publicKey.toBase58();
|
|
1987
|
+
} catch {
|
|
1988
|
+
throw new Error(
|
|
1989
|
+
"Invalid private key. Expected a base58-encoded Solana secret key."
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
} else if (chain === "ethereum") {
|
|
1993
|
+
const cleanKey = key.startsWith("0x") ? key.slice(2) : key;
|
|
1994
|
+
if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
|
|
1995
|
+
throw new Error(
|
|
1996
|
+
"Invalid private key. Expected a 64-character hex string for EVM."
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
const { deriveKeyForChain: deriveKeyForChain2 } = await import("./chains-R754DQM5.js");
|
|
2000
|
+
const { HDKey } = await import("@scure/bip32");
|
|
2001
|
+
const ecc2 = await import("tiny-secp256k1");
|
|
2002
|
+
const { createHash: createHash3 } = await import("crypto");
|
|
2003
|
+
const privKeyBytes = Buffer.from(cleanKey, "hex");
|
|
2004
|
+
const pubKey = ecc2.pointFromScalar(privKeyBytes);
|
|
2005
|
+
if (!pubKey) throw new Error("Invalid private key.");
|
|
2006
|
+
const uncompressed = ecc2.pointCompress(pubKey, false);
|
|
2007
|
+
const hash = createHash3("sha3-256").update(uncompressed.slice(1)).digest();
|
|
2008
|
+
address = "0x" + hash.slice(-20).toString("hex");
|
|
2009
|
+
key = cleanKey;
|
|
2010
|
+
} else {
|
|
2011
|
+
const cleanKey = key.startsWith("0x") ? key.slice(2) : key;
|
|
2012
|
+
if (!/^[0-9a-fA-F]{64}$/.test(cleanKey)) {
|
|
2013
|
+
throw new Error(
|
|
2014
|
+
"Invalid private key. Expected a 64-character hex string for Bitcoin."
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
const bitcoin = await import("bitcoinjs-lib");
|
|
2018
|
+
const ECPairFactory = (await import("ecpair")).default;
|
|
2019
|
+
const ecc2 = await import("tiny-secp256k1");
|
|
2020
|
+
const ECPair = ECPairFactory(ecc2);
|
|
2021
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(cleanKey, "hex"));
|
|
2022
|
+
const { address: btcAddress } = bitcoin.payments.p2wpkh({
|
|
2023
|
+
pubkey: Buffer.from(keyPair.publicKey)
|
|
2024
|
+
});
|
|
2025
|
+
if (!btcAddress) throw new Error("Failed to derive Bitcoin address.");
|
|
2026
|
+
address = btcAddress;
|
|
2027
|
+
key = cleanKey;
|
|
2028
|
+
}
|
|
2029
|
+
const vaultRef = addImportedEntry(chain, key);
|
|
2030
|
+
const metadata = {
|
|
2031
|
+
name,
|
|
2032
|
+
type: "imported",
|
|
2033
|
+
vaultRef,
|
|
2034
|
+
account: 0,
|
|
2035
|
+
addresses: { [chain]: address },
|
|
2036
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2037
|
+
};
|
|
2038
|
+
saveWalletMetadata(metadata);
|
|
2039
|
+
return { name, type: "imported", addresses: { [chain]: address } };
|
|
2040
|
+
}
|
|
1866
2041
|
|
|
1867
2042
|
// src/tools/wallet/list/schema.ts
|
|
1868
|
-
import { z as
|
|
2043
|
+
import { z as z3 } from "zod";
|
|
1869
2044
|
var walletListSchema = defineToolSchema({
|
|
1870
2045
|
name: "wallet_list",
|
|
1871
2046
|
description: "List all local wallets",
|
|
1872
|
-
input:
|
|
1873
|
-
output:
|
|
2047
|
+
input: z3.object({}),
|
|
2048
|
+
output: z3.array(walletInfoSchema)
|
|
1874
2049
|
});
|
|
1875
2050
|
|
|
1876
2051
|
// src/tools/wallet/list/tool.ts
|
|
1877
2052
|
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
|
-
});
|
|
2053
|
+
return loadAllWallets();
|
|
1892
2054
|
});
|
|
1893
2055
|
|
|
1894
2056
|
// src/tools/wallet/retrieve/schema.ts
|
|
1895
|
-
import { z as
|
|
2057
|
+
import { z as z4 } from "zod";
|
|
1896
2058
|
var walletRetrieveSchema = defineToolSchema({
|
|
1897
2059
|
name: "wallet_retrieve",
|
|
1898
2060
|
description: "Get details of a specific wallet",
|
|
1899
|
-
input:
|
|
1900
|
-
wallet:
|
|
2061
|
+
input: z4.object({
|
|
2062
|
+
wallet: z4.string().describe("Wallet name or address")
|
|
1901
2063
|
}),
|
|
1902
2064
|
output: walletInfoSchema
|
|
1903
2065
|
});
|
|
@@ -1906,31 +2068,22 @@ var walletRetrieveSchema = defineToolSchema({
|
|
|
1906
2068
|
var walletRetrieve = createTool(
|
|
1907
2069
|
walletRetrieveSchema,
|
|
1908
2070
|
async (params) => {
|
|
1909
|
-
|
|
1910
|
-
return {
|
|
1911
|
-
name: wallet.name,
|
|
1912
|
-
address: wallet.address,
|
|
1913
|
-
chain: wallet.chain,
|
|
1914
|
-
createdAt: wallet.createdAt
|
|
1915
|
-
};
|
|
2071
|
+
return loadWallet(params.wallet);
|
|
1916
2072
|
}
|
|
1917
2073
|
);
|
|
1918
2074
|
|
|
1919
|
-
// src/tools/wallet/delete/tool.ts
|
|
1920
|
-
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1921
|
-
|
|
1922
2075
|
// src/tools/wallet/delete/schema.ts
|
|
1923
|
-
import { z as
|
|
2076
|
+
import { z as z5 } from "zod";
|
|
1924
2077
|
var walletDeleteSchema = defineToolSchema({
|
|
1925
2078
|
name: "wallet_delete",
|
|
1926
2079
|
description: "Permanently delete a local wallet. This removes the private key file and cannot be undone.",
|
|
1927
|
-
input:
|
|
1928
|
-
wallet:
|
|
1929
|
-
confirm:
|
|
2080
|
+
input: z5.object({
|
|
2081
|
+
wallet: z5.string().describe("Name or address of the wallet to delete"),
|
|
2082
|
+
confirm: z5.boolean().describe("Must be true to confirm deletion")
|
|
1930
2083
|
}),
|
|
1931
|
-
output:
|
|
1932
|
-
name:
|
|
1933
|
-
deleted:
|
|
2084
|
+
output: z5.object({
|
|
2085
|
+
name: z5.string().describe("Name of the deleted wallet"),
|
|
2086
|
+
deleted: z5.literal(true)
|
|
1934
2087
|
})
|
|
1935
2088
|
});
|
|
1936
2089
|
|
|
@@ -1941,26 +2094,29 @@ var walletDelete = createTool(walletDeleteSchema, async (params) => {
|
|
|
1941
2094
|
"Deletion not confirmed. Pass --confirm to permanently delete this wallet."
|
|
1942
2095
|
);
|
|
1943
2096
|
}
|
|
1944
|
-
const wallet =
|
|
1945
|
-
|
|
2097
|
+
const wallet = loadWallet(params.wallet);
|
|
2098
|
+
removeVaultEntry(wallet.vaultRef);
|
|
2099
|
+
deleteWalletFile(wallet.name);
|
|
1946
2100
|
return { name: wallet.name, deleted: true };
|
|
1947
2101
|
});
|
|
1948
2102
|
|
|
1949
2103
|
// src/tools/transaction/sign/tool.ts
|
|
1950
|
-
import { Keypair as
|
|
1951
|
-
import bs583 from "bs58";
|
|
2104
|
+
import { Keypair as Keypair2, VersionedTransaction } from "@solana/web3.js";
|
|
1952
2105
|
|
|
1953
2106
|
// src/tools/transaction/sign/schema.ts
|
|
1954
|
-
import { z as
|
|
2107
|
+
import { z as z6 } from "zod";
|
|
1955
2108
|
var transactionSignSchema = defineToolSchema({
|
|
1956
2109
|
name: "transaction_sign",
|
|
1957
|
-
description: "Sign a
|
|
1958
|
-
input:
|
|
1959
|
-
wallet:
|
|
1960
|
-
|
|
2110
|
+
description: "Sign a transaction with a local wallet. Supports Solana (VersionedTransaction), EVM (RLP-encoded), and Bitcoin (PSBT) transactions.",
|
|
2111
|
+
input: z6.object({
|
|
2112
|
+
wallet: z6.string().describe("Wallet name or address"),
|
|
2113
|
+
chain: chainSchema.describe(
|
|
2114
|
+
"Chain: solana, ethereum, base, arbitrum, or bitcoin"
|
|
2115
|
+
),
|
|
2116
|
+
transaction: z6.string().describe("Base64-encoded unsigned transaction")
|
|
1961
2117
|
}),
|
|
1962
|
-
output:
|
|
1963
|
-
transaction:
|
|
2118
|
+
output: z6.object({
|
|
2119
|
+
transaction: z6.string().describe("Base64-encoded signed transaction")
|
|
1964
2120
|
})
|
|
1965
2121
|
});
|
|
1966
2122
|
|
|
@@ -1968,47 +2124,274 @@ var transactionSignSchema = defineToolSchema({
|
|
|
1968
2124
|
var transactionSign = createTool(
|
|
1969
2125
|
transactionSignSchema,
|
|
1970
2126
|
async (params) => {
|
|
1971
|
-
const
|
|
2127
|
+
const wallet = loadWallet(params.wallet);
|
|
2128
|
+
const chain = params.chain;
|
|
2129
|
+
const keyChain = KEY_CHAIN_MAP[chain];
|
|
2130
|
+
const { privateKey } = resolveSigningKey(wallet, chain);
|
|
1972
2131
|
const txBytes = Buffer.from(params.transaction.trim(), "base64");
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2132
|
+
switch (keyChain) {
|
|
2133
|
+
case "solana":
|
|
2134
|
+
return signSolana(privateKey, txBytes);
|
|
2135
|
+
case "ethereum":
|
|
2136
|
+
return signEvm(privateKey, txBytes);
|
|
2137
|
+
case "bitcoin":
|
|
2138
|
+
return signBitcoin(privateKey, txBytes);
|
|
2139
|
+
case "tron":
|
|
2140
|
+
return signEvm(privateKey, txBytes);
|
|
2141
|
+
}
|
|
1980
2142
|
}
|
|
1981
2143
|
);
|
|
2144
|
+
function signSolana(privateKey, txBytes) {
|
|
2145
|
+
const tx = VersionedTransaction.deserialize(txBytes);
|
|
2146
|
+
const keypair = Keypair2.fromSecretKey(privateKey);
|
|
2147
|
+
tx.sign([keypair]);
|
|
2148
|
+
return { transaction: Buffer.from(tx.serialize()).toString("base64") };
|
|
2149
|
+
}
|
|
2150
|
+
async function signEvm(privateKey, txBytes) {
|
|
2151
|
+
const { createHash: createHash3 } = await import("crypto");
|
|
2152
|
+
const ecc2 = await import("tiny-secp256k1");
|
|
2153
|
+
const hash = createHash3("sha3-256").update(txBytes).digest();
|
|
2154
|
+
const sig = ecc2.sign(hash, privateKey);
|
|
2155
|
+
if (!sig) throw new Error("EVM signing failed");
|
|
2156
|
+
const signed = Buffer.concat([txBytes, Buffer.from(sig)]);
|
|
2157
|
+
return { transaction: signed.toString("base64") };
|
|
2158
|
+
}
|
|
2159
|
+
async function signBitcoin(privateKey, txBytes) {
|
|
2160
|
+
const bitcoin = await import("bitcoinjs-lib");
|
|
2161
|
+
const ECPairFactory = (await import("ecpair")).default;
|
|
2162
|
+
const ecc2 = await import("tiny-secp256k1");
|
|
2163
|
+
const ECPair = ECPairFactory(ecc2);
|
|
2164
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(privateKey));
|
|
2165
|
+
const psbt = bitcoin.Psbt.fromBase64(txBytes.toString("base64"));
|
|
2166
|
+
psbt.signAllInputs(keyPair);
|
|
2167
|
+
return { transaction: psbt.toBase64() };
|
|
2168
|
+
}
|
|
1982
2169
|
|
|
1983
2170
|
// src/tools/message/sign/tool.ts
|
|
1984
|
-
import {
|
|
1985
|
-
import
|
|
2171
|
+
import { createHash as createHash2 } from "crypto";
|
|
2172
|
+
import { Keypair as Keypair3 } from "@solana/web3.js";
|
|
2173
|
+
import bs582 from "bs58";
|
|
1986
2174
|
import nacl from "tweetnacl";
|
|
2175
|
+
import * as ecc from "tiny-secp256k1";
|
|
1987
2176
|
|
|
1988
2177
|
// src/tools/message/sign/schema.ts
|
|
1989
|
-
import { z as
|
|
2178
|
+
import { z as z7 } from "zod";
|
|
1990
2179
|
var messageSignSchema = defineToolSchema({
|
|
1991
2180
|
name: "message_sign",
|
|
1992
|
-
description: "Sign a message with a local wallet.
|
|
1993
|
-
input:
|
|
1994
|
-
wallet:
|
|
1995
|
-
|
|
2181
|
+
description: "Sign a message with a local wallet. Supports Solana (ed25519), EVM (EIP-191 personal sign), and Bitcoin (secp256k1 ECDSA).",
|
|
2182
|
+
input: z7.object({
|
|
2183
|
+
wallet: z7.string().describe("Wallet address or name to sign with"),
|
|
2184
|
+
chain: chainSchema.describe(
|
|
2185
|
+
"Chain: solana, ethereum, base, arbitrum, or bitcoin"
|
|
2186
|
+
),
|
|
2187
|
+
message: z7.string().describe("Message text to sign")
|
|
1996
2188
|
}),
|
|
1997
|
-
output:
|
|
1998
|
-
signature:
|
|
2189
|
+
output: z7.object({
|
|
2190
|
+
signature: z7.string().describe(
|
|
2191
|
+
"Signature: base58 for Solana, hex (0x-prefixed) for EVM/Bitcoin"
|
|
2192
|
+
)
|
|
1999
2193
|
})
|
|
2000
2194
|
});
|
|
2001
2195
|
|
|
2002
2196
|
// src/tools/message/sign/tool.ts
|
|
2003
2197
|
var messageSign = createTool(messageSignSchema, async (params) => {
|
|
2004
|
-
const
|
|
2005
|
-
const
|
|
2006
|
-
const
|
|
2198
|
+
const wallet = loadWallet(params.wallet);
|
|
2199
|
+
const chain = params.chain;
|
|
2200
|
+
const keyChain = KEY_CHAIN_MAP[chain];
|
|
2201
|
+
const { privateKey } = resolveSigningKey(wallet, chain);
|
|
2007
2202
|
const messageBytes = Buffer.from(params.message, "utf8");
|
|
2203
|
+
switch (keyChain) {
|
|
2204
|
+
case "solana":
|
|
2205
|
+
return signSolana2(privateKey, messageBytes);
|
|
2206
|
+
case "ethereum":
|
|
2207
|
+
return signEvm2(privateKey, messageBytes);
|
|
2208
|
+
case "bitcoin":
|
|
2209
|
+
return signBitcoin2(privateKey, messageBytes);
|
|
2210
|
+
case "tron":
|
|
2211
|
+
return signEvm2(privateKey, messageBytes);
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
function signSolana2(privateKey, messageBytes) {
|
|
2215
|
+
const keypair = Keypair3.fromSecretKey(privateKey);
|
|
2008
2216
|
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
|
|
2009
|
-
return { signature:
|
|
2217
|
+
return { signature: bs582.encode(signatureBytes) };
|
|
2218
|
+
}
|
|
2219
|
+
function signEvm2(privateKey, messageBytes) {
|
|
2220
|
+
const prefix = Buffer.from(
|
|
2221
|
+
`Ethereum Signed Message:
|
|
2222
|
+
${messageBytes.length}`,
|
|
2223
|
+
"utf8"
|
|
2224
|
+
);
|
|
2225
|
+
const prefixed = Buffer.concat([prefix, messageBytes]);
|
|
2226
|
+
const hash = createHash2("sha3-256").update(prefixed).digest();
|
|
2227
|
+
const sig = ecc.sign(hash, privateKey);
|
|
2228
|
+
if (!sig) throw new Error("EVM message signing failed");
|
|
2229
|
+
return { signature: "0x" + Buffer.from(sig).toString("hex") };
|
|
2230
|
+
}
|
|
2231
|
+
function signBitcoin2(privateKey, messageBytes) {
|
|
2232
|
+
const hash1 = createHash2("sha256").update(messageBytes).digest();
|
|
2233
|
+
const hash2 = createHash2("sha256").update(hash1).digest();
|
|
2234
|
+
const sig = ecc.sign(hash2, privateKey);
|
|
2235
|
+
if (!sig) throw new Error("Bitcoin message signing failed");
|
|
2236
|
+
return { signature: "0x" + Buffer.from(sig).toString("hex") };
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// src/tools/wallet/lock/tool.ts
|
|
2240
|
+
import { createInterface as createInterface3 } from "readline";
|
|
2241
|
+
|
|
2242
|
+
// src/tools/wallet/lock/schema.ts
|
|
2243
|
+
import { z as z8 } from "zod";
|
|
2244
|
+
var walletLockSchema = defineToolSchema({
|
|
2245
|
+
name: "wallet_lock",
|
|
2246
|
+
description: "Encrypt the vault with a password. All signing operations will fail until `wallet unlock` is called. Password is prompted interactively.",
|
|
2247
|
+
input: z8.object({}),
|
|
2248
|
+
output: z8.object({
|
|
2249
|
+
locked: z8.literal(true)
|
|
2250
|
+
})
|
|
2010
2251
|
});
|
|
2011
2252
|
|
|
2253
|
+
// src/tools/wallet/lock/tool.ts
|
|
2254
|
+
async function promptPassword(prompt) {
|
|
2255
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
2256
|
+
return new Promise((resolve) => {
|
|
2257
|
+
rl.question(prompt, (answer) => {
|
|
2258
|
+
rl.close();
|
|
2259
|
+
resolve(answer);
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
var walletLock = createTool(walletLockSchema, async () => {
|
|
2264
|
+
if (isVaultLocked()) {
|
|
2265
|
+
throw new Error("Vault is already locked.");
|
|
2266
|
+
}
|
|
2267
|
+
const password = await promptPassword("Enter password to encrypt vault: ");
|
|
2268
|
+
if (!password) {
|
|
2269
|
+
throw new Error("Password cannot be empty.");
|
|
2270
|
+
}
|
|
2271
|
+
const confirm = await promptPassword("Confirm password: ");
|
|
2272
|
+
if (password !== confirm) {
|
|
2273
|
+
throw new Error("Passwords do not match.");
|
|
2274
|
+
}
|
|
2275
|
+
lockVault(password);
|
|
2276
|
+
return { locked: true };
|
|
2277
|
+
});
|
|
2278
|
+
|
|
2279
|
+
// src/tools/wallet/unlock/tool.ts
|
|
2280
|
+
import { createInterface as createInterface4 } from "readline";
|
|
2281
|
+
|
|
2282
|
+
// src/tools/wallet/unlock/schema.ts
|
|
2283
|
+
import { z as z9 } from "zod";
|
|
2284
|
+
var walletUnlockSchema = defineToolSchema({
|
|
2285
|
+
name: "wallet_unlock",
|
|
2286
|
+
description: "Decrypt the vault with a password. Restores access to all signing operations. Password is prompted interactively.",
|
|
2287
|
+
input: z9.object({}),
|
|
2288
|
+
output: z9.object({
|
|
2289
|
+
unlocked: z9.literal(true)
|
|
2290
|
+
})
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
// src/tools/wallet/unlock/tool.ts
|
|
2294
|
+
async function promptPassword2(prompt) {
|
|
2295
|
+
const rl = createInterface4({ input: process.stdin, output: process.stderr });
|
|
2296
|
+
return new Promise((resolve) => {
|
|
2297
|
+
rl.question(prompt, (answer) => {
|
|
2298
|
+
rl.close();
|
|
2299
|
+
resolve(answer);
|
|
2300
|
+
});
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
var walletUnlock = createTool(walletUnlockSchema, async () => {
|
|
2304
|
+
if (!isVaultLocked()) {
|
|
2305
|
+
throw new Error("Vault is not locked.");
|
|
2306
|
+
}
|
|
2307
|
+
const password = await promptPassword2("Enter password to decrypt vault: ");
|
|
2308
|
+
unlockVault(password);
|
|
2309
|
+
return { unlocked: true };
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
// src/tools/bitcoin/balance/tool.ts
|
|
2313
|
+
import https from "https";
|
|
2314
|
+
|
|
2315
|
+
// src/tools/bitcoin/balance/schema.ts
|
|
2316
|
+
import { z as z10 } from "zod";
|
|
2317
|
+
var bitcoinBalanceRetrieveSchema = defineToolSchema({
|
|
2318
|
+
name: "bitcoin_balance_retrieve",
|
|
2319
|
+
description: "Get the BTC balance of a Bitcoin address. Returns confirmed and unconfirmed balances in BTC and satoshis.",
|
|
2320
|
+
input: z10.object({
|
|
2321
|
+
wallet: z10.string().describe("Bitcoin address (bc1...) or wallet name")
|
|
2322
|
+
}),
|
|
2323
|
+
output: z10.object({
|
|
2324
|
+
address: z10.string().describe("Bitcoin address"),
|
|
2325
|
+
confirmed: z10.object({
|
|
2326
|
+
btc: z10.string().describe("Confirmed balance in BTC"),
|
|
2327
|
+
sats: z10.number().describe("Confirmed balance in satoshis")
|
|
2328
|
+
}),
|
|
2329
|
+
unconfirmed: z10.object({
|
|
2330
|
+
btc: z10.string().describe("Unconfirmed (pending) balance in BTC"),
|
|
2331
|
+
sats: z10.number().describe("Unconfirmed (pending) balance in satoshis")
|
|
2332
|
+
}),
|
|
2333
|
+
total: z10.object({
|
|
2334
|
+
btc: z10.string().describe("Total balance in BTC"),
|
|
2335
|
+
sats: z10.number().describe("Total balance in satoshis")
|
|
2336
|
+
})
|
|
2337
|
+
})
|
|
2338
|
+
});
|
|
2339
|
+
|
|
2340
|
+
// src/tools/bitcoin/balance/tool.ts
|
|
2341
|
+
var SATS_PER_BTC = 1e8;
|
|
2342
|
+
function satsToBtc(sats) {
|
|
2343
|
+
return (sats / SATS_PER_BTC).toFixed(8);
|
|
2344
|
+
}
|
|
2345
|
+
function fetchJson(url) {
|
|
2346
|
+
return new Promise((resolve, reject) => {
|
|
2347
|
+
https.get(url, { headers: { "User-Agent": "moonpay-cli" } }, (res) => {
|
|
2348
|
+
if (res.statusCode !== 200) {
|
|
2349
|
+
reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
2350
|
+
res.resume();
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
let data = "";
|
|
2354
|
+
res.on("data", (chunk) => data += chunk);
|
|
2355
|
+
res.on("end", () => {
|
|
2356
|
+
try {
|
|
2357
|
+
resolve(JSON.parse(data));
|
|
2358
|
+
} catch {
|
|
2359
|
+
reject(new Error("Invalid JSON response"));
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
}).on("error", reject);
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
var bitcoinBalanceRetrieve = createTool(
|
|
2366
|
+
bitcoinBalanceRetrieveSchema,
|
|
2367
|
+
async (params) => {
|
|
2368
|
+
let address = params.wallet;
|
|
2369
|
+
if (!address.startsWith("bc1") && !address.startsWith("1") && !address.startsWith("3")) {
|
|
2370
|
+
const { loadWallet: loadWallet2 } = await import("./server-IUOCZFT7.js");
|
|
2371
|
+
const wallet = loadWallet2(address);
|
|
2372
|
+
const btcAddress = wallet.addresses.bitcoin;
|
|
2373
|
+
if (!btcAddress) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
`Wallet "${address}" has no Bitcoin address. It may be an imported single-chain wallet.`
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
address = btcAddress;
|
|
2379
|
+
}
|
|
2380
|
+
const data = await fetchJson(
|
|
2381
|
+
`https://mempool.space/api/address/${address}`
|
|
2382
|
+
);
|
|
2383
|
+
const confirmedSats = data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum;
|
|
2384
|
+
const unconfirmedSats = data.mempool_stats.funded_txo_sum - data.mempool_stats.spent_txo_sum;
|
|
2385
|
+
const totalSats = confirmedSats + unconfirmedSats;
|
|
2386
|
+
return {
|
|
2387
|
+
address,
|
|
2388
|
+
confirmed: { btc: satsToBtc(confirmedSats), sats: confirmedSats },
|
|
2389
|
+
unconfirmed: { btc: satsToBtc(unconfirmedSats), sats: unconfirmedSats },
|
|
2390
|
+
total: { btc: satsToBtc(totalSats), sats: totalSats }
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
);
|
|
2394
|
+
|
|
2012
2395
|
export {
|
|
2013
2396
|
getConfigOrDefault,
|
|
2014
2397
|
clearCredentials,
|
|
@@ -2018,13 +2401,16 @@ export {
|
|
|
2018
2401
|
schemas_default,
|
|
2019
2402
|
defineToolSchema,
|
|
2020
2403
|
createTool,
|
|
2021
|
-
|
|
2404
|
+
resolveSigningKey,
|
|
2022
2405
|
walletCreate,
|
|
2023
2406
|
walletImport,
|
|
2024
2407
|
walletList,
|
|
2025
2408
|
walletRetrieve,
|
|
2026
2409
|
walletDelete,
|
|
2027
2410
|
transactionSign,
|
|
2028
|
-
messageSign
|
|
2411
|
+
messageSign,
|
|
2412
|
+
walletLock,
|
|
2413
|
+
walletUnlock,
|
|
2414
|
+
bitcoinBalanceRetrieve
|
|
2029
2415
|
};
|
|
2030
|
-
//# sourceMappingURL=chunk-
|
|
2416
|
+
//# sourceMappingURL=chunk-HKQ2DNOD.js.map
|