@jellylegsai/aether-cli 1.8.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/LICENSE +21 -0
- package/README.md +110 -0
- package/aether-cli-1.0.0.tgz +0 -0
- package/aether-cli-1.8.0.tgz +0 -0
- package/aether-hub-1.0.5.tgz +0 -0
- package/aether-hub-1.1.8.tgz +0 -0
- package/aether-hub-1.2.1.tgz +0 -0
- package/commands/account.js +280 -0
- package/commands/apy.js +499 -0
- package/commands/balance.js +241 -0
- package/commands/blockhash.js +181 -0
- package/commands/broadcast.js +387 -0
- package/commands/claim.js +490 -0
- package/commands/config.js +851 -0
- package/commands/delegations.js +582 -0
- package/commands/doctor.js +769 -0
- package/commands/emergency.js +667 -0
- package/commands/epoch.js +275 -0
- package/commands/fees.js +276 -0
- package/commands/index.js +78 -0
- package/commands/info.js +495 -0
- package/commands/init.js +816 -0
- package/commands/install.js +666 -0
- package/commands/kyc.js +272 -0
- package/commands/logs.js +315 -0
- package/commands/monitor.js +431 -0
- package/commands/multisig.js +701 -0
- package/commands/network.js +429 -0
- package/commands/nft.js +857 -0
- package/commands/ping.js +266 -0
- package/commands/price.js +253 -0
- package/commands/rewards.js +931 -0
- package/commands/sdk-test.js +477 -0
- package/commands/sdk.js +656 -0
- package/commands/slot.js +155 -0
- package/commands/snapshot.js +470 -0
- package/commands/stake-info.js +139 -0
- package/commands/stake-positions.js +205 -0
- package/commands/stake.js +516 -0
- package/commands/stats.js +396 -0
- package/commands/status.js +327 -0
- package/commands/supply.js +391 -0
- package/commands/tps.js +238 -0
- package/commands/transfer.js +495 -0
- package/commands/tx-history.js +346 -0
- package/commands/unstake.js +597 -0
- package/commands/validator-info.js +657 -0
- package/commands/validator-register.js +593 -0
- package/commands/validator-start.js +323 -0
- package/commands/validator-status.js +227 -0
- package/commands/validators.js +626 -0
- package/commands/wallet.js +1570 -0
- package/index.js +593 -0
- package/lib/errors.js +398 -0
- package/package.json +76 -0
- package/sdk/README.md +210 -0
- package/sdk/index.js +1639 -0
- package/sdk/package.json +34 -0
- package/sdk/rpc.js +254 -0
- package/sdk/test.js +85 -0
- package/test/doctor.test.js +76 -0
- package/validator-identity.json +4 -0
package/commands/nft.js
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli nft
|
|
4
|
+
*
|
|
5
|
+
* Complete NFT management suite - fully wired to @jellylegsai/aether-sdk.
|
|
6
|
+
* All blockchain calls use real HTTP RPC via AetherClient.
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* aether nft create --metadata <url> [--royalties <bps>] [--address <wallet>]
|
|
10
|
+
* aether nft list --address <wallet> [--creator <addr>] [--json]
|
|
11
|
+
* aether nft transfer --nft <id> --to <addr> [--from <wallet>]
|
|
12
|
+
* aether nft info --nft <id> [--json]
|
|
13
|
+
* aether nft update --nft <id> --metadata <url> [--address <wallet>]
|
|
14
|
+
* aether nft burn --nft <id> [--address <wallet>]
|
|
15
|
+
*
|
|
16
|
+
* SDK Methods:
|
|
17
|
+
* - client.createNFT() → Creates new NFT with metadata
|
|
18
|
+
* - client.transferNFT() → Transfer NFT ownership
|
|
19
|
+
* - client.updateMetadata()→ Update NFT metadata URL
|
|
20
|
+
* - getTokenAccounts() → List NFTs for address
|
|
21
|
+
* - getAccountInfo() → Get NFT details
|
|
22
|
+
*
|
|
23
|
+
* Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const os = require('os');
|
|
29
|
+
const readline = require('readline');
|
|
30
|
+
const nacl = require('tweetnacl');
|
|
31
|
+
const bs58 = require('bs58').default;
|
|
32
|
+
const bip39 = require('bip39');
|
|
33
|
+
|
|
34
|
+
// Import SDK for real blockchain RPC calls
|
|
35
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
36
|
+
const aether = require(sdkPath);
|
|
37
|
+
|
|
38
|
+
// ANSI colours
|
|
39
|
+
const C = {
|
|
40
|
+
reset: '\x1b[0m',
|
|
41
|
+
bright: '\x1b[1m',
|
|
42
|
+
dim: '\x1b[2m',
|
|
43
|
+
red: '\x1b[31m',
|
|
44
|
+
green: '\x1b[32m',
|
|
45
|
+
yellow: '\x1b[33m',
|
|
46
|
+
cyan: '\x1b[36m',
|
|
47
|
+
magenta: '\x1b[35m',
|
|
48
|
+
blue: '\x1b[34m',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const CLI_VERSION = '1.0.0';
|
|
52
|
+
const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// SDK Client Setup
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
function getDefaultRpc() {
|
|
59
|
+
return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createClient(rpcUrl) {
|
|
63
|
+
return new aether.AetherClient({ rpcUrl });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Config & Wallet
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
function getAetherDir() {
|
|
71
|
+
return path.join(os.homedir(), '.aether');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getConfigPath() {
|
|
75
|
+
return path.join(getAetherDir(), 'config.json');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function loadConfig() {
|
|
79
|
+
if (!fs.existsSync(getConfigPath())) {
|
|
80
|
+
return { defaultWallet: null };
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
|
84
|
+
} catch {
|
|
85
|
+
return { defaultWallet: null };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadWallet(address) {
|
|
90
|
+
const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
|
|
91
|
+
if (!fs.existsSync(fp)) return null;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Crypto Helpers
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
function deriveKeypair(mnemonic) {
|
|
104
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
105
|
+
throw new Error('Invalid mnemonic phrase');
|
|
106
|
+
}
|
|
107
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
|
|
108
|
+
const seed32 = seedBuffer.slice(0, 32);
|
|
109
|
+
const keyPair = nacl.sign.keyPair.fromSeed(seed32);
|
|
110
|
+
return {
|
|
111
|
+
publicKey: Buffer.from(keyPair.publicKey),
|
|
112
|
+
secretKey: Buffer.from(keyPair.secretKey),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatAddress(publicKey) {
|
|
117
|
+
return 'ATH' + bs58.encode(publicKey);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function signTransaction(tx, secretKey) {
|
|
121
|
+
const txBytes = Buffer.from(JSON.stringify(tx));
|
|
122
|
+
const sig = nacl.sign.detached(txBytes, secretKey);
|
|
123
|
+
return bs58.encode(sig);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Format Helpers
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
function formatAether(lamports) {
|
|
131
|
+
if (!lamports || lamports === '0') return '0 AETH';
|
|
132
|
+
const aeth = Number(lamports) / 1e9;
|
|
133
|
+
return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function shortAddress(addr) {
|
|
137
|
+
if (!addr || addr.length < 16) return addr || 'unknown';
|
|
138
|
+
return addr.slice(0, 8) + '...' + addr.slice(-8);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function truncate(str, len) {
|
|
142
|
+
if (!str) return '';
|
|
143
|
+
if (str.length <= len) return str;
|
|
144
|
+
return str.slice(0, len - 3) + '...';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Readline Helpers
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
function createRl() {
|
|
152
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function question(rl, q) {
|
|
156
|
+
return new Promise((res) => rl.question(q, res));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function askMnemonic(rl, promptText) {
|
|
160
|
+
console.log(`\n${C.cyan}${promptText}${C.reset}`);
|
|
161
|
+
console.log(`${C.dim}Enter your 12 or 24-word passphrase:${C.reset}`);
|
|
162
|
+
const raw = await question(rl, ` > ${C.reset}`);
|
|
163
|
+
return raw.trim().toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// NFT Create Command - Uses SDK createNFT()
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
async function nftCreate(args) {
|
|
171
|
+
const rl = createRl();
|
|
172
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
173
|
+
|
|
174
|
+
// Resolve wallet address
|
|
175
|
+
let address = args.address;
|
|
176
|
+
if (!address) {
|
|
177
|
+
const config = loadConfig();
|
|
178
|
+
address = config.defaultWallet;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!address) {
|
|
182
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
|
|
183
|
+
console.log(` ${C.dim}Usage: aether nft create --metadata <url> --address <wallet>${C.reset}\n`);
|
|
184
|
+
rl.close();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const wallet = loadWallet(address);
|
|
189
|
+
if (!wallet) {
|
|
190
|
+
console.log(`\n ${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
|
|
191
|
+
console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
|
|
192
|
+
rl.close();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Get metadata URL
|
|
197
|
+
let metadataUrl = args.metadata;
|
|
198
|
+
if (!metadataUrl) {
|
|
199
|
+
console.log(`\n${C.bright}${C.cyan}── NFT Creation ─────────────────────────────────────────${C.reset}\n`);
|
|
200
|
+
console.log(` ${C.dim}Enter the metadata URL for your NFT:${C.reset}`);
|
|
201
|
+
metadataUrl = await question(rl, ` Metadata URL > ${C.reset}`);
|
|
202
|
+
if (!metadataUrl.trim()) {
|
|
203
|
+
console.log(`\n ${C.red}✗ Metadata URL is required.${C.reset}\n`);
|
|
204
|
+
rl.close();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Get royalties (default 5% = 500 bps)
|
|
210
|
+
let royalties = args.royalties;
|
|
211
|
+
if (!royalties) {
|
|
212
|
+
const royaltyAns = await question(rl, ` ${C.dim}Royalties (basis points, default 500 = 5%):${C.reset} `);
|
|
213
|
+
royalties = parseInt(royaltyAns.trim() || '500', 10);
|
|
214
|
+
} else {
|
|
215
|
+
royalties = parseInt(royalties, 10);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (isNaN(royalties) || royalties < 0 || royalties > 10000) {
|
|
219
|
+
console.log(`\n ${C.red}✗ Invalid royalties. Must be 0-10000 basis points.${C.reset}\n`);
|
|
220
|
+
rl.close();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(`\n ${C.green}★${C.reset} Creator: ${C.bright}${address}${C.reset}`);
|
|
225
|
+
console.log(` ${C.green}★${C.reset} Metadata: ${C.bright}${truncate(metadataUrl, 50)}${C.reset}`);
|
|
226
|
+
console.log(` ${C.green}★${C.reset} Royalties: ${C.bright}${(royalties / 100).toFixed(2)}%${C.reset}`);
|
|
227
|
+
console.log();
|
|
228
|
+
|
|
229
|
+
// Ask for mnemonic for signing
|
|
230
|
+
console.log(`${C.yellow} ⚠ Creating NFT requires your wallet passphrase.${C.reset}`);
|
|
231
|
+
const mnemonic = await askMnemonic(rl, 'Enter passphrase to sign the NFT creation');
|
|
232
|
+
console.log();
|
|
233
|
+
|
|
234
|
+
let keyPair;
|
|
235
|
+
try {
|
|
236
|
+
keyPair = deriveKeypair(mnemonic);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
239
|
+
rl.close();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Verify derived address matches
|
|
244
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
245
|
+
if (derivedAddress !== address) {
|
|
246
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
247
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
248
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
|
|
249
|
+
rl.close();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Confirm
|
|
254
|
+
const confirm = await question(rl, ` ${C.yellow}Create NFT? [y/N]${C.reset} > `);
|
|
255
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
256
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
257
|
+
rl.close();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
rl.close();
|
|
261
|
+
|
|
262
|
+
// Create NFT via SDK
|
|
263
|
+
const client = createClient(rpc);
|
|
264
|
+
|
|
265
|
+
console.log(`\n ${C.dim}Submitting NFT creation via SDK...${C.reset}`);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// SDK call: createNFT with signing function
|
|
269
|
+
const result = await client.createNFT({
|
|
270
|
+
creator: address.startsWith('ATH') ? address.slice(3) : address,
|
|
271
|
+
metadataUrl: metadataUrl.trim(),
|
|
272
|
+
royalties: royalties,
|
|
273
|
+
signFn: async (tx) => signTransaction(tx, keyPair.secretKey),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (result.error) {
|
|
277
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const nftId = result.nft_id || result.id || result.signature;
|
|
281
|
+
|
|
282
|
+
console.log(`\n${C.green}✓ NFT created successfully!${C.reset}`);
|
|
283
|
+
console.log(` ${C.dim}NFT ID:${C.reset} ${C.cyan}${C.bright}${nftId}${C.reset}`);
|
|
284
|
+
console.log(` ${C.dim}Creator:${C.reset} ${address}`);
|
|
285
|
+
console.log(` ${C.dim}Metadata:${C.reset} ${truncate(metadataUrl, 45)}`);
|
|
286
|
+
console.log(` ${C.dim}Royalties:${C.reset} ${(royalties / 100).toFixed(2)}%`);
|
|
287
|
+
if (result.signature) {
|
|
288
|
+
console.log(` ${C.dim}Signature:${C.reset} ${shortAddress(result.signature)}`);
|
|
289
|
+
}
|
|
290
|
+
if (result.slot) {
|
|
291
|
+
console.log(` ${C.dim}Slot:${C.reset} ${result.slot}`);
|
|
292
|
+
}
|
|
293
|
+
console.log(` ${C.dim}SDK:${C.reset} createNFT() → POST /v1/transaction`);
|
|
294
|
+
console.log();
|
|
295
|
+
console.log(` ${C.dim}View your NFT:${C.reset}`);
|
|
296
|
+
console.log(` ${C.cyan}aether nft info --nft ${nftId}${C.reset}\n`);
|
|
297
|
+
|
|
298
|
+
return result;
|
|
299
|
+
} catch (err) {
|
|
300
|
+
console.log(`\n ${C.red}✗ NFT creation failed:${C.reset} ${err.message}`);
|
|
301
|
+
console.log(` ${C.dim}Common causes:${C.reset}`);
|
|
302
|
+
console.log(` • Insufficient balance for NFT creation fee (typically 0.01 AETH)`);
|
|
303
|
+
console.log(` • RPC endpoint not accepting transactions`);
|
|
304
|
+
console.log(` • Invalid metadata URL format\n`);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// NFT List Command - Uses SDK getTokenAccounts()
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
async function nftList(args) {
|
|
314
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
315
|
+
const isJson = args.json || false;
|
|
316
|
+
|
|
317
|
+
let address = args.address;
|
|
318
|
+
if (!address) {
|
|
319
|
+
const config = loadConfig();
|
|
320
|
+
address = config.defaultWallet;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!address) {
|
|
324
|
+
if (isJson) {
|
|
325
|
+
console.log(JSON.stringify({ error: 'No address provided' }, null, 2));
|
|
326
|
+
} else {
|
|
327
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
|
|
328
|
+
console.log(` ${C.dim}Usage: aether nft list --address <wallet>${C.reset}\n`);
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const client = createClient(rpc);
|
|
334
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
335
|
+
|
|
336
|
+
if (!isJson) {
|
|
337
|
+
console.log(`\n${C.bright}${C.cyan}── NFT Collection ───────────────────────────────────────${C.reset}\n`);
|
|
338
|
+
console.log(` ${C.dim}Wallet:${C.reset} ${address}`);
|
|
339
|
+
console.log(` ${C.dim}RPC:${C.reset} ${rpc}\n`);
|
|
340
|
+
console.log(` ${C.dim}Fetching NFTs via SDK...${C.reset}\n`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
// SDK call: getTokenAccounts → GET /v1/tokens/<address>
|
|
345
|
+
const tokenAccounts = await client.getTokenAccounts(rawAddr);
|
|
346
|
+
|
|
347
|
+
// Filter for NFTs (amount = 1, has metadata)
|
|
348
|
+
const nfts = (tokenAccounts || []).filter(t =>
|
|
349
|
+
t.amount === '1' || t.amount === 1 || t.is_nft || t.nft_id
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if (isJson) {
|
|
353
|
+
console.log(JSON.stringify({
|
|
354
|
+
address,
|
|
355
|
+
rpc,
|
|
356
|
+
nft_count: nfts.length,
|
|
357
|
+
nfts: nfts.map(n => ({
|
|
358
|
+
id: n.mint || n.nft_id || n.id,
|
|
359
|
+
amount: n.amount,
|
|
360
|
+
metadata: n.metadata_url || n.metadata,
|
|
361
|
+
creator: n.creator,
|
|
362
|
+
royalties: n.royalties,
|
|
363
|
+
})),
|
|
364
|
+
cli_version: CLI_VERSION,
|
|
365
|
+
fetched_at: new Date().toISOString(),
|
|
366
|
+
}, null, 2));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (nfts.length === 0) {
|
|
371
|
+
console.log(` ${C.yellow}⚠ No NFTs found for this wallet.${C.reset}`);
|
|
372
|
+
console.log(` ${C.dim}Create your first NFT:${C.reset}`);
|
|
373
|
+
console.log(` ${C.cyan}aether nft create --metadata <url>${C.reset}\n`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log(` ${C.bright}Found ${nfts.length} NFT${nfts.length === 1 ? '' : 's'}${C.reset}\n`);
|
|
378
|
+
|
|
379
|
+
// Display table
|
|
380
|
+
console.log(` ${C.dim}┌─────────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
381
|
+
console.log(` ${C.dim}│${C.reset} ${C.bright}# NFT ID${C.reset} ${C.bright}Metadata${C.reset} ${C.bright}Royalties${C.reset} ${C.dim}│${C.reset}`);
|
|
382
|
+
console.log(` ${C.dim}├─────────────────────────────────────────────────────────────────┤${C.reset}`);
|
|
383
|
+
|
|
384
|
+
nfts.forEach((nft, i) => {
|
|
385
|
+
const id = shortAddress(nft.mint || nft.nft_id || nft.id, 10).padEnd(24);
|
|
386
|
+
const meta = truncate(nft.metadata_url || nft.metadata || 'N/A', 22).padEnd(22);
|
|
387
|
+
const royalty = nft.royalties !== undefined
|
|
388
|
+
? `${(nft.royalties / 100).toFixed(0)}%`.padEnd(9)
|
|
389
|
+
: 'N/A ';
|
|
390
|
+
console.log(` ${C.dim}│${C.reset} ${(i + 1).toString().padStart(2)} ${C.cyan}${id}${C.reset} ${meta} ${royalty} ${C.dim}│${C.reset}`);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
console.log(` ${C.dim}└─────────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
394
|
+
console.log();
|
|
395
|
+
console.log(` ${C.dim}SDK: getTokenAccounts() → GET /v1/tokens/${shortAddress(rawAddr)}${C.reset}\n`);
|
|
396
|
+
|
|
397
|
+
} catch (err) {
|
|
398
|
+
if (isJson) {
|
|
399
|
+
console.log(JSON.stringify({ error: err.message, address }, null, 2));
|
|
400
|
+
} else {
|
|
401
|
+
console.log(` ${C.red}✗ Failed to fetch NFTs:${C.reset} ${err.message}`);
|
|
402
|
+
console.log(` ${C.dim}Is your validator running? RPC: ${rpc}${C.reset}\n`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// NFT Transfer Command - Uses SDK transferNFT()
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
async function nftTransfer(args) {
|
|
412
|
+
const rl = createRl();
|
|
413
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
414
|
+
|
|
415
|
+
let fromAddress = args.from;
|
|
416
|
+
if (!fromAddress) {
|
|
417
|
+
const config = loadConfig();
|
|
418
|
+
fromAddress = config.defaultWallet;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (!fromAddress) {
|
|
422
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
|
|
423
|
+
console.log(` ${C.dim}Usage: aether nft transfer --nft <id> --to <addr> --from <wallet>${C.reset}\n`);
|
|
424
|
+
rl.close();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Get NFT ID
|
|
429
|
+
let nftId = args.nft;
|
|
430
|
+
if (!nftId) {
|
|
431
|
+
console.log(`\n ${C.dim}Enter the NFT ID to transfer:${C.reset}`);
|
|
432
|
+
nftId = await question(rl, ` NFT ID > ${C.reset}`);
|
|
433
|
+
if (!nftId.trim()) {
|
|
434
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}\n`);
|
|
435
|
+
rl.close();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Get recipient
|
|
441
|
+
let toAddress = args.to;
|
|
442
|
+
if (!toAddress) {
|
|
443
|
+
console.log(` ${C.dim}Enter recipient address:${C.reset}`);
|
|
444
|
+
toAddress = await question(rl, ` Recipient > ${C.reset}`);
|
|
445
|
+
if (!toAddress.trim()) {
|
|
446
|
+
console.log(`\n ${C.red}✗ Recipient address is required.${C.reset}\n`);
|
|
447
|
+
rl.close();
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log(`\n ${C.green}★${C.reset} From: ${C.bright}${fromAddress}${C.reset}`);
|
|
453
|
+
console.log(` ${C.green}★${C.reset} To: ${C.bright}${toAddress}${C.reset}`);
|
|
454
|
+
console.log(` ${C.green}★${C.reset} NFT: ${C.bright}${shortAddress(nftId)}${C.reset}`);
|
|
455
|
+
console.log();
|
|
456
|
+
|
|
457
|
+
// Ask for mnemonic
|
|
458
|
+
console.log(`${C.yellow} ⚠ Transferring NFT requires your wallet passphrase.${C.reset}`);
|
|
459
|
+
const mnemonic = await askMnemonic(rl, 'Enter passphrase to sign the transfer');
|
|
460
|
+
console.log();
|
|
461
|
+
|
|
462
|
+
let keyPair;
|
|
463
|
+
try {
|
|
464
|
+
keyPair = deriveKeypair(mnemonic);
|
|
465
|
+
} catch (e) {
|
|
466
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
467
|
+
rl.close();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Verify derived address
|
|
472
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
473
|
+
if (derivedAddress !== fromAddress) {
|
|
474
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
475
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
476
|
+
console.log(` ${C.dim} Expected: ${fromAddress}${C.reset}\n`);
|
|
477
|
+
rl.close();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Confirm
|
|
482
|
+
const confirm = await question(rl, ` ${C.yellow}Confirm NFT transfer? [y/N]${C.reset} > `);
|
|
483
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
484
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
485
|
+
rl.close();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
rl.close();
|
|
489
|
+
|
|
490
|
+
// Transfer via SDK
|
|
491
|
+
const client = createClient(rpc);
|
|
492
|
+
|
|
493
|
+
console.log(`\n ${C.dim}Submitting NFT transfer via SDK...${C.reset}`);
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const result = await client.transferNFT({
|
|
497
|
+
from: fromAddress.startsWith('ATH') ? fromAddress.slice(3) : fromAddress,
|
|
498
|
+
nftId: nftId.trim(),
|
|
499
|
+
to: toAddress.startsWith('ATH') ? toAddress.slice(3) : toAddress,
|
|
500
|
+
signFn: async (tx) => signTransaction(tx, keyPair.secretKey),
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
if (result.error) {
|
|
504
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
console.log(`\n${C.green}✓ NFT transferred successfully!${C.reset}`);
|
|
508
|
+
console.log(` ${C.dim}NFT ID:${C.reset} ${C.cyan}${C.bright}${nftId}${C.reset}`);
|
|
509
|
+
console.log(` ${C.dim}From:${C.reset} ${fromAddress}`);
|
|
510
|
+
console.log(` ${C.dim}To:${C.reset} ${C.green}${toAddress}${C.reset}`);
|
|
511
|
+
if (result.signature) {
|
|
512
|
+
console.log(` ${C.dim}Signature:${C.reset} ${shortAddress(result.signature)}`);
|
|
513
|
+
}
|
|
514
|
+
if (result.slot) {
|
|
515
|
+
console.log(` ${C.dim}Slot:${C.reset} ${result.slot}`);
|
|
516
|
+
}
|
|
517
|
+
console.log(` ${C.dim}SDK:${C.reset} transferNFT() → POST /v1/transaction`);
|
|
518
|
+
console.log();
|
|
519
|
+
console.log(` ${C.dim}Verify transfer:${C.reset}`);
|
|
520
|
+
console.log(` ${C.cyan}aether nft list --address ${toAddress}${C.reset}\n`);
|
|
521
|
+
|
|
522
|
+
} catch (err) {
|
|
523
|
+
console.log(`\n ${C.red}✗ NFT transfer failed:${C.reset} ${err.message}`);
|
|
524
|
+
console.log(` ${C.dim}Common causes:${C.reset}`);
|
|
525
|
+
console.log(` • You don't own this NFT`);
|
|
526
|
+
console.log(` • Invalid NFT ID`);
|
|
527
|
+
console.log(` • Recipient address is invalid`);
|
|
528
|
+
console.log(` • Insufficient balance for transaction fee\n`);
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// NFT Info Command - Uses SDK getAccountInfo()
|
|
535
|
+
// ============================================================================
|
|
536
|
+
|
|
537
|
+
async function nftInfo(args) {
|
|
538
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
539
|
+
const isJson = args.json || false;
|
|
540
|
+
|
|
541
|
+
let nftId = args.nft;
|
|
542
|
+
if (!nftId) {
|
|
543
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}`);
|
|
544
|
+
console.log(` ${C.dim}Usage: aether nft info --nft <id>${C.reset}\n`);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const client = createClient(rpc);
|
|
549
|
+
|
|
550
|
+
if (!isJson) {
|
|
551
|
+
console.log(`\n${C.bright}${C.cyan}── NFT Details ──────────────────────────────────────────${C.reset}\n`);
|
|
552
|
+
console.log(` ${C.dim}Fetching NFT info via SDK...${C.reset}\n`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
// SDK call: getAccountInfo → GET /v1/account/<nft_id>
|
|
557
|
+
const accountInfo = await client.getAccountInfo(nftId);
|
|
558
|
+
|
|
559
|
+
if (!accountInfo || accountInfo.error) {
|
|
560
|
+
throw new Error(accountInfo?.error || 'NFT not found');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Extract NFT-specific data
|
|
564
|
+
const nftData = {
|
|
565
|
+
id: nftId,
|
|
566
|
+
owner: accountInfo.owner,
|
|
567
|
+
metadata: accountInfo.data?.metadata || accountInfo.metadata_url,
|
|
568
|
+
creator: accountInfo.data?.creator,
|
|
569
|
+
royalties: accountInfo.data?.royalties,
|
|
570
|
+
lamports: accountInfo.lamports,
|
|
571
|
+
rentEpoch: accountInfo.rent_epoch,
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
if (isJson) {
|
|
575
|
+
console.log(JSON.stringify({
|
|
576
|
+
nft: nftData,
|
|
577
|
+
rpc,
|
|
578
|
+
cli_version: CLI_VERSION,
|
|
579
|
+
fetched_at: new Date().toISOString(),
|
|
580
|
+
}, null, 2));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.bright}${C.cyan}${nftId}${C.reset}`);
|
|
585
|
+
console.log(` ${C.green}★${C.reset} Owner: ${nftData.owner || 'Unknown'}`);
|
|
586
|
+
console.log(` ${C.green}★${C.reset} Creator: ${nftData.creator || 'Unknown'}`);
|
|
587
|
+
console.log(` ${C.green}★${C.reset} Metadata: ${truncate(nftData.metadata, 50) || 'N/A'}`);
|
|
588
|
+
if (nftData.royalties !== undefined) {
|
|
589
|
+
console.log(` ${C.green}★${C.reset} Royalties: ${(nftData.royalties / 100).toFixed(2)}%`);
|
|
590
|
+
}
|
|
591
|
+
if (nftData.lamports !== undefined) {
|
|
592
|
+
console.log(` ${C.green}★${C.reset} Lamports: ${nftData.lamports.toLocaleString()}`);
|
|
593
|
+
}
|
|
594
|
+
console.log();
|
|
595
|
+
console.log(` ${C.dim}SDK: getAccountInfo() → GET /v1/account/${shortAddress(nftId)}${C.reset}\n`);
|
|
596
|
+
|
|
597
|
+
} catch (err) {
|
|
598
|
+
if (isJson) {
|
|
599
|
+
console.log(JSON.stringify({ error: err.message, nft_id: nftId }, null, 2));
|
|
600
|
+
} else {
|
|
601
|
+
console.log(` ${C.red}✗ Failed to fetch NFT info:${C.reset} ${err.message}`);
|
|
602
|
+
console.log(` ${C.dim}The NFT may not exist or the RPC endpoint is unavailable.${C.reset}\n`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ============================================================================
|
|
608
|
+
// NFT Update Command - Uses SDK updateMetadata()
|
|
609
|
+
// ============================================================================
|
|
610
|
+
|
|
611
|
+
async function nftUpdate(args) {
|
|
612
|
+
const rl = createRl();
|
|
613
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
614
|
+
|
|
615
|
+
let address = args.address;
|
|
616
|
+
if (!address) {
|
|
617
|
+
const config = loadConfig();
|
|
618
|
+
address = config.defaultWallet;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!address) {
|
|
622
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
|
|
623
|
+
console.log(` ${C.dim}Usage: aether nft update --nft <id> --metadata <url> --address <wallet>${C.reset}\n`);
|
|
624
|
+
rl.close();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let nftId = args.nft;
|
|
629
|
+
if (!nftId) {
|
|
630
|
+
console.log(`\n ${C.dim}Enter the NFT ID to update:${C.reset}`);
|
|
631
|
+
nftId = await question(rl, ` NFT ID > ${C.reset}`);
|
|
632
|
+
if (!nftId.trim()) {
|
|
633
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}\n`);
|
|
634
|
+
rl.close();
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
let metadataUrl = args.metadata;
|
|
640
|
+
if (!metadataUrl) {
|
|
641
|
+
console.log(` ${C.dim}Enter new metadata URL:${C.reset}`);
|
|
642
|
+
metadataUrl = await question(rl, ` New Metadata URL > ${C.reset}`);
|
|
643
|
+
if (!metadataUrl.trim()) {
|
|
644
|
+
console.log(`\n ${C.red}✗ Metadata URL is required.${C.reset}\n`);
|
|
645
|
+
rl.close();
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
console.log(`\n ${C.green}★${C.reset} Updater: ${C.bright}${address}${C.reset}`);
|
|
651
|
+
console.log(` ${C.green}★${C.reset} NFT: ${C.bright}${shortAddress(nftId)}${C.reset}`);
|
|
652
|
+
console.log(` ${C.green}★${C.reset} New Meta: ${C.bright}${truncate(metadataUrl, 40)}${C.reset}`);
|
|
653
|
+
console.log();
|
|
654
|
+
|
|
655
|
+
// Ask for mnemonic
|
|
656
|
+
console.log(`${C.yellow} ⚠ Updating metadata requires your wallet passphrase.${C.reset}`);
|
|
657
|
+
const mnemonic = await askMnemonic(rl, 'Enter passphrase to sign the update');
|
|
658
|
+
console.log();
|
|
659
|
+
|
|
660
|
+
let keyPair;
|
|
661
|
+
try {
|
|
662
|
+
keyPair = deriveKeypair(mnemonic);
|
|
663
|
+
} catch (e) {
|
|
664
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
665
|
+
rl.close();
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Verify derived address
|
|
670
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
671
|
+
if (derivedAddress !== address) {
|
|
672
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
673
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
674
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
|
|
675
|
+
rl.close();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Confirm
|
|
680
|
+
const confirm = await question(rl, ` ${C.yellow}Confirm metadata update? [y/N]${C.reset} > `);
|
|
681
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
682
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
683
|
+
rl.close();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
rl.close();
|
|
687
|
+
|
|
688
|
+
// Update via SDK
|
|
689
|
+
const client = createClient(rpc);
|
|
690
|
+
|
|
691
|
+
console.log(`\n ${C.dim}Submitting metadata update via SDK...${C.reset}`);
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
const result = await client.updateMetadata({
|
|
695
|
+
creator: address.startsWith('ATH') ? address.slice(3) : address,
|
|
696
|
+
nftId: nftId.trim(),
|
|
697
|
+
metadataUrl: metadataUrl.trim(),
|
|
698
|
+
signFn: async (tx) => signTransaction(tx, keyPair.secretKey),
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
if (result.error) {
|
|
702
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
console.log(`\n${C.green}✓ Metadata updated successfully!${C.reset}`);
|
|
706
|
+
console.log(` ${C.dim}NFT ID:${C.reset} ${C.cyan}${C.bright}${nftId}${C.reset}`);
|
|
707
|
+
console.log(` ${C.dim}New Meta:${C.reset} ${truncate(metadataUrl, 45)}`);
|
|
708
|
+
if (result.signature) {
|
|
709
|
+
console.log(` ${C.dim}Signature:${C.reset} ${shortAddress(result.signature)}`);
|
|
710
|
+
}
|
|
711
|
+
if (result.slot) {
|
|
712
|
+
console.log(` ${C.dim}Slot:${C.reset} ${result.slot}`);
|
|
713
|
+
}
|
|
714
|
+
console.log(` ${C.dim}SDK:${C.reset} updateMetadata() → POST /v1/transaction`);
|
|
715
|
+
console.log();
|
|
716
|
+
console.log(` ${C.dim}Verify update:${C.reset}`);
|
|
717
|
+
console.log(` ${C.cyan}aether nft info --nft ${nftId}${C.reset}\n`);
|
|
718
|
+
|
|
719
|
+
} catch (err) {
|
|
720
|
+
console.log(`\n ${C.red}✗ Metadata update failed:${C.reset} ${err.message}`);
|
|
721
|
+
console.log(` ${C.dim}Common causes:${C.reset}`);
|
|
722
|
+
console.log(` • You are not the NFT creator`);
|
|
723
|
+
console.log(` • Invalid NFT ID`);
|
|
724
|
+
console.log(` • Insufficient balance for transaction fee\n`);
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// ============================================================================
|
|
730
|
+
// Help
|
|
731
|
+
// ============================================================================
|
|
732
|
+
|
|
733
|
+
function showHelp() {
|
|
734
|
+
console.log(`
|
|
735
|
+
${C.bright}${C.cyan}aether-cli nft${C.reset} — NFT management suite
|
|
736
|
+
|
|
737
|
+
${C.bright}COMMANDS${C.reset}
|
|
738
|
+
create Create a new NFT with metadata and royalties
|
|
739
|
+
list List all NFTs owned by a wallet
|
|
740
|
+
transfer Transfer an NFT to another address
|
|
741
|
+
info Show detailed info about an NFT
|
|
742
|
+
update Update NFT metadata (creator only)
|
|
743
|
+
|
|
744
|
+
${C.bright}USAGE${C.reset}
|
|
745
|
+
aether nft create --metadata <url> [--royalties <bps>] [--address <wallet>]
|
|
746
|
+
aether nft list --address <wallet> [--json]
|
|
747
|
+
aether nft transfer --nft <id> --to <addr> [--from <wallet>]
|
|
748
|
+
aether nft info --nft <id> [--json]
|
|
749
|
+
aether nft update --nft <id> --metadata <url> [--address <wallet>]
|
|
750
|
+
|
|
751
|
+
${C.bright}SDK METHODS USED${C.reset}
|
|
752
|
+
client.createNFT() → POST /v1/transaction (CreateNFT)
|
|
753
|
+
client.transferNFT() → POST /v1/transaction (TransferNFT)
|
|
754
|
+
client.updateMetadata() → POST /v1/transaction (UpdateMetadata)
|
|
755
|
+
client.getTokenAccounts() → GET /v1/tokens/<addr>
|
|
756
|
+
client.getAccountInfo() → GET /v1/account/<addr>
|
|
757
|
+
|
|
758
|
+
${C.bright}EXAMPLES${C.reset}
|
|
759
|
+
aether nft create --metadata https://example.com/nft.json --royalties 500
|
|
760
|
+
aether nft list --address ATHxxx... --json
|
|
761
|
+
aether nft transfer --nft NFTxxx... --to ATHyyy...
|
|
762
|
+
aether nft info --nft NFTxxx... --json
|
|
763
|
+
|
|
764
|
+
${C.bright}NOTES${C.reset}
|
|
765
|
+
• Only the creator can update NFT metadata
|
|
766
|
+
• Royalties are specified in basis points (100 = 1%, 500 = 5%)
|
|
767
|
+
• NFT creation fee is typically 0.01 AETH
|
|
768
|
+
• Metadata must be a valid JSON file hosted at the provided URL
|
|
769
|
+
|
|
770
|
+
${C.green}✓ Fully wired to @jellylegsai/aether-sdk${C.reset}
|
|
771
|
+
`);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ============================================================================
|
|
775
|
+
// Argument Parser
|
|
776
|
+
// ============================================================================
|
|
777
|
+
|
|
778
|
+
function parseArgs() {
|
|
779
|
+
const rawArgs = process.argv.slice(3);
|
|
780
|
+
const subcmd = rawArgs[0] || 'help';
|
|
781
|
+
const allArgs = rawArgs.slice(1);
|
|
782
|
+
|
|
783
|
+
const rpcIndex = allArgs.findIndex(a => a === '--rpc' || a === '-r');
|
|
784
|
+
const rpc = rpcIndex !== -1 && allArgs[rpcIndex + 1] ? allArgs[rpcIndex + 1] : getDefaultRpc();
|
|
785
|
+
|
|
786
|
+
const parsed = {
|
|
787
|
+
subcmd,
|
|
788
|
+
rpc,
|
|
789
|
+
json: allArgs.includes('--json') || allArgs.includes('-j'),
|
|
790
|
+
address: null,
|
|
791
|
+
from: null,
|
|
792
|
+
to: null,
|
|
793
|
+
nft: null,
|
|
794
|
+
metadata: null,
|
|
795
|
+
royalties: null,
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const addrIdx = allArgs.findIndex(a => a === '--address' || a === '-a');
|
|
799
|
+
if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
|
|
800
|
+
|
|
801
|
+
const fromIdx = allArgs.findIndex(a => a === '--from' || a === '-f');
|
|
802
|
+
if (fromIdx !== -1 && allArgs[fromIdx + 1]) parsed.from = allArgs[fromIdx + 1];
|
|
803
|
+
|
|
804
|
+
const toIdx = allArgs.findIndex(a => a === '--to' || a === '-t');
|
|
805
|
+
if (toIdx !== -1 && allArgs[toIdx + 1]) parsed.to = allArgs[toIdx + 1];
|
|
806
|
+
|
|
807
|
+
const nftIdx = allArgs.findIndex(a => a === '--nft' || a === '-n');
|
|
808
|
+
if (nftIdx !== -1 && allArgs[nftIdx + 1]) parsed.nft = allArgs[nftIdx + 1];
|
|
809
|
+
|
|
810
|
+
const metaIdx = allArgs.findIndex(a => a === '--metadata' || a === '-m');
|
|
811
|
+
if (metaIdx !== -1 && allArgs[metaIdx + 1]) parsed.metadata = allArgs[metaIdx + 1];
|
|
812
|
+
|
|
813
|
+
const royaltyIdx = allArgs.findIndex(a => a === '--royalties' || a === '-r');
|
|
814
|
+
if (royaltyIdx !== -1 && allArgs[royaltyIdx + 1]) parsed.royalties = allArgs[royaltyIdx + 1];
|
|
815
|
+
|
|
816
|
+
return parsed;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ============================================================================
|
|
820
|
+
// Main Entry Point
|
|
821
|
+
// ============================================================================
|
|
822
|
+
|
|
823
|
+
async function nftCommand() {
|
|
824
|
+
const args = parseArgs();
|
|
825
|
+
|
|
826
|
+
switch (args.subcmd) {
|
|
827
|
+
case 'create':
|
|
828
|
+
await nftCreate(args);
|
|
829
|
+
break;
|
|
830
|
+
case 'list':
|
|
831
|
+
await nftList(args);
|
|
832
|
+
break;
|
|
833
|
+
case 'transfer':
|
|
834
|
+
await nftTransfer(args);
|
|
835
|
+
break;
|
|
836
|
+
case 'info':
|
|
837
|
+
await nftInfo(args);
|
|
838
|
+
break;
|
|
839
|
+
case 'update':
|
|
840
|
+
await nftUpdate(args);
|
|
841
|
+
break;
|
|
842
|
+
case 'help':
|
|
843
|
+
case '--help':
|
|
844
|
+
case '-h':
|
|
845
|
+
default:
|
|
846
|
+
showHelp();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
module.exports = { nftCommand };
|
|
851
|
+
|
|
852
|
+
if (require.main === module) {
|
|
853
|
+
nftCommand().catch(err => {
|
|
854
|
+
console.error(`\n${C.red}✗ NFT command failed:${C.reset}`, err.message, '\n');
|
|
855
|
+
process.exit(1);
|
|
856
|
+
});
|
|
857
|
+
}
|