@execra/core 1.0.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/cli/commands.d.ts +21 -0
- package/dist/cli/commands.js +686 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +1162 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +12 -0
- package/dist/cli/prompts.js +98 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/cli/ui.d.ts +38 -0
- package/dist/cli/ui.js +990 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/handler/index.d.ts +93 -0
- package/dist/handler/index.js +628 -0
- package/dist/handler/index.js.map +1 -0
- package/dist/handler/walletConnect.d.ts +6 -0
- package/dist/handler/walletConnect.js +623 -0
- package/dist/handler/walletConnect.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +2046 -0
- package/dist/index.js.map +1 -0
- package/dist/session/index.d.ts +20 -0
- package/dist/session/index.js +79 -0
- package/dist/session/index.js.map +1 -0
- package/dist/solana/index.d.ts +5 -0
- package/dist/solana/index.js +302 -0
- package/dist/solana/index.js.map +1 -0
- package/dist/solana/rpc.d.ts +45 -0
- package/dist/solana/rpc.js +120 -0
- package/dist/solana/rpc.js.map +1 -0
- package/dist/solana/simulate.d.ts +41 -0
- package/dist/solana/simulate.js +173 -0
- package/dist/solana/simulate.js.map +1 -0
- package/dist/solana/tx.d.ts +54 -0
- package/dist/solana/tx.js +141 -0
- package/dist/solana/tx.js.map +1 -0
- package/dist/vault/accounts.d.ts +88 -0
- package/dist/vault/accounts.js +126 -0
- package/dist/vault/accounts.js.map +1 -0
- package/dist/vault/config.d.ts +40 -0
- package/dist/vault/config.js +131 -0
- package/dist/vault/config.js.map +1 -0
- package/dist/vault/index.d.ts +122 -0
- package/dist/vault/index.js +580 -0
- package/dist/vault/index.js.map +1 -0
- package/dist/vault/keystore.d.ts +44 -0
- package/dist/vault/keystore.js +118 -0
- package/dist/vault/keystore.js.map +1 -0
- package/dist/vault/mnemonic.d.ts +43 -0
- package/dist/vault/mnemonic.js +37 -0
- package/dist/vault/mnemonic.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2046 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/vault/mnemonic.ts
|
|
4
|
+
import * as bip39 from "bip39";
|
|
5
|
+
function generateMnemonic2(strength = 12) {
|
|
6
|
+
const mnemonic = bip39.generateMnemonic(strength == 12 ? 128 : 256);
|
|
7
|
+
const wordCount = strength;
|
|
8
|
+
return { mnemonic, wordCount };
|
|
9
|
+
}
|
|
10
|
+
function validateMnemonic2(mnemonic) {
|
|
11
|
+
const cleaned = cleanMnemonic(mnemonic);
|
|
12
|
+
return bip39.validateMnemonic(cleaned);
|
|
13
|
+
}
|
|
14
|
+
async function mnemonicToSeed2(mnemonic, passphrase = "") {
|
|
15
|
+
const cleaned = cleanMnemonic(mnemonic);
|
|
16
|
+
if (!validateMnemonic2(cleaned)) {
|
|
17
|
+
throw new Error("Invalid mnemonic: failed wordlist or checksum validation");
|
|
18
|
+
}
|
|
19
|
+
return bip39.mnemonicToSeed(cleaned, passphrase);
|
|
20
|
+
}
|
|
21
|
+
function mnemonicToNumberedWords(mnemonic) {
|
|
22
|
+
return cleanMnemonic(mnemonic).split(" ").map((word, i) => `${i + 1}. ${word}`);
|
|
23
|
+
}
|
|
24
|
+
function wordsToMnemonic(words) {
|
|
25
|
+
return words.map((w) => w.trim().toLowerCase()).join(" ");
|
|
26
|
+
}
|
|
27
|
+
function cleanMnemonic(mnemonic) {
|
|
28
|
+
return mnemonic.trim().toLowerCase().replace(/\s+/g, " ");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/vault/keystore.ts
|
|
32
|
+
import crypto from "crypto";
|
|
33
|
+
import fs from "fs";
|
|
34
|
+
import os from "os";
|
|
35
|
+
import path from "path";
|
|
36
|
+
var VAULT_VERSION = 1;
|
|
37
|
+
var PBKDF2_ITERATIONS = 21e4;
|
|
38
|
+
var PBKDF2_DIGEST = "sha512";
|
|
39
|
+
var KEY_LENGTH = 32;
|
|
40
|
+
var SALT_LENGTH = 32;
|
|
41
|
+
var IV_LENGTH = 16;
|
|
42
|
+
var CIPHER = "aes-256-gcm";
|
|
43
|
+
function getWalletDir() {
|
|
44
|
+
return path.join(os.homedir(), ".wallet");
|
|
45
|
+
}
|
|
46
|
+
function getVaultPath() {
|
|
47
|
+
return path.join(getWalletDir(), "vault.enc");
|
|
48
|
+
}
|
|
49
|
+
function getConfigPath() {
|
|
50
|
+
return path.join(getWalletDir(), "config.json");
|
|
51
|
+
}
|
|
52
|
+
function getSessionsPath() {
|
|
53
|
+
return path.join(getWalletDir(), "sessions.json");
|
|
54
|
+
}
|
|
55
|
+
var SESSIONS_FILE = getSessionsPath();
|
|
56
|
+
function ensureWalletDir() {
|
|
57
|
+
const dir = getWalletDir();
|
|
58
|
+
if (!fs.existsSync(dir)) {
|
|
59
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function vaultExists() {
|
|
63
|
+
return fs.existsSync(getVaultPath());
|
|
64
|
+
}
|
|
65
|
+
async function saveVault(data, password) {
|
|
66
|
+
ensureWalletDir();
|
|
67
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
68
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
69
|
+
const key = await deriveKey(password, salt);
|
|
70
|
+
const plaintext = JSON.stringify(data);
|
|
71
|
+
const cipher = crypto.createCipheriv(CIPHER, key, iv);
|
|
72
|
+
const encrypted = Buffer.concat([
|
|
73
|
+
cipher.update(plaintext, "utf8"),
|
|
74
|
+
cipher.final()
|
|
75
|
+
]);
|
|
76
|
+
const authTag = cipher.getAuthTag();
|
|
77
|
+
const vault = {
|
|
78
|
+
version: VAULT_VERSION,
|
|
79
|
+
salt: salt.toString("hex"),
|
|
80
|
+
iv: iv.toString("hex"),
|
|
81
|
+
authTag: authTag.toString("hex"),
|
|
82
|
+
ciphertext: encrypted.toString("hex")
|
|
83
|
+
};
|
|
84
|
+
fs.writeFileSync(getVaultPath(), JSON.stringify(vault, null, 2), {
|
|
85
|
+
mode: 384
|
|
86
|
+
// owner read/write only
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function loadVault(password) {
|
|
90
|
+
if (!vaultExists()) {
|
|
91
|
+
throw new Error("No vault found. Run `wallet init` to create one.");
|
|
92
|
+
}
|
|
93
|
+
const raw = fs.readFileSync(getVaultPath(), "utf8");
|
|
94
|
+
const vault = JSON.parse(raw);
|
|
95
|
+
if (vault.version !== VAULT_VERSION) {
|
|
96
|
+
throw new Error(`Unsupported vault version: ${vault.version}`);
|
|
97
|
+
}
|
|
98
|
+
const salt = Buffer.from(vault.salt, "hex");
|
|
99
|
+
const iv = Buffer.from(vault.iv, "hex");
|
|
100
|
+
const authTag = Buffer.from(vault.authTag, "hex");
|
|
101
|
+
const ciphertext = Buffer.from(vault.ciphertext, "hex");
|
|
102
|
+
const key = await deriveKey(password, salt);
|
|
103
|
+
try {
|
|
104
|
+
const decipher = crypto.createDecipheriv(CIPHER, key, iv);
|
|
105
|
+
decipher.setAuthTag(authTag);
|
|
106
|
+
const decrypted = Buffer.concat([
|
|
107
|
+
decipher.update(ciphertext),
|
|
108
|
+
decipher.final()
|
|
109
|
+
]);
|
|
110
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
111
|
+
} catch {
|
|
112
|
+
throw new Error("Decryption failed: wrong password or vault is corrupted.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function changePassword(oldPassword, newPassword) {
|
|
116
|
+
const data = await loadVault(oldPassword);
|
|
117
|
+
await saveVault(data, newPassword);
|
|
118
|
+
}
|
|
119
|
+
function deriveKey(password, salt) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
crypto.pbkdf2(
|
|
122
|
+
password,
|
|
123
|
+
salt,
|
|
124
|
+
PBKDF2_ITERATIONS,
|
|
125
|
+
KEY_LENGTH,
|
|
126
|
+
PBKDF2_DIGEST,
|
|
127
|
+
(err, key) => {
|
|
128
|
+
if (err) reject(err);
|
|
129
|
+
else resolve(key);
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/vault/accounts.ts
|
|
136
|
+
import { derivePath } from "ed25519-hd-key";
|
|
137
|
+
import nacl from "tweetnacl";
|
|
138
|
+
import bs58 from "bs58";
|
|
139
|
+
function deriveAccount(seed, index, name = `Account ${index + 1}`) {
|
|
140
|
+
const path2 = derivationPath(index);
|
|
141
|
+
const { key: privateKeyBytes } = derivePath(path2, seed.toString("hex"));
|
|
142
|
+
const keypair = nacl.sign.keyPair.fromSeed(privateKeyBytes);
|
|
143
|
+
const publicKey = bs58.encode(Buffer.from(keypair.publicKey));
|
|
144
|
+
return {
|
|
145
|
+
index,
|
|
146
|
+
name,
|
|
147
|
+
publicKey,
|
|
148
|
+
secretKey: keypair.secretKey,
|
|
149
|
+
// 64 bytes: seed + public key
|
|
150
|
+
derivationPath: path2,
|
|
151
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function deriveAccounts(seed, accountStore) {
|
|
155
|
+
return accountStore.accounts.map(
|
|
156
|
+
(account) => deriveAccount(seed, account.index, account.name)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async function deriveAccountFromMnemonic(mnemonic, index, name, passphrase) {
|
|
160
|
+
const seed = await mnemonicToSeed2(mnemonic, passphrase);
|
|
161
|
+
return deriveAccount(seed, index, name);
|
|
162
|
+
}
|
|
163
|
+
function createAccountStore(firstAccountName = "Account 1") {
|
|
164
|
+
return {
|
|
165
|
+
accounts: [
|
|
166
|
+
{
|
|
167
|
+
index: 0,
|
|
168
|
+
name: firstAccountName,
|
|
169
|
+
publicKey: "",
|
|
170
|
+
// filled in after derivation
|
|
171
|
+
derivationPath: derivationPath(0),
|
|
172
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
activeIndex: 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function addAccount(store, seed, name) {
|
|
179
|
+
const nextIndex = store.accounts.length > 0 ? Math.max(...store.accounts.map((a) => a.index)) + 1 : 0;
|
|
180
|
+
const accountName = name ?? `Account ${nextIndex + 1}`;
|
|
181
|
+
const keypair = deriveAccount(seed, nextIndex, accountName);
|
|
182
|
+
const newAccount = {
|
|
183
|
+
index: nextIndex,
|
|
184
|
+
name: accountName,
|
|
185
|
+
publicKey: keypair.publicKey,
|
|
186
|
+
derivationPath: keypair.derivationPath,
|
|
187
|
+
createdAt: keypair.createdAt
|
|
188
|
+
};
|
|
189
|
+
const updatedStore = {
|
|
190
|
+
...store,
|
|
191
|
+
accounts: [...store.accounts, newAccount]
|
|
192
|
+
};
|
|
193
|
+
return { store: updatedStore, keypair };
|
|
194
|
+
}
|
|
195
|
+
function setActiveAccount(store, index) {
|
|
196
|
+
const exists = store.accounts.find((a) => a.index === index);
|
|
197
|
+
if (!exists) {
|
|
198
|
+
throw new Error(`Account index ${index} does not exist.`);
|
|
199
|
+
}
|
|
200
|
+
return { ...store, activeIndex: index };
|
|
201
|
+
}
|
|
202
|
+
function renameAccount(store, index, newName) {
|
|
203
|
+
return {
|
|
204
|
+
...store,
|
|
205
|
+
accounts: store.accounts.map(
|
|
206
|
+
(a) => a.index === index ? { ...a, name: newName } : a
|
|
207
|
+
)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function getActiveAccount(store) {
|
|
211
|
+
const account = store.accounts.find((a) => a.index === store.activeIndex);
|
|
212
|
+
if (!account) throw new Error("No active account found.");
|
|
213
|
+
return account;
|
|
214
|
+
}
|
|
215
|
+
function signMessage(message, secretKey) {
|
|
216
|
+
const signature = nacl.sign.detached(message, secretKey);
|
|
217
|
+
return bs58.encode(Buffer.from(signature));
|
|
218
|
+
}
|
|
219
|
+
function formatAccount(account, isActive) {
|
|
220
|
+
const activeMarker = isActive ? "\u25CF" : "\u25CB";
|
|
221
|
+
const shortKey = `${account.publicKey.slice(0, 4)}...${account.publicKey.slice(-4)}`;
|
|
222
|
+
return `${activeMarker} [${account.index}] ${account.name.padEnd(20)} ${shortKey}`;
|
|
223
|
+
}
|
|
224
|
+
function derivationPath(index) {
|
|
225
|
+
return `m/44'/501'/${index}'/0'`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/vault/config.ts
|
|
229
|
+
import fs2 from "fs";
|
|
230
|
+
var HELIUS_API_KEY = process.env.HELIUS_API_KEY;
|
|
231
|
+
var RPC_URLS = {
|
|
232
|
+
"mainnet-beta": HELIUS_API_KEY ? `https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}` : "https://api.mainnet-beta.solana.com",
|
|
233
|
+
devnet: HELIUS_API_KEY ? `https://devnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}` : "https://api.devnet.solana.com",
|
|
234
|
+
testnet: "https://api.testnet.solana.com",
|
|
235
|
+
localnet: "http://localhost:8899"
|
|
236
|
+
};
|
|
237
|
+
var CONFIG_VERSION = 1;
|
|
238
|
+
function configExists() {
|
|
239
|
+
return fs2.existsSync(getConfigPath());
|
|
240
|
+
}
|
|
241
|
+
function loadConfig() {
|
|
242
|
+
if (!configExists()) {
|
|
243
|
+
throw new Error("No config found. Run `wallet init` first.");
|
|
244
|
+
}
|
|
245
|
+
const raw = fs2.readFileSync(getConfigPath(), "utf8");
|
|
246
|
+
return JSON.parse(raw);
|
|
247
|
+
}
|
|
248
|
+
function saveConfig(config) {
|
|
249
|
+
ensureWalletDir();
|
|
250
|
+
const updated = { ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
251
|
+
fs2.writeFileSync(getConfigPath(), JSON.stringify(updated, null, 2), {
|
|
252
|
+
mode: 384
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function createConfig(walletConnectProjectId = "", cluster = "devnet") {
|
|
256
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
257
|
+
return {
|
|
258
|
+
version: CONFIG_VERSION,
|
|
259
|
+
cluster,
|
|
260
|
+
rpcUrl: RPC_URLS[cluster],
|
|
261
|
+
walletConnectProjectId,
|
|
262
|
+
accountStore: createAccountStore("Account 1"),
|
|
263
|
+
createdAt: now,
|
|
264
|
+
updatedAt: now
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function updateCluster(cluster) {
|
|
268
|
+
const config = loadConfig();
|
|
269
|
+
config.cluster = cluster;
|
|
270
|
+
config.rpcUrl = RPC_URLS[cluster];
|
|
271
|
+
saveConfig(config);
|
|
272
|
+
}
|
|
273
|
+
function updateRpcUrl(url) {
|
|
274
|
+
const config = loadConfig();
|
|
275
|
+
config.rpcUrl = url;
|
|
276
|
+
saveConfig(config);
|
|
277
|
+
}
|
|
278
|
+
function updateAccountStore(accountStore) {
|
|
279
|
+
const config = loadConfig();
|
|
280
|
+
config.accountStore = accountStore;
|
|
281
|
+
saveConfig(config);
|
|
282
|
+
}
|
|
283
|
+
function getRpcUrl() {
|
|
284
|
+
return loadConfig().rpcUrl;
|
|
285
|
+
}
|
|
286
|
+
function getCluster() {
|
|
287
|
+
return loadConfig().cluster;
|
|
288
|
+
}
|
|
289
|
+
function getAccountStore() {
|
|
290
|
+
return loadConfig().accountStore;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/vault/index.ts
|
|
294
|
+
var WalletVault = class {
|
|
295
|
+
_keypairs = [];
|
|
296
|
+
_seed = null;
|
|
297
|
+
// cached for agent account derivation
|
|
298
|
+
_config = null;
|
|
299
|
+
_unlocked = false;
|
|
300
|
+
_mnemonic = null;
|
|
301
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
302
|
+
/**
|
|
303
|
+
* Initialize a brand new wallet.
|
|
304
|
+
* Generates a mnemonic, encrypts it, writes config.
|
|
305
|
+
* Returns the mnemonic for the user to write down — shown ONCE.
|
|
306
|
+
*/
|
|
307
|
+
async init(password, options = {}) {
|
|
308
|
+
if (vaultExists()) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
"Wallet already exists. Use `wallet unlock` to access it."
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
const { mnemonic } = generateMnemonic2(options.strength ?? 12);
|
|
314
|
+
const vaultData = {
|
|
315
|
+
mnemonic,
|
|
316
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
317
|
+
version: 1
|
|
318
|
+
};
|
|
319
|
+
await saveVault(vaultData, password);
|
|
320
|
+
const config = createConfig(
|
|
321
|
+
options.walletConnectProjectId ?? "",
|
|
322
|
+
options.cluster ?? "devnet"
|
|
323
|
+
);
|
|
324
|
+
if (options.firstAccountName) {
|
|
325
|
+
config.accountStore.accounts[0].name = options.firstAccountName;
|
|
326
|
+
}
|
|
327
|
+
const seed = await mnemonicToSeed2(mnemonic);
|
|
328
|
+
const firstKeypair = deriveAccount(
|
|
329
|
+
seed,
|
|
330
|
+
0,
|
|
331
|
+
config.accountStore.accounts[0].name
|
|
332
|
+
);
|
|
333
|
+
config.accountStore.accounts[0].publicKey = firstKeypair.publicKey;
|
|
334
|
+
saveConfig(config);
|
|
335
|
+
return mnemonic;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Unlock the wallet for a session.
|
|
339
|
+
* Decrypts the vault, derives all account keypairs into memory.
|
|
340
|
+
*/
|
|
341
|
+
async unlock(password) {
|
|
342
|
+
const vaultData = await loadVault(password);
|
|
343
|
+
const config = loadConfig();
|
|
344
|
+
const seed = await mnemonicToSeed2(vaultData.mnemonic);
|
|
345
|
+
this._seed = seed;
|
|
346
|
+
this._mnemonic = vaultData.mnemonic;
|
|
347
|
+
this._keypairs = deriveAccounts(seed, config.accountStore);
|
|
348
|
+
this._config = config;
|
|
349
|
+
this._unlocked = true;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Restore wallet from an existing mnemonic (import flow).
|
|
353
|
+
*/
|
|
354
|
+
async restore(mnemonic, password, options = {}) {
|
|
355
|
+
if (!validateMnemonic2(mnemonic)) {
|
|
356
|
+
throw new Error("Invalid mnemonic phrase.");
|
|
357
|
+
}
|
|
358
|
+
const vaultData = {
|
|
359
|
+
mnemonic,
|
|
360
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
361
|
+
version: 1
|
|
362
|
+
};
|
|
363
|
+
await saveVault(vaultData, password);
|
|
364
|
+
const config = createConfig("", options.cluster ?? "devnet");
|
|
365
|
+
const seed = await mnemonicToSeed2(mnemonic);
|
|
366
|
+
const firstKeypair = deriveAccount(seed, 0, "Account 1");
|
|
367
|
+
config.accountStore.accounts[0].publicKey = firstKeypair.publicKey;
|
|
368
|
+
saveConfig(config);
|
|
369
|
+
}
|
|
370
|
+
lock() {
|
|
371
|
+
this._keypairs = [];
|
|
372
|
+
this._mnemonic = null;
|
|
373
|
+
this._seed = null;
|
|
374
|
+
this._config = null;
|
|
375
|
+
this._unlocked = false;
|
|
376
|
+
}
|
|
377
|
+
// ── Account Management ─────────────────────────────────────────────────────
|
|
378
|
+
/**
|
|
379
|
+
* Get the currently active keypair (used for signing + dApp connections).
|
|
380
|
+
*/
|
|
381
|
+
getActiveKeypair() {
|
|
382
|
+
this.assertUnlocked();
|
|
383
|
+
const active = getActiveAccount(this._config.accountStore);
|
|
384
|
+
const keypair = this._keypairs.find((k) => k.index === active.index);
|
|
385
|
+
if (!keypair) throw new Error("Active keypair not found in session.");
|
|
386
|
+
return keypair;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Lock the wallet — wipe all keypairs from memory.
|
|
390
|
+
*/
|
|
391
|
+
/**
|
|
392
|
+
* Returns the mnemonic — only available while unlocked.
|
|
393
|
+
* Used by agent_connect to derive new accounts on demand.
|
|
394
|
+
*/
|
|
395
|
+
getMnemonic() {
|
|
396
|
+
if (!this._mnemonic) throw new Error("Wallet is locked");
|
|
397
|
+
return this._mnemonic;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Reload config and re-derive keypairs from seed.
|
|
401
|
+
* Call after adding new accounts so the vault picks them up.
|
|
402
|
+
*/
|
|
403
|
+
async reload() {
|
|
404
|
+
if (!this._seed) throw new Error("Wallet is locked");
|
|
405
|
+
this._config = loadConfig();
|
|
406
|
+
this._keypairs = deriveAccounts(this._seed, this._config.accountStore);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get a keypair by account index.
|
|
410
|
+
*/
|
|
411
|
+
getKeypair(index) {
|
|
412
|
+
this.assertUnlocked();
|
|
413
|
+
const keypair = this._keypairs.find((k) => k.index === index);
|
|
414
|
+
if (!keypair) throw new Error(`Account ${index} not found.`);
|
|
415
|
+
return keypair;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get all loaded keypairs.
|
|
419
|
+
*/
|
|
420
|
+
getAllKeypairs() {
|
|
421
|
+
this.assertUnlocked();
|
|
422
|
+
return [...this._keypairs];
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Add a new derived account.
|
|
426
|
+
*/
|
|
427
|
+
async addAccount(name, password) {
|
|
428
|
+
this.assertUnlocked();
|
|
429
|
+
if (!password) throw new Error("Password required to add a new account.");
|
|
430
|
+
const vaultData = await loadVault(password);
|
|
431
|
+
const seed = await mnemonicToSeed2(vaultData.mnemonic);
|
|
432
|
+
const { store, keypair } = addAccount(
|
|
433
|
+
this._config.accountStore,
|
|
434
|
+
seed,
|
|
435
|
+
name
|
|
436
|
+
);
|
|
437
|
+
this._config.accountStore = store;
|
|
438
|
+
this._keypairs.push(keypair);
|
|
439
|
+
updateAccountStore(store);
|
|
440
|
+
return keypair;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Switch the active account.
|
|
444
|
+
*/
|
|
445
|
+
setActiveAccount(index) {
|
|
446
|
+
this.assertUnlocked();
|
|
447
|
+
const updated = setActiveAccount(this._config.accountStore, index);
|
|
448
|
+
this._config.accountStore = updated;
|
|
449
|
+
updateAccountStore(updated);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Rename an account.
|
|
453
|
+
*/
|
|
454
|
+
renameAccount(index, newName) {
|
|
455
|
+
this.assertUnlocked();
|
|
456
|
+
const updated = renameAccount(this._config.accountStore, index, newName);
|
|
457
|
+
this._config.accountStore = updated;
|
|
458
|
+
this._keypairs = this._keypairs.map(
|
|
459
|
+
(k) => k.index === index ? { ...k, name: newName } : k
|
|
460
|
+
);
|
|
461
|
+
updateAccountStore(updated);
|
|
462
|
+
}
|
|
463
|
+
// ── Display ────────────────────────────────────────────────────────────────
|
|
464
|
+
/**
|
|
465
|
+
* Format all accounts for terminal display.
|
|
466
|
+
*/
|
|
467
|
+
listAccounts() {
|
|
468
|
+
this.assertUnlocked();
|
|
469
|
+
const activeIndex = this._config.accountStore.activeIndex;
|
|
470
|
+
return this._keypairs.map((k) => formatAccount(k, k.index === activeIndex));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Sign a message with the active account.
|
|
474
|
+
*/
|
|
475
|
+
signMessage(message) {
|
|
476
|
+
const keypair = this.getActiveKeypair();
|
|
477
|
+
return signMessage(message, keypair.secretKey);
|
|
478
|
+
}
|
|
479
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
480
|
+
get isUnlocked() {
|
|
481
|
+
return this._unlocked;
|
|
482
|
+
}
|
|
483
|
+
get config() {
|
|
484
|
+
this.assertUnlocked();
|
|
485
|
+
return this._config;
|
|
486
|
+
}
|
|
487
|
+
static exists() {
|
|
488
|
+
return vaultExists() && configExists();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Display mnemonic as numbered words — for backup verification flow.
|
|
492
|
+
*/
|
|
493
|
+
static formatMnemonicForDisplay(mnemonic) {
|
|
494
|
+
const words = mnemonicToNumberedWords(mnemonic);
|
|
495
|
+
const cols = 3;
|
|
496
|
+
const rows = [];
|
|
497
|
+
for (let i = 0; i < words.length; i += cols) {
|
|
498
|
+
rows.push(
|
|
499
|
+
words.slice(i, i + cols).map((w) => w.padEnd(18)).join(" ")
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return rows.join("\n");
|
|
503
|
+
}
|
|
504
|
+
// ── Internal ───────────────────────────────────────────────────────────────
|
|
505
|
+
assertUnlocked() {
|
|
506
|
+
if (!this._unlocked) {
|
|
507
|
+
throw new Error("Wallet is locked. Run `wallet unlock` first.");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Find an account by name or create it if it doesn't exist.
|
|
512
|
+
* Uses the cached seed — no password needed.
|
|
513
|
+
* This is the primary way agents acquire their account.
|
|
514
|
+
*/
|
|
515
|
+
async findOrCreate(name) {
|
|
516
|
+
this.assertUnlocked();
|
|
517
|
+
const existing = this._keypairs.find((k) => k.name === name);
|
|
518
|
+
if (existing) return existing;
|
|
519
|
+
const nextIndex = this._config.accountStore.accounts.length;
|
|
520
|
+
const { store, keypair } = addAccount(
|
|
521
|
+
this._config.accountStore,
|
|
522
|
+
this._seed,
|
|
523
|
+
name
|
|
524
|
+
);
|
|
525
|
+
this._config.accountStore = store;
|
|
526
|
+
this._keypairs.push(keypair);
|
|
527
|
+
updateAccountStore(store);
|
|
528
|
+
console.log(
|
|
529
|
+
`[Vault] Created account "${name}" at index ${nextIndex} (${keypair.publicKey})`
|
|
530
|
+
);
|
|
531
|
+
return keypair;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Find an account by name. Returns null if not found.
|
|
535
|
+
*/
|
|
536
|
+
findByName(name) {
|
|
537
|
+
this.assertUnlocked();
|
|
538
|
+
return this._keypairs.find((k) => k.name === name) ?? null;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/solana/rpc.ts
|
|
543
|
+
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
544
|
+
var _connection = null;
|
|
545
|
+
function getConnection(forceNew = false) {
|
|
546
|
+
if (!_connection || forceNew) {
|
|
547
|
+
const rpcUrl = getRpcUrl();
|
|
548
|
+
_connection = new Connection(rpcUrl, "confirmed");
|
|
549
|
+
}
|
|
550
|
+
return _connection;
|
|
551
|
+
}
|
|
552
|
+
function resetConnection() {
|
|
553
|
+
_connection = null;
|
|
554
|
+
}
|
|
555
|
+
async function getBalance(publicKeyStr) {
|
|
556
|
+
const connection = getConnection();
|
|
557
|
+
const pubkey = new PublicKey(publicKeyStr);
|
|
558
|
+
const lamports = await connection.getBalance(pubkey);
|
|
559
|
+
return {
|
|
560
|
+
lamports,
|
|
561
|
+
sol: lamports / LAMPORTS_PER_SOL
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
async function requestAirdrop(publicKeyStr, solAmount = 1) {
|
|
565
|
+
const cluster = getCluster();
|
|
566
|
+
if (cluster === "mainnet-beta") {
|
|
567
|
+
throw new Error("Airdrops are not available on mainnet.");
|
|
568
|
+
}
|
|
569
|
+
const connection = getConnection();
|
|
570
|
+
const pubkey = new PublicKey(publicKeyStr);
|
|
571
|
+
const lamports = solAmount * LAMPORTS_PER_SOL;
|
|
572
|
+
const signature = await connection.requestAirdrop(pubkey, lamports);
|
|
573
|
+
await connection.confirmTransaction(signature, "confirmed");
|
|
574
|
+
return signature;
|
|
575
|
+
}
|
|
576
|
+
async function getLatestBlockhash() {
|
|
577
|
+
const connection = getConnection();
|
|
578
|
+
return connection.getLatestBlockhash("confirmed");
|
|
579
|
+
}
|
|
580
|
+
async function confirmTransaction(signature, timeoutMs = 3e4) {
|
|
581
|
+
const connection = getConnection();
|
|
582
|
+
const { blockhash, lastValidBlockHeight } = await getLatestBlockhash();
|
|
583
|
+
const result = await connection.confirmTransaction(
|
|
584
|
+
{ signature, blockhash, lastValidBlockHeight },
|
|
585
|
+
"confirmed"
|
|
586
|
+
);
|
|
587
|
+
return !result.value.err;
|
|
588
|
+
}
|
|
589
|
+
async function healthCheck() {
|
|
590
|
+
try {
|
|
591
|
+
const connection = getConnection();
|
|
592
|
+
await connection.getVersion();
|
|
593
|
+
return true;
|
|
594
|
+
} catch {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/solana/simulate.ts
|
|
600
|
+
import {
|
|
601
|
+
Transaction,
|
|
602
|
+
VersionedTransaction,
|
|
603
|
+
LAMPORTS_PER_SOL as LAMPORTS_PER_SOL2
|
|
604
|
+
} from "@solana/web3.js";
|
|
605
|
+
async function simulateTransaction(serializedTx, signerPublicKey, conn) {
|
|
606
|
+
const connection = conn ?? getConnection();
|
|
607
|
+
try {
|
|
608
|
+
const txBuffer = Buffer.from(serializedTx, "base64");
|
|
609
|
+
let simulation;
|
|
610
|
+
try {
|
|
611
|
+
const versionedTx = VersionedTransaction.deserialize(txBuffer);
|
|
612
|
+
simulation = await connection.simulateTransaction(versionedTx, {
|
|
613
|
+
sigVerify: false,
|
|
614
|
+
// skip sig verification — wallet hasn't signed yet
|
|
615
|
+
replaceRecentBlockhash: true
|
|
616
|
+
});
|
|
617
|
+
} catch {
|
|
618
|
+
const legacyTx = Transaction.from(txBuffer);
|
|
619
|
+
simulation = await connection.simulateTransaction(legacyTx, []);
|
|
620
|
+
}
|
|
621
|
+
const { value } = simulation;
|
|
622
|
+
return parseSimulationResult(value, signerPublicKey, txBuffer);
|
|
623
|
+
} catch (err) {
|
|
624
|
+
return {
|
|
625
|
+
success: false,
|
|
626
|
+
error: err instanceof Error ? err.message : "Simulation failed",
|
|
627
|
+
logs: [],
|
|
628
|
+
accountChanges: [],
|
|
629
|
+
tokenChanges: [],
|
|
630
|
+
computeUnitsConsumed: 0,
|
|
631
|
+
programIds: [],
|
|
632
|
+
fee: 0,
|
|
633
|
+
rawLogs: []
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function parseSimulationResult(value, signerPublicKey, txBuffer) {
|
|
638
|
+
const success = !value.err;
|
|
639
|
+
const logs = value.logs ?? [];
|
|
640
|
+
const accountChanges = (value.accounts ?? []).map((account, i) => {
|
|
641
|
+
if (!account) return null;
|
|
642
|
+
const preBalance = account.lamports ?? 0;
|
|
643
|
+
const postBalance = account.lamports ?? 0;
|
|
644
|
+
return {
|
|
645
|
+
address: signerPublicKey,
|
|
646
|
+
// simplified — full impl maps account indices
|
|
647
|
+
preBalance,
|
|
648
|
+
postBalance,
|
|
649
|
+
solDelta: (postBalance - preBalance) / LAMPORTS_PER_SOL2,
|
|
650
|
+
isWritable: true,
|
|
651
|
+
isSigner: i === 0
|
|
652
|
+
};
|
|
653
|
+
}).filter(Boolean);
|
|
654
|
+
const programIds = extractProgramIds(logs);
|
|
655
|
+
const computeUnitsConsumed = extractComputeUnits(logs);
|
|
656
|
+
const fee = 5e3;
|
|
657
|
+
return {
|
|
658
|
+
success,
|
|
659
|
+
error: value.err ? JSON.stringify(value.err) : null,
|
|
660
|
+
logs: formatLogs(logs),
|
|
661
|
+
accountChanges,
|
|
662
|
+
tokenChanges: [],
|
|
663
|
+
// full SPL parsing requires additional RPC calls
|
|
664
|
+
computeUnitsConsumed,
|
|
665
|
+
programIds,
|
|
666
|
+
fee,
|
|
667
|
+
rawLogs: logs
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function extractProgramIds(logs) {
|
|
671
|
+
const ids = /* @__PURE__ */ new Set();
|
|
672
|
+
const invokePattern = /Program (\w+) invoke/;
|
|
673
|
+
for (const log of logs) {
|
|
674
|
+
const match = log.match(invokePattern);
|
|
675
|
+
if (match) ids.add(match[1]);
|
|
676
|
+
}
|
|
677
|
+
return Array.from(ids);
|
|
678
|
+
}
|
|
679
|
+
function extractComputeUnits(logs) {
|
|
680
|
+
for (const log of logs) {
|
|
681
|
+
const match = log.match(/consumed (\d+) of/);
|
|
682
|
+
if (match) return parseInt(match[1], 10);
|
|
683
|
+
}
|
|
684
|
+
return 0;
|
|
685
|
+
}
|
|
686
|
+
function formatLogs(logs) {
|
|
687
|
+
return logs.map((log) => {
|
|
688
|
+
return log.replace(/\b([1-9A-HJ-NP-Za-km-z]{32,44})\b/g, (addr) => {
|
|
689
|
+
const known = KNOWN_PROGRAMS[addr];
|
|
690
|
+
return known ? known : `${addr.slice(0, 4)}..${addr.slice(-4)}`;
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
var KNOWN_PROGRAMS = {
|
|
695
|
+
"11111111111111111111111111111111": "System Program",
|
|
696
|
+
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: "SPL Token",
|
|
697
|
+
JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: "Jupiter V6",
|
|
698
|
+
"9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin": "Serum DEX",
|
|
699
|
+
ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJe1bv: "Associated Token",
|
|
700
|
+
SysvarRent111111111111111111111111111111111: "Sysvar Rent",
|
|
701
|
+
SysvarC1ock11111111111111111111111111111111: "Sysvar Clock"
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/solana/tx.ts
|
|
705
|
+
import {
|
|
706
|
+
Transaction as Transaction2,
|
|
707
|
+
VersionedTransaction as VersionedTransaction2,
|
|
708
|
+
Keypair
|
|
709
|
+
} from "@solana/web3.js";
|
|
710
|
+
import nacl2 from "tweetnacl";
|
|
711
|
+
import bs582 from "bs58";
|
|
712
|
+
async function signTransaction(serializedTx, keypair) {
|
|
713
|
+
const txBuffer = Buffer.from(serializedTx, "base64");
|
|
714
|
+
const solanaKeypair = toSolanaKeypair(keypair);
|
|
715
|
+
try {
|
|
716
|
+
const versionedTx = VersionedTransaction2.deserialize(txBuffer);
|
|
717
|
+
versionedTx.sign([solanaKeypair]);
|
|
718
|
+
const serialized = Buffer.from(versionedTx.serialize()).toString("base64");
|
|
719
|
+
const signature = bs582.encode(versionedTx.signatures[0]);
|
|
720
|
+
return { serialized, signature };
|
|
721
|
+
} catch {
|
|
722
|
+
const legacyTx = Transaction2.from(txBuffer);
|
|
723
|
+
legacyTx.partialSign(solanaKeypair);
|
|
724
|
+
const serialized = legacyTx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
725
|
+
const signature = bs582.encode(legacyTx.signature);
|
|
726
|
+
return { serialized, signature };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async function signAllTransactions(serializedTxs, keypair) {
|
|
730
|
+
return Promise.all(serializedTxs.map((tx) => signTransaction(tx, keypair)));
|
|
731
|
+
}
|
|
732
|
+
async function signAndSendTransaction(serializedTx, keypair, options = {}, conn) {
|
|
733
|
+
const { serialized } = await signTransaction(serializedTx, keypair);
|
|
734
|
+
return sendSignedTransaction(serialized, options, conn);
|
|
735
|
+
}
|
|
736
|
+
async function sendSignedTransaction(serializedSignedTx, options = {}, conn) {
|
|
737
|
+
const connection = conn ?? getConnection();
|
|
738
|
+
const txBuffer = Buffer.from(serializedSignedTx, "base64");
|
|
739
|
+
const sendOptions = {
|
|
740
|
+
skipPreflight: false,
|
|
741
|
+
// run preflight checks
|
|
742
|
+
preflightCommitment: "confirmed",
|
|
743
|
+
...options
|
|
744
|
+
};
|
|
745
|
+
let txHash;
|
|
746
|
+
try {
|
|
747
|
+
const versionedTx = VersionedTransaction2.deserialize(txBuffer);
|
|
748
|
+
txHash = await connection.sendTransaction(versionedTx, sendOptions);
|
|
749
|
+
} catch {
|
|
750
|
+
const legacyTx = Transaction2.from(txBuffer);
|
|
751
|
+
txHash = await connection.sendRawTransaction(
|
|
752
|
+
legacyTx.serialize(),
|
|
753
|
+
sendOptions
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
757
|
+
const confirmResult = await connection.confirmTransaction(
|
|
758
|
+
{ signature: txHash, blockhash, lastValidBlockHeight },
|
|
759
|
+
"confirmed"
|
|
760
|
+
);
|
|
761
|
+
return {
|
|
762
|
+
txHash,
|
|
763
|
+
confirmed: !confirmResult.value.err
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function signOffchainMessage(message, keypair) {
|
|
767
|
+
const signature = nacl2.sign.detached(message, keypair.secretKey);
|
|
768
|
+
return bs582.encode(Buffer.from(signature));
|
|
769
|
+
}
|
|
770
|
+
function toSolanaKeypair(keypair) {
|
|
771
|
+
return Keypair.fromSecretKey(keypair.secretKey);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/cli/ui.tsx
|
|
775
|
+
import React2, { useState as useState2, useEffect, useRef as useRef2 } from "react";
|
|
776
|
+
import { render as render2, Box as Box2, Text as Text2, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
777
|
+
|
|
778
|
+
// src/cli/commands.ts
|
|
779
|
+
import chalk2 from "chalk";
|
|
780
|
+
|
|
781
|
+
// src/handler/walletConnect.ts
|
|
782
|
+
import SignClientPkg from "@walletconnect/sign-client";
|
|
783
|
+
import { getSdkError } from "@walletconnect/utils";
|
|
784
|
+
import chalk from "chalk";
|
|
785
|
+
|
|
786
|
+
// src/handler/index.ts
|
|
787
|
+
import bs583 from "bs58";
|
|
788
|
+
var ERROR_CODES = {
|
|
789
|
+
USER_REJECTED: 4001,
|
|
790
|
+
UNAUTHORIZED: 4100,
|
|
791
|
+
UNSUPPORTED_METHOD: 4200,
|
|
792
|
+
DISCONNECTED: 4900,
|
|
793
|
+
CHAIN_DISCONNECTED: 4901
|
|
794
|
+
};
|
|
795
|
+
var WalletRequestHandler = class {
|
|
796
|
+
vault;
|
|
797
|
+
constructor(vault) {
|
|
798
|
+
this.vault = vault;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Handle any incoming wallet request.
|
|
802
|
+
* This is the single entry point — all bridges call this.
|
|
803
|
+
*/
|
|
804
|
+
async handle(request) {
|
|
805
|
+
try {
|
|
806
|
+
switch (request.method) {
|
|
807
|
+
case "solana_connect":
|
|
808
|
+
return this.handleConnect(request);
|
|
809
|
+
case "solana_disconnect":
|
|
810
|
+
return this.handleDisconnect(request);
|
|
811
|
+
case "solana_signTransaction":
|
|
812
|
+
return this.handleSignTransaction(request);
|
|
813
|
+
case "solana_signAndSendTransaction":
|
|
814
|
+
return this.handleSignAndSend(request);
|
|
815
|
+
case "solana_signAllTransactions":
|
|
816
|
+
return this.handleSignAll(request);
|
|
817
|
+
case "solana_signMessage":
|
|
818
|
+
return this.handleSignMessage(request);
|
|
819
|
+
default:
|
|
820
|
+
return this.errorResponse(
|
|
821
|
+
request.id,
|
|
822
|
+
ERROR_CODES.UNSUPPORTED_METHOD,
|
|
823
|
+
`Method not supported: ${request.method}`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
return this.errorResponse(
|
|
828
|
+
request.id,
|
|
829
|
+
4e3,
|
|
830
|
+
err instanceof Error ? err.message : "Unknown error"
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// ── Connect / Disconnect ───────────────────────────────────────────────────
|
|
835
|
+
async handleConnect(request) {
|
|
836
|
+
const keypair = this.vault.getActiveKeypair();
|
|
837
|
+
return {
|
|
838
|
+
id: request.id,
|
|
839
|
+
result: {
|
|
840
|
+
publicKey: keypair.publicKey
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
async handleDisconnect(request) {
|
|
845
|
+
return { id: request.id, result: { disconnected: true } };
|
|
846
|
+
}
|
|
847
|
+
// ── Sign Transaction ───────────────────────────────────────────────────────
|
|
848
|
+
async handleSignTransaction(request) {
|
|
849
|
+
const tx = request.params.transaction;
|
|
850
|
+
if (!tx) return this.errorResponse(request.id, 4e3, "Missing transaction");
|
|
851
|
+
const keypair = this.vault.getActiveKeypair();
|
|
852
|
+
const simulation = await simulateTransaction(tx, keypair.publicKey);
|
|
853
|
+
this.log(request, `simulate \u2192 ${simulation.success ? "ok" : "fail"}`);
|
|
854
|
+
if (!simulation.success) {
|
|
855
|
+
return this.errorResponse(
|
|
856
|
+
request.id,
|
|
857
|
+
4e3,
|
|
858
|
+
`Simulation failed: ${simulation.error}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
const signed = await signTransaction(tx, keypair);
|
|
862
|
+
this.log(request, `signed \u2192 ${signed.signature.slice(0, 8)}...`);
|
|
863
|
+
return {
|
|
864
|
+
id: request.id,
|
|
865
|
+
result: { signedTransaction: signed.serialized }
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
// ── Sign and Send ──────────────────────────────────────────────────────────
|
|
869
|
+
async handleSignAndSend(request) {
|
|
870
|
+
const tx = request.params.transaction;
|
|
871
|
+
if (!tx) return this.errorResponse(request.id, 4e3, "Missing transaction");
|
|
872
|
+
const keypair = this.vault.getActiveKeypair();
|
|
873
|
+
const simulation = await simulateTransaction(tx, keypair.publicKey);
|
|
874
|
+
this.log(request, `simulate \u2192 ${simulation.success ? "ok" : "fail"}`);
|
|
875
|
+
if (!simulation.success) {
|
|
876
|
+
return this.errorResponse(
|
|
877
|
+
request.id,
|
|
878
|
+
4e3,
|
|
879
|
+
`Simulation failed: ${simulation.error}`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
const result = await signAndSendTransaction(tx, keypair);
|
|
883
|
+
this.log(request, `sent \u2192 ${result.txHash.slice(0, 8)}...`);
|
|
884
|
+
return {
|
|
885
|
+
id: request.id,
|
|
886
|
+
result: {
|
|
887
|
+
signature: result.txHash,
|
|
888
|
+
confirmed: result.confirmed
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
// ── Sign All Transactions ──────────────────────────────────────────────────
|
|
893
|
+
async handleSignAll(request) {
|
|
894
|
+
const txs = request.params.transactions;
|
|
895
|
+
if (!txs?.length)
|
|
896
|
+
return this.errorResponse(request.id, 4e3, "Missing transactions");
|
|
897
|
+
const keypair = this.vault.getActiveKeypair();
|
|
898
|
+
for (const tx of txs) {
|
|
899
|
+
const simulation = await simulateTransaction(tx, keypair.publicKey);
|
|
900
|
+
if (!simulation.success) {
|
|
901
|
+
return this.errorResponse(
|
|
902
|
+
request.id,
|
|
903
|
+
4e3,
|
|
904
|
+
`Simulation failed for tx in batch: ${simulation.error}`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const signed = await signAllTransactions(txs, keypair);
|
|
909
|
+
this.log(request, `signed ${signed.length} transactions`);
|
|
910
|
+
return {
|
|
911
|
+
id: request.id,
|
|
912
|
+
result: { signedTransactions: signed.map((s) => s.serialized) }
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
// ── Sign Message ───────────────────────────────────────────────────────────
|
|
916
|
+
async handleSignMessage(request) {
|
|
917
|
+
const message = request.params.message;
|
|
918
|
+
if (!message)
|
|
919
|
+
return this.errorResponse(request.id, 4e3, "Missing message");
|
|
920
|
+
const keypair = this.vault.getActiveKeypair();
|
|
921
|
+
let messageBytes;
|
|
922
|
+
try {
|
|
923
|
+
messageBytes = decodeMessage(message);
|
|
924
|
+
} catch {
|
|
925
|
+
return this.errorResponse(
|
|
926
|
+
request.id,
|
|
927
|
+
4e3,
|
|
928
|
+
"Could not decode message bytes"
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
const signature = signOffchainMessage(messageBytes, keypair);
|
|
932
|
+
this.log(request, "message signed");
|
|
933
|
+
return {
|
|
934
|
+
id: request.id,
|
|
935
|
+
result: {
|
|
936
|
+
signature,
|
|
937
|
+
publicKey: keypair.publicKey
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
942
|
+
errorResponse(id, code, message) {
|
|
943
|
+
return { id, error: { code, message } };
|
|
944
|
+
}
|
|
945
|
+
// TODO: handle logging
|
|
946
|
+
log(request, note) {
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
function decodeMessage(message) {
|
|
950
|
+
try {
|
|
951
|
+
const decoded = bs583.decode(message);
|
|
952
|
+
if (decoded.length > 0) return decoded;
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
const decoded = Buffer.from(message, "base64");
|
|
957
|
+
if (decoded.length > 0) return decoded;
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
return new TextEncoder().encode(message);
|
|
961
|
+
}
|
|
962
|
+
async function handleRequest(request, options) {
|
|
963
|
+
const handler = new WalletRequestHandler(options.vault);
|
|
964
|
+
return handler.handle(request);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/session/index.ts
|
|
968
|
+
import * as fs3 from "fs";
|
|
969
|
+
function read() {
|
|
970
|
+
ensureWalletDir();
|
|
971
|
+
if (!fs3.existsSync(SESSIONS_FILE)) return { sessions: [] };
|
|
972
|
+
return JSON.parse(fs3.readFileSync(SESSIONS_FILE, "utf8"));
|
|
973
|
+
}
|
|
974
|
+
function write(store) {
|
|
975
|
+
ensureWalletDir();
|
|
976
|
+
fs3.writeFileSync(SESSIONS_FILE, JSON.stringify(store, null, 2));
|
|
977
|
+
}
|
|
978
|
+
function saveSession(session) {
|
|
979
|
+
const store = read();
|
|
980
|
+
const idx = store.sessions.findIndex((s) => s.topic === session.topic);
|
|
981
|
+
if (idx >= 0) {
|
|
982
|
+
store.sessions[idx] = session;
|
|
983
|
+
} else {
|
|
984
|
+
store.sessions.push(session);
|
|
985
|
+
}
|
|
986
|
+
write(store);
|
|
987
|
+
}
|
|
988
|
+
function removeSession(topic) {
|
|
989
|
+
const store = read();
|
|
990
|
+
store.sessions = store.sessions.filter((s) => s.topic !== topic);
|
|
991
|
+
write(store);
|
|
992
|
+
}
|
|
993
|
+
function getSession(topic) {
|
|
994
|
+
return read().sessions.find((s) => s.topic === topic);
|
|
995
|
+
}
|
|
996
|
+
function getAllSessions() {
|
|
997
|
+
return read().sessions.filter((s) => s.active);
|
|
998
|
+
}
|
|
999
|
+
function updateActivity(topic) {
|
|
1000
|
+
const store = read();
|
|
1001
|
+
const sess = store.sessions.find((s) => s.topic === topic);
|
|
1002
|
+
if (sess) {
|
|
1003
|
+
sess.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
|
+
write(store);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function markInactive(topic) {
|
|
1008
|
+
const store = read();
|
|
1009
|
+
const sess = store.sessions.find((s) => s.topic === topic);
|
|
1010
|
+
if (sess) {
|
|
1011
|
+
sess.active = false;
|
|
1012
|
+
write(store);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/handler/walletConnect.ts
|
|
1017
|
+
var SignClient = SignClientPkg.default ?? SignClientPkg;
|
|
1018
|
+
var CHAIN_ID = "solana:devnet";
|
|
1019
|
+
var signClient = null;
|
|
1020
|
+
async function initWalletConnect(opts) {
|
|
1021
|
+
signClient = await SignClient.init({
|
|
1022
|
+
projectId: opts.projectId,
|
|
1023
|
+
metadata: {
|
|
1024
|
+
name: "Agentic Wallet",
|
|
1025
|
+
description: "Autonomous AI agent wallet for Solana",
|
|
1026
|
+
url: "https://github.com/your-repo/agentic-wallet",
|
|
1027
|
+
icons: []
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
console.log(chalk.green("[WC] WalletConnect initialized"));
|
|
1031
|
+
signClient.on("session_proposal", async ({ id, params }) => {
|
|
1032
|
+
const meta = params.proposer.metadata;
|
|
1033
|
+
console.log(
|
|
1034
|
+
chalk.cyan(`
|
|
1035
|
+
[WC] Session proposal from: ${meta.name} (${meta.url})`)
|
|
1036
|
+
);
|
|
1037
|
+
let approved = true;
|
|
1038
|
+
if (opts.onSessionProposal) {
|
|
1039
|
+
approved = await opts.onSessionProposal(meta);
|
|
1040
|
+
}
|
|
1041
|
+
if (!approved) {
|
|
1042
|
+
await signClient.reject({ id, reason: getSdkError("USER_REJECTED") });
|
|
1043
|
+
console.log(chalk.red("[WC] Session rejected"));
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const account = opts.handlerOptions.vault.getActiveKeypair();
|
|
1047
|
+
const { topic, acknowledged } = await signClient.approve({
|
|
1048
|
+
id,
|
|
1049
|
+
namespaces: {
|
|
1050
|
+
solana: {
|
|
1051
|
+
accounts: [`${CHAIN_ID}:${account.publicKey}`],
|
|
1052
|
+
methods: [
|
|
1053
|
+
"solana_signTransaction",
|
|
1054
|
+
"solana_signAndSendTransaction",
|
|
1055
|
+
"solana_signAllTransactions",
|
|
1056
|
+
"solana_signMessage"
|
|
1057
|
+
],
|
|
1058
|
+
events: []
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
await acknowledged();
|
|
1063
|
+
saveSession({
|
|
1064
|
+
topic,
|
|
1065
|
+
dappName: meta.name,
|
|
1066
|
+
dappUrl: meta.url,
|
|
1067
|
+
dappIcon: meta.icons?.[0],
|
|
1068
|
+
connectedAccount: account.publicKey,
|
|
1069
|
+
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1070
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1071
|
+
permissions: [
|
|
1072
|
+
"solana_signTransaction",
|
|
1073
|
+
"solana_signAndSendTransaction",
|
|
1074
|
+
"solana_signAllTransactions",
|
|
1075
|
+
"solana_signMessage"
|
|
1076
|
+
],
|
|
1077
|
+
chainId: CHAIN_ID,
|
|
1078
|
+
active: true
|
|
1079
|
+
});
|
|
1080
|
+
console.log(chalk.green(`[WC] Connected to ${meta.name}`));
|
|
1081
|
+
console.log(chalk.dim(` Address: ${account.publicKey}`));
|
|
1082
|
+
});
|
|
1083
|
+
signClient.on("session_request", async ({ id, topic, params }) => {
|
|
1084
|
+
const session = signClient.session.get(topic);
|
|
1085
|
+
const dapp = {
|
|
1086
|
+
name: session.peer.metadata.name,
|
|
1087
|
+
url: session.peer.metadata.url
|
|
1088
|
+
};
|
|
1089
|
+
console.log(
|
|
1090
|
+
chalk.yellow(
|
|
1091
|
+
`
|
|
1092
|
+
[WC] Request: ${params.request.method} from ${dapp.name}`
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1095
|
+
const request = {
|
|
1096
|
+
id,
|
|
1097
|
+
method: params.request.method,
|
|
1098
|
+
params: params.request.params,
|
|
1099
|
+
dapp,
|
|
1100
|
+
topic
|
|
1101
|
+
};
|
|
1102
|
+
const response = await handleRequest(request, opts.handlerOptions);
|
|
1103
|
+
const jsonRpcResponse = response.error ? {
|
|
1104
|
+
id,
|
|
1105
|
+
jsonrpc: "2.0",
|
|
1106
|
+
error: { code: response.error.code, message: response.error.message }
|
|
1107
|
+
} : { id, jsonrpc: "2.0", result: response.result ?? null };
|
|
1108
|
+
await signClient.respond({ topic, response: jsonRpcResponse });
|
|
1109
|
+
if (response.error) {
|
|
1110
|
+
console.log(chalk.red(`[WC] Rejected: ${response.error.message}`));
|
|
1111
|
+
} else {
|
|
1112
|
+
console.log(chalk.green(`[WC] Request fulfilled`));
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
signClient.on("session_delete", ({ topic }) => {
|
|
1116
|
+
markInactive(topic);
|
|
1117
|
+
console.log(
|
|
1118
|
+
chalk.dim(`[WC] dApp disconnected (topic: ${topic.slice(0, 8)}...)`)
|
|
1119
|
+
);
|
|
1120
|
+
});
|
|
1121
|
+
return signClient;
|
|
1122
|
+
}
|
|
1123
|
+
async function pairWithDapp(uri) {
|
|
1124
|
+
if (!signClient) throw new Error("WalletConnect not initialised");
|
|
1125
|
+
console.log(chalk.dim("[WC] Connecting to dApp..."));
|
|
1126
|
+
await new Promise((resolve, reject) => {
|
|
1127
|
+
const timeout = setTimeout(() => {
|
|
1128
|
+
signClient.off("session_proposal", onProposal);
|
|
1129
|
+
reject(new Error("WalletConnect pairing timed out after 30s"));
|
|
1130
|
+
}, 3e4);
|
|
1131
|
+
const onProposal = ({ params }) => {
|
|
1132
|
+
clearTimeout(timeout);
|
|
1133
|
+
signClient.off("session_proposal", onProposal);
|
|
1134
|
+
const meta = params.proposer.metadata;
|
|
1135
|
+
console.log(
|
|
1136
|
+
chalk.cyan(
|
|
1137
|
+
`[WC] Request from: ${chalk.bold(meta.name)} (${meta.url})`,
|
|
1138
|
+
params
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
resolve();
|
|
1142
|
+
};
|
|
1143
|
+
signClient.on("session_proposal", onProposal);
|
|
1144
|
+
signClient.core.pairing.pair({ uri }).catch((err) => {
|
|
1145
|
+
clearTimeout(timeout);
|
|
1146
|
+
signClient.off("session_proposal", onProposal);
|
|
1147
|
+
reject(err);
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
async function disconnectDapp(topic) {
|
|
1152
|
+
if (!signClient) throw new Error("WalletConnect not initialised");
|
|
1153
|
+
await signClient.disconnect({
|
|
1154
|
+
topic,
|
|
1155
|
+
reason: getSdkError("USER_DISCONNECTED")
|
|
1156
|
+
});
|
|
1157
|
+
removeSession(topic);
|
|
1158
|
+
console.log(
|
|
1159
|
+
chalk.dim(`[WC] Disconnected from topic ${topic.slice(0, 8)}...`)
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
function getSignClient() {
|
|
1163
|
+
return signClient;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/cli/prompts.tsx
|
|
1167
|
+
import React, { useState, useRef } from "react";
|
|
1168
|
+
import { render, Box, Text, useInput, useApp } from "ink";
|
|
1169
|
+
function inkPassword(question) {
|
|
1170
|
+
return new Promise((resolve) => {
|
|
1171
|
+
let result = "";
|
|
1172
|
+
function PasswordPrompt() {
|
|
1173
|
+
const { exit } = useApp();
|
|
1174
|
+
const valueRef = useRef("");
|
|
1175
|
+
const [maskLen, setMaskLen] = useState(0);
|
|
1176
|
+
useInput((input, key) => {
|
|
1177
|
+
if (key.return) {
|
|
1178
|
+
result = valueRef.current;
|
|
1179
|
+
exit();
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (key.backspace || key.delete) {
|
|
1183
|
+
if (valueRef.current.length > 0) {
|
|
1184
|
+
valueRef.current = valueRef.current.slice(0, -1);
|
|
1185
|
+
setMaskLen(valueRef.current.length);
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1190
|
+
valueRef.current += input;
|
|
1191
|
+
setMaskLen(valueRef.current.length);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, question, " "), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "*".repeat(maskLen)));
|
|
1195
|
+
}
|
|
1196
|
+
const { waitUntilExit } = render(/* @__PURE__ */ React.createElement(PasswordPrompt, null));
|
|
1197
|
+
waitUntilExit().then(() => resolve(result));
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
function inkSelect(message, choices) {
|
|
1201
|
+
return new Promise((resolve) => {
|
|
1202
|
+
let result = choices[0].value;
|
|
1203
|
+
function SelectPrompt() {
|
|
1204
|
+
const { exit } = useApp();
|
|
1205
|
+
const cursorRef = useRef(0);
|
|
1206
|
+
const [cursor, setCursor] = useState(0);
|
|
1207
|
+
useInput((_, key) => {
|
|
1208
|
+
if (key.upArrow) {
|
|
1209
|
+
const n = Math.max(0, cursorRef.current - 1);
|
|
1210
|
+
cursorRef.current = n;
|
|
1211
|
+
setCursor(n);
|
|
1212
|
+
}
|
|
1213
|
+
if (key.downArrow) {
|
|
1214
|
+
const n = Math.min(choices.length - 1, cursorRef.current + 1);
|
|
1215
|
+
cursorRef.current = n;
|
|
1216
|
+
setCursor(n);
|
|
1217
|
+
}
|
|
1218
|
+
if (key.return) {
|
|
1219
|
+
result = choices[cursorRef.current].value;
|
|
1220
|
+
exit();
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true }, message), choices.map((choice, i) => /* @__PURE__ */ React.createElement(Box, { key: i }, /* @__PURE__ */ React.createElement(Text, { color: i === cursor ? "cyan" : "gray" }, i === cursor ? "\u276F " : " ", choice.name))));
|
|
1224
|
+
}
|
|
1225
|
+
const { waitUntilExit } = render(/* @__PURE__ */ React.createElement(SelectPrompt, null));
|
|
1226
|
+
waitUntilExit().then(() => resolve(result));
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
function inkConfirm(message, defaultYes = false) {
|
|
1230
|
+
return new Promise((resolve) => {
|
|
1231
|
+
let result = defaultYes;
|
|
1232
|
+
function ConfirmPrompt() {
|
|
1233
|
+
const { exit } = useApp();
|
|
1234
|
+
useInput((input, key) => {
|
|
1235
|
+
if (key.return) {
|
|
1236
|
+
result = defaultYes;
|
|
1237
|
+
exit();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const ch = input.toLowerCase();
|
|
1241
|
+
if (ch === "y") {
|
|
1242
|
+
result = true;
|
|
1243
|
+
exit();
|
|
1244
|
+
} else if (ch === "n") {
|
|
1245
|
+
result = false;
|
|
1246
|
+
exit();
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
1250
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, message, " "), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "[", hint, "] "));
|
|
1251
|
+
}
|
|
1252
|
+
const { waitUntilExit } = render(/* @__PURE__ */ React.createElement(ConfirmPrompt, null));
|
|
1253
|
+
waitUntilExit().then(() => resolve(result));
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/cli/commands.ts
|
|
1258
|
+
var sessionPassword = null;
|
|
1259
|
+
var sessionMnemonic = null;
|
|
1260
|
+
function isUnlocked() {
|
|
1261
|
+
return sessionMnemonic !== null;
|
|
1262
|
+
}
|
|
1263
|
+
async function requireUnlocked() {
|
|
1264
|
+
if (!sessionPassword || !sessionMnemonic) {
|
|
1265
|
+
throw new Error("Wallet is locked. Run: wallet unlock");
|
|
1266
|
+
}
|
|
1267
|
+
return { password: sessionPassword, mnemonic: sessionMnemonic };
|
|
1268
|
+
}
|
|
1269
|
+
async function cmdInit() {
|
|
1270
|
+
if (vaultExists()) {
|
|
1271
|
+
console.log(
|
|
1272
|
+
chalk2.yellow("Vault already exists. Use `wallet unlock` to access it.")
|
|
1273
|
+
);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const wordCount = await inkSelect("Mnemonic length:", [
|
|
1277
|
+
{ name: "12 words (standard)", value: 12 },
|
|
1278
|
+
{ name: "24 words (extra secure)", value: 24 }
|
|
1279
|
+
]);
|
|
1280
|
+
const { mnemonic } = generateMnemonic2(wordCount);
|
|
1281
|
+
console.log(
|
|
1282
|
+
chalk2.bold("\n\u26A0\uFE0F Write down your seed phrase. Store it safely.\n")
|
|
1283
|
+
);
|
|
1284
|
+
console.log(chalk2.yellow(mnemonic));
|
|
1285
|
+
console.log();
|
|
1286
|
+
const confirmed = await inkConfirm(
|
|
1287
|
+
"I have written down my seed phrase",
|
|
1288
|
+
false
|
|
1289
|
+
);
|
|
1290
|
+
if (!confirmed) {
|
|
1291
|
+
console.log(chalk2.red("Aborted."));
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const password = await inkPassword("Set vault password:");
|
|
1295
|
+
const confirm = await inkPassword("Confirm password:");
|
|
1296
|
+
if (password !== confirm) {
|
|
1297
|
+
console.log(chalk2.red("Passwords do not match."));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const seed = await mnemonicToSeed2(mnemonic);
|
|
1301
|
+
const firstKp = deriveAccount(seed, 0, "Main");
|
|
1302
|
+
await saveVault(
|
|
1303
|
+
{ mnemonic, createdAt: (/* @__PURE__ */ new Date()).toISOString(), version: 1 },
|
|
1304
|
+
password
|
|
1305
|
+
);
|
|
1306
|
+
const config = createConfig("");
|
|
1307
|
+
config.accountStore.accounts[0] = {
|
|
1308
|
+
index: 0,
|
|
1309
|
+
name: "Main",
|
|
1310
|
+
publicKey: firstKp.publicKey,
|
|
1311
|
+
derivationPath: `m/44'/501'/0'/0'`,
|
|
1312
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1313
|
+
};
|
|
1314
|
+
saveConfig(config);
|
|
1315
|
+
sessionPassword = password;
|
|
1316
|
+
sessionMnemonic = mnemonic;
|
|
1317
|
+
console.log(chalk2.green("\n\u2713 Wallet created"));
|
|
1318
|
+
console.log(chalk2.dim(` Address: ${firstKp.publicKey}`));
|
|
1319
|
+
}
|
|
1320
|
+
async function cmdUnlock() {
|
|
1321
|
+
if (!vaultExists()) {
|
|
1322
|
+
console.log(chalk2.red("No vault found. Run: wallet init"));
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const password = await inkPassword("Vault password:");
|
|
1326
|
+
try {
|
|
1327
|
+
const vault = await loadVault(password);
|
|
1328
|
+
sessionPassword = password;
|
|
1329
|
+
sessionMnemonic = vault.mnemonic;
|
|
1330
|
+
console.log(chalk2.green("\u2713 Wallet unlocked"));
|
|
1331
|
+
} catch {
|
|
1332
|
+
console.log(chalk2.red("Wrong password."));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
function cmdLock() {
|
|
1336
|
+
sessionPassword = null;
|
|
1337
|
+
sessionMnemonic = null;
|
|
1338
|
+
console.log(chalk2.green("\u2713 Wallet locked"));
|
|
1339
|
+
}
|
|
1340
|
+
async function cmdAccounts() {
|
|
1341
|
+
await requireUnlocked();
|
|
1342
|
+
const config = loadConfig();
|
|
1343
|
+
console.log(chalk2.bold("\n\u2500\u2500 Accounts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1344
|
+
for (const entry of config.accountStore.accounts) {
|
|
1345
|
+
const active = entry.index === config.accountStore.activeIndex;
|
|
1346
|
+
const marker = active ? chalk2.green("\u25CF") : chalk2.dim("\u25CB");
|
|
1347
|
+
let balance = "...";
|
|
1348
|
+
try {
|
|
1349
|
+
const { sol } = await getBalance(entry.publicKey);
|
|
1350
|
+
balance = sol.toFixed(4) + " SOL";
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
console.log(
|
|
1354
|
+
` ${marker} [${entry.index}] ${entry.name.padEnd(16)} ${entry.publicKey} ${balance}`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
console.log(chalk2.dim(`
|
|
1358
|
+
Network: ${config.cluster} (${config.rpcUrl})`));
|
|
1359
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
1360
|
+
}
|
|
1361
|
+
async function cmdNewAccount(name) {
|
|
1362
|
+
const { mnemonic } = await requireUnlocked();
|
|
1363
|
+
const config = loadConfig();
|
|
1364
|
+
const nextIndex = config.accountStore.accounts.length;
|
|
1365
|
+
const accountName = name || `Account ${nextIndex + 1}`;
|
|
1366
|
+
const seed = await mnemonicToSeed2(mnemonic);
|
|
1367
|
+
const keypair = deriveAccount(seed, nextIndex, accountName);
|
|
1368
|
+
updateAccountStore({
|
|
1369
|
+
...config.accountStore,
|
|
1370
|
+
accounts: [
|
|
1371
|
+
...config.accountStore.accounts,
|
|
1372
|
+
{
|
|
1373
|
+
index: nextIndex,
|
|
1374
|
+
name: accountName,
|
|
1375
|
+
publicKey: keypair.publicKey,
|
|
1376
|
+
derivationPath: `m/44'/501'/${nextIndex}'/0'`,
|
|
1377
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1378
|
+
}
|
|
1379
|
+
]
|
|
1380
|
+
});
|
|
1381
|
+
console.log(chalk2.green(`\u2713 Account created: ${accountName}`));
|
|
1382
|
+
console.log(chalk2.dim(` Address: ${keypair.publicKey}`));
|
|
1383
|
+
}
|
|
1384
|
+
async function cmdUseAccount(nameOrIndex) {
|
|
1385
|
+
await requireUnlocked();
|
|
1386
|
+
const config = loadConfig();
|
|
1387
|
+
const found = config.accountStore.accounts.find(
|
|
1388
|
+
(a) => a.name === nameOrIndex || a.index === parseInt(nameOrIndex)
|
|
1389
|
+
);
|
|
1390
|
+
if (!found) {
|
|
1391
|
+
console.log(chalk2.red(`Account not found: ${nameOrIndex}`));
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
updateAccountStore({ ...config.accountStore, activeIndex: found.index });
|
|
1395
|
+
console.log(
|
|
1396
|
+
chalk2.green(`\u2713 Active account: ${found.name} (${found.publicKey})`)
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
async function cmdAirdrop(sol = 1) {
|
|
1400
|
+
await requireUnlocked();
|
|
1401
|
+
const config = loadConfig();
|
|
1402
|
+
const active = config.accountStore.accounts.find(
|
|
1403
|
+
(a) => a.index === config.accountStore.activeIndex
|
|
1404
|
+
);
|
|
1405
|
+
if (!active) {
|
|
1406
|
+
console.log(chalk2.red("No active account."));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
console.log(chalk2.dim(`Requesting ${sol} SOL airdrop...`));
|
|
1410
|
+
try {
|
|
1411
|
+
const sig = await requestAirdrop(active.publicKey, sol);
|
|
1412
|
+
console.log(chalk2.green(`\u2713 Airdrop complete`));
|
|
1413
|
+
console.log(chalk2.dim(` tx: ${sig}`));
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
console.log(chalk2.red(`Airdrop failed: ${err.message}`));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
async function cmdConnect(wcUri) {
|
|
1419
|
+
if (!wcUri || !wcUri.startsWith("wc:")) {
|
|
1420
|
+
console.log(chalk2.red("Invalid WalletConnect URI. Must start with wc:"));
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
console.log(chalk2.dim("Pairing with dApp..."));
|
|
1424
|
+
await pairWithDapp(wcUri);
|
|
1425
|
+
}
|
|
1426
|
+
function cmdSessions() {
|
|
1427
|
+
const sessions = getAllSessions();
|
|
1428
|
+
if (sessions.length === 0) {
|
|
1429
|
+
console.log(chalk2.dim("No active sessions."));
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
console.log(chalk2.bold("\n\u2500\u2500 Connected dApps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1433
|
+
sessions.forEach((s) => {
|
|
1434
|
+
console.log(
|
|
1435
|
+
` ${chalk2.cyan(s.dappName.padEnd(20))} ${s.connectedAccount.slice(0, 8)}... since ${s.connectedAt.slice(0, 10)}`
|
|
1436
|
+
);
|
|
1437
|
+
console.log(
|
|
1438
|
+
chalk2.dim(` ${s.dappUrl} topic: ${s.topic.slice(0, 8)}...`)
|
|
1439
|
+
);
|
|
1440
|
+
});
|
|
1441
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
1442
|
+
}
|
|
1443
|
+
async function cmdDisconnect(dappName) {
|
|
1444
|
+
const sessions = getAllSessions();
|
|
1445
|
+
const session = sessions.find(
|
|
1446
|
+
(s) => s.dappName.toLowerCase().includes(dappName.toLowerCase())
|
|
1447
|
+
);
|
|
1448
|
+
if (!session) {
|
|
1449
|
+
console.log(chalk2.red(`No session found for: ${dappName}`));
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
await disconnectDapp(session.topic);
|
|
1453
|
+
console.log(chalk2.green(`\u2713 Disconnected from ${session.dappName}`));
|
|
1454
|
+
}
|
|
1455
|
+
async function cmdSend(to, sol, vault) {
|
|
1456
|
+
const { PublicKey: PublicKey2, SystemProgram, Transaction: Transaction3, LAMPORTS_PER_SOL: LAMPORTS_PER_SOL3 } = await import("@solana/web3.js");
|
|
1457
|
+
const keypair = vault.getActiveKeypair();
|
|
1458
|
+
const { Keypair: Keypair2 } = await import("@solana/web3.js");
|
|
1459
|
+
const solanaKeypair = Keypair2.fromSecretKey(keypair.secretKey);
|
|
1460
|
+
const connection = getConnection();
|
|
1461
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
|
|
1462
|
+
const tx = new Transaction3().add(
|
|
1463
|
+
SystemProgram.transfer({
|
|
1464
|
+
fromPubkey: solanaKeypair.publicKey,
|
|
1465
|
+
toPubkey: new PublicKey2(to),
|
|
1466
|
+
lamports: Math.round(sol * LAMPORTS_PER_SOL3)
|
|
1467
|
+
})
|
|
1468
|
+
);
|
|
1469
|
+
tx.recentBlockhash = blockhash;
|
|
1470
|
+
tx.feePayer = solanaKeypair.publicKey;
|
|
1471
|
+
tx.sign(solanaKeypair);
|
|
1472
|
+
const raw = tx.serialize();
|
|
1473
|
+
const sig = await connection.sendRawTransaction(raw, {
|
|
1474
|
+
skipPreflight: false,
|
|
1475
|
+
preflightCommitment: "confirmed"
|
|
1476
|
+
});
|
|
1477
|
+
await connection.confirmTransaction(
|
|
1478
|
+
{ signature: sig, blockhash, lastValidBlockHeight },
|
|
1479
|
+
"confirmed"
|
|
1480
|
+
);
|
|
1481
|
+
console.log(chalk2.green(`\u2713 Sent ${sol} SOL to ${to}`));
|
|
1482
|
+
console.log(chalk2.dim(` tx: ${sig}`));
|
|
1483
|
+
}
|
|
1484
|
+
function cmdSetCluster(cluster) {
|
|
1485
|
+
const valid = ["mainnet-beta", "devnet", "testnet"];
|
|
1486
|
+
if (!valid.includes(cluster)) {
|
|
1487
|
+
console.log(chalk2.red(`Valid clusters: ${valid.join(", ")}`));
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
updateCluster(cluster);
|
|
1491
|
+
console.log(chalk2.green(`\u2713 Cluster set to: ${cluster}`));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/cli/ui.tsx
|
|
1495
|
+
function stripAnsi(str) {
|
|
1496
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1497
|
+
}
|
|
1498
|
+
function logColor(type) {
|
|
1499
|
+
switch (type) {
|
|
1500
|
+
case "success":
|
|
1501
|
+
return "green";
|
|
1502
|
+
case "error":
|
|
1503
|
+
return "red";
|
|
1504
|
+
case "warn":
|
|
1505
|
+
return "yellow";
|
|
1506
|
+
default:
|
|
1507
|
+
return "gray";
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function Header({ cluster }) {
|
|
1511
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u2B21 Agentic Wallet"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " ", cluster)), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(60)));
|
|
1512
|
+
}
|
|
1513
|
+
function AccountRow({
|
|
1514
|
+
index,
|
|
1515
|
+
name,
|
|
1516
|
+
publicKey,
|
|
1517
|
+
balance,
|
|
1518
|
+
isActive
|
|
1519
|
+
}) {
|
|
1520
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: isActive ? "green" : "gray" }, isActive ? "\u25CF" : "\u25CB", " "), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "[", index, "] "), /* @__PURE__ */ React2.createElement(Text2, { bold: isActive }, name.padEnd(18)), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, publicKey.slice(0, 6), "...", publicKey.slice(-4), " "), /* @__PURE__ */ React2.createElement(Text2, { color: isActive ? "cyan" : "gray" }, balance));
|
|
1521
|
+
}
|
|
1522
|
+
function SessionRow({
|
|
1523
|
+
name,
|
|
1524
|
+
url,
|
|
1525
|
+
account
|
|
1526
|
+
}) {
|
|
1527
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u25C9 "), /* @__PURE__ */ React2.createElement(Text2, { bold: true }, name.padEnd(20)), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, url.slice(0, 30).padEnd(32)), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, account.slice(0, 6), "...", account.slice(-4)));
|
|
1528
|
+
}
|
|
1529
|
+
function AgentRow({ agent }) {
|
|
1530
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: agent.running ? "green" : "gray" }, agent.running ? "\u25B6" : "\u25A0", " "), /* @__PURE__ */ React2.createElement(Text2, { bold: agent.running }, agent.id.padEnd(22)), /* @__PURE__ */ React2.createElement(Text2, { color: agent.running ? "cyan" : "gray" }, agent.running ? "RUNNING" : "STOPPED", " "), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "runs: ", agent.runCount), agent.error && /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, " \u26A0 ", agent.error.slice(0, 30)));
|
|
1531
|
+
}
|
|
1532
|
+
function LogRow({ entry }) {
|
|
1533
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, entry.time, " "), /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, entry.source.padEnd(20)), /* @__PURE__ */ React2.createElement(Text2, { color: logColor(entry.type) }, entry.message));
|
|
1534
|
+
}
|
|
1535
|
+
function PromptRow({
|
|
1536
|
+
prompt,
|
|
1537
|
+
maskLen,
|
|
1538
|
+
cursor
|
|
1539
|
+
}) {
|
|
1540
|
+
if (prompt.type === "password") {
|
|
1541
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, prompt.question, " "), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "*".repeat(maskLen)), /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2588"));
|
|
1542
|
+
}
|
|
1543
|
+
if (prompt.type === "select") {
|
|
1544
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true }, prompt.message), prompt.choices.map((c, i) => /* @__PURE__ */ React2.createElement(Box2, { key: i }, /* @__PURE__ */ React2.createElement(Text2, { color: i === cursor ? "cyan" : "gray" }, i === cursor ? "\u276F " : " ", c.name))));
|
|
1545
|
+
}
|
|
1546
|
+
if (prompt.type === "confirm") {
|
|
1547
|
+
const hint = prompt.defaultYes ? "Y/n" : "y/N";
|
|
1548
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true }, prompt.message, " "), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "[", hint, "]"));
|
|
1549
|
+
}
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
function CommandBar({ buffer, running }) {
|
|
1553
|
+
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green", bold: true }, "wallet"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " \u203A "), running ? /* @__PURE__ */ React2.createElement(Text2, { color: "yellow", dimColor: true }, "running\u2026") : /* @__PURE__ */ React2.createElement(Text2, null, buffer, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2588")));
|
|
1554
|
+
}
|
|
1555
|
+
function Dashboard({ vault, agentStatus, onExit }) {
|
|
1556
|
+
const { exit } = useApp2();
|
|
1557
|
+
const [view, setView] = useState2("dashboard");
|
|
1558
|
+
const [accounts, setAccounts] = useState2([]);
|
|
1559
|
+
const [balances, setBalances] = useState2({});
|
|
1560
|
+
const [sessions, setSessions] = useState2([]);
|
|
1561
|
+
const [cluster, setCluster] = useState2("devnet");
|
|
1562
|
+
const [cmdBuffer, setCmdBuffer] = useState2("");
|
|
1563
|
+
const [cmdRunning, setCmdRunning] = useState2(false);
|
|
1564
|
+
const [promptState, setPromptState] = useState2(null);
|
|
1565
|
+
const promptResolverRef = useRef2(null);
|
|
1566
|
+
const passwordValueRef = useRef2("");
|
|
1567
|
+
const [passwordMaskLen, setPasswordMaskLen] = useState2(0);
|
|
1568
|
+
const selectCursorRef = useRef2(0);
|
|
1569
|
+
const [selectCursor, setSelectCursor] = useState2(0);
|
|
1570
|
+
const [uiLogs, setUiLogs] = useState2([...logBuffer]);
|
|
1571
|
+
useEffect(() => {
|
|
1572
|
+
const interval = setInterval(() => {
|
|
1573
|
+
try {
|
|
1574
|
+
const keypairs = vault.getAllKeypairs();
|
|
1575
|
+
setAccounts(keypairs);
|
|
1576
|
+
setCluster(getCluster());
|
|
1577
|
+
setSessions(getAllSessions());
|
|
1578
|
+
keypairs.forEach(async (kp) => {
|
|
1579
|
+
try {
|
|
1580
|
+
const { sol } = await getBalance(kp.publicKey);
|
|
1581
|
+
setBalances((prev) => ({
|
|
1582
|
+
...prev,
|
|
1583
|
+
[kp.publicKey]: sol.toFixed(4) + " SOL"
|
|
1584
|
+
}));
|
|
1585
|
+
} catch {
|
|
1586
|
+
setBalances((prev) => ({ ...prev, [kp.publicKey]: "..." }));
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
} catch {
|
|
1590
|
+
}
|
|
1591
|
+
}, 2e3);
|
|
1592
|
+
return () => clearInterval(interval);
|
|
1593
|
+
}, []);
|
|
1594
|
+
useEffect(() => {
|
|
1595
|
+
const interval = setInterval(() => setSessions(getAllSessions()), 5e3);
|
|
1596
|
+
return () => clearInterval(interval);
|
|
1597
|
+
}, []);
|
|
1598
|
+
useEffect(() => {
|
|
1599
|
+
const fn = (updated) => setUiLogs(updated);
|
|
1600
|
+
logListeners.push(fn);
|
|
1601
|
+
return () => {
|
|
1602
|
+
const idx = logListeners.indexOf(fn);
|
|
1603
|
+
if (idx >= 0) logListeners.splice(idx, 1);
|
|
1604
|
+
};
|
|
1605
|
+
}, []);
|
|
1606
|
+
const requestPrompt = (state) => new Promise((resolve) => {
|
|
1607
|
+
promptResolverRef.current = resolve;
|
|
1608
|
+
if (state.type === "password") {
|
|
1609
|
+
passwordValueRef.current = "";
|
|
1610
|
+
setPasswordMaskLen(0);
|
|
1611
|
+
}
|
|
1612
|
+
if (state.type === "select") {
|
|
1613
|
+
selectCursorRef.current = 0;
|
|
1614
|
+
setSelectCursor(0);
|
|
1615
|
+
}
|
|
1616
|
+
setPromptState(state);
|
|
1617
|
+
});
|
|
1618
|
+
const resolvePrompt = (val) => {
|
|
1619
|
+
promptResolverRef.current?.(val);
|
|
1620
|
+
promptResolverRef.current = null;
|
|
1621
|
+
passwordValueRef.current = "";
|
|
1622
|
+
setPasswordMaskLen(0);
|
|
1623
|
+
selectCursorRef.current = 0;
|
|
1624
|
+
setSelectCursor(0);
|
|
1625
|
+
setPromptState(null);
|
|
1626
|
+
};
|
|
1627
|
+
const withCapture = async (fn) => {
|
|
1628
|
+
const origLog = console.log;
|
|
1629
|
+
const origError = console.error;
|
|
1630
|
+
console.log = (...args) => {
|
|
1631
|
+
const msg = stripAnsi(args.map(String).join(" ")).trim();
|
|
1632
|
+
if (msg) addLog("wallet", msg, "info");
|
|
1633
|
+
};
|
|
1634
|
+
console.error = (...args) => {
|
|
1635
|
+
const msg = stripAnsi(args.map(String).join(" ")).trim();
|
|
1636
|
+
if (msg) addLog("wallet", msg, "error");
|
|
1637
|
+
};
|
|
1638
|
+
try {
|
|
1639
|
+
await fn();
|
|
1640
|
+
} finally {
|
|
1641
|
+
console.log = origLog;
|
|
1642
|
+
console.error = origError;
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
const handleInit = async () => {
|
|
1646
|
+
if (vaultExists()) {
|
|
1647
|
+
addLog(
|
|
1648
|
+
"wallet",
|
|
1649
|
+
"Vault already exists. Use `unlock` to access it.",
|
|
1650
|
+
"warn"
|
|
1651
|
+
);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const wordCount = await requestPrompt({
|
|
1655
|
+
type: "select",
|
|
1656
|
+
message: "Mnemonic length:",
|
|
1657
|
+
choices: [
|
|
1658
|
+
{ name: "12 words (standard)", value: 12 },
|
|
1659
|
+
{ name: "24 words (extra secure)", value: 24 }
|
|
1660
|
+
]
|
|
1661
|
+
});
|
|
1662
|
+
const { mnemonic } = generateMnemonic2(wordCount);
|
|
1663
|
+
addLog(
|
|
1664
|
+
"wallet",
|
|
1665
|
+
"\u26A0 Write down your seed phrase and store it safely.",
|
|
1666
|
+
"warn"
|
|
1667
|
+
);
|
|
1668
|
+
addLog("wallet", mnemonic, "warn");
|
|
1669
|
+
const confirmed = await requestPrompt({
|
|
1670
|
+
type: "confirm",
|
|
1671
|
+
message: "I have written down my seed phrase",
|
|
1672
|
+
defaultYes: false
|
|
1673
|
+
});
|
|
1674
|
+
if (!confirmed) {
|
|
1675
|
+
addLog("wallet", "Aborted.", "error");
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const password = await requestPrompt({
|
|
1679
|
+
type: "password",
|
|
1680
|
+
question: "Set vault password:"
|
|
1681
|
+
});
|
|
1682
|
+
const confirmPw = await requestPrompt({
|
|
1683
|
+
type: "password",
|
|
1684
|
+
question: "Confirm password:"
|
|
1685
|
+
});
|
|
1686
|
+
if (password !== confirmPw) {
|
|
1687
|
+
addLog("wallet", "Passwords do not match.", "error");
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
const seed = await mnemonicToSeed2(mnemonic);
|
|
1691
|
+
const firstKp = deriveAccount(seed, 0, "Main");
|
|
1692
|
+
await saveVault(
|
|
1693
|
+
{ mnemonic, createdAt: (/* @__PURE__ */ new Date()).toISOString(), version: 1 },
|
|
1694
|
+
password
|
|
1695
|
+
);
|
|
1696
|
+
const cfg = createConfig("");
|
|
1697
|
+
cfg.accountStore.accounts[0] = {
|
|
1698
|
+
index: 0,
|
|
1699
|
+
name: "Main",
|
|
1700
|
+
publicKey: firstKp.publicKey,
|
|
1701
|
+
derivationPath: `m/44'/501'/0'/0'`,
|
|
1702
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1703
|
+
};
|
|
1704
|
+
saveConfig(cfg);
|
|
1705
|
+
addLog("wallet", `\u2713 Wallet created \u2014 ${firstKp.publicKey}`, "success");
|
|
1706
|
+
};
|
|
1707
|
+
const handleUnlock = async () => {
|
|
1708
|
+
if (!vaultExists()) {
|
|
1709
|
+
addLog("wallet", "No vault found. Run: init", "error");
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const password = await requestPrompt({
|
|
1713
|
+
type: "password",
|
|
1714
|
+
question: "Vault password:"
|
|
1715
|
+
});
|
|
1716
|
+
try {
|
|
1717
|
+
await loadVault(password);
|
|
1718
|
+
addLog("wallet", "\u2713 Wallet unlocked", "success");
|
|
1719
|
+
} catch {
|
|
1720
|
+
addLog("wallet", "Wrong password.", "error");
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
const handleSend = async (to, amount) => {
|
|
1724
|
+
if (!to || !amount) {
|
|
1725
|
+
addLog("wallet", "Usage: send <address> <sol>", "warn");
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
const confirmed = await requestPrompt({
|
|
1729
|
+
type: "confirm",
|
|
1730
|
+
message: `Send ${amount} SOL to ${to.slice(0, 8)}...${to.slice(-4)}?`,
|
|
1731
|
+
defaultYes: false
|
|
1732
|
+
});
|
|
1733
|
+
if (!confirmed) {
|
|
1734
|
+
addLog("wallet", "Cancelled.", "warn");
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
await withCapture(() => cmdSend(to, amount, vault));
|
|
1738
|
+
};
|
|
1739
|
+
const executeCommand = async (line) => {
|
|
1740
|
+
const parts = line.trim().split(/\s+/);
|
|
1741
|
+
const cmd = parts[0];
|
|
1742
|
+
setCmdRunning(true);
|
|
1743
|
+
addLog("wallet", `> ${line}`, "info");
|
|
1744
|
+
try {
|
|
1745
|
+
switch (cmd) {
|
|
1746
|
+
case "":
|
|
1747
|
+
break;
|
|
1748
|
+
case "help":
|
|
1749
|
+
addLog(
|
|
1750
|
+
"wallet",
|
|
1751
|
+
"init unlock lock accounts [new|use] send <addr> <sol> airdrop [sol] connect <wc:uri> sessions disconnect <dapp> cluster <name> exit",
|
|
1752
|
+
"info"
|
|
1753
|
+
);
|
|
1754
|
+
break;
|
|
1755
|
+
case "exit":
|
|
1756
|
+
case "quit":
|
|
1757
|
+
onExit();
|
|
1758
|
+
exit();
|
|
1759
|
+
break;
|
|
1760
|
+
case "init":
|
|
1761
|
+
await handleInit();
|
|
1762
|
+
break;
|
|
1763
|
+
case "unlock":
|
|
1764
|
+
await handleUnlock();
|
|
1765
|
+
break;
|
|
1766
|
+
case "lock":
|
|
1767
|
+
await withCapture(async () => cmdLock());
|
|
1768
|
+
break;
|
|
1769
|
+
case "send":
|
|
1770
|
+
await handleSend(parts[1], Number(parts[2]));
|
|
1771
|
+
break;
|
|
1772
|
+
case "airdrop":
|
|
1773
|
+
await withCapture(() => cmdAirdrop(Number(parts[1]) || 1));
|
|
1774
|
+
break;
|
|
1775
|
+
case "connect":
|
|
1776
|
+
await withCapture(() => cmdConnect(parts[1]));
|
|
1777
|
+
break;
|
|
1778
|
+
case "sessions":
|
|
1779
|
+
await withCapture(async () => cmdSessions());
|
|
1780
|
+
break;
|
|
1781
|
+
case "disconnect":
|
|
1782
|
+
await withCapture(() => cmdDisconnect(parts.slice(1).join(" ")));
|
|
1783
|
+
break;
|
|
1784
|
+
case "cluster":
|
|
1785
|
+
await withCapture(async () => cmdSetCluster(parts[1]));
|
|
1786
|
+
break;
|
|
1787
|
+
case "accounts":
|
|
1788
|
+
if (parts[1] === "new") {
|
|
1789
|
+
await withCapture(() => cmdNewAccount(parts.slice(2).join(" ")));
|
|
1790
|
+
} else if (parts[1] === "use") {
|
|
1791
|
+
await withCapture(() => cmdUseAccount(parts[2]));
|
|
1792
|
+
} else {
|
|
1793
|
+
await withCapture(() => cmdAccounts());
|
|
1794
|
+
}
|
|
1795
|
+
break;
|
|
1796
|
+
default:
|
|
1797
|
+
addLog("wallet", `Unknown: ${cmd}. Type "help".`, "warn");
|
|
1798
|
+
}
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
addLog("wallet", `Error: ${err.message}`, "error");
|
|
1801
|
+
} finally {
|
|
1802
|
+
setCmdRunning(false);
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
useInput2((input, key) => {
|
|
1806
|
+
if (key.ctrl && input === "c") {
|
|
1807
|
+
onExit();
|
|
1808
|
+
exit();
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (promptState) {
|
|
1812
|
+
if (promptState.type === "password") {
|
|
1813
|
+
if (key.return) {
|
|
1814
|
+
resolvePrompt(passwordValueRef.current);
|
|
1815
|
+
} else if (key.backspace || key.delete) {
|
|
1816
|
+
if (passwordValueRef.current.length > 0) {
|
|
1817
|
+
passwordValueRef.current = passwordValueRef.current.slice(0, -1);
|
|
1818
|
+
setPasswordMaskLen(passwordValueRef.current.length);
|
|
1819
|
+
}
|
|
1820
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
1821
|
+
passwordValueRef.current += input;
|
|
1822
|
+
setPasswordMaskLen(passwordValueRef.current.length);
|
|
1823
|
+
}
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (promptState.type === "select") {
|
|
1827
|
+
if (key.upArrow) {
|
|
1828
|
+
const n = Math.max(0, selectCursorRef.current - 1);
|
|
1829
|
+
selectCursorRef.current = n;
|
|
1830
|
+
setSelectCursor(n);
|
|
1831
|
+
} else if (key.downArrow) {
|
|
1832
|
+
const n = Math.min(
|
|
1833
|
+
promptState.choices.length - 1,
|
|
1834
|
+
selectCursorRef.current + 1
|
|
1835
|
+
);
|
|
1836
|
+
selectCursorRef.current = n;
|
|
1837
|
+
setSelectCursor(n);
|
|
1838
|
+
} else if (key.return) {
|
|
1839
|
+
resolvePrompt(promptState.choices[selectCursorRef.current].value);
|
|
1840
|
+
}
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
if (promptState.type === "confirm") {
|
|
1844
|
+
if (key.return) {
|
|
1845
|
+
resolvePrompt(promptState.defaultYes);
|
|
1846
|
+
} else if (input === "y" || input === "Y") {
|
|
1847
|
+
resolvePrompt(true);
|
|
1848
|
+
} else if (input === "n" || input === "N") {
|
|
1849
|
+
resolvePrompt(false);
|
|
1850
|
+
}
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (!cmdBuffer && !cmdRunning) {
|
|
1855
|
+
if (input === "1") {
|
|
1856
|
+
setView("dashboard");
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (input === "2") {
|
|
1860
|
+
setView("accounts");
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
if (input === "q") {
|
|
1864
|
+
onExit();
|
|
1865
|
+
exit();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (cmdRunning) return;
|
|
1870
|
+
if (key.return) {
|
|
1871
|
+
const cmd = cmdBuffer.trim();
|
|
1872
|
+
setCmdBuffer("");
|
|
1873
|
+
if (cmd) executeCommand(cmd);
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
if (key.backspace || key.delete) {
|
|
1877
|
+
setCmdBuffer((b) => b.slice(0, -1));
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
if (input && !key.ctrl && !key.meta && !key.escape) {
|
|
1881
|
+
setCmdBuffer((b) => b + input);
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
const activeKeypair = (() => {
|
|
1885
|
+
try {
|
|
1886
|
+
return vault.getActiveKeypair();
|
|
1887
|
+
} catch {
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
})();
|
|
1891
|
+
const recentLogs = uiLogs.slice(-12);
|
|
1892
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React2.createElement(Header, { cluster }), /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(
|
|
1893
|
+
Text2,
|
|
1894
|
+
{
|
|
1895
|
+
color: view === "dashboard" ? "cyan" : "gray",
|
|
1896
|
+
bold: view === "dashboard"
|
|
1897
|
+
},
|
|
1898
|
+
"[1] Dashboard",
|
|
1899
|
+
" "
|
|
1900
|
+
), /* @__PURE__ */ React2.createElement(
|
|
1901
|
+
Text2,
|
|
1902
|
+
{
|
|
1903
|
+
color: view === "accounts" ? "cyan" : "gray",
|
|
1904
|
+
bold: view === "accounts"
|
|
1905
|
+
},
|
|
1906
|
+
"[2] Accounts",
|
|
1907
|
+
" "
|
|
1908
|
+
), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " q: quit")), view === "dashboard" && /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Active Account"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(40)), activeKeypair ? /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u25CF "), /* @__PURE__ */ React2.createElement(Text2, { bold: true }, activeKeypair.name.padEnd(18)), /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, activeKeypair.publicKey), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " ", balances[activeKeypair.publicKey] ?? "...")) : /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "No active account")), /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Connected dApps (", sessions.length, ")"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(40)), sessions.length === 0 ? /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "None \u2014 paste a wc: URI to connect") : sessions.map((s) => /* @__PURE__ */ React2.createElement(
|
|
1909
|
+
SessionRow,
|
|
1910
|
+
{
|
|
1911
|
+
key: s.topic,
|
|
1912
|
+
name: s.dappName,
|
|
1913
|
+
url: s.dappUrl,
|
|
1914
|
+
account: s.connectedAccount
|
|
1915
|
+
}
|
|
1916
|
+
))), agentStatus.length > 0 && /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Agents (", agentStatus.filter((a) => a.running).length, " running)"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(40)), agentStatus.map((agent) => /* @__PURE__ */ React2.createElement(AgentRow, { key: agent.id, agent }))), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "Activity"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(40)), recentLogs.length === 0 ? /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Waiting for activity\u2026") : recentLogs.map((entry, i) => /* @__PURE__ */ React2.createElement(LogRow, { key: i, entry })))), view === "accounts" && /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, "All Accounts"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(60)), accounts.length === 0 ? /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "No accounts found") : accounts.map((acc) => /* @__PURE__ */ React2.createElement(
|
|
1917
|
+
AccountRow,
|
|
1918
|
+
{
|
|
1919
|
+
key: acc.publicKey,
|
|
1920
|
+
index: acc.index,
|
|
1921
|
+
name: acc.name,
|
|
1922
|
+
publicKey: acc.publicKey,
|
|
1923
|
+
balance: balances[acc.publicKey] ?? "...",
|
|
1924
|
+
isActive: acc.publicKey === activeKeypair?.publicKey
|
|
1925
|
+
}
|
|
1926
|
+
))), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(60))), /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "WebSocket: "), /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, "ws://localhost:3000"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, " \u2502 [1] Dashboard [2] Accounts [q] Quit")), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u2500".repeat(60)), promptState ? /* @__PURE__ */ React2.createElement(
|
|
1927
|
+
PromptRow,
|
|
1928
|
+
{
|
|
1929
|
+
prompt: promptState,
|
|
1930
|
+
maskLen: passwordMaskLen,
|
|
1931
|
+
cursor: selectCursor
|
|
1932
|
+
}
|
|
1933
|
+
) : /* @__PURE__ */ React2.createElement(CommandBar, { buffer: cmdBuffer, running: cmdRunning })));
|
|
1934
|
+
}
|
|
1935
|
+
var logBuffer = [];
|
|
1936
|
+
var logListeners = [];
|
|
1937
|
+
function addLog(source, message, type = "info") {
|
|
1938
|
+
const entry = {
|
|
1939
|
+
time: (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8),
|
|
1940
|
+
source,
|
|
1941
|
+
message,
|
|
1942
|
+
type
|
|
1943
|
+
};
|
|
1944
|
+
logBuffer.push(entry);
|
|
1945
|
+
if (logBuffer.length > 200) logBuffer.shift();
|
|
1946
|
+
logListeners.forEach((fn) => fn([...logBuffer]));
|
|
1947
|
+
}
|
|
1948
|
+
function startInkUI(vault, getAgentStatus, onExit) {
|
|
1949
|
+
function App() {
|
|
1950
|
+
const [agentStatus, setAgentStatus] = useState2(getAgentStatus());
|
|
1951
|
+
useEffect(() => {
|
|
1952
|
+
const interval = setInterval(
|
|
1953
|
+
() => setAgentStatus(getAgentStatus()),
|
|
1954
|
+
2e3
|
|
1955
|
+
);
|
|
1956
|
+
return () => clearInterval(interval);
|
|
1957
|
+
}, []);
|
|
1958
|
+
return /* @__PURE__ */ React2.createElement(Dashboard, { vault, agentStatus, onExit });
|
|
1959
|
+
}
|
|
1960
|
+
render2(/* @__PURE__ */ React2.createElement(App, null));
|
|
1961
|
+
}
|
|
1962
|
+
export {
|
|
1963
|
+
ERROR_CODES,
|
|
1964
|
+
KNOWN_PROGRAMS,
|
|
1965
|
+
RPC_URLS,
|
|
1966
|
+
SESSIONS_FILE,
|
|
1967
|
+
WalletRequestHandler,
|
|
1968
|
+
WalletVault,
|
|
1969
|
+
addAccount,
|
|
1970
|
+
addLog,
|
|
1971
|
+
changePassword,
|
|
1972
|
+
cmdAccounts,
|
|
1973
|
+
cmdAirdrop,
|
|
1974
|
+
cmdConnect,
|
|
1975
|
+
cmdDisconnect,
|
|
1976
|
+
cmdInit,
|
|
1977
|
+
cmdLock,
|
|
1978
|
+
cmdNewAccount,
|
|
1979
|
+
cmdSend,
|
|
1980
|
+
cmdSessions,
|
|
1981
|
+
cmdSetCluster,
|
|
1982
|
+
cmdUnlock,
|
|
1983
|
+
cmdUseAccount,
|
|
1984
|
+
configExists,
|
|
1985
|
+
confirmTransaction,
|
|
1986
|
+
createAccountStore,
|
|
1987
|
+
createConfig,
|
|
1988
|
+
deriveAccount,
|
|
1989
|
+
deriveAccountFromMnemonic,
|
|
1990
|
+
deriveAccounts,
|
|
1991
|
+
disconnectDapp,
|
|
1992
|
+
ensureWalletDir,
|
|
1993
|
+
formatAccount,
|
|
1994
|
+
generateMnemonic2 as generateMnemonic,
|
|
1995
|
+
getAccountStore,
|
|
1996
|
+
getActiveAccount,
|
|
1997
|
+
getAllSessions,
|
|
1998
|
+
getBalance,
|
|
1999
|
+
getCluster,
|
|
2000
|
+
getConfigPath,
|
|
2001
|
+
getConnection,
|
|
2002
|
+
getLatestBlockhash,
|
|
2003
|
+
getRpcUrl,
|
|
2004
|
+
getSession,
|
|
2005
|
+
getSessionsPath,
|
|
2006
|
+
getSignClient,
|
|
2007
|
+
getVaultPath,
|
|
2008
|
+
getWalletDir,
|
|
2009
|
+
handleRequest,
|
|
2010
|
+
healthCheck,
|
|
2011
|
+
initWalletConnect,
|
|
2012
|
+
inkConfirm,
|
|
2013
|
+
inkPassword,
|
|
2014
|
+
inkSelect,
|
|
2015
|
+
isUnlocked,
|
|
2016
|
+
loadConfig,
|
|
2017
|
+
loadVault,
|
|
2018
|
+
markInactive,
|
|
2019
|
+
mnemonicToNumberedWords,
|
|
2020
|
+
mnemonicToSeed2 as mnemonicToSeed,
|
|
2021
|
+
pairWithDapp,
|
|
2022
|
+
removeSession,
|
|
2023
|
+
renameAccount,
|
|
2024
|
+
requestAirdrop,
|
|
2025
|
+
resetConnection,
|
|
2026
|
+
saveConfig,
|
|
2027
|
+
saveSession,
|
|
2028
|
+
saveVault,
|
|
2029
|
+
sendSignedTransaction,
|
|
2030
|
+
setActiveAccount,
|
|
2031
|
+
signAllTransactions,
|
|
2032
|
+
signAndSendTransaction,
|
|
2033
|
+
signMessage,
|
|
2034
|
+
signOffchainMessage,
|
|
2035
|
+
signTransaction,
|
|
2036
|
+
simulateTransaction,
|
|
2037
|
+
startInkUI,
|
|
2038
|
+
updateAccountStore,
|
|
2039
|
+
updateActivity,
|
|
2040
|
+
updateCluster,
|
|
2041
|
+
updateRpcUrl,
|
|
2042
|
+
validateMnemonic2 as validateMnemonic,
|
|
2043
|
+
vaultExists,
|
|
2044
|
+
wordsToMnemonic
|
|
2045
|
+
};
|
|
2046
|
+
//# sourceMappingURL=index.js.map
|