@0xobelisk/sui-cli 1.1.13 → 1.2.0-pre.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.
@@ -1,19 +1,22 @@
1
1
  import { Dubhe, Transaction } from '@0xobelisk/sui-client';
2
2
  import { execSync } from 'child_process';
3
3
  import chalk from 'chalk';
4
- import { DubheCliError } from './errors';
5
4
  import {
6
5
  saveContractData,
7
- validatePrivateKey,
8
6
  updateDubheDependency,
9
7
  switchEnv,
10
8
  delay,
11
- getSchemaId
9
+ getDubheSchemaId,
10
+ initializeDubhe
12
11
  } from './utils';
13
12
  import { DubheConfig } from '@0xobelisk/sui-common';
14
13
  import * as fs from 'fs';
15
14
  import * as path from 'path';
16
15
 
16
+ const MAX_RETRIES = 60; // 60s timeout
17
+ const RETRY_INTERVAL = 1000; // 1s retry interval
18
+ const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
19
+
17
20
  async function removeEnvContent(
18
21
  filePath: string,
19
22
  networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
@@ -143,6 +146,142 @@ function buildContract(projectPath: string): string[][] {
143
146
  return [modules, dependencies];
144
147
  }
145
148
 
149
+ interface ObjectChange {
150
+ type: string;
151
+ objectType?: string;
152
+ packageId?: string;
153
+ objectId?: string;
154
+ }
155
+
156
+ async function waitForNode(dubhe: Dubhe): Promise<string> {
157
+ let retryCount = 0;
158
+ let spinnerIndex = 0;
159
+ const startTime = Date.now();
160
+ let isInterrupted = false;
161
+ let chainId = '';
162
+ let hasShownBalanceWarning = false;
163
+
164
+ const handleInterrupt = () => {
165
+ isInterrupted = true;
166
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
167
+ console.log('\n └─ Operation cancelled by user');
168
+ process.exit(0);
169
+ };
170
+ process.on('SIGINT', handleInterrupt);
171
+
172
+ try {
173
+ // 第一阶段:等待获取 chainId
174
+ while (retryCount < MAX_RETRIES && !isInterrupted && !chainId) {
175
+ try {
176
+ chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
177
+ } catch (error) {
178
+ // 忽略错误,继续重试
179
+ }
180
+
181
+ if (isInterrupted) break;
182
+
183
+ if (!chainId) {
184
+ retryCount++;
185
+ if (retryCount === MAX_RETRIES) {
186
+ console.log(chalk.red(` └─ Failed to connect to node after ${MAX_RETRIES} attempts`));
187
+ console.log(chalk.red(' └─ Please check if the Sui node is running.'));
188
+ process.exit(1);
189
+ }
190
+
191
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
192
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
193
+ spinnerIndex++;
194
+
195
+ process.stdout.write(`\r ├─ ${spinner} Waiting for node... (${elapsedTime}s)`);
196
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
197
+ }
198
+ }
199
+
200
+ // 显示 chainId
201
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
202
+ console.log(` ├─ ChainId: ${chainId}`);
203
+
204
+ // 第二阶段:检查部署账户余额
205
+ retryCount = 0;
206
+ while (retryCount < MAX_RETRIES && !isInterrupted) {
207
+ try {
208
+ const address = dubhe.getAddress();
209
+ const coins = await dubhe.suiInteractor.currentClient.getCoins({
210
+ owner: address,
211
+ coinType: '0x2::sui::SUI'
212
+ });
213
+
214
+ if (coins.data.length > 0) {
215
+ const balance = coins.data.reduce((sum, coin) => sum + Number(coin.balance), 0);
216
+ if (balance > 0) {
217
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
218
+ console.log(` ├─ Deployer balance: ${balance / 10 ** 9} SUI`);
219
+ return chainId;
220
+ } else if (!hasShownBalanceWarning) {
221
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
222
+ console.log(
223
+ chalk.yellow(
224
+ ` ├─ Deployer balance: 0 SUI, please ensure your account has sufficient SUI balance`
225
+ )
226
+ );
227
+ hasShownBalanceWarning = true;
228
+ }
229
+ } else if (!hasShownBalanceWarning) {
230
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
231
+ console.log(
232
+ chalk.yellow(
233
+ ` ├─ No SUI coins found in deployer account, please ensure your account has sufficient SUI balance`
234
+ )
235
+ );
236
+ hasShownBalanceWarning = true;
237
+ }
238
+
239
+ retryCount++;
240
+ if (retryCount === MAX_RETRIES) {
241
+ console.log(
242
+ chalk.red(` └─ Deployer account has no SUI balance after ${MAX_RETRIES} attempts`)
243
+ );
244
+ console.log(chalk.red(' └─ Please ensure your account has sufficient SUI balance.'));
245
+ process.exit(1);
246
+ }
247
+
248
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
249
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
250
+ spinnerIndex++;
251
+
252
+ process.stdout.write(`\r ├─ ${spinner} Checking deployer balance... (${elapsedTime}s)`);
253
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
254
+ } catch (error) {
255
+ if (isInterrupted) break;
256
+
257
+ retryCount++;
258
+ if (retryCount === MAX_RETRIES) {
259
+ console.log(
260
+ chalk.red(` └─ Failed to check deployer balance after ${MAX_RETRIES} attempts`)
261
+ );
262
+ console.log(chalk.red(' └─ Please check your account and network connection.'));
263
+ process.exit(1);
264
+ }
265
+
266
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
267
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
268
+ spinnerIndex++;
269
+
270
+ process.stdout.write(`\r ├─ ${spinner} Checking deployer balance... (${elapsedTime}s)`);
271
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
272
+ }
273
+ }
274
+ } finally {
275
+ process.removeListener('SIGINT', handleInterrupt);
276
+ }
277
+
278
+ if (isInterrupted) {
279
+ process.exit(0);
280
+ }
281
+
282
+ throw new Error('Failed to connect to node');
283
+ }
284
+
146
285
  async function publishContract(
147
286
  dubhe: Dubhe,
148
287
  dubheConfig: DubheConfig,
@@ -150,14 +289,15 @@ async function publishContract(
150
289
  projectPath: string,
151
290
  gasBudget?: number
152
291
  ) {
153
- const chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
154
- await removeEnvContent(`${projectPath}/Move.lock`, network);
155
292
  console.log('\n🚀 Starting Contract Publication...');
156
293
  console.log(` ├─ Project: ${projectPath}`);
157
294
  console.log(` ├─ Network: ${network}`);
158
- console.log(` ├─ ChainId: ${chainId}`);
295
+ console.log(' ├─ Waiting for node...');
296
+
297
+ const chainId = await waitForNode(dubhe);
159
298
  console.log(' ├─ Validating Environment...');
160
299
 
300
+ await removeEnvContent(`${projectPath}/Move.lock`, network);
161
301
  console.log(` └─ Account: ${dubhe.getAddress()}`);
162
302
 
163
303
  console.log('\n📦 Building Contract...');
@@ -171,16 +311,53 @@ async function publishContract(
171
311
  const [upgradeCap] = tx.publish({ modules, dependencies });
172
312
  tx.transferObjects([upgradeCap], dubhe.getAddress());
173
313
 
174
- let result;
314
+ let result: any = null;
315
+ let retryCount = 0;
316
+ let spinnerIndex = 0;
317
+ const startTime = Date.now();
318
+ let isInterrupted = false;
319
+
320
+ const handleInterrupt = () => {
321
+ isInterrupted = true;
322
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
323
+ console.log('\n └─ Operation cancelled by user');
324
+ process.exit(0);
325
+ };
326
+ process.on('SIGINT', handleInterrupt);
327
+
175
328
  try {
176
- result = await dubhe.signAndSendTxn({ tx });
177
- } catch (error: any) {
178
- console.error(chalk.red(' └─ Publication failed'));
179
- console.error(error.message);
180
- process.exit(1);
329
+ while (retryCount < MAX_RETRIES && !result && !isInterrupted) {
330
+ try {
331
+ result = await dubhe.signAndSendTxn({ tx });
332
+ } catch (error) {
333
+ if (isInterrupted) break;
334
+
335
+ retryCount++;
336
+ if (retryCount === MAX_RETRIES) {
337
+ console.log(chalk.red(` └─ Publication failed after ${MAX_RETRIES} attempts`));
338
+ console.log(chalk.red(' └─ Please check your network connection and try again later.'));
339
+ process.exit(1);
340
+ }
341
+
342
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
343
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
344
+ spinnerIndex++;
345
+
346
+ process.stdout.write(`\r ├─ ${spinner} Retrying... (${elapsedTime}s)`);
347
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
348
+ }
349
+ }
350
+ } finally {
351
+ process.removeListener('SIGINT', handleInterrupt);
181
352
  }
182
353
 
183
- if (result.effects?.status.status === 'failure') {
354
+ if (isInterrupted) {
355
+ process.exit(0);
356
+ }
357
+
358
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
359
+
360
+ if (!result || result.effects?.status.status === 'failure') {
184
361
  console.log(chalk.red(' └─ Publication failed'));
185
362
  process.exit(1);
186
363
  }
@@ -192,14 +369,18 @@ async function publishContract(
192
369
  let schemas = dubheConfig.schemas;
193
370
  let upgradeCapId = '';
194
371
 
195
- result.objectChanges!.map((object) => {
372
+ result.objectChanges!.map((object: ObjectChange) => {
196
373
  if (object.type === 'published') {
197
374
  console.log(` ├─ Package ID: ${object.packageId}`);
198
- packageId = object.packageId;
375
+ packageId = object.packageId || '';
199
376
  }
200
- if (object.type === 'created' && object.objectType === '0x2::package::UpgradeCap') {
377
+ if (
378
+ object.type === 'created' &&
379
+ object.objectType &&
380
+ object.objectType === '0x2::package::UpgradeCap'
381
+ ) {
201
382
  console.log(` ├─ Upgrade Cap: ${object.objectId}`);
202
- upgradeCapId = object.objectId;
383
+ upgradeCapId = object.objectId || '';
203
384
  }
204
385
  });
205
386
 
@@ -213,8 +394,8 @@ async function publishContract(
213
394
  const deployHookTx = new Transaction();
214
395
  let args = [];
215
396
  if (dubheConfig.name !== 'dubhe') {
216
- let dubheSchemaId = network === 'localnet' ? await getSchemaId(`${process.cwd()}/contracts/dubhe-framework`, network) : "0xa565cbb3641fff8f7e8ef384b215808db5f1837aa72c1cca1803b5d973699aac";
217
- args.push(deployHookTx.object(dubheSchemaId));
397
+ let dubheSchemaId = await getDubheSchemaId(network);
398
+ args.push(deployHookTx.object(dubheSchemaId));
218
399
  }
219
400
  args.push(deployHookTx.object('0x6'));
220
401
  deployHookTx.moveCall({
@@ -236,12 +417,17 @@ async function publishContract(
236
417
  console.log(` ├─ Transaction: ${deployHookResult.digest}`);
237
418
 
238
419
  console.log('\n📋 Created Schemas:');
239
- deployHookResult.objectChanges?.map((object) => {
240
- if (object.type === 'created' && object.objectType.includes('schema::Schema')) {
241
- schemaId = object.objectId;
420
+ deployHookResult.objectChanges?.map((object: ObjectChange) => {
421
+ if (
422
+ object.type === 'created' &&
423
+ object.objectType &&
424
+ object.objectType.includes('schema::Schema')
425
+ ) {
426
+ schemaId = object.objectId || '';
242
427
  }
243
428
  if (
244
429
  object.type === 'created' &&
430
+ object.objectType &&
245
431
  object.objectType.includes('schema') &&
246
432
  !object.objectType.includes('dynamic_field')
247
433
  ) {
@@ -300,24 +486,64 @@ export async function publishDubheFramework(
300
486
  return;
301
487
  }
302
488
 
303
- // const chainId = await client.getChainIdentifier();
304
- const chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
489
+ console.log('\n🚀 Starting Dubhe Framework Publication...');
490
+ console.log(' ├─ Waiting for node...');
491
+
492
+ const chainId = await waitForNode(dubhe);
493
+
305
494
  await removeEnvContent(`${projectPath}/Move.lock`, network);
306
495
  const [modules, dependencies] = buildContract(projectPath);
307
496
  const tx = new Transaction();
308
497
  const [upgradeCap] = tx.publish({ modules, dependencies });
309
498
  tx.transferObjects([upgradeCap], dubhe.getAddress());
310
499
 
311
- let result;
500
+ let result: any = null;
501
+ let retryCount = 0;
502
+ let spinnerIndex = 0;
503
+ const startTime = Date.now();
504
+ let isInterrupted = false;
505
+
506
+ const handleInterrupt = () => {
507
+ isInterrupted = true;
508
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
509
+ console.log('\n └─ Operation cancelled by user');
510
+ process.exit(0);
511
+ };
512
+ process.on('SIGINT', handleInterrupt);
513
+
312
514
  try {
313
- result = await dubhe.signAndSendTxn({ tx });
314
- } catch (error: any) {
315
- console.error(chalk.red(' └─ Publication failed'));
316
- console.error(error.message);
317
- process.exit(1);
515
+ while (retryCount < MAX_RETRIES && !result && !isInterrupted) {
516
+ try {
517
+ result = await dubhe.signAndSendTxn({ tx });
518
+ } catch (error) {
519
+ if (isInterrupted) break;
520
+
521
+ retryCount++;
522
+ if (retryCount === MAX_RETRIES) {
523
+ console.log(chalk.red(` └─ Publication failed after ${MAX_RETRIES} attempts`));
524
+ console.log(chalk.red(' └─ Please check your network connection and try again later.'));
525
+ process.exit(1);
526
+ }
527
+
528
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
529
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
530
+ spinnerIndex++;
531
+
532
+ process.stdout.write(`\r ├─ ${spinner} Retrying... (${elapsedTime}s)`);
533
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
534
+ }
535
+ }
536
+ } finally {
537
+ process.removeListener('SIGINT', handleInterrupt);
318
538
  }
319
539
 
320
- if (result.effects?.status.status === 'failure') {
540
+ if (isInterrupted) {
541
+ process.exit(0);
542
+ }
543
+
544
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
545
+
546
+ if (!result || result.effects?.status.status === 'failure') {
321
547
  console.log(chalk.red(' └─ Publication failed'));
322
548
  process.exit(1);
323
549
  }
@@ -328,12 +554,16 @@ export async function publishDubheFramework(
328
554
  let schemas: Record<string, string> = {};
329
555
  let upgradeCapId = '';
330
556
 
331
- result.objectChanges!.map((object) => {
557
+ result.objectChanges!.map((object: ObjectChange) => {
332
558
  if (object.type === 'published') {
333
- packageId = object.packageId;
559
+ packageId = object.packageId || '';
334
560
  }
335
- if (object.type === 'created' && object.objectType === '0x2::package::UpgradeCap') {
336
- upgradeCapId = object.objectId;
561
+ if (
562
+ object.type === 'created' &&
563
+ object.objectType &&
564
+ object.objectType === '0x2::package::UpgradeCap'
565
+ ) {
566
+ upgradeCapId = object.objectId || '';
337
567
  }
338
568
  });
339
569
 
@@ -355,22 +585,18 @@ export async function publishDubheFramework(
355
585
  }
356
586
 
357
587
  if (deployHookResult.effects?.status.status === 'success') {
358
- deployHookResult.objectChanges?.map((object) => {
359
- if (object.type === 'created' && object.objectType.includes('dubhe_schema::Schema')) {
360
- schemaId = object.objectId;
588
+ deployHookResult.objectChanges?.map((object: ObjectChange) => {
589
+ if (
590
+ object.type === 'created' &&
591
+ object.objectType &&
592
+ object.objectType.includes('dubhe_schema::Schema')
593
+ ) {
594
+ schemaId = object.objectId || '';
361
595
  }
362
596
  });
363
597
  }
364
598
 
365
- saveContractData(
366
- 'dubhe-framework',
367
- network,
368
- packageId,
369
- schemaId,
370
- upgradeCapId,
371
- version,
372
- schemas
373
- );
599
+ saveContractData('dubhe-framework', network, packageId, schemaId, upgradeCapId, version, schemas);
374
600
 
375
601
  updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
376
602
  await delay(1000);
@@ -383,22 +609,8 @@ export async function publishHandler(
383
609
  ) {
384
610
  await switchEnv(network);
385
611
 
386
- const privateKey = process.env.PRIVATE_KEY;
387
- if (!privateKey) {
388
- throw new DubheCliError(
389
- `Missing PRIVATE_KEY environment variable.
390
- Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
391
- in your contracts directory to use the default sui private key.`
392
- );
393
- }
394
- const privateKeyFormat = validatePrivateKey(privateKey);
395
- if (privateKeyFormat === false) {
396
- throw new DubheCliError(`Please check your privateKey.`);
397
- }
398
-
399
- const dubhe = new Dubhe({
400
- secretKey: privateKeyFormat,
401
- networkType: network
612
+ const dubhe = initializeDubhe({
613
+ network
402
614
  });
403
615
 
404
616
  if (network === 'localnet') {
@@ -1,6 +1,6 @@
1
- import { Dubhe, loadMetadata } from '@0xobelisk/sui-client';
1
+ import { loadMetadata } from '@0xobelisk/sui-client';
2
2
  import { DubheCliError } from './errors';
3
- import { validatePrivateKey, getOldPackageId, getSchemaId } from './utils';
3
+ import { getOldPackageId, getSchemaId, initializeDubhe } from './utils';
4
4
  import { DubheConfig } from '@0xobelisk/sui-common';
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
@@ -50,19 +50,6 @@ export async function queryStorage({
50
50
  packageId?: string;
51
51
  metadataFilePath?: string;
52
52
  }) {
53
- const privateKey = process.env.PRIVATE_KEY;
54
- if (!privateKey) {
55
- throw new DubheCliError(
56
- `Missing PRIVATE_KEY environment variable.
57
- Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
58
- in your contracts directory to use the default sui private key.`
59
- );
60
- }
61
- const privateKeyFormat = validatePrivateKey(privateKey);
62
- if (privateKeyFormat === false) {
63
- throw new DubheCliError(`Please check your privateKey.`);
64
- }
65
-
66
53
  const path = process.cwd();
67
54
  const projectPath = `${path}/contracts/${dubheConfig.name}`;
68
55
 
@@ -101,9 +88,8 @@ in your contracts directory to use the default sui private key.`
101
88
  );
102
89
  }
103
90
 
104
- const dubhe = new Dubhe({
105
- secretKey: privateKeyFormat,
106
- networkType: network,
91
+ const dubhe = initializeDubhe({
92
+ network,
107
93
  packageId,
108
94
  metadata
109
95
  });