@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xobelisk/sui-cli",
3
- "version": "1.1.13",
3
+ "version": "1.2.0-pre.1",
4
4
  "description": "Tookit for interacting with move eps framework",
5
5
  "keywords": [
6
6
  "sui",
@@ -33,6 +33,7 @@
33
33
  "dependencies": {
34
34
  "@mysten/sui": "^1.19.0",
35
35
  "@types/sqlite3": "^3.1.11",
36
+ "@types/wait-on": "^5.3.4",
36
37
  "chalk": "^5.0.1",
37
38
  "child_process": "^1.0.2",
38
39
  "chokidar": "^3.5.3",
@@ -40,28 +41,30 @@
40
41
  "ejs": "^3.1.8",
41
42
  "execa": "^7.0.0",
42
43
  "glob": "^8.0.3",
44
+ "ora": "^5.4.1",
43
45
  "path": "^0.12.7",
44
46
  "sqlite": "^5.1.1",
45
47
  "sqlite3": "^5.1.7",
46
48
  "typescript": "5.1.6",
49
+ "wait-on": "^7.0.1",
47
50
  "yargs": "^17.7.1",
48
51
  "zod": "^3.22.3",
49
52
  "zod-validation-error": "^1.3.0",
50
- "@0xobelisk/sui-client": "1.1.12",
51
- "@0xobelisk/sui-common": "1.1.13"
53
+ "@0xobelisk/sui-client": "1.2.0-pre.1",
54
+ "@0xobelisk/sui-common": "1.2.0-pre.1"
52
55
  },
53
56
  "devDependencies": {
54
57
  "@types/ejs": "^3.1.1",
55
58
  "@types/glob": "^7.2.0",
56
59
  "@types/node": "^18.15.11",
57
60
  "@types/yargs": "^17.0.10",
61
+ "eslint": "^8.56.0",
62
+ "eslint-config-prettier": "^9.1.0",
63
+ "prettier": "3.3.3",
58
64
  "ts-node": "^10.9.1",
59
65
  "tsup": "^6.7.0",
60
66
  "tsx": "^3.12.6",
61
- "vitest": "0.31.4",
62
- "eslint": "^8.56.0",
63
- "eslint-config-prettier": "^9.1.0",
64
- "prettier": "3.3.3"
67
+ "vitest": "0.31.4"
65
68
  },
66
69
  "scripts": {
67
70
  "build": "pnpm run type-check && pnpm run build:js",
@@ -1,14 +1,17 @@
1
- import { Dubhe } from '@0xobelisk/sui-client';
2
1
  import type { CommandModule } from 'yargs';
3
2
  import { requestSuiFromFaucetV0, getFaucetHost } from '@mysten/sui/faucet';
4
3
  import { SuiClient, getFullnodeUrl, GetBalanceParams } from '@mysten/sui/client';
5
- import { validatePrivateKey, DubheCliError } from '../utils';
4
+ import { initializeDubhe } from '../utils';
6
5
 
7
6
  type Options = {
8
7
  network: any;
9
8
  recipient?: string;
10
9
  };
11
10
 
11
+ const MAX_RETRIES = 60; // 60s timeout
12
+ const RETRY_INTERVAL = 1000; // 1s retry interval
13
+ const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
14
+
12
15
  const commandModule: CommandModule<Options, Options> = {
13
16
  command: 'faucet',
14
17
 
@@ -32,21 +35,7 @@ const commandModule: CommandModule<Options, Options> = {
32
35
  async handler({ network, recipient }) {
33
36
  let faucet_address = '';
34
37
  if (recipient === undefined) {
35
- const privateKey = process.env.PRIVATE_KEY;
36
- if (!privateKey)
37
- throw new DubheCliError(
38
- `Missing PRIVATE_KEY environment variable.
39
- Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
40
- in your contracts directory to use the default sui private key.`
41
- );
42
-
43
- const privateKeyFormat = validatePrivateKey(privateKey);
44
- if (privateKeyFormat === false) {
45
- throw new DubheCliError(`Please check your PRIVATE_KEY.`);
46
- }
47
- const dubhe = new Dubhe({
48
- secretKey: privateKeyFormat
49
- });
38
+ const dubhe = initializeDubhe(network);
50
39
  faucet_address = dubhe.getAddress();
51
40
  } else {
52
41
  faucet_address = recipient;
@@ -63,10 +52,55 @@ const commandModule: CommandModule<Options, Options> = {
63
52
  }
64
53
 
65
54
  console.log(' ├─ Requesting funds from faucet...');
66
- await requestSuiFromFaucetV0({
67
- host: getFaucetHost(network),
68
- recipient: faucet_address
69
- });
55
+
56
+ let retryCount = 0;
57
+ let success = false;
58
+ let spinnerIndex = 0;
59
+ const startTime = Date.now();
60
+ let isInterrupted = false;
61
+
62
+ const handleInterrupt = () => {
63
+ isInterrupted = true;
64
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
65
+ console.log('\n └─ Operation cancelled by user');
66
+ process.exit(0);
67
+ };
68
+ process.on('SIGINT', handleInterrupt);
69
+
70
+ try {
71
+ while (retryCount < MAX_RETRIES && !success && !isInterrupted) {
72
+ try {
73
+ await requestSuiFromFaucetV0({
74
+ host: getFaucetHost(network),
75
+ recipient: faucet_address
76
+ });
77
+ success = true;
78
+ } catch (error) {
79
+ if (isInterrupted) break;
80
+
81
+ retryCount++;
82
+ if (retryCount === MAX_RETRIES) {
83
+ console.log(` └─ Failed to request funds after ${MAX_RETRIES} attempts.`);
84
+ console.log(' └─ Please check your network connection and try again later.');
85
+ process.exit(1);
86
+ }
87
+
88
+ const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
89
+ const spinner = SPINNER[spinnerIndex % SPINNER.length];
90
+ spinnerIndex++;
91
+
92
+ process.stdout.write(`\r ├─ ${spinner} Retrying... (${elapsedTime}s)`);
93
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
94
+ }
95
+ }
96
+ } finally {
97
+ process.removeListener('SIGINT', handleInterrupt);
98
+ }
99
+
100
+ if (isInterrupted) {
101
+ process.exit(0);
102
+ }
103
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
70
104
 
71
105
  console.log(' └─ Checking balance...');
72
106
  const client = new SuiClient({ url: getFullnodeUrl(network) });
@@ -3,27 +3,27 @@ import { generateAccountHandler } from '../utils/generateAccount';
3
3
 
4
4
  type Options = {
5
5
  force?: boolean;
6
- 'output-ts-path'?: string;
6
+ 'use-next-public'?: boolean;
7
7
  };
8
8
 
9
9
  const commandModule: CommandModule<Options, Options> = {
10
10
  command: 'generate-key',
11
- describe:
12
- 'Generate a new account key pair and save it to a .env file, with an option to output to a TypeScript file.',
11
+ describe: 'Generate a new account keypair and save it to a .env file',
13
12
  builder: {
14
13
  force: {
15
14
  type: 'boolean',
16
15
  default: false,
17
- desc: 'Force generate a new key pair'
16
+ desc: 'Force generate a new keypair'
18
17
  },
19
- 'output-ts-path': {
20
- type: 'string',
21
- desc: 'Specify the path to output the TypeScript file containing the key pair (e.g., ./src/config/key.ts)'
18
+ 'use-next-public': {
19
+ type: 'boolean',
20
+ default: false,
21
+ desc: 'Use the NEXT_PUBLIC_ prefix for client-side usage'
22
22
  }
23
23
  },
24
- async handler({ force, 'output-ts-path': outputTsPath }) {
24
+ async handler({ force, 'use-next-public': useNextPublic }) {
25
25
  try {
26
- await generateAccountHandler(force, outputTsPath);
26
+ await generateAccountHandler(force, useNextPublic);
27
27
  } catch (error) {
28
28
  console.error('Error generating account:', error);
29
29
  process.exit(1);
@@ -13,8 +13,8 @@ import checkBalance from './checkBalance';
13
13
  import configStore from './configStore';
14
14
  import query from './query';
15
15
  import call from './call';
16
- import indexer from './indexer';
17
16
  import watch from './watch';
17
+ import wait from './wait';
18
18
 
19
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Each command has different options
20
20
  export const commands: CommandModule<any, any>[] = [
@@ -31,6 +31,6 @@ export const commands: CommandModule<any, any>[] = [
31
31
  generateKey,
32
32
  checkBalance,
33
33
  configStore,
34
- indexer,
35
- watch
34
+ watch,
35
+ wait
36
36
  ];
@@ -7,12 +7,18 @@ const commandModule: CommandModule = {
7
7
  describe: 'Manage local Sui node',
8
8
 
9
9
  builder(yargs) {
10
- return yargs;
10
+ return yargs
11
+ .option('force-regenesis', {
12
+ alias: 'f',
13
+ type: 'boolean',
14
+ description: 'Force regenesis the local node',
15
+ default: true
16
+ });
11
17
  },
12
18
 
13
- async handler() {
19
+ async handler(argv) {
14
20
  try {
15
- await startLocalNode();
21
+ await startLocalNode({ forceRegenesis: argv['force-regenesis'] as boolean });
16
22
  } catch (error) {
17
23
  console.error('Error executing command:', error);
18
24
  process.exit(1);
@@ -0,0 +1,59 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import waitOn from 'wait-on';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+
6
+ interface WaitOptions {
7
+ url: string;
8
+ timeout: number;
9
+ interval: number;
10
+ }
11
+
12
+ const commandModule: CommandModule = {
13
+ command: 'wait',
14
+ describe: 'Wait for service to be ready',
15
+ builder(yargs) {
16
+ return yargs
17
+ .option('url', {
18
+ type: 'string',
19
+ description: 'URL to wait for'
20
+ })
21
+ .option('timeout', {
22
+ type: 'number',
23
+ description: 'Timeout (in milliseconds)',
24
+ default: 180000
25
+ })
26
+ .option('interval', {
27
+ type: 'number',
28
+ description: 'Check interval (in milliseconds)',
29
+ default: 1000
30
+ });
31
+ },
32
+ async handler(argv) {
33
+ const options = argv as unknown as WaitOptions;
34
+ const spinner = ora({
35
+ text: `Waiting for service to start ${chalk.cyan(options.url)}...`,
36
+ color: 'cyan'
37
+ });
38
+
39
+ spinner.start();
40
+
41
+ try {
42
+ await waitOn({
43
+ resources: [options.url],
44
+ timeout: options.timeout,
45
+ interval: options.interval,
46
+ validateStatus: (status: number) => status === 200
47
+ });
48
+
49
+ spinner.succeed(chalk.green('Service is ready!'));
50
+ process.exit(0);
51
+ } catch (error) {
52
+ spinner.fail(chalk.red('Timeout waiting for service'));
53
+ console.error(chalk.yellow('Please make sure the service is running...'));
54
+ process.exit(1);
55
+ }
56
+ }
57
+ };
58
+
59
+ export default commandModule;
@@ -1,6 +1,6 @@
1
- import { Dubhe, loadMetadata, Transaction, TransactionResult } from '@0xobelisk/sui-client';
1
+ import { loadMetadata, Transaction, TransactionResult } from '@0xobelisk/sui-client';
2
2
  import { DubheCliError } from './errors';
3
- import { validatePrivateKey, getOldPackageId } from './utils';
3
+ import { getOldPackageId, initializeDubhe } from './utils';
4
4
  import { DubheConfig } from '@0xobelisk/sui-common';
5
5
  import { loadMetadataFromFile } from './queryStorage';
6
6
 
@@ -91,19 +91,6 @@ export async function callHandler({
91
91
  packageId?: string;
92
92
  metadataFilePath?: string;
93
93
  }) {
94
- const privateKey = process.env.PRIVATE_KEY;
95
- if (!privateKey) {
96
- throw new DubheCliError(
97
- `Missing PRIVATE_KEY environment variable.
98
- Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
99
- in your contracts directory to use the default sui private key.`
100
- );
101
- }
102
- const privateKeyFormat = validatePrivateKey(privateKey);
103
- if (privateKeyFormat === false) {
104
- throw new DubheCliError(`Please check your privateKey.`);
105
- }
106
-
107
94
  const path = process.cwd();
108
95
  const projectPath = `${path}/contracts/${dubheConfig.name}`;
109
96
 
@@ -123,29 +110,10 @@ in your contracts directory to use the default sui private key.`
123
110
  );
124
111
  }
125
112
 
126
- // if (!dubheConfig.schemas[schema]) {
127
- // throw new DubheCliError(
128
- // `Schema "${schema}" not found in dubhe config. Available schemas: ${Object.keys(
129
- // dubheConfig.schemas
130
- // ).join(', ')}`
131
- // );
132
- // }
133
-
134
- // if (!dubheConfig.schemas[schema].structure[struct]) {
135
- // throw new DubheCliError(
136
- // `Struct "${struct}" not found in schema "${schema}". Available structs: ${Object.keys(
137
- // dubheConfig.schemas[schema].structure
138
- // ).join(', ')}`
139
- // );
140
- // }
141
-
142
- // const storageType = dubheConfig.schemas[schema].structure[struct];
143
-
144
113
  const processedParams = params || [];
145
114
  validateParams(processedParams);
146
- const dubhe = new Dubhe({
147
- secretKey: privateKeyFormat,
148
- networkType: network,
115
+ const dubhe = initializeDubhe({
116
+ network,
149
117
  packageId,
150
118
  metadata
151
119
  });
@@ -1,28 +1,13 @@
1
- import { Dubhe, NetworkType } from '@0xobelisk/sui-client';
2
1
  import chalk from 'chalk';
3
2
  import dotenv from 'dotenv';
4
- import { validatePrivateKey } from './utils';
3
+ import { initializeDubhe } from './utils';
5
4
  import { DubheCliError } from './errors';
6
5
  dotenv.config();
7
6
 
8
7
  export async function checkBalanceHandler(network: 'mainnet' | 'testnet' | 'devnet' | 'localnet') {
9
8
  try {
10
- const privateKey = process.env.PRIVATE_KEY;
11
- if (!privateKey) {
12
- throw new DubheCliError(
13
- `Missing PRIVATE_KEY environment variable.
14
- Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
15
- in your contracts directory to use the default sui private key.`
16
- );
17
- }
18
- const privateKeyFormat = validatePrivateKey(privateKey);
19
- if (privateKeyFormat === false) {
20
- throw new DubheCliError(`Please check your privateKey.`);
21
- }
22
-
23
- const dubhe = new Dubhe({
24
- secretKey: process.env.PRIVATE_KEY,
25
- networkType: network as NetworkType
9
+ const dubhe = initializeDubhe({
10
+ network
26
11
  });
27
12
 
28
13
  const balance = await dubhe.getBalance();
@@ -2,96 +2,100 @@ import { Dubhe } from '@0xobelisk/sui-client';
2
2
  import * as fs from 'fs';
3
3
  import chalk from 'chalk';
4
4
 
5
- export async function generateAccountHandler(force: boolean = false, outputTsPath?: string) {
6
- if (outputTsPath) {
5
+ export async function generateAccountHandler(
6
+ force: boolean = false,
7
+ useNextPublic: boolean = false
8
+ ) {
9
+ if (useNextPublic) {
7
10
  console.log(
8
- chalk.blue(
9
- 'Note: The generated account will be stored in the .env file and the TypeScript file specified by the --output-ts-path option.'
11
+ chalk.gray(
12
+ 'Note: The generated account will be stored in the .env file with NEXT_PUBLIC_ prefix for client-side usage.'
10
13
  )
11
14
  );
12
15
  console.log(
13
- chalk.yellow('Warning: Do not expose the key file. It is intended for local testing only.\n')
16
+ chalk.yellow('Warning: Do not expose the .env file, it is intended for local testing only.\n')
14
17
  );
15
18
  }
16
19
  const path = process.cwd();
17
- let privateKey: string;
20
+ let privateKey: string | undefined;
21
+ let envContent = '';
18
22
 
19
- if (force) {
20
- const dubhe = new Dubhe();
21
- const keypair = dubhe.getSigner();
22
- privateKey = keypair.getSecretKey();
23
-
24
- fs.writeFileSync(`${path}/.env`, `PRIVATE_KEY=${privateKey}`);
25
- console.log(chalk.green(`File created at: ${path}/.env`));
23
+ // Check if .env file exists
24
+ try {
25
+ envContent = fs.readFileSync(`${path}/.env`, 'utf8');
26
26
 
27
- if (outputTsPath) {
28
- const dir = outputTsPath.substring(0, outputTsPath.lastIndexOf('/'));
29
- if (!fs.existsSync(dir)) {
30
- fs.mkdirSync(dir, { recursive: true });
31
- }
32
- fs.writeFileSync(
33
- outputTsPath,
34
- `export const PRIVATEKEY = '${privateKey}';
35
- export const ACCOUNT = '${keypair.toSuiAddress()}';
36
- `
37
- );
38
- console.log(chalk.green(`File created at: ${outputTsPath}\n`));
27
+ // privateKey = process.env.PRIVATE_KEY || process.env.NEXT_PUBLIC_PRIVATE_KEY;
28
+ let privateKey = process.env.PRIVATE_KEY || process.env.NEXT_PUBLIC_PRIVATE_KEY;
29
+ if (useNextPublic) {
30
+ privateKey = process.env.NEXT_PUBLIC_PRIVATE_KEY || process.env.PRIVATE_KEY;
39
31
  }
40
32
 
41
- console.log(chalk.blue(`Force generate new Account: ${keypair.toSuiAddress()}`));
42
- return;
43
- }
33
+ if (privateKey) {
34
+ // If key exists, decide whether to update keyname based on useNextPublic
35
+ const newKeyName = useNextPublic ? 'NEXT_PUBLIC_PRIVATE_KEY' : 'PRIVATE_KEY';
44
36
 
45
- // Check if .env file exists and has content
46
- try {
47
- const envContent = fs.readFileSync(`${path}/.env`, 'utf8');
48
- const match = envContent.match(/PRIVATE_KEY=(.+)/);
49
- if (match && match[1]) {
50
- privateKey = match[1];
51
- const dubhe = new Dubhe({ secretKey: privateKey });
52
- const keypair = dubhe.getSigner();
37
+ // Find and update the last matching line based on privateKey value
38
+ const lines = envContent.split('\n');
39
+ let shouldUpdate = false;
53
40
 
54
- if (outputTsPath) {
55
- const dir = outputTsPath.substring(0, outputTsPath.lastIndexOf('/'));
56
- if (!fs.existsSync(dir)) {
57
- fs.mkdirSync(dir, { recursive: true });
41
+ // First check if the last matching line already has the correct keyname
42
+ for (let i = lines.length - 1; i >= 0; i--) {
43
+ const line = lines[i];
44
+ if (line.endsWith(privateKey)) {
45
+ // If useNextPublic is true, only update if the line starts with PRIVATE_KEY=
46
+ // If useNextPublic is false, only update if the line starts with NEXT_PUBLIC_PRIVATE_KEY=
47
+ const [currentKeyName] = line.split('=');
48
+ if (useNextPublic) {
49
+ shouldUpdate = currentKeyName === 'PRIVATE_KEY';
50
+ } else {
51
+ shouldUpdate = currentKeyName === 'NEXT_PUBLIC_PRIVATE_KEY';
52
+ }
53
+ break;
58
54
  }
59
- fs.writeFileSync(
60
- outputTsPath,
61
- `export const PRIVATEKEY = '${privateKey}';
62
- export const ACCOUNT = '${keypair.toSuiAddress()}';
63
- `
64
- );
65
- console.log(chalk.green(`File created at: ${outputTsPath}\n`));
66
55
  }
67
56
 
68
- console.log(chalk.blue(`Using existing Account: ${keypair.toSuiAddress()}`));
57
+ // Only update if necessary
58
+ if (shouldUpdate) {
59
+ for (let i = lines.length - 1; i >= 0; i--) {
60
+ const line = lines[i];
61
+ if (line.endsWith(privateKey)) {
62
+ const newLine = `${newKeyName}=${privateKey}`;
63
+ lines[i] = newLine;
64
+ envContent = lines.join('\n');
65
+ fs.writeFileSync(`${path}/.env`, envContent);
66
+ break;
67
+ }
68
+ }
69
+ }
70
+
71
+ const dubhe = new Dubhe({ secretKey: privateKey });
72
+ const keypair = dubhe.getSigner();
73
+ console.log(chalk.blue(`Using existing account: ${keypair.toSuiAddress()}`));
69
74
  return;
70
75
  }
71
76
  } catch (error) {
72
77
  // .env file doesn't exist or failed to read, continue to generate new account
73
78
  }
74
79
 
75
- // If no existing private key, generate new account
76
- const dubhe = new Dubhe();
77
- const keypair = dubhe.getSigner();
78
- privateKey = keypair.getSecretKey();
79
- fs.writeFileSync(`${path}/.env`, `PRIVATE_KEY=${privateKey}`);
80
- console.log(chalk.green(`File created at: ${path}/.env`));
80
+ // Generate a new account if no existing key is found or force generation is requested
81
+ if (force || !privateKey) {
82
+ const dubhe = new Dubhe();
83
+ const keypair = dubhe.getSigner();
84
+ privateKey = keypair.getSecretKey();
81
85
 
82
- if (outputTsPath) {
83
- const dir = outputTsPath.substring(0, outputTsPath.lastIndexOf('/'));
84
- if (!fs.existsSync(dir)) {
85
- fs.mkdirSync(dir, { recursive: true });
86
+ const newKeyName = useNextPublic ? 'NEXT_PUBLIC_PRIVATE_KEY' : 'PRIVATE_KEY';
87
+ const newContent = `${newKeyName}=${privateKey}`;
88
+
89
+ // If .env file exists, append new content; otherwise create a new file
90
+ if (envContent) {
91
+ envContent = envContent.trim() + '\n' + newContent;
92
+ } else {
93
+ envContent = newContent;
86
94
  }
87
- fs.writeFileSync(
88
- outputTsPath,
89
- `export const PRIVATEKEY = '${privateKey}';
90
- export const ACCOUNT = '${keypair.toSuiAddress()}';
91
- `
92
- );
93
- console.log(chalk.green(`File created at: ${outputTsPath}\n`));
94
- }
95
95
 
96
- console.log(chalk.blue(`Generate new Account: ${keypair.toSuiAddress()}`));
96
+ fs.writeFileSync(`${path}/.env`, envContent);
97
+ console.log(chalk.green(`File created/updated at: ${path}/.env`));
98
+
99
+ console.log(chalk.blue(`New account generated: ${keypair.toSuiAddress()}`));
100
+ }
97
101
  }
@@ -5,4 +5,3 @@ export * from './printDubhe';
5
5
  export * from './utils';
6
6
  export * from './queryStorage';
7
7
  export * from './callHandler';
8
- export * from './indexerHandler';