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,445 @@
1
+ const { PublicKey } = require("@solana/web3.js");
2
+ const chalk = require("chalk");
3
+ const { loadConfig, saveConfig, setNestedValue } = require("../config");
4
+ const { getActiveCapsule } = require("../capsules");
5
+ const { NETWORK_PRESETS } = require("../constants");
6
+ const { getConfig: getHostedConfig, me } = require("../hosted");
7
+ const { loadWalletKeypair } = require("../wallets");
8
+ const { keyValue } = require("../ui");
9
+
10
+ const sourceLabel = (source, backendSource) => {
11
+ if (!source || source === "custom") {
12
+ return "custom";
13
+ }
14
+ if (source === "backend") {
15
+ return backendSource === "custom" ? "custom" : "auto";
16
+ }
17
+ if (source === "official") {
18
+ return "auto";
19
+ }
20
+ return "auto";
21
+ };
22
+
23
+ const tag = (value) => chalk.gray(`(${value})`);
24
+
25
+ const backendLabel = (config) => {
26
+ if (config.backendSource) {
27
+ return config.backendSource;
28
+ }
29
+ if (config.backendUrl === "http://174.138.42.117:3333") {
30
+ return "official";
31
+ }
32
+ return "custom";
33
+ };
34
+
35
+ const printConfigSummary = async (config) => {
36
+ console.log("NEBULON - Configuration");
37
+ console.log("");
38
+ keyValue("Capsule", getActiveCapsule());
39
+ keyValue("Mode", `${config.mode} ${tag(backendLabel(config))}`);
40
+
41
+ let statusValue = "n/a";
42
+ let profile = null;
43
+ if (config.mode === "hosted") {
44
+ if (config.auth && config.auth.token) {
45
+ try {
46
+ profile = await me(config.backendUrl, config.auth.token);
47
+ statusValue = "Connected To Server";
48
+ } catch (error) {
49
+ statusValue = "Disconnected From Server";
50
+ }
51
+ } else {
52
+ statusValue = "Disconnected From Server";
53
+ }
54
+ }
55
+ keyValue("Status", statusValue);
56
+
57
+ const nebHandle =
58
+ (profile && (profile.nebulonId || profile.handle)) ||
59
+ (config.auth && config.auth.handle) ||
60
+ "none";
61
+ keyValue("NebulonID", config.mode === "hosted" ? nebHandle : "n/a");
62
+
63
+ if (config.activeWallet) {
64
+ try {
65
+ const keypair = loadWalletKeypair(config, config.activeWallet);
66
+ keyValue(
67
+ "Wallet",
68
+ `${config.activeWallet} (${keypair.publicKey.toBase58()})`
69
+ );
70
+ } catch (error) {
71
+ keyValue("Wallet", config.activeWallet);
72
+ }
73
+ } else {
74
+ keyValue("Wallet", "none");
75
+ }
76
+
77
+ console.log("");
78
+ console.log("Server Settings");
79
+ keyValue(
80
+ "Server URL",
81
+ config.mode === "hosted"
82
+ ? `${config.backendUrl} ${tag(backendLabel(config))}`
83
+ : "n/a"
84
+ );
85
+ keyValue(
86
+ "Network",
87
+ `${config.network} ${tag(sourceLabel(config.networkSource, config.backendSource))}`
88
+ );
89
+ if (config.ephemeralProviderUrl) {
90
+ keyValue(
91
+ "MagicBlock RPC URL",
92
+ `${config.ephemeralProviderUrl} ${tag(sourceLabel(config.ephemeralProviderUrlSource, config.backendSource))}`
93
+ );
94
+ }
95
+ if (config.ephemeralWsUrl) {
96
+ keyValue(
97
+ "MagicBlock WebSocket URL",
98
+ `${config.ephemeralWsUrl} ${tag(sourceLabel(config.ephemeralWsUrlSource, config.backendSource))}`
99
+ );
100
+ }
101
+ if (config.ephemeralValidatorIdentity) {
102
+ keyValue(
103
+ "MagicBlock Validator Identity",
104
+ `${config.ephemeralValidatorIdentity} ${tag(sourceLabel(config.ephemeralValidatorIdentitySource, config.backendSource))}`
105
+ );
106
+ }
107
+ if (config.ephemeralPermissionEndpoint) {
108
+ keyValue(
109
+ "MagicBlock Permission Endpoint",
110
+ `${config.ephemeralPermissionEndpoint} ${tag(sourceLabel(config.ephemeralPermissionEndpointSource, config.backendSource))}`
111
+ );
112
+ }
113
+ if (config.ephemeralTeeEndpoint) {
114
+ keyValue(
115
+ "MagicBlock TEE Endpoint",
116
+ `${config.ephemeralTeeEndpoint} ${tag(sourceLabel(config.ephemeralTeeEndpointSource, config.backendSource))}`
117
+ );
118
+ }
119
+ if (config.ephemeralTeeWsEndpoint) {
120
+ keyValue(
121
+ "MagicBlock TEE WS Endpoint",
122
+ `${config.ephemeralTeeWsEndpoint} ${tag(sourceLabel(config.ephemeralTeeWsEndpointSource, config.backendSource))}`
123
+ );
124
+ }
125
+ keyValue(
126
+ "Solana JSON RPC URL",
127
+ `${config.rpcUrl} ${tag(sourceLabel(config.rpcUrlSource, config.backendSource))}`
128
+ );
129
+ keyValue(
130
+ "Solana WebSocket URL",
131
+ `${config.wsUrl} ${tag(sourceLabel(config.wsUrlSource, config.backendSource))}`
132
+ );
133
+
134
+ console.log("");
135
+ console.log("Web3 Settings");
136
+ keyValue(
137
+ "Program ID",
138
+ `${config.programId} ${tag(sourceLabel(config.programIdSource, config.backendSource))}`
139
+ );
140
+ keyValue(
141
+ "USDC mint address:",
142
+ `${config.usdcMint} ${tag(sourceLabel(config.usdcMintSource, config.backendSource))}`
143
+ );
144
+ console.log("");
145
+ };
146
+
147
+ const isValidUrl = (value, protocols) => {
148
+ try {
149
+ const url = new URL(value);
150
+ if (protocols && !protocols.includes(url.protocol)) {
151
+ return false;
152
+ }
153
+ return true;
154
+ } catch (error) {
155
+ return false;
156
+ }
157
+ };
158
+
159
+ const isValidPubkey = (value) => {
160
+ try {
161
+ // eslint-disable-next-line no-new
162
+ new PublicKey(value);
163
+ return true;
164
+ } catch (error) {
165
+ return false;
166
+ }
167
+ };
168
+
169
+ const applyNetworkPreset = (config, network) => {
170
+ const preset = NETWORK_PRESETS[network];
171
+ if (!preset) {
172
+ config.network = network;
173
+ return;
174
+ }
175
+ config.network = network;
176
+ config.rpcUrl = preset.rpcUrl;
177
+ config.wsUrl = preset.wsUrl;
178
+ config.usdcMint = preset.usdcMint;
179
+ config.rpcUrlSource = "official";
180
+ config.wsUrlSource = "official";
181
+ config.usdcMintSource = "official";
182
+ };
183
+
184
+ const maybeRefreshFromBackend = async (config) => {
185
+ if (config.mode !== "hosted" || !config.backendUrl) {
186
+ return false;
187
+ }
188
+ try {
189
+ const backendConfig = await getHostedConfig(config.backendUrl);
190
+ if (backendConfig.programId && config.programIdSource === "backend") {
191
+ config.programId = backendConfig.programId;
192
+ }
193
+ if (backendConfig.usdcMint && config.usdcMintSource === "backend") {
194
+ config.usdcMint = backendConfig.usdcMint;
195
+ }
196
+ if (backendConfig.rpcUrl && config.rpcUrlSource === "backend") {
197
+ config.rpcUrl = backendConfig.rpcUrl;
198
+ }
199
+ if (backendConfig.wsUrl && config.wsUrlSource === "backend") {
200
+ config.wsUrl = backendConfig.wsUrl;
201
+ }
202
+ if (
203
+ backendConfig.ephemeralProviderUrl &&
204
+ config.ephemeralProviderUrlSource === "backend"
205
+ ) {
206
+ config.ephemeralProviderUrl = backendConfig.ephemeralProviderUrl;
207
+ }
208
+ if (
209
+ backendConfig.ephemeralWsUrl &&
210
+ config.ephemeralWsUrlSource === "backend"
211
+ ) {
212
+ config.ephemeralWsUrl = backendConfig.ephemeralWsUrl;
213
+ }
214
+ if (
215
+ backendConfig.ephemeralValidatorIdentity &&
216
+ config.ephemeralValidatorIdentitySource === "backend"
217
+ ) {
218
+ config.ephemeralValidatorIdentity = backendConfig.ephemeralValidatorIdentity;
219
+ }
220
+ if (
221
+ backendConfig.ephemeralPermissionEndpoint &&
222
+ config.ephemeralPermissionEndpointSource === "backend"
223
+ ) {
224
+ config.ephemeralPermissionEndpoint = backendConfig.ephemeralPermissionEndpoint;
225
+ }
226
+ if (
227
+ backendConfig.ephemeralTeeEndpoint &&
228
+ config.ephemeralTeeEndpointSource === "backend"
229
+ ) {
230
+ config.ephemeralTeeEndpoint = backendConfig.ephemeralTeeEndpoint;
231
+ }
232
+ if (
233
+ backendConfig.ephemeralTeeWsEndpoint &&
234
+ config.ephemeralTeeWsEndpointSource === "backend"
235
+ ) {
236
+ config.ephemeralTeeWsEndpoint = backendConfig.ephemeralTeeWsEndpoint;
237
+ }
238
+ if (backendConfig.network && config.networkSource === "backend") {
239
+ config.network = backendConfig.network;
240
+ }
241
+ return true;
242
+ } catch (error) {
243
+ return false;
244
+ }
245
+ };
246
+
247
+ const setConfigValue = async (config, key, value) => {
248
+ if (key === "mode") {
249
+ if (!["hosted", "direct"].includes(value)) {
250
+ throw new Error("Mode must be hosted or direct.");
251
+ }
252
+ config.mode = value;
253
+ return;
254
+ }
255
+
256
+ if (key === "network") {
257
+ if (!["localnet", "devnet", "mainnet", "custom"].includes(value)) {
258
+ throw new Error("Network must be localnet, devnet, mainnet, or custom.");
259
+ }
260
+ applyNetworkPreset(config, value);
261
+ config.networkSource = "custom";
262
+ return;
263
+ }
264
+
265
+ if (key === "rpcUrl") {
266
+ if (!isValidUrl(value, ["http:", "https:"])) {
267
+ throw new Error("Invalid RPC URL.");
268
+ }
269
+ config.rpcUrl = value;
270
+ config.rpcUrlSource = "custom";
271
+ return;
272
+ }
273
+
274
+ if (key === "wsUrl") {
275
+ if (!isValidUrl(value, ["ws:", "wss:"])) {
276
+ throw new Error("Invalid WebSocket URL.");
277
+ }
278
+ config.wsUrl = value;
279
+ config.wsUrlSource = "custom";
280
+ return;
281
+ }
282
+
283
+ if (key === "backendUrl") {
284
+ if (!isValidUrl(value, ["http:", "https:"])) {
285
+ throw new Error("Invalid backend URL.");
286
+ }
287
+ config.backendUrl = value;
288
+ config.backendSource =
289
+ value === "http://174.138.42.117:3333" ? "official" : "custom";
290
+ await maybeRefreshFromBackend(config);
291
+ return;
292
+ }
293
+
294
+ if (key === "ephemeralProviderUrl") {
295
+ if (!isValidUrl(value, ["http:", "https:"])) {
296
+ throw new Error("Invalid MagicBlock RPC URL.");
297
+ }
298
+ config.ephemeralProviderUrl = value;
299
+ config.ephemeralProviderUrlSource = "custom";
300
+ return;
301
+ }
302
+
303
+ if (key === "ephemeralWsUrl") {
304
+ if (!isValidUrl(value, ["ws:", "wss:"])) {
305
+ throw new Error("Invalid MagicBlock WebSocket URL.");
306
+ }
307
+ config.ephemeralWsUrl = value;
308
+ config.ephemeralWsUrlSource = "custom";
309
+ return;
310
+ }
311
+
312
+ if (key === "ephemeralValidatorIdentity") {
313
+ if (!isValidPubkey(value)) {
314
+ throw new Error("Invalid MagicBlock validator identity.");
315
+ }
316
+ config.ephemeralValidatorIdentity = value;
317
+ config.ephemeralValidatorIdentitySource = "custom";
318
+ return;
319
+ }
320
+
321
+ if (key === "ephemeralPermissionEndpoint") {
322
+ if (!isValidUrl(value, ["http:", "https:"])) {
323
+ throw new Error("Invalid MagicBlock permission endpoint.");
324
+ }
325
+ config.ephemeralPermissionEndpoint = value;
326
+ config.ephemeralPermissionEndpointSource = "custom";
327
+ return;
328
+ }
329
+
330
+ if (key === "ephemeralTeeEndpoint") {
331
+ if (!isValidUrl(value, ["http:", "https:"])) {
332
+ throw new Error("Invalid MagicBlock TEE endpoint.");
333
+ }
334
+ config.ephemeralTeeEndpoint = value;
335
+ config.ephemeralTeeEndpointSource = "custom";
336
+ return;
337
+ }
338
+
339
+ if (key === "ephemeralTeeWsEndpoint") {
340
+ if (!isValidUrl(value, ["ws:", "wss:"])) {
341
+ throw new Error("Invalid MagicBlock TEE WS endpoint.");
342
+ }
343
+ config.ephemeralTeeWsEndpoint = value;
344
+ config.ephemeralTeeWsEndpointSource = "custom";
345
+ return;
346
+ }
347
+
348
+ if (key === "programIdSource") {
349
+ if (!["backend", "custom"].includes(value)) {
350
+ throw new Error("Program ID source must be backend or custom.");
351
+ }
352
+ config.programIdSource = value;
353
+ if (value === "backend") {
354
+ const refreshed = await maybeRefreshFromBackend(config);
355
+ if (!refreshed) {
356
+ throw new Error("Unable to fetch backend config.");
357
+ }
358
+ }
359
+ return;
360
+ }
361
+
362
+ if (key === "usdcMintSource") {
363
+ if (!["backend", "custom"].includes(value)) {
364
+ throw new Error("Test-USDC mint source must be backend or custom.");
365
+ }
366
+ config.usdcMintSource = value;
367
+ if (value === "backend") {
368
+ const refreshed = await maybeRefreshFromBackend(config);
369
+ if (!refreshed) {
370
+ throw new Error("Unable to fetch backend config.");
371
+ }
372
+ }
373
+ return;
374
+ }
375
+
376
+ if (key === "programId") {
377
+ if (!isValidPubkey(value)) {
378
+ throw new Error("Invalid program ID.");
379
+ }
380
+ config.programId = value;
381
+ config.programIdSource = "custom";
382
+ return;
383
+ }
384
+
385
+ if (key === "usdcMint") {
386
+ if (!isValidPubkey(value)) {
387
+ throw new Error("Invalid mint address.");
388
+ }
389
+ config.usdcMint = value;
390
+ config.usdcMintSource = "custom";
391
+ return;
392
+ }
393
+
394
+ if (key === "activeWallet") {
395
+ if (!config.wallets || !config.wallets[value]) {
396
+ throw new Error("Wallet not found.");
397
+ }
398
+ config.activeWallet = value;
399
+ return;
400
+ }
401
+
402
+ setNestedValue(config, key, value);
403
+ };
404
+
405
+ const runConfigGet = (key) => {
406
+ const config = loadConfig();
407
+ const parts = key.split(".").filter(Boolean);
408
+ let current = config;
409
+ for (const part of parts) {
410
+ if (current && Object.prototype.hasOwnProperty.call(current, part)) {
411
+ current = current[part];
412
+ } else {
413
+ current = undefined;
414
+ break;
415
+ }
416
+ }
417
+ if (typeof current === "undefined") {
418
+ console.log("not found");
419
+ } else {
420
+ console.log(current);
421
+ }
422
+ };
423
+
424
+ const runConfigSet = async (key, value) => {
425
+ const config = loadConfig();
426
+ try {
427
+ await setConfigValue(config, key, value);
428
+ } catch (error) {
429
+ console.error(error.message);
430
+ process.exit(1);
431
+ }
432
+ saveConfig(config);
433
+ console.log("Updated.");
434
+ };
435
+
436
+ const runConfigSummary = async () => {
437
+ const config = loadConfig();
438
+ await printConfigSummary(config);
439
+ };
440
+
441
+ module.exports = {
442
+ runConfigGet,
443
+ runConfigSet,
444
+ runConfigSummary,
445
+ };