nebulon-escrow-cli 0.1.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.
@@ -0,0 +1,90 @@
1
+ const { loadConfig } = require("../config");
2
+ const { runLogin } = require("./login");
3
+ const { me, faucet } = require("../hosted");
4
+ const { ensureHostedSession } = require("../session");
5
+ const { successMessage } = require("../ui");
6
+
7
+ const ensureHosted = (config) => {
8
+ if (config.mode !== "hosted") {
9
+ console.error("Hosted commands require hosted mode.");
10
+ process.exit(1);
11
+ }
12
+ };
13
+
14
+ const runHostedLogin = async (options = {}) => {
15
+ await runLogin(options);
16
+ };
17
+
18
+ const runHostedMe = async () => {
19
+ const config = loadConfig();
20
+ ensureHosted(config);
21
+ try {
22
+ await ensureHostedSession(config, { quiet: true });
23
+ } catch (error) {
24
+ console.error(error.message);
25
+ process.exit(1);
26
+ }
27
+ const profile = await me(config.backendUrl, config.auth.token);
28
+ console.log(JSON.stringify(profile, null, 2));
29
+ };
30
+
31
+ const runHostedFaucet = async () => {
32
+ const config = loadConfig();
33
+ ensureHosted(config);
34
+ try {
35
+ await ensureHostedSession(config, { quiet: true });
36
+ } catch (error) {
37
+ console.error(error.message);
38
+ process.exit(1);
39
+ }
40
+ try {
41
+ const result = await faucet(config.backendUrl, config.auth.token);
42
+ console.log("Faucet request submitted.");
43
+ console.log(`Amount: ${result.amount} USDC`);
44
+ console.log(`Mint: ${result.mint}`);
45
+ console.log(`Token account: ${result.tokenAccount}`);
46
+ console.log(`Network: ${result.network}`);
47
+ console.log(`Tx: ${result.txSig}`);
48
+ if (result.nextAvailableAt) {
49
+ console.log("Next available: try again later.");
50
+ }
51
+ successMessage("Faucet request completed.");
52
+ } catch (error) {
53
+ const code = error.message || "faucet_failed";
54
+ const details = error.data || {};
55
+ if (code === "wallet_rate_limited" || code === "ip_rate_limited") {
56
+ console.error("Faucet rate limit reached.");
57
+ console.error("Try again later.");
58
+ return;
59
+ }
60
+ if (code === "faucet_disabled") {
61
+ console.error("Faucet is disabled on this server.");
62
+ return;
63
+ }
64
+ if (code === "faucet_network_blocked") {
65
+ console.error("Faucet is not enabled for this network.");
66
+ if (details.network) {
67
+ console.error(`Network: ${details.network}`);
68
+ }
69
+ return;
70
+ }
71
+ if (code === "faucet_not_configured") {
72
+ console.error("Faucet is not configured yet.");
73
+ return;
74
+ }
75
+ if (code === "faucet_failed") {
76
+ console.error("Faucet request failed.");
77
+ if (details.message) {
78
+ console.error(details.message);
79
+ }
80
+ return;
81
+ }
82
+ console.error(`Faucet request failed: ${code}`);
83
+ }
84
+ };
85
+
86
+ module.exports = {
87
+ runHostedLogin,
88
+ runHostedMe,
89
+ runHostedFaucet,
90
+ };
@@ -0,0 +1,458 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { prompt } = require("enquirer");
4
+ const chalk = require("chalk");
5
+ const { loadConfig, saveConfig } = require("../config");
6
+ const { getActiveCapsule, setActiveCapsule } = require("../capsules");
7
+ const { capsuleWalletsDir } = require("../paths");
8
+ const { NETWORK_PRESETS, PROGRAM_ID_DEFAULT } = require("../constants");
9
+ const { getConfig } = require("../hosted");
10
+ const {
11
+ addWalletEntry,
12
+ generateWallet,
13
+ importWalletFromFile,
14
+ setActiveWallet,
15
+ } = require("../wallets");
16
+ const { banner, successMessage } = require("../ui");
17
+
18
+ const isPromptCancel = (error) => {
19
+ if (!error) {
20
+ return true;
21
+ }
22
+ if (typeof error === "string") {
23
+ return error.trim() === "";
24
+ }
25
+ const message = (error.message || "").toLowerCase();
26
+ return (
27
+ message.includes("cancel") ||
28
+ message.includes("aborted") ||
29
+ message.includes("terminated")
30
+ );
31
+ };
32
+
33
+ const ensureFileExists = (value) => {
34
+ const trimmed = value.trim();
35
+ if (!trimmed || !fs.existsSync(trimmed)) {
36
+ return "File not found.";
37
+ }
38
+ return true;
39
+ };
40
+
41
+ const promptForNetwork = async (current) => {
42
+ const networkAnswer = await prompt({
43
+ type: "select",
44
+ name: "network",
45
+ message: "Select network",
46
+ initial: current,
47
+ choices: [
48
+ { name: "localnet", message: "localnet" },
49
+ { name: "devnet", message: "devnet" },
50
+ { name: "mainnet", message: "mainnet" },
51
+ { name: "custom", message: "custom" },
52
+ ],
53
+ });
54
+ return networkAnswer.network;
55
+ };
56
+
57
+ const listImportOptions = (importDir) => {
58
+ if (!importDir) {
59
+ return [];
60
+ }
61
+ if (!fs.existsSync(importDir)) {
62
+ fs.mkdirSync(importDir, { recursive: true });
63
+ }
64
+ return fs
65
+ .readdirSync(importDir)
66
+ .filter((file) => file.endsWith(".json"))
67
+ .map((file) => ({
68
+ name: file,
69
+ message: file,
70
+ value: path.join(importDir, file),
71
+ }));
72
+ };
73
+
74
+ const promptForWallet = async (config) => {
75
+ const importDir = path.join("C:\\", "Nebulon", "Wallets");
76
+ const capsule = getActiveCapsule();
77
+ const capsuleWalletDir = capsuleWalletsDir(capsule);
78
+
79
+ while (true) {
80
+ const walletAnswer = await prompt({
81
+ type: "select",
82
+ name: "walletAction",
83
+ message: "Wallet setup",
84
+ choices: [
85
+ { name: "generate", message: "Generate new wallet" },
86
+ { name: "import", message: "Import keypair file" },
87
+ ],
88
+ });
89
+
90
+ if (walletAnswer.walletAction === "generate") {
91
+ return { action: "generate", name: "default" };
92
+ }
93
+
94
+ const importChoices = listImportOptions(importDir);
95
+ if (!importChoices.length) {
96
+ console.log(`No wallet files found in ${importDir}.`);
97
+ continue;
98
+ }
99
+ const importAnswer = await prompt({
100
+ type: "select",
101
+ name: "walletPath",
102
+ message: `Select keypair file (${importDir})`,
103
+ choices: [...importChoices, { name: "back", message: "Back", value: "back" }],
104
+ });
105
+
106
+ if (importAnswer.walletPath === "back") {
107
+ continue;
108
+ }
109
+
110
+ const resolvedPath = path.isAbsolute(importAnswer.walletPath)
111
+ ? importAnswer.walletPath
112
+ : path.join(importDir, importAnswer.walletPath);
113
+ return {
114
+ action: "import",
115
+ name: "default",
116
+ path: path.resolve(resolvedPath),
117
+ };
118
+ }
119
+ };
120
+
121
+ const runInit = async (options = {}, capsuleName) => {
122
+ try {
123
+ if (!options.noBanner) {
124
+ banner();
125
+ }
126
+ if (capsuleName) {
127
+ try {
128
+ setActiveCapsule(capsuleName);
129
+ } catch (error) {
130
+ console.error(error.message);
131
+ process.exit(1);
132
+ }
133
+ }
134
+ const config = loadConfig();
135
+
136
+ const modeAnswer = { mode: "hosted" };
137
+
138
+ let backendAnswer = { backendUrl: config.backendUrl };
139
+ let backendSource = config.backendSource || "custom";
140
+ let backendNetwork = null;
141
+ let serverChoice = null;
142
+ try {
143
+ await getConfig((config.backendUrl || "http://174.138.42.117:3333").trim());
144
+ serverChoice = await prompt({
145
+ type: "select",
146
+ name: "server",
147
+ message: "Use official server or custom?",
148
+ choices: [
149
+ { name: "official", message: "Official server" },
150
+ { name: "custom", message: "Custom server" },
151
+ ],
152
+ });
153
+ } catch (error) {
154
+ serverChoice = { server: "custom" };
155
+ }
156
+
157
+ if (serverChoice.server === "custom") {
158
+ backendAnswer = await prompt({
159
+ type: "input",
160
+ name: "backendUrl",
161
+ message: "Backend server URL",
162
+ initial: config.backendUrl,
163
+ });
164
+ backendSource = "custom";
165
+ } else {
166
+ backendAnswer = { backendUrl: "http://174.138.42.117:3333" };
167
+ backendSource = "official";
168
+ }
169
+
170
+ let programId = config.programId || PROGRAM_ID_DEFAULT;
171
+ let usdcMint = config.usdcMint;
172
+ let rpcUrl = config.rpcUrl;
173
+ let wsUrl = config.wsUrl;
174
+ let ephemeralProviderUrl = config.ephemeralProviderUrl || "";
175
+ let ephemeralWsUrl = config.ephemeralWsUrl || "";
176
+ let ephemeralValidatorIdentity = config.ephemeralValidatorIdentity || "";
177
+ let ephemeralPermissionEndpoint = config.ephemeralPermissionEndpoint || "";
178
+ let ephemeralTeeEndpoint = config.ephemeralTeeEndpoint || "";
179
+ let ephemeralTeeWsEndpoint = config.ephemeralTeeWsEndpoint || "";
180
+ let programIdSource = "custom";
181
+ let usdcMintSource = "custom";
182
+ let rpcUrlSource = config.rpcUrlSource || "custom";
183
+ let wsUrlSource = config.wsUrlSource || "custom";
184
+ let ephemeralProviderUrlSource =
185
+ config.ephemeralProviderUrlSource || "custom";
186
+ let ephemeralWsUrlSource = config.ephemeralWsUrlSource || "custom";
187
+ let ephemeralValidatorIdentitySource =
188
+ config.ephemeralValidatorIdentitySource || "custom";
189
+ let ephemeralPermissionEndpointSource =
190
+ config.ephemeralPermissionEndpointSource || "custom";
191
+ let ephemeralTeeEndpointSource =
192
+ config.ephemeralTeeEndpointSource || "custom";
193
+ let ephemeralTeeWsEndpointSource =
194
+ config.ephemeralTeeWsEndpointSource || "custom";
195
+ let networkSource = config.networkSource || "custom";
196
+
197
+ if (modeAnswer.mode === "hosted") {
198
+ let backendConfig = null;
199
+ try {
200
+ backendConfig = await getConfig(backendAnswer.backendUrl.trim());
201
+ } catch (error) {
202
+ backendConfig = null;
203
+ }
204
+ if (!backendConfig) {
205
+ const maintenanceAnswer = await prompt({
206
+ type: "confirm",
207
+ name: "continue",
208
+ message:
209
+ "Servers are currently on maintenance. Would you like to continue anyways? (invites, and profiles may not load correctly)",
210
+ initial: false,
211
+ });
212
+ if (!maintenanceAnswer.continue) {
213
+ console.log("Init canceled.");
214
+ return;
215
+ }
216
+ }
217
+
218
+ console.log("\u2713 Fetching ProgramID address + T-USDC Mint address from server...");
219
+ try {
220
+ const backendConfig = await getConfig(backendAnswer.backendUrl.trim());
221
+ if (backendConfig.network) {
222
+ backendNetwork = backendConfig.network;
223
+ networkSource = "backend";
224
+ }
225
+ if (backendConfig.rpcUrl) {
226
+ rpcUrl = backendConfig.rpcUrl;
227
+ rpcUrlSource = "backend";
228
+ }
229
+ if (backendConfig.wsUrl) {
230
+ wsUrl = backendConfig.wsUrl;
231
+ wsUrlSource = "backend";
232
+ }
233
+ if (backendConfig.ephemeralProviderUrl) {
234
+ ephemeralProviderUrl = backendConfig.ephemeralProviderUrl;
235
+ ephemeralProviderUrlSource = "backend";
236
+ }
237
+ if (backendConfig.ephemeralWsUrl) {
238
+ ephemeralWsUrl = backendConfig.ephemeralWsUrl;
239
+ ephemeralWsUrlSource = "backend";
240
+ }
241
+ if (backendConfig.ephemeralValidatorIdentity) {
242
+ ephemeralValidatorIdentity = backendConfig.ephemeralValidatorIdentity;
243
+ ephemeralValidatorIdentitySource = "backend";
244
+ }
245
+ if (backendConfig.ephemeralPermissionEndpoint) {
246
+ ephemeralPermissionEndpoint = backendConfig.ephemeralPermissionEndpoint;
247
+ ephemeralPermissionEndpointSource = "backend";
248
+ }
249
+ if (backendConfig.ephemeralTeeEndpoint) {
250
+ ephemeralTeeEndpoint = backendConfig.ephemeralTeeEndpoint;
251
+ ephemeralTeeEndpointSource = "backend";
252
+ }
253
+ if (backendConfig.ephemeralTeeWsEndpoint) {
254
+ ephemeralTeeWsEndpoint = backendConfig.ephemeralTeeWsEndpoint;
255
+ ephemeralTeeWsEndpointSource = "backend";
256
+ }
257
+ if (backendConfig.programId) {
258
+ programId = backendConfig.programId;
259
+ programIdSource = "backend";
260
+ }
261
+ if (backendConfig.usdcMint) {
262
+ usdcMint = backendConfig.usdcMint;
263
+ usdcMintSource = "backend";
264
+ }
265
+ console.log("\u2713 Fetching succeeded!");
266
+ } catch (error) {
267
+ console.log("\u2713 Fetching failed. Using existing values.");
268
+ }
269
+ }
270
+
271
+ const network =
272
+ backendNetwork || (await promptForNetwork(config.network || "localnet"));
273
+ if (!backendNetwork) {
274
+ networkSource = "custom";
275
+ }
276
+ const networkPreset = NETWORK_PRESETS[network];
277
+
278
+ if (modeAnswer.mode === "hosted") {
279
+ if (!rpcUrl || !wsUrl) {
280
+ rpcUrl = networkPreset ? networkPreset.rpcUrl : config.rpcUrl;
281
+ wsUrl = networkPreset ? networkPreset.wsUrl : config.wsUrl;
282
+ if (networkPreset) {
283
+ rpcUrlSource = "official";
284
+ wsUrlSource = "official";
285
+ } else {
286
+ rpcUrlSource = "custom";
287
+ wsUrlSource = "custom";
288
+ }
289
+ }
290
+ if (backendNetwork) {
291
+ console.log(`\u2713 Select network \u00b7 ${backendNetwork}`);
292
+ }
293
+ console.log(`\u2713 Solana JSON RPC URL \u00b7 ${rpcUrl}`);
294
+ console.log(`\u2713 Solana WebSocket URL \u00b7 ${wsUrl}`);
295
+ if (ephemeralProviderUrl) {
296
+ console.log(`\u2713 MagicBlock RPC URL \u00b7 ${ephemeralProviderUrl}`);
297
+ }
298
+ if (ephemeralWsUrl) {
299
+ console.log(`\u2713 MagicBlock WebSocket URL \u00b7 ${ephemeralWsUrl}`);
300
+ }
301
+ if (ephemeralValidatorIdentity) {
302
+ console.log(
303
+ `\u2713 MagicBlock Validator Identity \u00b7 ${ephemeralValidatorIdentity}`
304
+ );
305
+ }
306
+ if (ephemeralPermissionEndpoint) {
307
+ console.log(
308
+ `\u2713 MagicBlock Permission Endpoint \u00b7 ${ephemeralPermissionEndpoint}`
309
+ );
310
+ }
311
+ if (ephemeralTeeEndpoint) {
312
+ console.log(`\u2713 MagicBlock TEE Endpoint \u00b7 ${ephemeralTeeEndpoint}`);
313
+ }
314
+ if (ephemeralTeeWsEndpoint) {
315
+ console.log(
316
+ `\u2713 MagicBlock TEE WS Endpoint \u00b7 ${ephemeralTeeWsEndpoint}`
317
+ );
318
+ }
319
+ console.log(
320
+ chalk.gray("Hint: you can change settings later with `nebulon config`.")
321
+ );
322
+ } else {
323
+ rpcUrl = networkPreset ? networkPreset.rpcUrl : config.rpcUrl;
324
+ wsUrl = networkPreset ? networkPreset.wsUrl : config.wsUrl;
325
+
326
+ if (network === "localnet" || network === "custom") {
327
+ const rpcAnswer = await prompt({
328
+ type: "input",
329
+ name: "rpcUrl",
330
+ message: "Solana JSON RPC URL",
331
+ initial: networkPreset ? networkPreset.rpcUrl : config.rpcUrl,
332
+ });
333
+
334
+ const wsAnswer = await prompt({
335
+ type: "input",
336
+ name: "wsUrl",
337
+ message: "Solana WebSocket URL",
338
+ initial: networkPreset ? networkPreset.wsUrl : config.wsUrl,
339
+ });
340
+
341
+ rpcUrl = rpcAnswer.rpcUrl.trim();
342
+ wsUrl = wsAnswer.wsUrl.trim();
343
+ rpcUrlSource = "custom";
344
+ wsUrlSource = "custom";
345
+ } else if (networkPreset) {
346
+ rpcUrlSource = "official";
347
+ wsUrlSource = "official";
348
+ console.log(
349
+ `Using official Solana RPC endpoints for ${network} (change later with nebulon config).`
350
+ );
351
+ }
352
+ }
353
+
354
+ if (modeAnswer.mode !== "hosted") {
355
+ const programAnswer = await prompt({
356
+ type: "input",
357
+ name: "programId",
358
+ message: "Program ID",
359
+ initial: programId,
360
+ });
361
+ programId = programAnswer.programId.trim();
362
+ programIdSource = "custom";
363
+ }
364
+
365
+ if (modeAnswer.mode !== "hosted") {
366
+ const usdcAnswer = await prompt({
367
+ type: "input",
368
+ name: "usdcMint",
369
+ message: "Test-USDC mint",
370
+ initial: usdcMint,
371
+ });
372
+ usdcMint = usdcAnswer.usdcMint.trim();
373
+ usdcMintSource = "custom";
374
+ }
375
+
376
+ const walletConfig = await promptForWallet(config);
377
+
378
+ config.mode = modeAnswer.mode;
379
+ config.network = network;
380
+ config.rpcUrl = rpcUrl;
381
+ config.wsUrl = wsUrl;
382
+ config.programId = programId;
383
+ config.usdcMint = usdcMint;
384
+ config.programIdSource = programIdSource;
385
+ config.usdcMintSource = usdcMintSource;
386
+ config.rpcUrlSource = rpcUrlSource;
387
+ config.wsUrlSource = wsUrlSource;
388
+ config.ephemeralProviderUrlSource = ephemeralProviderUrlSource;
389
+ config.ephemeralWsUrlSource = ephemeralWsUrlSource;
390
+ config.ephemeralValidatorIdentitySource = ephemeralValidatorIdentitySource;
391
+ config.ephemeralPermissionEndpointSource = ephemeralPermissionEndpointSource;
392
+ config.ephemeralTeeEndpointSource = ephemeralTeeEndpointSource;
393
+ config.ephemeralTeeWsEndpointSource = ephemeralTeeWsEndpointSource;
394
+ config.networkSource = networkSource;
395
+ config.backendSource = backendSource;
396
+ config.ephemeralProviderUrl = ephemeralProviderUrl;
397
+ config.ephemeralWsUrl = ephemeralWsUrl;
398
+ config.ephemeralValidatorIdentity = ephemeralValidatorIdentity;
399
+ config.ephemeralPermissionEndpoint = ephemeralPermissionEndpoint;
400
+ config.ephemeralTeeEndpoint = ephemeralTeeEndpoint;
401
+ config.ephemeralTeeWsEndpoint = ephemeralTeeWsEndpoint;
402
+ if (modeAnswer.mode === "hosted") {
403
+ config.backendUrl = backendAnswer.backendUrl.trim();
404
+ }
405
+
406
+ if (walletConfig) {
407
+ if (walletConfig.action === "generate") {
408
+ config.wallets = {};
409
+ const { path: walletPath, keypair } = generateWallet(walletConfig.name);
410
+ addWalletEntry(config, walletConfig.name, walletPath);
411
+ setActiveWallet(config, walletConfig.name);
412
+ console.log(`Wallet address: ${keypair.publicKey.toBase58()}`);
413
+ console.log("Tip: run `nebulon wallet export` to back it up.");
414
+ } else if (walletConfig.action === "import") {
415
+ config.wallets = {};
416
+ const { path: walletPath, keypair } = importWalletFromFile(
417
+ walletConfig.name,
418
+ walletConfig.path
419
+ );
420
+ addWalletEntry(config, walletConfig.name, walletPath);
421
+ setActiveWallet(config, walletConfig.name);
422
+ console.log(`Wallet address: ${keypair.publicKey.toBase58()}`);
423
+ console.log("Tip: run `nebulon wallet export` to back it up.");
424
+ const deleteAnswer = await prompt({
425
+ type: "confirm",
426
+ name: "delete",
427
+ message: `Delete "${path.basename(walletConfig.path)}" from the wallets folder? (recommended)`,
428
+ initial: false,
429
+ });
430
+ if (deleteAnswer.delete === true) {
431
+ try {
432
+ fs.unlinkSync(walletConfig.path);
433
+ } catch (error) {
434
+ console.error("Unable to delete the imported file.");
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ saveConfig(config);
441
+
442
+ console.log("");
443
+ successMessage("Initial setup complete!");
444
+ console.log("Now run:");
445
+ console.log(" nebulon login");
446
+ console.log(" nebulon status");
447
+ } catch (error) {
448
+ if (isPromptCancel(error)) {
449
+ console.log("Init canceled early. No changes were applied.");
450
+ return;
451
+ }
452
+ throw error;
453
+ }
454
+ };
455
+
456
+ module.exports = {
457
+ runInit,
458
+ };