@twin.org/nft-cli 0.0.1-next.3

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,378 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { CLIParam, CLIDisplay, CLIOptions, CLIUtils, CLIBase } from '@twin.org/cli-core';
4
+ import { buildCommandMnemonic, buildCommandAddress } from '@twin.org/crypto-cli';
5
+ import { buildCommandFaucet } from '@twin.org/wallet-cli';
6
+ import { I18n, Converter, StringHelper, Is } from '@twin.org/core';
7
+ import { IotaNftConnector, IotaNftUtils } from '@twin.org/nft-connector-iota';
8
+ import { VaultConnectorFactory } from '@twin.org/vault-models';
9
+ import { Command } from 'commander';
10
+ import { MemoryEntityStorageConnector } from '@twin.org/entity-storage-connector-memory';
11
+ import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
12
+ import { initSchema, EntityStorageVaultConnector } from '@twin.org/vault-connector-entity-storage';
13
+
14
+ // Copyright 2024 IOTA Stiftung.
15
+ // SPDX-License-Identifier: Apache-2.0.
16
+ /**
17
+ * Setup the vault for use in the CLI commands.
18
+ */
19
+ function setupVault() {
20
+ initSchema();
21
+ EntityStorageConnectorFactory.register("vault-key", () => new MemoryEntityStorageConnector({
22
+ entitySchema: "VaultKey"
23
+ }));
24
+ EntityStorageConnectorFactory.register("vault-secret", () => new MemoryEntityStorageConnector({
25
+ entitySchema: "VaultSecret"
26
+ }));
27
+ const vaultConnector = new EntityStorageVaultConnector();
28
+ VaultConnectorFactory.register("vault", () => vaultConnector);
29
+ }
30
+
31
+ // Copyright 2024 IOTA Stiftung.
32
+ // SPDX-License-Identifier: Apache-2.0.
33
+ /**
34
+ * Build the nft burn command for the CLI.
35
+ * @returns The command.
36
+ */
37
+ function buildCommandNftBurn() {
38
+ const command = new Command();
39
+ command
40
+ .name("nft-burn")
41
+ .summary(I18n.formatMessage("commands.nft-burn.summary"))
42
+ .description(I18n.formatMessage("commands.nft-burn.description"))
43
+ .requiredOption(I18n.formatMessage("commands.nft-burn.options.seed.param"), I18n.formatMessage("commands.nft-burn.options.seed.description"))
44
+ .requiredOption(I18n.formatMessage("commands.nft-burn.options.issuer.param"), I18n.formatMessage("commands.nft-burn.options.issuer.description"))
45
+ .requiredOption(I18n.formatMessage("commands.nft-burn.options.id.param"), I18n.formatMessage("commands.nft-burn.options.id.description"));
46
+ command
47
+ .option(I18n.formatMessage("commands.common.options.node.param"), I18n.formatMessage("commands.common.options.node.description"), "!NODE_URL")
48
+ .option(I18n.formatMessage("commands.common.options.explorer.param"), I18n.formatMessage("commands.common.options.explorer.description"), "!EXPLORER_URL")
49
+ .action(actionCommandNftBurn);
50
+ return command;
51
+ }
52
+ /**
53
+ * Action the nft burn command.
54
+ * @param opts The options for the command.
55
+ * @param opts.seed The seed required for signing by the issuer.
56
+ * @param opts.issuer The issuer address of the NFT.
57
+ * @param opts.id The id of the NFT to burn in urn format.
58
+ * @param opts.node The node URL.
59
+ * @param opts.explorer The explorer URL.
60
+ */
61
+ async function actionCommandNftBurn(opts) {
62
+ const seed = CLIParam.hexBase64("seed", opts.seed);
63
+ const issuer = CLIParam.bech32("issuer", opts.issuer);
64
+ const id = CLIParam.stringValue("id", opts.id);
65
+ const nodeEndpoint = CLIParam.url("node", opts.node);
66
+ const explorerEndpoint = CLIParam.url("explorer", opts.explorer);
67
+ CLIDisplay.value(I18n.formatMessage("commands.nft-burn.labels.issuer"), issuer);
68
+ CLIDisplay.value(I18n.formatMessage("commands.nft-burn.labels.nftId"), id);
69
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.node"), nodeEndpoint);
70
+ CLIDisplay.break();
71
+ setupVault();
72
+ const localIdentity = "local";
73
+ const vaultSeedId = "local-seed";
74
+ const vaultConnector = VaultConnectorFactory.get("vault");
75
+ await vaultConnector.setSecret(`${localIdentity}/${vaultSeedId}`, Converter.bytesToBase64(seed));
76
+ const iotaNftConnector = new IotaNftConnector({
77
+ config: {
78
+ clientOptions: {
79
+ nodes: [nodeEndpoint],
80
+ localPow: true
81
+ },
82
+ vaultSeedId
83
+ }
84
+ });
85
+ CLIDisplay.task(I18n.formatMessage("commands.nft-burn.progress.burningNft"));
86
+ CLIDisplay.break();
87
+ CLIDisplay.spinnerStart();
88
+ await iotaNftConnector.burn(localIdentity, id);
89
+ CLIDisplay.spinnerStop();
90
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.explore"), `${StringHelper.trimTrailingSlashes(explorerEndpoint)}/addr/${IotaNftUtils.nftIdToAddress(id)}`);
91
+ CLIDisplay.break();
92
+ CLIDisplay.done();
93
+ }
94
+
95
+ // Copyright 2024 IOTA Stiftung.
96
+ // SPDX-License-Identifier: Apache-2.0.
97
+ /**
98
+ * Build the nft mint command for the CLI.
99
+ * @returns The command.
100
+ */
101
+ function buildCommandNftMint() {
102
+ const command = new Command();
103
+ command
104
+ .name("nft-mint")
105
+ .summary(I18n.formatMessage("commands.nft-mint.summary"))
106
+ .description(I18n.formatMessage("commands.nft-mint.description"))
107
+ .requiredOption(I18n.formatMessage("commands.nft-mint.options.seed.param"), I18n.formatMessage("commands.nft-mint.options.seed.description"))
108
+ .requiredOption(I18n.formatMessage("commands.nft-mint.options.issuer.param"), I18n.formatMessage("commands.nft-mint.options.issuer.description"))
109
+ .requiredOption(I18n.formatMessage("commands.nft-mint.options.tag.param"), I18n.formatMessage("commands.nft-mint.options.tag.description"))
110
+ .option(I18n.formatMessage("commands.nft-mint.options.immutable-json.param"), I18n.formatMessage("commands.nft-mint.options.immutable-json.description"))
111
+ .option(I18n.formatMessage("commands.nft-mint.options.mutable-json.param"), I18n.formatMessage("commands.nft-mint.options.mutable-json.description"));
112
+ CLIOptions.output(command, {
113
+ noConsole: true,
114
+ json: true,
115
+ env: true,
116
+ mergeJson: true,
117
+ mergeEnv: true
118
+ });
119
+ command
120
+ .option(I18n.formatMessage("commands.common.options.node.param"), I18n.formatMessage("commands.common.options.node.description"), "!NODE_URL")
121
+ .option(I18n.formatMessage("commands.common.options.explorer.param"), I18n.formatMessage("commands.common.options.explorer.description"), "!EXPLORER_URL")
122
+ .action(actionCommandNftMint);
123
+ return command;
124
+ }
125
+ /**
126
+ * Action the nft mint command.
127
+ * @param opts The options for the command.
128
+ * @param opts.seed The seed required for signing by the issuer.
129
+ * @param opts.issuer The issuer address of the NFT.
130
+ * @param opts.tag The tag for the NFT.
131
+ * @param opts.immutableJson Filename of the immutable JSON data.
132
+ * @param opts.mutableJson Filename of the mutable JSON data.
133
+ * @param opts.node The node URL.
134
+ * @param opts.explorer The explorer URL.
135
+ */
136
+ async function actionCommandNftMint(opts) {
137
+ const seed = CLIParam.hexBase64("seed", opts.seed);
138
+ const issuer = CLIParam.bech32("issuer", opts.issuer);
139
+ const tag = CLIParam.stringValue("tag", opts.tag);
140
+ const immutableJson = opts.immutableJson
141
+ ? path.resolve(opts.immutableJson)
142
+ : undefined;
143
+ const mutableJson = opts.mutableJson
144
+ ? path.resolve(opts.mutableJson)
145
+ : undefined;
146
+ const nodeEndpoint = CLIParam.url("node", opts.node);
147
+ const explorerEndpoint = CLIParam.url("explorer", opts.explorer);
148
+ CLIDisplay.value(I18n.formatMessage("commands.nft-mint.labels.issuer"), issuer);
149
+ CLIDisplay.value(I18n.formatMessage("commands.nft-mint.labels.tag"), tag);
150
+ if (Is.stringValue(immutableJson)) {
151
+ CLIDisplay.value(I18n.formatMessage("commands.nft-mint.labels.immutableJsonFilename"), immutableJson);
152
+ }
153
+ if (Is.stringValue(mutableJson)) {
154
+ CLIDisplay.value(I18n.formatMessage("commands.nft-mint.labels.mutableJsonFilename"), mutableJson);
155
+ }
156
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.node"), nodeEndpoint);
157
+ CLIDisplay.break();
158
+ setupVault();
159
+ const localIdentity = "local";
160
+ const vaultSeedId = "local-seed";
161
+ const vaultConnector = VaultConnectorFactory.get("vault");
162
+ await vaultConnector.setSecret(`${localIdentity}/${vaultSeedId}`, Converter.bytesToBase64(seed));
163
+ const iotaNftConnector = new IotaNftConnector({
164
+ config: {
165
+ clientOptions: {
166
+ nodes: [nodeEndpoint],
167
+ localPow: true
168
+ },
169
+ vaultSeedId
170
+ }
171
+ });
172
+ const immutableJsonData = Is.stringValue(immutableJson)
173
+ ? await CLIUtils.readJsonFile(immutableJson)
174
+ : undefined;
175
+ const mutableJsonData = Is.stringValue(mutableJson)
176
+ ? await CLIUtils.readJsonFile(mutableJson)
177
+ : undefined;
178
+ if (Is.object(immutableJsonData)) {
179
+ CLIDisplay.section(I18n.formatMessage("commands.nft-mint.labels.immutableJson"));
180
+ CLIDisplay.json(immutableJsonData);
181
+ CLIDisplay.break();
182
+ }
183
+ if (Is.object(mutableJsonData)) {
184
+ CLIDisplay.section(I18n.formatMessage("commands.nft-mint.labels.mutableJson"));
185
+ CLIDisplay.json(mutableJsonData);
186
+ CLIDisplay.break();
187
+ }
188
+ CLIDisplay.task(I18n.formatMessage("commands.nft-mint.progress.mintingNft"));
189
+ CLIDisplay.break();
190
+ CLIDisplay.spinnerStart();
191
+ const nftId = await iotaNftConnector.mint(localIdentity, issuer, tag, immutableJsonData, mutableJsonData);
192
+ CLIDisplay.spinnerStop();
193
+ if (opts.console) {
194
+ CLIDisplay.value(I18n.formatMessage("commands.nft-mint.labels.nftId"), nftId);
195
+ CLIDisplay.break();
196
+ }
197
+ if (Is.stringValue(opts?.json)) {
198
+ await CLIUtils.writeJsonFile(opts.json, { nftId }, opts.mergeJson);
199
+ }
200
+ if (Is.stringValue(opts?.env)) {
201
+ await CLIUtils.writeEnvFile(opts.env, [`NFT_ID="${nftId}"`], opts.mergeEnv);
202
+ }
203
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.explore"), `${StringHelper.trimTrailingSlashes(explorerEndpoint)}/addr/${IotaNftUtils.nftIdToAddress(nftId)}`);
204
+ CLIDisplay.break();
205
+ CLIDisplay.done();
206
+ }
207
+
208
+ // Copyright 2024 IOTA Stiftung.
209
+ // SPDX-License-Identifier: Apache-2.0.
210
+ /**
211
+ * Build the nft resolve command for the CLI.
212
+ * @returns The command.
213
+ */
214
+ function buildCommandNftResolve() {
215
+ const command = new Command();
216
+ command
217
+ .name("nft-resolve")
218
+ .summary(I18n.formatMessage("commands.nft-resolve.summary"))
219
+ .description(I18n.formatMessage("commands.nft-resolve.description"))
220
+ .requiredOption(I18n.formatMessage("commands.nft-resolve.options.id.param"), I18n.formatMessage("commands.nft-resolve.options.id.description"));
221
+ CLIOptions.output(command, {
222
+ noConsole: true,
223
+ json: true,
224
+ env: false,
225
+ mergeJson: true,
226
+ mergeEnv: false
227
+ });
228
+ command
229
+ .option(I18n.formatMessage("commands.common.options.node.param"), I18n.formatMessage("commands.common.options.node.description"), "!NODE_URL")
230
+ .option(I18n.formatMessage("commands.common.options.explorer.param"), I18n.formatMessage("commands.common.options.explorer.description"), "!EXPLORER_URL")
231
+ .action(actionCommandNftResolve);
232
+ return command;
233
+ }
234
+ /**
235
+ * Action the nft resolve command.
236
+ * @param opts The options for the command.
237
+ * @param opts.id The id of the NFT to resolve in urn format.
238
+ * @param opts.node The node URL.
239
+ * @param opts.explorer The explorer URL.
240
+ */
241
+ async function actionCommandNftResolve(opts) {
242
+ const id = CLIParam.stringValue("id", opts.id);
243
+ const nodeEndpoint = CLIParam.url("node", opts.node);
244
+ const explorerEndpoint = CLIParam.url("explorer", opts.explorer);
245
+ CLIDisplay.value(I18n.formatMessage("commands.nft-resolve.labels.nftId"), id);
246
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.node"), nodeEndpoint);
247
+ CLIDisplay.break();
248
+ setupVault();
249
+ const iotaNftConnector = new IotaNftConnector({
250
+ config: {
251
+ clientOptions: {
252
+ nodes: [nodeEndpoint],
253
+ localPow: true
254
+ }
255
+ }
256
+ });
257
+ CLIDisplay.task(I18n.formatMessage("commands.nft-resolve.progress.resolvingNft"));
258
+ CLIDisplay.break();
259
+ CLIDisplay.spinnerStart();
260
+ const nft = await iotaNftConnector.resolve(id);
261
+ CLIDisplay.spinnerStop();
262
+ if (opts.console) {
263
+ CLIDisplay.section(I18n.formatMessage("commands.nft-resolve.labels.nft"));
264
+ CLIDisplay.json(nft);
265
+ CLIDisplay.break();
266
+ }
267
+ if (Is.stringValue(opts?.json)) {
268
+ await CLIUtils.writeJsonFile(opts.json, nft, opts.mergeJson);
269
+ }
270
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.explore"), `${StringHelper.trimTrailingSlashes(explorerEndpoint)}/addr/${IotaNftUtils.nftIdToAddress(id)}`);
271
+ CLIDisplay.break();
272
+ CLIDisplay.done();
273
+ }
274
+
275
+ // Copyright 2024 IOTA Stiftung.
276
+ // SPDX-License-Identifier: Apache-2.0.
277
+ /**
278
+ * Build the nft transfer command for the CLI.
279
+ * @returns The command.
280
+ */
281
+ function buildCommandNftTransfer() {
282
+ const command = new Command();
283
+ command
284
+ .name("nft-transfer")
285
+ .summary(I18n.formatMessage("commands.nft-transfer.summary"))
286
+ .description(I18n.formatMessage("commands.nft-transfer.description"))
287
+ .requiredOption(I18n.formatMessage("commands.nft-transfer.options.seed.param"), I18n.formatMessage("commands.nft-transfer.options.seed.description"))
288
+ .requiredOption(I18n.formatMessage("commands.nft-transfer.options.id.param"), I18n.formatMessage("commands.nft-transfer.options.id.description"))
289
+ .requiredOption(I18n.formatMessage("commands.nft-transfer.options.recipient.param"), I18n.formatMessage("commands.nft-transfer.options.recipient.description"));
290
+ command
291
+ .option(I18n.formatMessage("commands.common.options.node.param"), I18n.formatMessage("commands.common.options.node.description"), "!NODE_URL")
292
+ .option(I18n.formatMessage("commands.common.options.explorer.param"), I18n.formatMessage("commands.common.options.explorer.description"), "!EXPLORER_URL")
293
+ .action(actionCommandNftTransfer);
294
+ return command;
295
+ }
296
+ /**
297
+ * Action the nft transfer command.
298
+ * @param opts The options for the command.
299
+ * @param opts.seed The seed required for signing by the issuer.
300
+ * @param opts.id The id of the NFT to transfer in urn format.
301
+ * @param opts.recipient The recipient address of the NFT.
302
+ * @param opts.node The node URL.
303
+ * @param opts.explorer The explorer URL.
304
+ */
305
+ async function actionCommandNftTransfer(opts) {
306
+ const seed = CLIParam.hexBase64("seed", opts.seed);
307
+ const id = CLIParam.stringValue("id", opts.id);
308
+ const recipient = CLIParam.bech32("recipient", opts.recipient);
309
+ const nodeEndpoint = CLIParam.url("node", opts.node);
310
+ const explorerEndpoint = CLIParam.url("explorer", opts.explorer);
311
+ CLIDisplay.value(I18n.formatMessage("commands.nft-transfer.labels.nftId"), id);
312
+ CLIDisplay.value(I18n.formatMessage("commands.nft-transfer.labels.recipient"), recipient);
313
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.node"), nodeEndpoint);
314
+ CLIDisplay.break();
315
+ setupVault();
316
+ const localIdentity = "local";
317
+ const vaultSeedId = "local-seed";
318
+ const vaultConnector = VaultConnectorFactory.get("vault");
319
+ await vaultConnector.setSecret(`${localIdentity}/${vaultSeedId}`, Converter.bytesToBase64(seed));
320
+ const iotaNftConnector = new IotaNftConnector({
321
+ config: {
322
+ clientOptions: {
323
+ nodes: [nodeEndpoint],
324
+ localPow: true
325
+ },
326
+ vaultSeedId
327
+ }
328
+ });
329
+ CLIDisplay.task(I18n.formatMessage("commands.nft-transfer.progress.transferringNft"));
330
+ CLIDisplay.break();
331
+ CLIDisplay.spinnerStart();
332
+ await iotaNftConnector.transfer(localIdentity, id, recipient);
333
+ CLIDisplay.spinnerStop();
334
+ CLIDisplay.value(I18n.formatMessage("commands.common.labels.explore"), `${StringHelper.trimTrailingSlashes(explorerEndpoint)}/addr/${IotaNftUtils.nftIdToAddress(id)}`);
335
+ CLIDisplay.break();
336
+ CLIDisplay.done();
337
+ }
338
+
339
+ // Copyright 2024 IOTA Stiftung.
340
+ // SPDX-License-Identifier: Apache-2.0.
341
+ /**
342
+ * The main entry point for the CLI.
343
+ */
344
+ class CLI extends CLIBase {
345
+ /**
346
+ * Run the app.
347
+ * @param argv The process arguments.
348
+ * @param localesDirectory The directory for the locales, default to relative to the script.
349
+ * @returns The exit code.
350
+ */
351
+ async run(argv, localesDirectory) {
352
+ return this.execute({
353
+ title: "TWIN NFT",
354
+ appName: "twin-nft",
355
+ version: "0.0.1-next.3",
356
+ icon: "🌍",
357
+ supportsEnvFiles: true
358
+ }, localesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), "../locales"), argv);
359
+ }
360
+ /**
361
+ * Get the commands for the CLI.
362
+ * @param program The main program to add the commands to.
363
+ * @internal
364
+ */
365
+ getCommands(program) {
366
+ return [
367
+ buildCommandMnemonic(),
368
+ buildCommandAddress(),
369
+ buildCommandFaucet(),
370
+ buildCommandNftMint(),
371
+ buildCommandNftResolve(),
372
+ buildCommandNftBurn(),
373
+ buildCommandNftTransfer()
374
+ ];
375
+ }
376
+ }
377
+
378
+ export { CLI, actionCommandNftBurn, actionCommandNftMint, actionCommandNftResolve, actionCommandNftTransfer, buildCommandNftBurn, buildCommandNftMint, buildCommandNftResolve, buildCommandNftTransfer };