@jellylegsai/aether-cli 1.9.2 → 2.0.1
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/IMPLEMENTATION_REPORT.md +319 -0
- package/commands/blockheight.js +230 -0
- package/commands/call.js +981 -0
- package/commands/claim.js +98 -72
- package/commands/deploy.js +959 -0
- package/commands/index.js +2 -0
- package/commands/init.js +33 -49
- package/commands/network-diagnostics.js +706 -0
- package/commands/network.js +412 -429
- package/commands/rewards.js +311 -266
- package/commands/sdk.js +791 -656
- package/commands/slot.js +3 -11
- package/commands/stake.js +581 -516
- package/commands/supply.js +483 -391
- package/commands/token-accounts.js +275 -0
- package/commands/transfer.js +3 -11
- package/commands/unstake.js +3 -11
- package/commands/validator-start.js +681 -323
- package/commands/validator.js +959 -0
- package/commands/validators.js +623 -626
- package/commands/version.js +240 -0
- package/commands/wallet.js +17 -24
- package/cycle-report-issue-116.txt +165 -0
- package/index.js +501 -602
- package/lib/ui.js +623 -0
- package/package.json +10 -3
- package/sdk/index.d.ts +546 -0
- package/sdk/index.js +130 -0
- package/sdk/package.json +2 -1
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli deploy
|
|
4
|
+
*
|
|
5
|
+
* Deploy smart contracts/programs to the Aether blockchain.
|
|
6
|
+
* Fully wired to @jellylegsai/aether-sdk for real blockchain RPC calls.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* aether deploy <contract.wasm> Deploy a WASM contract
|
|
10
|
+
* aether deploy <program.so> --type bpf Deploy a BPF program
|
|
11
|
+
* aether deploy <contract.wasm> --name <name> Deploy with custom name
|
|
12
|
+
* aether deploy <contract.wasm> --upgradeable Deploy as upgradeable contract
|
|
13
|
+
* aether deploy --list-templates Show available contract templates
|
|
14
|
+
* aether deploy --verify <address> Verify deployed contract
|
|
15
|
+
* aether deploy --status <address> Check deployment status
|
|
16
|
+
*
|
|
17
|
+
* SDK wired to:
|
|
18
|
+
* - client.sendTransaction(tx) → POST /v1/transaction (Deploy)
|
|
19
|
+
* - client.getAccountInfo(addr) → GET /v1/account/<addr>
|
|
20
|
+
* - client.getProgram(programId) → GET /v1/program/<id>
|
|
21
|
+
* - client.getSlot() → GET /v1/slot
|
|
22
|
+
* - client.getRecentBlockhash() → GET /v1/recent-blockhash
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const os = require('os');
|
|
28
|
+
const readline = require('readline');
|
|
29
|
+
const crypto = require('crypto');
|
|
30
|
+
const nacl = require('tweetnacl');
|
|
31
|
+
const bs58 = require('bs58').default;
|
|
32
|
+
const bip39 = require('bip39');
|
|
33
|
+
|
|
34
|
+
// Import SDK for ALL blockchain RPC calls
|
|
35
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
36
|
+
const aether = require(sdkPath);
|
|
37
|
+
|
|
38
|
+
// Import UI framework for consistent branding
|
|
39
|
+
const { BRANDING, C, indicators, success, error, warning, info, code, key, value, startSpinner, stopSpinner, formatHelp, drawBox } = require('../lib/ui');
|
|
40
|
+
|
|
41
|
+
const CLI_VERSION = '2.0.0';
|
|
42
|
+
const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
|
|
43
|
+
|
|
44
|
+
// Deployment constants
|
|
45
|
+
const DEPLOYMENT_FEE_LAMPORTS = 50000; // 0.00005 AETH base fee
|
|
46
|
+
const MIN_RENT_EXEMPTION_LAMPORTS = 890880; // ~0.00089 AETH for rent exemption
|
|
47
|
+
const MAX_CONTRACT_SIZE = 10 * 1024 * 1024; // 10MB max
|
|
48
|
+
|
|
49
|
+
// Contract templates
|
|
50
|
+
const CONTRACT_TEMPLATES = {
|
|
51
|
+
token: {
|
|
52
|
+
name: 'SPL Token Contract',
|
|
53
|
+
description: 'Standard token contract with mint/burn/transfer',
|
|
54
|
+
minSize: 10000,
|
|
55
|
+
maxSize: 50000,
|
|
56
|
+
},
|
|
57
|
+
nft: {
|
|
58
|
+
name: 'NFT Collection Contract',
|
|
59
|
+
description: 'NFT minting and management contract',
|
|
60
|
+
minSize: 15000,
|
|
61
|
+
maxSize: 100000,
|
|
62
|
+
},
|
|
63
|
+
staking: {
|
|
64
|
+
name: 'Staking Pool Contract',
|
|
65
|
+
description: 'Stake tokens and earn rewards',
|
|
66
|
+
minSize: 20000,
|
|
67
|
+
maxSize: 150000,
|
|
68
|
+
},
|
|
69
|
+
governance: {
|
|
70
|
+
name: 'Governance Contract',
|
|
71
|
+
description: 'On-chain voting and proposals',
|
|
72
|
+
minSize: 25000,
|
|
73
|
+
maxSize: 200000,
|
|
74
|
+
},
|
|
75
|
+
multisig: {
|
|
76
|
+
name: 'Multi-Signature Wallet',
|
|
77
|
+
description: 'Requires N-of-M signatures for transactions',
|
|
78
|
+
minSize: 18000,
|
|
79
|
+
maxSize: 80000,
|
|
80
|
+
},
|
|
81
|
+
custom: {
|
|
82
|
+
name: 'Custom Contract',
|
|
83
|
+
description: 'Your own compiled WASM/BPF program',
|
|
84
|
+
minSize: 1000,
|
|
85
|
+
maxSize: MAX_CONTRACT_SIZE,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// SDK Setup
|
|
91
|
+
// ============================================================================
|
|
92
|
+
|
|
93
|
+
function getDefaultRpc() {
|
|
94
|
+
return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createClient(rpcUrl) {
|
|
98
|
+
return new aether.AetherClient({ rpcUrl });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Config & Wallet
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
function getAetherDir() {
|
|
106
|
+
return path.join(os.homedir(), '.aether');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getConfigPath() {
|
|
110
|
+
return path.join(getAetherDir(), 'config.json');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function loadConfig() {
|
|
114
|
+
if (!fs.existsSync(getConfigPath())) {
|
|
115
|
+
return { defaultWallet: null, deployments: [] };
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
|
119
|
+
} catch {
|
|
120
|
+
return { defaultWallet: null, deployments: [] };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function saveConfig(cfg) {
|
|
125
|
+
if (!fs.existsSync(getAetherDir())) {
|
|
126
|
+
fs.mkdirSync(getAetherDir(), { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(cfg, null, 2));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function loadWallet(address) {
|
|
132
|
+
const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
|
|
133
|
+
if (!fs.existsSync(fp)) return null;
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function saveDeployment(deployment) {
|
|
142
|
+
const cfg = loadConfig();
|
|
143
|
+
if (!cfg.deployments) cfg.deployments = [];
|
|
144
|
+
cfg.deployments.push({
|
|
145
|
+
...deployment,
|
|
146
|
+
deployedAt: new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
saveConfig(cfg);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Crypto Helpers
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
function deriveKeypair(mnemonic) {
|
|
156
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
157
|
+
throw new Error('Invalid mnemonic phrase');
|
|
158
|
+
}
|
|
159
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
|
|
160
|
+
const seed32 = seedBuffer.slice(0, 32);
|
|
161
|
+
const keyPair = nacl.sign.keyPair.fromSeed(seed32);
|
|
162
|
+
return {
|
|
163
|
+
publicKey: Buffer.from(keyPair.publicKey),
|
|
164
|
+
secretKey: Buffer.from(keyPair.secretKey),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatAddress(publicKey) {
|
|
169
|
+
return 'ATH' + bs58.encode(publicKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function signTransaction(tx, secretKey) {
|
|
173
|
+
const txBytes = Buffer.from(JSON.stringify(tx));
|
|
174
|
+
const sig = nacl.sign.detached(txBytes, secretKey);
|
|
175
|
+
return bs58.encode(sig);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function generateProgramId() {
|
|
179
|
+
const keyPair = nacl.sign.keyPair();
|
|
180
|
+
return bs58.encode(Buffer.from(keyPair.publicKey));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Format Helpers
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
function formatAether(lamports) {
|
|
188
|
+
const aeth = Number(lamports) / 1e9;
|
|
189
|
+
if (aeth === 0) return '0 AETH';
|
|
190
|
+
return aeth.toFixed(9).replace(/\.?0+$/, '') + ' AETH';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatBytes(bytes) {
|
|
194
|
+
if (bytes < 1024) return bytes + ' B';
|
|
195
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
|
|
196
|
+
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function shortAddress(addr) {
|
|
200
|
+
if (!addr || addr.length < 16) return addr || 'unknown';
|
|
201
|
+
return addr.slice(0, 8) + '…' + addr.slice(-8);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatDuration(ms) {
|
|
205
|
+
if (ms < 1000) return `${ms}ms`;
|
|
206
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
207
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Readline Helpers
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
function createRl() {
|
|
215
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function question(rl, q) {
|
|
219
|
+
return new Promise((res) => rl.question(q, res));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function askMnemonic(rl, promptText) {
|
|
223
|
+
console.log(`\n${C.cyan}${promptText}${C.reset}`);
|
|
224
|
+
console.log(`${C.dim}Enter your 12 or 24-word passphrase:${C.reset}`);
|
|
225
|
+
const raw = await question(rl, ` > ${C.reset}`);
|
|
226
|
+
return raw.trim().toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// Contract Validation
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
function validateContractFile(filePath) {
|
|
234
|
+
const errors = [];
|
|
235
|
+
const warnings = [];
|
|
236
|
+
|
|
237
|
+
// Check file exists
|
|
238
|
+
if (!fs.existsSync(filePath)) {
|
|
239
|
+
errors.push(`File not found: ${filePath}`);
|
|
240
|
+
return { valid: false, errors, warnings };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check it's a file
|
|
244
|
+
const stats = fs.statSync(filePath);
|
|
245
|
+
if (!stats.isFile()) {
|
|
246
|
+
errors.push(`Path is not a file: ${filePath}`);
|
|
247
|
+
return { valid: false, errors, warnings };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check size
|
|
251
|
+
const size = stats.size;
|
|
252
|
+
if (size === 0) {
|
|
253
|
+
errors.push('Contract file is empty');
|
|
254
|
+
} else if (size > MAX_CONTRACT_SIZE) {
|
|
255
|
+
errors.push(`Contract size ${formatBytes(size)} exceeds maximum ${formatBytes(MAX_CONTRACT_SIZE)}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check extension
|
|
259
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
260
|
+
const validExts = ['.wasm', '.so', '.bin'];
|
|
261
|
+
if (!validExts.includes(ext)) {
|
|
262
|
+
warnings.push(`Unusual extension "${ext}". Expected: .wasm, .so, or .bin`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Try to read and validate basic structure
|
|
266
|
+
try {
|
|
267
|
+
const buffer = fs.readFileSync(filePath);
|
|
268
|
+
|
|
269
|
+
// Check WASM magic number for .wasm files
|
|
270
|
+
if (ext === '.wasm' || buffer.slice(0, 4).toString('hex') === '0061736d') {
|
|
271
|
+
const wasmMagic = buffer.slice(0, 4);
|
|
272
|
+
if (wasmMagic.toString('hex') !== '0061736d') {
|
|
273
|
+
warnings.push('File does not have standard WASM magic number');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Calculate hash
|
|
278
|
+
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
valid: errors.length === 0,
|
|
282
|
+
errors,
|
|
283
|
+
warnings,
|
|
284
|
+
size,
|
|
285
|
+
formattedSize: formatBytes(size),
|
|
286
|
+
hash: hash.slice(0, 16) + '...',
|
|
287
|
+
fullHash: hash,
|
|
288
|
+
};
|
|
289
|
+
} catch (e) {
|
|
290
|
+
errors.push(`Failed to read file: ${e.message}`);
|
|
291
|
+
return { valid: false, errors, warnings };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Argument Parsing
|
|
297
|
+
// ============================================================================
|
|
298
|
+
|
|
299
|
+
function parseArgs() {
|
|
300
|
+
const args = process.argv.slice(2);
|
|
301
|
+
const opts = {
|
|
302
|
+
filePath: null,
|
|
303
|
+
contractType: 'custom',
|
|
304
|
+
name: null,
|
|
305
|
+
upgradeable: false,
|
|
306
|
+
rpc: getDefaultRpc(),
|
|
307
|
+
json: false,
|
|
308
|
+
dryRun: false,
|
|
309
|
+
force: false,
|
|
310
|
+
wallet: null,
|
|
311
|
+
listTemplates: false,
|
|
312
|
+
verify: null,
|
|
313
|
+
status: null,
|
|
314
|
+
help: false,
|
|
315
|
+
programId: null,
|
|
316
|
+
rentExempt: true,
|
|
317
|
+
computeUnits: 200000,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
for (let i = 0; i < args.length; i++) {
|
|
321
|
+
const arg = args[i];
|
|
322
|
+
|
|
323
|
+
if (arg === '--type' || arg === '-t') {
|
|
324
|
+
opts.contractType = (args[++i] || 'custom').toLowerCase();
|
|
325
|
+
} else if (arg === '--name' || arg === '-n') {
|
|
326
|
+
opts.name = args[++i];
|
|
327
|
+
} else if (arg === '--upgradeable' || arg === '-u') {
|
|
328
|
+
opts.upgradeable = true;
|
|
329
|
+
} else if (arg === '--rpc' || arg === '-r') {
|
|
330
|
+
opts.rpc = args[++i];
|
|
331
|
+
} else if (arg === '--json' || arg === '-j') {
|
|
332
|
+
opts.json = true;
|
|
333
|
+
} else if (arg === '--dry-run') {
|
|
334
|
+
opts.dryRun = true;
|
|
335
|
+
} else if (arg === '--force' || arg === '-f') {
|
|
336
|
+
opts.force = true;
|
|
337
|
+
} else if (arg === '--wallet' || arg === '-w') {
|
|
338
|
+
opts.wallet = args[++i];
|
|
339
|
+
} else if (arg === '--list-templates' || arg === '-l') {
|
|
340
|
+
opts.listTemplates = true;
|
|
341
|
+
} else if (arg === '--verify' || arg === '-v') {
|
|
342
|
+
opts.verify = args[++i];
|
|
343
|
+
} else if (arg === '--status' || arg === '-s') {
|
|
344
|
+
opts.status = args[++i];
|
|
345
|
+
} else if (arg === '--program-id' || arg === '-p') {
|
|
346
|
+
opts.programId = args[++i];
|
|
347
|
+
} else if (arg === '--no-rent-exempt') {
|
|
348
|
+
opts.rentExempt = false;
|
|
349
|
+
} else if (arg === '--compute-units' || arg === '-c') {
|
|
350
|
+
const cu = parseInt(args[++i], 10);
|
|
351
|
+
if (!isNaN(cu) && cu > 0) opts.computeUnits = cu;
|
|
352
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
353
|
+
opts.help = true;
|
|
354
|
+
} else if (!arg.startsWith('-') && !opts.filePath) {
|
|
355
|
+
opts.filePath = arg;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return opts;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function showHelp() {
|
|
363
|
+
console.log(BRANDING.header(CLI_VERSION));
|
|
364
|
+
console.log(`
|
|
365
|
+
${C.bright}${C.cyan}aether-cli deploy${C.reset} — Deploy smart contracts to Aether blockchain
|
|
366
|
+
|
|
367
|
+
${C.bright}USAGE${C.reset}
|
|
368
|
+
aether deploy <contract.wasm> [options]
|
|
369
|
+
aether deploy --list-templates
|
|
370
|
+
aether deploy --verify <program-id>
|
|
371
|
+
aether deploy --status <program-id>
|
|
372
|
+
|
|
373
|
+
${C.bright}COMMANDS${C.reset}
|
|
374
|
+
deploy <file> Deploy contract from file
|
|
375
|
+
--list-templates Show available contract templates
|
|
376
|
+
--verify <address> Verify deployed contract on-chain
|
|
377
|
+
--status <address> Check deployment/upgrade status
|
|
378
|
+
|
|
379
|
+
${C.bright}OPTIONS${C.reset}
|
|
380
|
+
-t, --type <type> Contract type: token, nft, staking, governance, multisig, custom
|
|
381
|
+
-n, --name <name> Contract name for identification
|
|
382
|
+
-u, --upgradeable Deploy as upgradeable contract
|
|
383
|
+
-w, --wallet <addr> Deployer wallet address
|
|
384
|
+
-r, --rpc <url> RPC endpoint
|
|
385
|
+
-c, --compute-units Max compute units (default: 200000)
|
|
386
|
+
--no-rent-exempt Skip rent exemption (not recommended)
|
|
387
|
+
--dry-run Preview deployment without submitting
|
|
388
|
+
--force Skip confirmation prompts
|
|
389
|
+
-j, --json JSON output for scripting
|
|
390
|
+
-h, --help Show this help
|
|
391
|
+
|
|
392
|
+
${C.bright}SDK METHODS USED${C.reset}
|
|
393
|
+
client.sendTransaction() → POST /v1/transaction
|
|
394
|
+
client.getAccountInfo() → GET /v1/account/<addr>
|
|
395
|
+
client.getSlot() → GET /v1/slot
|
|
396
|
+
client.getRecentBlockhash() → GET /v1/recent-blockhash
|
|
397
|
+
|
|
398
|
+
${C.bright}EXAMPLES${C.reset}
|
|
399
|
+
aether deploy token_contract.wasm --name "MyToken" --type token
|
|
400
|
+
aether deploy program.so --type bpf --upgradeable --wallet ATHxxx...
|
|
401
|
+
aether deploy --list-templates
|
|
402
|
+
aether deploy --verify ATHProgxxx...
|
|
403
|
+
aether deploy --status ATHProgxxx...
|
|
404
|
+
`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// List Templates
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
function listTemplates(asJson) {
|
|
412
|
+
if (asJson) {
|
|
413
|
+
console.log(JSON.stringify({
|
|
414
|
+
templates: Object.entries(CONTRACT_TEMPLATES).map(([id, t]) => ({
|
|
415
|
+
id,
|
|
416
|
+
name: t.name,
|
|
417
|
+
description: t.description,
|
|
418
|
+
minSize: t.minSize,
|
|
419
|
+
maxSize: t.maxSize,
|
|
420
|
+
})),
|
|
421
|
+
}, null, 2));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.log(`\n${C.bright}${C.cyan}═══ Available Contract Templates ═══${C.reset}\n`);
|
|
426
|
+
|
|
427
|
+
for (const [id, template] of Object.entries(CONTRACT_TEMPLATES)) {
|
|
428
|
+
const boxContent = `
|
|
429
|
+
${C.bright}${template.name}${C.reset}
|
|
430
|
+
${C.dim}${template.description}${C.reset}
|
|
431
|
+
|
|
432
|
+
${C.cyan}Size:${C.reset} ${formatBytes(template.minSize)} - ${formatBytes(template.maxSize)}
|
|
433
|
+
${C.cyan}Usage:${C.reset} aether deploy <file> --type ${id}`;
|
|
434
|
+
|
|
435
|
+
console.log(drawBox(boxContent, {
|
|
436
|
+
style: 'rounded',
|
|
437
|
+
width: 60,
|
|
438
|
+
borderColor: C.dim,
|
|
439
|
+
}));
|
|
440
|
+
console.log();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log(`${C.dim}Tip: Use --type custom for your own compiled contracts${C.reset}\n`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ============================================================================
|
|
447
|
+
// Verify Contract
|
|
448
|
+
// ============================================================================
|
|
449
|
+
|
|
450
|
+
async function verifyContract(opts) {
|
|
451
|
+
const programId = opts.verify;
|
|
452
|
+
const rpcUrl = opts.rpc;
|
|
453
|
+
|
|
454
|
+
if (!opts.json) {
|
|
455
|
+
console.log(`\n${C.bright}${C.cyan}═══ Verify Contract ═══${C.reset}\n`);
|
|
456
|
+
console.log(` ${C.dim}Program ID:${C.reset} ${programId}`);
|
|
457
|
+
console.log(` ${C.dim}RPC:${C.reset} ${rpcUrl}\n`);
|
|
458
|
+
startSpinner('Fetching on-chain data');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const client = createClient(rpcUrl);
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
// Try to get program account info
|
|
465
|
+
const rawProgramId = programId.startsWith('ATH') ? programId.slice(3) : programId;
|
|
466
|
+
const account = await client.getAccountInfo(rawProgramId);
|
|
467
|
+
|
|
468
|
+
stopSpinner(true);
|
|
469
|
+
|
|
470
|
+
if (!account || account.error) {
|
|
471
|
+
if (opts.json) {
|
|
472
|
+
console.log(JSON.stringify({
|
|
473
|
+
programId,
|
|
474
|
+
verified: false,
|
|
475
|
+
error: 'Program not found on-chain',
|
|
476
|
+
}, null, 2));
|
|
477
|
+
} else {
|
|
478
|
+
console.log(`\n ${indicators.error} ${error('Program not found on-chain')}\n`);
|
|
479
|
+
console.log(` ${C.dim}The program ID may be incorrect or the deployment failed.${C.reset}\n`);
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Program exists - verify it's executable
|
|
485
|
+
const isExecutable = account.executable === true || account.owner === 'BPFLoader2111111111111111111111111111111111';
|
|
486
|
+
const deployTime = account.rent_epoch ? `Epoch ${account.rent_epoch}` : 'Unknown';
|
|
487
|
+
|
|
488
|
+
if (opts.json) {
|
|
489
|
+
console.log(JSON.stringify({
|
|
490
|
+
programId,
|
|
491
|
+
verified: true,
|
|
492
|
+
executable: isExecutable,
|
|
493
|
+
owner: account.owner,
|
|
494
|
+
lamports: account.lamports,
|
|
495
|
+
dataSize: account.data ? account.data.length : 0,
|
|
496
|
+
rentEpoch: account.rent_epoch,
|
|
497
|
+
deployTime,
|
|
498
|
+
}, null, 2));
|
|
499
|
+
} else {
|
|
500
|
+
console.log(`\n ${indicators.success} ${success('Contract verified on-chain')}\n`);
|
|
501
|
+
console.log(` ${C.bright}Program ID:${C.reset} ${C.cyan}${programId}${C.reset}`);
|
|
502
|
+
console.log(` ${C.bright}Status:${C.reset} ${isExecutable ? C.green + 'Executable ✓' : C.yellow + 'Not Executable'}${C.reset}`);
|
|
503
|
+
console.log(` ${C.bright}Balance:${C.reset} ${formatAether(account.lamports || 0)}`);
|
|
504
|
+
console.log(` ${C.bright}Data Size:${C.reset} ${formatBytes(account.data ? account.data.length : 0)}`);
|
|
505
|
+
console.log(` ${C.bright}Owner:${C.reset} ${shortAddress(account.owner)}`);
|
|
506
|
+
console.log(` ${C.bright}Deployed:${C.reset} ${deployTime}`);
|
|
507
|
+
console.log();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return true;
|
|
511
|
+
} catch (err) {
|
|
512
|
+
stopSpinner(false);
|
|
513
|
+
if (opts.json) {
|
|
514
|
+
console.log(JSON.stringify({
|
|
515
|
+
programId,
|
|
516
|
+
verified: false,
|
|
517
|
+
error: err.message,
|
|
518
|
+
}, null, 2));
|
|
519
|
+
} else {
|
|
520
|
+
console.log(`\n ${indicators.error} ${error(`Verification failed: ${err.message}`)}\n`);
|
|
521
|
+
}
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ============================================================================
|
|
527
|
+
// Check Deployment Status
|
|
528
|
+
// ============================================================================
|
|
529
|
+
|
|
530
|
+
async function checkDeploymentStatus(opts) {
|
|
531
|
+
const programId = opts.status;
|
|
532
|
+
const rpcUrl = opts.rpc;
|
|
533
|
+
|
|
534
|
+
if (!opts.json) {
|
|
535
|
+
console.log(`\n${C.bright}${C.cyan}═══ Deployment Status ═══${C.reset}\n`);
|
|
536
|
+
console.log(` ${C.dim}Program ID:${C.reset} ${programId}`);
|
|
537
|
+
console.log(` ${C.dim}RPC:${C.reset} ${rpcUrl}\n`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const client = createClient(rpcUrl);
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const rawProgramId = programId.startsWith('ATH') ? programId.slice(3) : programId;
|
|
544
|
+
|
|
545
|
+
// Get multiple data points
|
|
546
|
+
const [account, slot, health] = await Promise.all([
|
|
547
|
+
client.getAccountInfo(rawProgramId).catch(() => null),
|
|
548
|
+
client.getSlot().catch(() => null),
|
|
549
|
+
client.getHealth().catch(() => 'unknown'),
|
|
550
|
+
]);
|
|
551
|
+
|
|
552
|
+
const status = {
|
|
553
|
+
programId,
|
|
554
|
+
exists: !!account,
|
|
555
|
+
executable: account?.executable || false,
|
|
556
|
+
currentSlot: slot,
|
|
557
|
+
nodeHealth: health,
|
|
558
|
+
timestamp: new Date().toISOString(),
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
if (opts.json) {
|
|
562
|
+
console.log(JSON.stringify(status, null, 2));
|
|
563
|
+
} else {
|
|
564
|
+
console.log(` ${C.bright}Program ID:${C.reset} ${C.cyan}${programId}${C.reset}`);
|
|
565
|
+
console.log(` ${C.bright}Status:${C.reset} ${status.exists ? C.green + '✓ Deployed' : C.red + '✗ Not Found'}${C.reset}`);
|
|
566
|
+
console.log(` ${C.bright}Executable:${C.reset} ${status.executable ? C.green + 'Yes' : C.yellow + 'No'}${C.reset}`);
|
|
567
|
+
console.log(` ${C.bright}Current Slot:${C.reset} ${slot || 'N/A'}`);
|
|
568
|
+
console.log(` ${C.bright}Node Health:${C.reset} ${health}${C.reset}`);
|
|
569
|
+
console.log();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return status.exists;
|
|
573
|
+
} catch (err) {
|
|
574
|
+
if (opts.json) {
|
|
575
|
+
console.log(JSON.stringify({
|
|
576
|
+
programId,
|
|
577
|
+
error: err.message,
|
|
578
|
+
}, null, 2));
|
|
579
|
+
} else {
|
|
580
|
+
console.log(`\n ${indicators.error} ${error(`Status check failed: ${err.message}`)}\n`);
|
|
581
|
+
}
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ============================================================================
|
|
587
|
+
// Core Deployment Logic
|
|
588
|
+
// ============================================================================
|
|
589
|
+
|
|
590
|
+
async function deployContract(opts) {
|
|
591
|
+
const startTime = Date.now();
|
|
592
|
+
const rl = createRl();
|
|
593
|
+
|
|
594
|
+
// Header
|
|
595
|
+
if (!opts.json) {
|
|
596
|
+
console.log(BRANDING.header(CLI_VERSION));
|
|
597
|
+
console.log(`\n${C.bright}${C.cyan}═══ Contract Deployment ═══${C.reset}\n`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Validate contract file
|
|
601
|
+
if (!opts.json) {
|
|
602
|
+
console.log(` ${C.dim}Validating contract file...${C.reset}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const validation = validateContractFile(opts.filePath);
|
|
606
|
+
if (!validation.valid) {
|
|
607
|
+
if (opts.json) {
|
|
608
|
+
console.log(JSON.stringify({
|
|
609
|
+
success: false,
|
|
610
|
+
stage: 'validation',
|
|
611
|
+
errors: validation.errors,
|
|
612
|
+
warnings: validation.warnings,
|
|
613
|
+
}, null, 2));
|
|
614
|
+
} else {
|
|
615
|
+
console.log(`\n ${indicators.error} ${error('Validation failed')}\n`);
|
|
616
|
+
validation.errors.forEach(e => console.log(` ${C.red}•${C.reset} ${e}`));
|
|
617
|
+
if (validation.warnings.length > 0) {
|
|
618
|
+
console.log();
|
|
619
|
+
validation.warnings.forEach(w => console.log(` ${C.yellow}⚠${C.reset} ${w}`));
|
|
620
|
+
}
|
|
621
|
+
console.log();
|
|
622
|
+
}
|
|
623
|
+
rl.close();
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (!opts.json) {
|
|
628
|
+
console.log(` ${indicators.success} ${success('File validated')}`);
|
|
629
|
+
console.log(` ${C.dim} Size: ${validation.formattedSize}${C.reset}`);
|
|
630
|
+
console.log(` ${C.dim} Hash: ${validation.hash}${C.reset}\n`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Resolve wallet
|
|
634
|
+
let walletAddress = opts.wallet;
|
|
635
|
+
if (!walletAddress) {
|
|
636
|
+
const cfg = loadConfig();
|
|
637
|
+
walletAddress = cfg.defaultWallet;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (!walletAddress) {
|
|
641
|
+
if (opts.json) {
|
|
642
|
+
console.log(JSON.stringify({ success: false, error: 'No wallet specified' }, null, 2));
|
|
643
|
+
} else {
|
|
644
|
+
console.log(`\n ${indicators.error} ${error('No wallet address')} — Use --wallet or set a default\n`);
|
|
645
|
+
}
|
|
646
|
+
rl.close();
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const wallet = loadWallet(walletAddress);
|
|
651
|
+
if (!wallet) {
|
|
652
|
+
if (opts.json) {
|
|
653
|
+
console.log(JSON.stringify({ success: false, error: 'Wallet not found' }, null, 2));
|
|
654
|
+
} else {
|
|
655
|
+
console.log(`\n ${indicators.error} ${error(`Wallet not found: ${walletAddress}`)}\n`);
|
|
656
|
+
}
|
|
657
|
+
rl.close();
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Initialize SDK client
|
|
662
|
+
const client = createClient(opts.rpc);
|
|
663
|
+
|
|
664
|
+
// Check balance
|
|
665
|
+
if (!opts.json) {
|
|
666
|
+
console.log(` ${C.dim}Checking wallet balance...${C.reset}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
let balance = 0;
|
|
670
|
+
try {
|
|
671
|
+
const rawAddr = walletAddress.startsWith('ATH') ? walletAddress.slice(3) : walletAddress;
|
|
672
|
+
balance = await client.getBalance(rawAddr);
|
|
673
|
+
} catch (e) {
|
|
674
|
+
// Continue with balance 0
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const minRequired = DEPLOYMENT_FEE_LAMPORTS + (opts.rentExempt ? MIN_RENT_EXEMPTION_LAMPORTS : 0) + validation.size;
|
|
678
|
+
|
|
679
|
+
if (balance < minRequired) {
|
|
680
|
+
if (opts.json) {
|
|
681
|
+
console.log(JSON.stringify({
|
|
682
|
+
success: false,
|
|
683
|
+
error: 'Insufficient balance',
|
|
684
|
+
required: minRequired,
|
|
685
|
+
available: balance,
|
|
686
|
+
}, null, 2));
|
|
687
|
+
} else {
|
|
688
|
+
console.log(`\n ${indicators.error} ${error('Insufficient balance')}\n`);
|
|
689
|
+
console.log(` Required: ${formatAether(minRequired)}`);
|
|
690
|
+
console.log(` Available: ${formatAether(balance)}\n`);
|
|
691
|
+
}
|
|
692
|
+
rl.close();
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (!opts.json) {
|
|
697
|
+
console.log(` ${indicators.success} ${success('Balance sufficient')}: ${formatAether(balance)}\n`);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Get network state
|
|
701
|
+
if (!opts.json) {
|
|
702
|
+
console.log(` ${C.dim}Fetching network state...${C.reset}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const [slot, blockhash, epoch] = await Promise.all([
|
|
706
|
+
client.getSlot().catch(() => 0),
|
|
707
|
+
client.getRecentBlockhash().catch(() => ({ blockhash: '11111111111111111111111111111111' })),
|
|
708
|
+
client.getEpochInfo().catch(() => ({ epoch: 0 })),
|
|
709
|
+
]);
|
|
710
|
+
|
|
711
|
+
if (!opts.json) {
|
|
712
|
+
console.log(` ${indicators.success} ${success('Network ready')}`);
|
|
713
|
+
console.log(` ${C.dim} Slot: ${slot}${C.reset}`);
|
|
714
|
+
console.log(` ${C.dim} Epoch: ${epoch.epoch}${C.reset}\n`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Generate or use provided program ID
|
|
718
|
+
const programId = opts.programId || generateProgramId();
|
|
719
|
+
|
|
720
|
+
// Read contract bytecode
|
|
721
|
+
const contractBytes = fs.readFileSync(opts.filePath);
|
|
722
|
+
const contractBase64 = contractBytes.toString('base64');
|
|
723
|
+
|
|
724
|
+
// Deployment summary
|
|
725
|
+
const deploymentName = opts.name || path.basename(opts.filePath, path.extname(opts.filePath));
|
|
726
|
+
|
|
727
|
+
if (!opts.json) {
|
|
728
|
+
console.log(`${C.bright}${C.cyan}── Deployment Summary ──${C.reset}\n`);
|
|
729
|
+
console.log(` ${C.bright}Contract:${C.reset} ${C.cyan}${deploymentName}${C.reset}`);
|
|
730
|
+
console.log(` ${C.bright}Type:${C.reset} ${opts.contractType}`);
|
|
731
|
+
console.log(` ${C.bright}Size:${C.reset} ${validation.formattedSize}`);
|
|
732
|
+
console.log(` ${C.bright}Program ID:${C.reset} ${shortAddress(programId)}`);
|
|
733
|
+
console.log(` ${C.bright}Deployer:${C.reset} ${shortAddress(walletAddress)}`);
|
|
734
|
+
console.log(` ${C.bright}Upgradeable:${C.reset} ${opts.upgradeable ? C.green + 'Yes' : C.dim + 'No'}${C.reset}`);
|
|
735
|
+
console.log(` ${C.bright}Rent Exempt:${C.reset} ${opts.rentExempt ? C.green + 'Yes' : C.yellow + 'No'}${C.reset}`);
|
|
736
|
+
console.log(` ${C.bright}RPC:${C.reset} ${opts.rpc}`);
|
|
737
|
+
console.log(` ${C.bright}Fee:${C.reset} ${formatAether(DEPLOYMENT_FEE_LAMPORTS)}`);
|
|
738
|
+
console.log();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Dry run mode
|
|
742
|
+
if (opts.dryRun) {
|
|
743
|
+
if (opts.json) {
|
|
744
|
+
console.log(JSON.stringify({
|
|
745
|
+
dryRun: true,
|
|
746
|
+
name: deploymentName,
|
|
747
|
+
type: opts.contractType,
|
|
748
|
+
size: validation.size,
|
|
749
|
+
programId,
|
|
750
|
+
deployer: walletAddress,
|
|
751
|
+
upgradeable: opts.upgradeable,
|
|
752
|
+
rentExempt: opts.rentExempt,
|
|
753
|
+
fee: DEPLOYMENT_FEE_LAMPORTS,
|
|
754
|
+
slot,
|
|
755
|
+
epoch: epoch.epoch,
|
|
756
|
+
}, null, 2));
|
|
757
|
+
} else {
|
|
758
|
+
console.log(` ${C.yellow}⚠ Dry run mode — No transaction submitted${C.reset}\n`);
|
|
759
|
+
}
|
|
760
|
+
rl.close();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Get mnemonic for signing
|
|
765
|
+
if (!opts.json) {
|
|
766
|
+
console.log(`${C.yellow} Signing requires your wallet passphrase${C.reset}\n`);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
let keyPair;
|
|
770
|
+
try {
|
|
771
|
+
const mnemonic = await askMnemonic(rl, 'Enter passphrase to sign deployment');
|
|
772
|
+
keyPair = deriveKeypair(mnemonic);
|
|
773
|
+
|
|
774
|
+
// Verify address
|
|
775
|
+
const derivedAddr = formatAddress(keyPair.publicKey);
|
|
776
|
+
if (derivedAddr !== walletAddress) {
|
|
777
|
+
if (!opts.json) {
|
|
778
|
+
console.log(`\n ${indicators.error} ${error('Passphrase mismatch')}\n`);
|
|
779
|
+
console.log(` Expected: ${walletAddress}`);
|
|
780
|
+
console.log(` Derived: ${derivedAddr}\n`);
|
|
781
|
+
} else {
|
|
782
|
+
console.log(JSON.stringify({ success: false, error: 'Passphrase mismatch' }, null, 2));
|
|
783
|
+
}
|
|
784
|
+
rl.close();
|
|
785
|
+
process.exit(1);
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
if (!opts.json) {
|
|
789
|
+
console.log(`\n ${indicators.error} ${error(`Failed: ${e.message}`)}\n`);
|
|
790
|
+
} else {
|
|
791
|
+
console.log(JSON.stringify({ success: false, error: e.message }, null, 2));
|
|
792
|
+
}
|
|
793
|
+
rl.close();
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Confirm deployment
|
|
798
|
+
if (!opts.json && !opts.force) {
|
|
799
|
+
const confirm = await question(rl, ` ${C.yellow}Confirm deployment? [y/N]${C.reset} > `);
|
|
800
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
801
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
802
|
+
rl.close();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
console.log();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
rl.close();
|
|
809
|
+
|
|
810
|
+
// Build deployment transaction
|
|
811
|
+
const rawWalletAddr = walletAddress.startsWith('ATH') ? walletAddress.slice(3) : walletAddress;
|
|
812
|
+
|
|
813
|
+
const tx = {
|
|
814
|
+
signer: rawWalletAddr,
|
|
815
|
+
tx_type: 'Deploy',
|
|
816
|
+
payload: {
|
|
817
|
+
type: 'Deploy',
|
|
818
|
+
data: {
|
|
819
|
+
program_id: programId,
|
|
820
|
+
bytecode: contractBase64,
|
|
821
|
+
name: deploymentName,
|
|
822
|
+
contract_type: opts.contractType,
|
|
823
|
+
upgradeable: opts.upgradeable,
|
|
824
|
+
rent_exempt: opts.rentExempt,
|
|
825
|
+
compute_units: opts.computeUnits,
|
|
826
|
+
bytecode_hash: validation.fullHash,
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
fee: DEPLOYMENT_FEE_LAMPORTS,
|
|
830
|
+
slot: slot,
|
|
831
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
832
|
+
recent_blockhash: blockhash.blockhash,
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// Sign transaction
|
|
836
|
+
tx.signature = signTransaction(tx, keyPair.secretKey);
|
|
837
|
+
|
|
838
|
+
// Submit deployment
|
|
839
|
+
if (!opts.json) {
|
|
840
|
+
startSpinner('Submitting deployment transaction');
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
try {
|
|
844
|
+
const result = await client.sendTransaction(tx);
|
|
845
|
+
|
|
846
|
+
stopSpinner(true);
|
|
847
|
+
|
|
848
|
+
const deployTime = Date.now() - startTime;
|
|
849
|
+
|
|
850
|
+
// Save deployment record
|
|
851
|
+
saveDeployment({
|
|
852
|
+
programId,
|
|
853
|
+
name: deploymentName,
|
|
854
|
+
type: opts.contractType,
|
|
855
|
+
size: validation.size,
|
|
856
|
+
deployer: walletAddress,
|
|
857
|
+
transaction: result.signature || result.txid,
|
|
858
|
+
slot: result.slot || slot,
|
|
859
|
+
upgradeable: opts.upgradeable,
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
if (opts.json) {
|
|
863
|
+
console.log(JSON.stringify({
|
|
864
|
+
success: true,
|
|
865
|
+
programId,
|
|
866
|
+
name: deploymentName,
|
|
867
|
+
type: opts.contractType,
|
|
868
|
+
size: validation.size,
|
|
869
|
+
deployer: walletAddress,
|
|
870
|
+
signature: result.signature || result.txid,
|
|
871
|
+
slot: result.slot || slot,
|
|
872
|
+
deployTimeMs: deployTime,
|
|
873
|
+
rpc: opts.rpc,
|
|
874
|
+
}, null, 2));
|
|
875
|
+
} else {
|
|
876
|
+
console.log(`\n ${indicators.success} ${C.green}${C.bright}Contract deployed successfully!${C.reset}\n`);
|
|
877
|
+
console.log(` ${C.bright}Program ID:${C.reset} ${C.cyan}${programId}${C.reset}`);
|
|
878
|
+
console.log(` ${C.bright}Name:${C.reset} ${deploymentName}`);
|
|
879
|
+
console.log(` ${C.bright}Type:${C.reset} ${opts.contractType}`);
|
|
880
|
+
console.log(` ${C.bright}Size:${C.reset} ${validation.formattedSize}`);
|
|
881
|
+
console.log(` ${C.bright}Deploy Time:${C.reset} ${formatDuration(deployTime)}`);
|
|
882
|
+
|
|
883
|
+
if (result.signature || result.txid) {
|
|
884
|
+
console.log(` ${C.bright}Signature:${C.reset} ${shortAddress(result.signature || result.txid)}`);
|
|
885
|
+
}
|
|
886
|
+
console.log(` ${C.bright}Slot:${C.reset} ${result.slot || slot}`);
|
|
887
|
+
console.log();
|
|
888
|
+
console.log(` ${C.dim}Verify: aether deploy --verify ${programId}${C.reset}`);
|
|
889
|
+
console.log(` ${C.dim}Status: aether deploy --status ${programId}${C.reset}`);
|
|
890
|
+
console.log(` ${C.dim}Explorer: https://explorer.aether.network/program/${programId}${C.reset}\n`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
} catch (err) {
|
|
894
|
+
stopSpinner(false);
|
|
895
|
+
|
|
896
|
+
if (opts.json) {
|
|
897
|
+
console.log(JSON.stringify({
|
|
898
|
+
success: false,
|
|
899
|
+
stage: 'deployment',
|
|
900
|
+
error: err.message,
|
|
901
|
+
}, null, 2));
|
|
902
|
+
} else {
|
|
903
|
+
console.log(`\n ${indicators.error} ${error(`Deployment failed: ${err.message}`)}\n`);
|
|
904
|
+
console.log(` ${C.dim}Common causes:${C.reset}`);
|
|
905
|
+
console.log(` • Contract bytecode is invalid or corrupted`);
|
|
906
|
+
console.log(` • Insufficient balance for deployment fee`);
|
|
907
|
+
console.log(` • RPC node rejected the transaction`);
|
|
908
|
+
console.log(` • Network congestion - retry with higher fee\n`);
|
|
909
|
+
}
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// ============================================================================
|
|
915
|
+
// Main Entry Point
|
|
916
|
+
// ============================================================================
|
|
917
|
+
|
|
918
|
+
async function deployCommand() {
|
|
919
|
+
const opts = parseArgs();
|
|
920
|
+
|
|
921
|
+
if (opts.help) {
|
|
922
|
+
showHelp();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (opts.listTemplates) {
|
|
927
|
+
listTemplates(opts.json);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (opts.verify) {
|
|
932
|
+
await verifyContract(opts);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (opts.status) {
|
|
937
|
+
await checkDeploymentStatus(opts);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (!opts.filePath) {
|
|
942
|
+
console.log(BRANDING.header(CLI_VERSION));
|
|
943
|
+
console.log(`\n ${indicators.error} ${error('No contract file specified')}\n`);
|
|
944
|
+
console.log(` ${C.dim}Usage: aether deploy <contract.wasm> [options]${C.reset}`);
|
|
945
|
+
console.log(` ${C.dim} aether deploy --help for more info${C.reset}\n`);
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
await deployContract(opts);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
module.exports = { deployCommand };
|
|
953
|
+
|
|
954
|
+
if (require.main === module) {
|
|
955
|
+
deployCommand().catch(err => {
|
|
956
|
+
console.error(`\n${C.red}✗ Deploy command failed: ${err.message}${C.reset}\n`);
|
|
957
|
+
process.exit(1);
|
|
958
|
+
});
|
|
959
|
+
}
|