@0xobelisk/sui-cli 0.5.30 → 0.5.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ import { Dubhe, NetworkType } from '@0xobelisk/sui-client';
2
+ import chalk from 'chalk';
3
+ import dotenv from 'dotenv';
4
+ import { validatePrivateKey } from './utils';
5
+ import { DubheCliError } from './errors';
6
+ dotenv.config();
7
+
8
+ export async function checkBalanceHandler(
9
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
10
+ amount: number = 2
11
+ ) {
12
+ try {
13
+ console.log(
14
+ chalk.blue(
15
+ `Note: You need at least 2 SUI for transaction fees and staking, and registering a Dapp will reserve 1 SUI for Dubhe Dapp Staking, which can be retrieved upon unregistering.`
16
+ )
17
+ );
18
+
19
+ const privateKey = process.env.PRIVATE_KEY;
20
+ if (!privateKey) {
21
+ throw new DubheCliError(
22
+ `Missing PRIVATE_KEY environment variable.
23
+ Run 'echo "PRIVATE_KEY=YOUR_PRIVATE_KEY" > .env'
24
+ in your contracts directory to use the default sui private key.`
25
+ );
26
+ }
27
+ const privateKeyFormat = validatePrivateKey(privateKey);
28
+ if (privateKeyFormat === false) {
29
+ throw new DubheCliError(`Please check your privateKey.`);
30
+ }
31
+
32
+ const dubhe = new Dubhe({
33
+ secretKey: process.env.PRIVATE_KEY,
34
+ networkType: network as NetworkType,
35
+ });
36
+
37
+ const balance = await dubhe.getBalance();
38
+ const balanceInSUI = Number(balance.totalBalance) / 1_000_000_000;
39
+
40
+ if (balanceInSUI < amount) {
41
+ // console.log(chalk.yellow(`Account balance is less than 2 SUI.`));
42
+ throw new DubheCliError(
43
+ `Account balance is less than ${amount} SUI. Please get more SUI.`
44
+ );
45
+ }
46
+
47
+ console.log(
48
+ chalk.green(
49
+ `Current account balance: ${balanceInSUI.toFixed(4)} SUI`
50
+ )
51
+ );
52
+ } catch (error) {
53
+ throw new DubheCliError('Failed to check balance: ' + error);
54
+ }
55
+ }
@@ -1,46 +1,46 @@
1
- import chalk from "chalk";
2
- import { ZodError } from "zod";
3
- import { fromZodError, ValidationError } from "zod-validation-error";
1
+ import chalk from 'chalk';
2
+ import { ZodError } from 'zod';
3
+ import { fromZodError, ValidationError } from 'zod-validation-error';
4
4
 
5
5
  export class NotInsideProjectError extends Error {
6
- name = "NotInsideProjectError";
7
- message = "You are not inside a Dubhe project";
6
+ name = 'NotInsideProjectError';
7
+ message = 'You are not inside a Dubhe project';
8
8
  }
9
9
 
10
10
  export class DubheCliError extends Error {
11
- name = "DubheCliError";
11
+ name = 'DubheCliError';
12
12
  }
13
13
 
14
14
  export class UpgradeError extends Error {
15
- name = "UpgradeError";
15
+ name = 'UpgradeError';
16
16
  }
17
17
 
18
18
  export class FsIibError extends Error {
19
- name = "FsIibError";
19
+ name = 'FsIibError';
20
20
  }
21
21
 
22
22
  export function logError(error: unknown) {
23
- if (error instanceof ValidationError) {
24
- console.log(chalk.redBright(error.message));
25
- } else if (error instanceof ZodError) {
26
- // TODO currently this error shouldn't happen, use `fromZodErrorCustom`
27
- const validationError = fromZodError(error, {
28
- prefixSeparator: "\n- ",
29
- issueSeparator: "\n- ",
30
- });
31
- console.log(chalk.redBright(validationError.message));
32
- } else if (error instanceof NotInsideProjectError) {
33
- console.log(chalk.red(error.message));
34
- console.log("");
35
- // TODO add docs to the website and update the link to the specific page
36
- console.log(
37
- chalk.blue(
38
- `To learn more about Dubhe's configuration, please go to https://github.com/0xobelisk`
39
- )
40
- );
41
- } else if (error instanceof DubheCliError) {
42
- console.log(chalk.red(error));
43
- } else {
44
- console.log(error);
45
- }
23
+ if (error instanceof ValidationError) {
24
+ console.log(chalk.redBright(error.message));
25
+ } else if (error instanceof ZodError) {
26
+ // TODO currently this error shouldn't happen, use `fromZodErrorCustom`
27
+ const validationError = fromZodError(error, {
28
+ prefixSeparator: '\n- ',
29
+ issueSeparator: '\n- ',
30
+ });
31
+ console.log(chalk.redBright(validationError.message));
32
+ } else if (error instanceof NotInsideProjectError) {
33
+ console.log(chalk.red(error.message));
34
+ console.log('');
35
+ // TODO add docs to the website and update the link to the specific page
36
+ console.log(
37
+ chalk.blue(
38
+ `To learn more about Dubhe's configuration, please go to https://github.com/0xobelisk`
39
+ )
40
+ );
41
+ } else if (error instanceof DubheCliError) {
42
+ console.log(chalk.red(error));
43
+ } else {
44
+ console.log(error);
45
+ }
46
46
  }
@@ -0,0 +1,112 @@
1
+ import { Dubhe } from '@0xobelisk/sui-client';
2
+ import * as fs from 'fs';
3
+ import chalk from 'chalk';
4
+
5
+ export async function generateAccountHandler(
6
+ force: boolean = false,
7
+ outputTsPath?: string
8
+ ) {
9
+ if (outputTsPath) {
10
+ console.log(
11
+ chalk.blue(
12
+ 'Note: The generated account will be stored in the .env file and the TypeScript file specified by the --output-ts-path option.'
13
+ )
14
+ );
15
+ console.log(
16
+ chalk.yellow(
17
+ 'Warning: Do not expose the key file. It is intended for local testing only.\n'
18
+ )
19
+ );
20
+ }
21
+ const path = process.cwd();
22
+ let privateKey: string;
23
+
24
+ if (force) {
25
+ const dubhe = new Dubhe();
26
+ const keypair = dubhe.getKeypair();
27
+ privateKey = keypair.getSecretKey();
28
+
29
+ fs.writeFileSync(`${path}/.env`, `PRIVATE_KEY=${privateKey}`);
30
+ console.log(chalk.green(`File created at: ${path}/.env`));
31
+
32
+ if (outputTsPath) {
33
+ const dir = outputTsPath.substring(
34
+ 0,
35
+ outputTsPath.lastIndexOf('/')
36
+ );
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ fs.writeFileSync(
41
+ outputTsPath,
42
+ `export const PRIVATEKEY = '${privateKey}';
43
+ export const ACCOUNT = '${keypair.toSuiAddress()}';
44
+ `
45
+ );
46
+ console.log(chalk.green(`File created at: ${outputTsPath}\n`));
47
+ }
48
+
49
+ console.log(
50
+ chalk.blue(`Force generate new Account: ${keypair.toSuiAddress()}`)
51
+ );
52
+ return;
53
+ }
54
+
55
+ // Check if .env file exists and has content
56
+ try {
57
+ const envContent = fs.readFileSync(`${path}/.env`, 'utf8');
58
+ const match = envContent.match(/PRIVATE_KEY=(.+)/);
59
+ if (match && match[1]) {
60
+ privateKey = match[1];
61
+ const dubhe = new Dubhe({ secretKey: privateKey });
62
+ const keypair = dubhe.getKeypair();
63
+
64
+ if (outputTsPath) {
65
+ const dir = outputTsPath.substring(
66
+ 0,
67
+ outputTsPath.lastIndexOf('/')
68
+ );
69
+ if (!fs.existsSync(dir)) {
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ }
72
+ fs.writeFileSync(
73
+ outputTsPath,
74
+ `export const PRIVATEKEY = '${privateKey}';
75
+ export const ACCOUNT = '${keypair.toSuiAddress()}';
76
+ `
77
+ );
78
+ console.log(chalk.green(`File created at: ${outputTsPath}\n`));
79
+ }
80
+
81
+ console.log(
82
+ chalk.blue(`Using existing Account: ${keypair.toSuiAddress()}`)
83
+ );
84
+ return;
85
+ }
86
+ } catch (error) {
87
+ // .env file doesn't exist or failed to read, continue to generate new account
88
+ }
89
+
90
+ // If no existing private key, generate new account
91
+ const dubhe = new Dubhe();
92
+ const keypair = dubhe.getKeypair();
93
+ privateKey = keypair.getSecretKey();
94
+ fs.writeFileSync(`${path}/.env`, `PRIVATE_KEY=${privateKey}`);
95
+ console.log(chalk.green(`File created at: ${path}/.env`));
96
+
97
+ if (outputTsPath) {
98
+ const dir = outputTsPath.substring(0, outputTsPath.lastIndexOf('/'));
99
+ if (!fs.existsSync(dir)) {
100
+ fs.mkdirSync(dir, { recursive: true });
101
+ }
102
+ fs.writeFileSync(
103
+ outputTsPath,
104
+ `export const PRIVATEKEY = '${privateKey}';
105
+ export const ACCOUNT = '${keypair.toSuiAddress()}';
106
+ `
107
+ );
108
+ console.log(chalk.green(`File created at: ${outputTsPath}\n`));
109
+ }
110
+
111
+ console.log(chalk.blue(`Generate new Account: ${keypair.toSuiAddress()}`));
112
+ }
@@ -12,7 +12,11 @@ import {
12
12
  updateVersionInFile,
13
13
  saveContractData,
14
14
  validatePrivateKey,
15
- schema, getSchemaHub, updateDubheDependency, switchEnv, delay,
15
+ schema,
16
+ getSchemaHub,
17
+ updateDubheDependency,
18
+ switchEnv,
19
+ delay,
16
20
  } from './utils';
17
21
  import { DubheConfig } from '@0xobelisk/sui-common';
18
22
  import * as fs from 'fs';
@@ -22,9 +26,12 @@ async function getDappsObjectId(
22
26
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
23
27
  ) {
24
28
  switch (network) {
25
- case "localnet": {
29
+ case 'localnet': {
26
30
  const path = process.cwd();
27
- return await getSchemaHub(`${path}/contracts/dubhe-framework`, network)
31
+ return await getSchemaHub(
32
+ `${path}/contracts/dubhe-framework`,
33
+ network
34
+ );
28
35
  }
29
36
  case 'testnet':
30
37
  return '0x8dbf8d28ac027ba214c9e0951b09f6842843be6cb87242b7d9a326a2677cd47a';
@@ -33,12 +40,18 @@ async function getDappsObjectId(
33
40
  }
34
41
  }
35
42
 
36
- function removeEnvContent(filePath: string, networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet'): void {
43
+ function removeEnvContent(
44
+ filePath: string,
45
+ networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
46
+ ): void {
37
47
  if (!fs.existsSync(filePath)) {
38
48
  return;
39
49
  }
40
50
  const content = fs.readFileSync(filePath, 'utf-8');
41
- const regex = new RegExp(`\\[env\\.${networkType}\\][\\s\\S]*?(?=\\[|$)`, 'g');
51
+ const regex = new RegExp(
52
+ `\\[env\\.${networkType}\\][\\s\\S]*?(?=\\[|$)`,
53
+ 'g'
54
+ );
42
55
  const updatedContent = content.replace(regex, '');
43
56
  fs.writeFileSync(filePath, updatedContent, 'utf-8');
44
57
  }
@@ -56,12 +69,20 @@ const chainIds: { [key: string]: string } = {
56
69
  mainnet: '35834a8a',
57
70
  };
58
71
 
59
- function updateEnvFile(filePath: string, networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet', operation: 'publish' | 'upgrade', chainId: string, publishedId: string): void {
72
+ function updateEnvFile(
73
+ filePath: string,
74
+ networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
75
+ operation: 'publish' | 'upgrade',
76
+ chainId: string,
77
+ publishedId: string
78
+ ): void {
60
79
  const envFilePath = path.resolve(filePath);
61
80
  const envContent = fs.readFileSync(envFilePath, 'utf-8');
62
81
  const envLines = envContent.split('\n');
63
82
 
64
- const networkSectionIndex = envLines.findIndex(line => line.trim() === `[env.${networkType}]`);
83
+ const networkSectionIndex = envLines.findIndex(
84
+ line => line.trim() === `[env.${networkType}]`
85
+ );
65
86
  const config: EnvConfig = {
66
87
  chainId: chainId,
67
88
  originalPublishedId: '',
@@ -76,14 +97,18 @@ function updateEnvFile(filePath: string, networkType: 'mainnet' | 'testnet' | 'd
76
97
  config.latestPublishedId = publishedId;
77
98
  config.publishedVersion = 1;
78
99
  } else {
79
- throw new Error(`Network type [env.${networkType}] not found in the file and cannot upgrade.`);
100
+ throw new Error(
101
+ `Network type [env.${networkType}] not found in the file and cannot upgrade.`
102
+ );
80
103
  }
81
104
  } else {
82
105
  for (let i = networkSectionIndex + 1; i < envLines.length; i++) {
83
106
  const line = envLines[i].trim();
84
107
  if (line.startsWith('[')) break; // End of the current network section
85
108
 
86
- const [key, value] = line.split('=').map(part => part.trim().replace(/"/g, ''));
109
+ const [key, value] = line
110
+ .split('=')
111
+ .map(part => part.trim().replace(/"/g, ''));
87
112
  switch (key) {
88
113
  case 'original-published-id':
89
114
  config.originalPublishedId = value;
@@ -115,9 +140,11 @@ latest-published-id = "${config.latestPublishedId}"
115
140
  published-version = "${config.publishedVersion}"
116
141
  `;
117
142
 
118
- const newEnvContent = networkSectionIndex === -1
119
- ? envContent + updatedSection
120
- : envLines.slice(0, networkSectionIndex).join('\n') + updatedSection;
143
+ const newEnvContent =
144
+ networkSectionIndex === -1
145
+ ? envContent + updatedSection
146
+ : envLines.slice(0, networkSectionIndex).join('\n') +
147
+ updatedSection;
121
148
 
122
149
  fs.writeFileSync(envFilePath, newEnvContent, 'utf-8');
123
150
  }
@@ -137,7 +164,7 @@ function getLastSegment(input: string): string {
137
164
  return segments.length > 0 ? segments[segments.length - 1] : '';
138
165
  }
139
166
 
140
- function buildContract(projectPath: string): string[][] {
167
+ function buildContract(projectPath: string): string[][] {
141
168
  let modules: any, dependencies: any;
142
169
  try {
143
170
  const buildResult = JSON.parse(
@@ -168,7 +195,7 @@ async function publishContract(
168
195
  projectPath: string
169
196
  ) {
170
197
  const dappsObjectId = await getDappsObjectId(network);
171
- console.log("dappsObjectId", dappsObjectId);
198
+ console.log('dappsObjectId', dappsObjectId);
172
199
  const chainId = await client.getChainIdentifier();
173
200
  removeEnvContent(`${projectPath}/Move.lock`, network);
174
201
  console.log('\n🚀 Starting Contract Publication...');
@@ -227,7 +254,7 @@ async function publishContract(
227
254
  }
228
255
  if (
229
256
  object.type === 'created' &&
230
- object.objectType.includes("schema_hub")
257
+ object.objectType.includes('schema_hub')
231
258
  ) {
232
259
  console.log(` ├─ Schema Hub: ${object.objectId}`);
233
260
  schemaHubId = object.objectId;
@@ -236,14 +263,20 @@ async function publishContract(
236
263
 
237
264
  console.log(` └─ Transaction: ${result.digest}`);
238
265
 
239
- updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
266
+ updateEnvFile(
267
+ `${projectPath}/Move.lock`,
268
+ network,
269
+ 'publish',
270
+ chainId,
271
+ packageId
272
+ );
240
273
 
241
274
  console.log('\n⚡ Executing Deploy Hook...');
242
275
  await delay(5000);
243
276
 
244
277
  const deployHookTx = new Transaction();
245
278
  deployHookTx.setGasBudget(2000000000);
246
- const [txCoin] = deployHookTx.splitCoins(deployHookTx.gas, ["1000000000"]);
279
+ const [txCoin] = deployHookTx.splitCoins(deployHookTx.gas, ['1000000000']);
247
280
  deployHookTx.moveCall({
248
281
  target: `${packageId}::deploy_hook::run`,
249
282
  arguments: [
@@ -251,7 +284,7 @@ async function publishContract(
251
284
  deployHookTx.object(dappsObjectId),
252
285
  deployHookTx.object(upgradeCapId),
253
286
  deployHookTx.object('0x6'),
254
- txCoin
287
+ txCoin,
255
288
  ],
256
289
  });
257
290
 
@@ -276,14 +309,18 @@ async function publishContract(
276
309
  deployHookResult.objectChanges?.map(object => {
277
310
  if (
278
311
  object.type === 'created' &&
279
- object.objectType.includes('_schema') && !object.objectType.includes("dynamic_field")
312
+ object.objectType.includes('_schema') &&
313
+ !object.objectType.includes('dynamic_field')
280
314
  ) {
281
315
  console.log(` ├─ ${object.objectType}`);
282
316
  console.log(` └─ ID: ${object.objectId}`);
283
317
 
284
318
  let structure: Record<string, string> = {};
285
319
  for (let schemaKey in dubheConfig.schemas) {
286
- if (capitalizeAndRemoveUnderscores(schemaKey) === getLastSegment(object.objectType)) {
320
+ if (
321
+ capitalizeAndRemoveUnderscores(schemaKey) ===
322
+ getLastSegment(object.objectType)
323
+ ) {
287
324
  structure = dubheConfig.schemas[schemaKey].structure;
288
325
  }
289
326
  }
@@ -303,7 +340,7 @@ async function publishContract(
303
340
  upgradeCapId,
304
341
  schemaHubId,
305
342
  version,
306
- schemas,
343
+ schemas
307
344
  );
308
345
  console.log('\n✅ Contract Publication Complete\n');
309
346
  } else {
@@ -319,7 +356,7 @@ async function publishContract(
319
356
  export async function publishDubheFramework(
320
357
  client: SuiClient,
321
358
  dubhe: Dubhe,
322
- network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
359
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
323
360
  ) {
324
361
  const path = process.cwd();
325
362
  const projectPath = `${path}/contracts/dubhe-framework`;
@@ -335,7 +372,6 @@ export async function publishDubheFramework(
335
372
  const keypair = dubhe.getKeypair();
336
373
  console.log(` └─ Account: ${keypair.toSuiAddress()}`);
337
374
 
338
-
339
375
  console.log('\n📦 Building Contract...');
340
376
  const [modules, dependencies] = buildContract(projectPath);
341
377
 
@@ -381,10 +417,7 @@ export async function publishDubheFramework(
381
417
  console.log(` ├─ Upgrade Cap: ${object.objectId}`);
382
418
  upgradeCapId = object.objectId;
383
419
  }
384
- if (
385
- object.type === 'created' &&
386
- object.objectType.includes("dapps")
387
- ) {
420
+ if (object.type === 'created' && object.objectType.includes('dapps')) {
388
421
  console.log(` ├─ Dapps: ${object.objectId}`);
389
422
  schemaHubId = object.objectId;
390
423
  }
@@ -392,23 +425,29 @@ export async function publishDubheFramework(
392
425
 
393
426
  console.log(` └─ Transaction: ${result.digest}`);
394
427
 
395
- updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
428
+ updateEnvFile(
429
+ `${projectPath}/Move.lock`,
430
+ network,
431
+ 'publish',
432
+ chainId,
433
+ packageId
434
+ );
396
435
 
397
436
  saveContractData(
398
- "dubhe-framework",
437
+ 'dubhe-framework',
399
438
  network,
400
439
  packageId,
401
440
  upgradeCapId,
402
441
  schemaHubId,
403
442
  version,
404
- schemas,
443
+ schemas
405
444
  );
406
445
  }
407
446
 
408
447
  export async function publishHandler(
409
448
  dubheConfig: DubheConfig,
410
449
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
411
- contractName?: string,
450
+ contractName?: string
412
451
  ) {
413
452
  await switchEnv(network);
414
453
 
@@ -428,7 +467,7 @@ in your contracts directory to use the default sui private key.`
428
467
  const dubhe = new Dubhe({ secretKey: privateKeyFormat });
429
468
  const client = new SuiClient({ url: getFullnodeUrl(network) });
430
469
 
431
- if (contractName == "dubhe-framework") {
470
+ if (contractName == 'dubhe-framework') {
432
471
  await publishDubheFramework(client, dubhe, network);
433
472
  } else {
434
473
  const path = process.cwd();
@@ -0,0 +1,79 @@
1
+ import * as fsAsync from 'fs/promises';
2
+ import { mkdirSync, writeFileSync } from 'fs';
3
+ import { exit } from 'process';
4
+ import { dirname } from 'path';
5
+ import { DeploymentJsonType, schema } from './utils';
6
+ import { DubheConfig } from '@0xobelisk/sui-common';
7
+
8
+ async function getDeploymentJson(
9
+ projectPath: string,
10
+ network: string
11
+ ): Promise<DeploymentJsonType> {
12
+ try {
13
+ const data = await fsAsync.readFile(
14
+ `${projectPath}/.history/sui_${network}/latest.json`,
15
+ 'utf8'
16
+ );
17
+ return JSON.parse(data) as DeploymentJsonType;
18
+ } catch (error) {
19
+ throw new Error(
20
+ `read .history/sui_${network}/latest.json failed. ${error}`
21
+ );
22
+ }
23
+ }
24
+
25
+ function storeConfig(
26
+ network: string,
27
+ packageId: string,
28
+ schemas: schema[],
29
+ outputPath: string
30
+ ) {
31
+ let code = `type NetworkType = 'testnet' | 'mainnet' | 'devnet' | 'localnet';
32
+
33
+ export const NETWORK: NetworkType = '${network}';
34
+
35
+ export const PACKAGE_ID = '${packageId}'
36
+
37
+ ${schemas
38
+ .map(
39
+ schema =>
40
+ `export const ${schema.name.split('::')[2]}_Object_Id = '${
41
+ schema.objectId
42
+ }'`
43
+ )
44
+ .join('\n')}
45
+ `;
46
+ // if (outputPath) {
47
+ writeOutput(code, outputPath, 'storeConfig');
48
+ // writeOutput(code, `${path}/src/chain/config.ts`, 'storeConfig');
49
+ // }
50
+ }
51
+
52
+ async function writeOutput(
53
+ output: string,
54
+ fullOutputPath: string,
55
+ logPrefix?: string
56
+ ): Promise<void> {
57
+ mkdirSync(dirname(fullOutputPath), { recursive: true });
58
+
59
+ writeFileSync(fullOutputPath, output);
60
+ if (logPrefix !== undefined) {
61
+ console.log(`${logPrefix}: ${fullOutputPath}`);
62
+ }
63
+ }
64
+
65
+ export async function storeConfigHandler(
66
+ dubheConfig: DubheConfig,
67
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
68
+ outputPath: string
69
+ ) {
70
+ const path = process.cwd();
71
+ const contractPath = `${path}/contracts/${dubheConfig.name}`;
72
+ const deployment = await getDeploymentJson(contractPath, network);
73
+ storeConfig(
74
+ deployment.network,
75
+ deployment.packageId,
76
+ deployment.schemas,
77
+ outputPath
78
+ );
79
+ }