@0xobelisk/sui-cli 1.2.0-pre.119 → 1.2.0-pre.120

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.2.0-pre.119",
3
+ "version": "1.2.0-pre.120",
4
4
  "description": "Tookit for interacting with move eps framework",
5
5
  "keywords": [
6
6
  "sui",
@@ -47,8 +47,8 @@
47
47
  "yargs": "^17.7.1",
48
48
  "zod": "^3.22.3",
49
49
  "zod-validation-error": "^1.3.0",
50
- "@0xobelisk/sui-client": "1.2.0-pre.119",
51
- "@0xobelisk/sui-common": "1.2.0-pre.119"
50
+ "@0xobelisk/sui-client": "1.2.0-pre.120",
51
+ "@0xobelisk/sui-common": "1.2.0-pre.120"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/cli-progress": "^3.11.5",
@@ -10,6 +10,7 @@ type Options = {
10
10
  'config-path': string;
11
11
  network: any;
12
12
  'dump-bytecode-as-base64'?: boolean;
13
+ 'rpc-url'?: string;
13
14
  };
14
15
 
15
16
  /**
@@ -61,6 +62,10 @@ const commandModule: CommandModule<Options, Options> = {
61
62
  type: 'boolean',
62
63
  default: false,
63
64
  desc: 'Dump bytecode as base64'
65
+ },
66
+ 'rpc-url': {
67
+ type: 'string',
68
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
64
69
  }
65
70
  });
66
71
  },
@@ -68,7 +73,8 @@ const commandModule: CommandModule<Options, Options> = {
68
73
  async handler({
69
74
  'config-path': configPath,
70
75
  network,
71
- 'dump-bytecode-as-base64': dumpBytecodeAsBase64
76
+ 'dump-bytecode-as-base64': dumpBytecodeAsBase64,
77
+ 'rpc-url': rpcUrl
72
78
  }) {
73
79
  try {
74
80
  if (network == 'default') {
@@ -77,7 +83,7 @@ const commandModule: CommandModule<Options, Options> = {
77
83
  }
78
84
  console.log('🚀 Running move build');
79
85
  const dubheConfig = (await loadConfig(configPath)) as DubheConfig;
80
- await switchEnv(network);
86
+ await switchEnv(network, rpcUrl);
81
87
 
82
88
  const projectPath = nodePath.join(process.cwd(), 'src', dubheConfig.name);
83
89
  const lintResults = lintSystemGuards(projectPath);
@@ -6,26 +6,34 @@ import { getDefaultNetwork } from '../utils';
6
6
 
7
7
  type Options = {
8
8
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet' | 'default';
9
+ 'rpc-url'?: string;
9
10
  };
10
11
 
11
12
  const commandModule: CommandModule<Options, Options> = {
12
13
  command: 'check-balance',
13
14
  describe: 'Check the balance of the account',
14
- builder: {
15
- network: {
16
- type: 'string',
17
- choices: ['mainnet', 'testnet', 'devnet', 'localnet', 'default'],
18
- desc: 'Network to check balance on',
19
- default: 'default'
20
- }
15
+ builder(yargs) {
16
+ return yargs.options({
17
+ network: {
18
+ type: 'string',
19
+ choices: ['mainnet', 'testnet', 'devnet', 'localnet', 'default'],
20
+ desc: 'Network to check balance on',
21
+ default: 'default'
22
+ },
23
+ 'rpc-url': {
24
+ type: 'string',
25
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
26
+ }
27
+ }) as any;
21
28
  },
22
- async handler({ network }) {
29
+ async handler({ network, 'rpc-url': rpcUrl }) {
23
30
  try {
24
31
  if (network == 'default') {
25
32
  network = await getDefaultNetwork();
26
33
  console.log(chalk.yellow(`Use default network: [${network}]`));
27
34
  }
28
- await checkBalanceHandler(network);
35
+ const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
36
+ await checkBalanceHandler(network, fullnodeUrls);
29
37
  } catch (error) {
30
38
  console.error('Error checking balance:', error);
31
39
  handlerExit(1);
@@ -7,6 +7,7 @@ import { handlerExit } from './shell';
7
7
  type Options = {
8
8
  network: any;
9
9
  recipient?: string;
10
+ 'rpc-url'?: string;
10
11
  };
11
12
 
12
13
  const MAX_RETRIES = 60; // 60s timeout
@@ -30,14 +31,18 @@ const commandModule: CommandModule<Options, Options> = {
30
31
  type: 'string',
31
32
  alias: 'r',
32
33
  desc: 'Sui address to fund'
34
+ },
35
+ 'rpc-url': {
36
+ type: 'string',
37
+ desc: 'Custom RPC endpoint URL for balance check (overrides the default for the selected network)'
33
38
  }
34
39
  });
35
40
  },
36
41
 
37
- async handler({ network, recipient }) {
42
+ async handler({ network, recipient, 'rpc-url': rpcUrl }) {
38
43
  let faucet_address = '';
39
44
  if (recipient === undefined) {
40
- const dubhe = initializeDubhe(network);
45
+ const dubhe = initializeDubhe({ network });
41
46
  faucet_address = dubhe.getAddress();
42
47
  } else {
43
48
  faucet_address = recipient;
@@ -108,7 +113,7 @@ const commandModule: CommandModule<Options, Options> = {
108
113
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
109
114
 
110
115
  console.log(' └─ Checking balance...');
111
- const client = new SuiClient({ url: getFullnodeUrl(network) });
116
+ const client = new SuiClient({ url: rpcUrl || getFullnodeUrl(network) });
112
117
  let params = {
113
118
  owner: faucet_address
114
119
  } as GetBalanceParams;
@@ -7,6 +7,7 @@ dotenv.config();
7
7
 
8
8
  type Options = {
9
9
  network: any;
10
+ 'rpc-url'?: string;
10
11
  };
11
12
 
12
13
  const InfoCommand: CommandModule<Options, Options> = {
@@ -19,16 +20,21 @@ const InfoCommand: CommandModule<Options, Options> = {
19
20
  choices: ['mainnet', 'testnet', 'devnet', 'localnet', 'default'],
20
21
  default: 'default',
21
22
  desc: 'Node network (mainnet/testnet/devnet/localnet)'
23
+ },
24
+ 'rpc-url': {
25
+ type: 'string',
26
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
22
27
  }
23
28
  });
24
29
  },
25
- handler: async ({ network }) => {
30
+ handler: async ({ network, 'rpc-url': rpcUrl }) => {
26
31
  try {
27
32
  if (network == 'default') {
28
33
  network = await getDefaultNetwork();
29
34
  console.log(chalk.yellow(`Use default network: [${network}]`));
30
35
  }
31
- const dubhe = initializeDubhe({ network });
36
+ const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
37
+ const dubhe = initializeDubhe({ network, fullnodeUrls });
32
38
  const keypair = dubhe.getSigner();
33
39
 
34
40
  console.log(chalk.blue('Account Information:'));
@@ -10,6 +10,7 @@ type Options = {
10
10
  network: any;
11
11
  'config-path': string;
12
12
  'package-id'?: string;
13
+ 'rpc-url'?: string;
13
14
  };
14
15
 
15
16
  const commandModule: CommandModule<Options, Options> = {
@@ -34,18 +35,28 @@ const commandModule: CommandModule<Options, Options> = {
34
35
  type: 'string',
35
36
  desc: 'Package ID to load metadata for',
36
37
  optional: true
38
+ },
39
+ 'rpc-url': {
40
+ type: 'string',
41
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
37
42
  }
38
43
  });
39
44
  },
40
45
 
41
- async handler({ network, 'config-path': configPath, 'package-id': packageId }) {
46
+ async handler({
47
+ network,
48
+ 'config-path': configPath,
49
+ 'package-id': packageId,
50
+ 'rpc-url': rpcUrl
51
+ }) {
42
52
  try {
43
53
  if (network == 'default') {
44
54
  network = await getDefaultNetwork();
45
55
  console.log(chalk.yellow(`Use default network: [${network}]`));
46
56
  }
47
57
  const dubheConfig = (await loadConfig(configPath)) as DubheConfig;
48
- await loadMetadataHandler(dubheConfig, network, packageId);
58
+ const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
59
+ await loadMetadataHandler(dubheConfig, network, packageId, fullnodeUrls);
49
60
  } catch (error: any) {
50
61
  logError(error);
51
62
  handlerExit(1);
@@ -18,6 +18,7 @@ type Options = {
18
18
  'config-path': string;
19
19
  force: boolean;
20
20
  'gas-budget'?: number;
21
+ 'rpc-url'?: string;
21
22
  };
22
23
 
23
24
  const commandModule: CommandModule<Options, Options> = {
@@ -47,11 +48,21 @@ const commandModule: CommandModule<Options, Options> = {
47
48
  type: 'boolean',
48
49
  default: false,
49
50
  desc: 'Clear existing published state for this network before build (use when re-publishing or to fix PublishErrorNonZeroAddress)'
51
+ },
52
+ 'rpc-url': {
53
+ type: 'string',
54
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
50
55
  }
51
56
  });
52
57
  },
53
58
 
54
- async handler({ network, 'config-path': configPath, 'gas-budget': gasBudget, force }) {
59
+ async handler({
60
+ network,
61
+ 'config-path': configPath,
62
+ 'gas-budget': gasBudget,
63
+ force,
64
+ 'rpc-url': rpcUrl
65
+ }) {
55
66
  try {
56
67
  if (network == 'default') {
57
68
  network = await getDefaultNetwork();
@@ -73,8 +84,9 @@ const commandModule: CommandModule<Options, Options> = {
73
84
  }
74
85
  }
75
86
 
87
+ const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
76
88
  execSync(`pnpm dubhe convert-json --config-path ${configPath}`, { encoding: 'utf-8' });
77
- await publishHandler(dubheConfig, network, force, gasBudget);
89
+ await publishHandler(dubheConfig, network, force, gasBudget, fullnodeUrls);
78
90
  } catch (error: any) {
79
91
  logError(error);
80
92
  handlerExit(1);
@@ -20,6 +20,7 @@ export const handlerExit = (status: number = 0) => {
20
20
 
21
21
  type Options = {
22
22
  network: any;
23
+ 'rpc-url'?: string;
23
24
  };
24
25
 
25
26
  const parseCommandNames = () => {
@@ -38,10 +39,14 @@ const ShellCommand: CommandModule<Options, Options> = {
38
39
  choices: ['mainnet', 'testnet', 'devnet', 'localnet', 'default'],
39
40
  default: 'default',
40
41
  desc: 'Node network (mainnet/testnet/devnet/localnet)'
42
+ },
43
+ 'rpc-url': {
44
+ type: 'string',
45
+ desc: 'Custom RPC endpoint URL injected into every sub-command (overrides the default for the selected network)'
41
46
  }
42
47
  });
43
48
  },
44
- handler: async ({ network }) => {
49
+ handler: async ({ network, 'rpc-url': rpcUrl }) => {
45
50
  if (network == 'default') {
46
51
  network = await getDefaultNetwork();
47
52
  console.log(chalk.yellow(`Use default network: [${network}]`));
@@ -60,7 +65,9 @@ const ShellCommand: CommandModule<Options, Options> = {
60
65
  const rl = readline.createInterface({
61
66
  input: process.stdin,
62
67
  output: process.stdout,
63
- prompt: `dubhe(${chalk.green(network)}) ${chalk.bold('>')} `,
68
+ prompt: `dubhe(${chalk.green(network)}${
69
+ rpcUrl ? chalk.gray('[custom-rpc]') : ''
70
+ }) ${chalk.bold('>')} `,
64
71
  completer: completer,
65
72
  historySize: 200
66
73
  });
@@ -134,9 +141,11 @@ const ShellCommand: CommandModule<Options, Options> = {
134
141
  }
135
142
  const userArgs = parts.slice(1);
136
143
  const hasNetworkFlag = userArgs.includes('--network') || userArgs.includes('-n');
144
+ const hasRpcUrlFlag = userArgs.includes('--rpc-url');
137
145
  const argv = yargsInstance.parseSync([
138
146
  commandName,
139
147
  ...(hasNetworkFlag ? [] : ['--network', network]),
148
+ ...(hasRpcUrlFlag || !rpcUrl ? [] : ['--rpc-url', rpcUrl]),
140
149
  ...userArgs
141
150
  ]);
142
151
  if (handler) {
@@ -4,21 +4,28 @@ import { handlerExit } from './shell';
4
4
 
5
5
  type Options = {
6
6
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet' | 'default';
7
+ 'rpc-url'?: string;
7
8
  };
8
9
 
9
10
  const commandModule: CommandModule<Options, Options> = {
10
11
  command: 'switch-env',
11
12
  describe: 'Switch environment',
12
13
  builder(yargs) {
13
- return yargs.option('network', {
14
- type: 'string',
15
- choices: ['mainnet', 'testnet', 'devnet', 'localnet'] as const,
16
- default: 'localnet',
17
- desc: 'Switch to node network (mainnet/testnet/devnet/localnet)'
14
+ return yargs.options({
15
+ network: {
16
+ type: 'string',
17
+ choices: ['mainnet', 'testnet', 'devnet', 'localnet'] as const,
18
+ default: 'localnet',
19
+ desc: 'Switch to node network (mainnet/testnet/devnet/localnet)'
20
+ },
21
+ 'rpc-url': {
22
+ type: 'string',
23
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
24
+ }
18
25
  }) as any;
19
26
  },
20
27
  async handler(argv: ArgumentsCamelCase<Options>) {
21
- await switchEnv(argv.network as 'mainnet' | 'testnet' | 'devnet' | 'localnet');
28
+ await switchEnv(argv.network as 'mainnet' | 'testnet' | 'devnet' | 'localnet', argv['rpc-url']);
22
29
  handlerExit();
23
30
  }
24
31
  };
@@ -11,6 +11,7 @@ type Options = {
11
11
  network: any;
12
12
  'config-path': string;
13
13
  'bump-version': boolean;
14
+ 'rpc-url'?: string;
14
15
  };
15
16
 
16
17
  const commandModule: CommandModule<Options, Options> = {
@@ -35,11 +36,20 @@ const commandModule: CommandModule<Options, Options> = {
35
36
  type: 'boolean',
36
37
  default: false,
37
38
  desc: 'Force a version bump even when no new resources were added (use for breaking logic changes or security fixes that must invalidate old clients)'
39
+ },
40
+ 'rpc-url': {
41
+ type: 'string',
42
+ desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
38
43
  }
39
44
  });
40
45
  },
41
46
 
42
- async handler({ network, 'config-path': configPath, 'bump-version': bumpVersion }) {
47
+ async handler({
48
+ network,
49
+ 'config-path': configPath,
50
+ 'bump-version': bumpVersion,
51
+ 'rpc-url': rpcUrl
52
+ }) {
43
53
  try {
44
54
  if (network == 'default') {
45
55
  network = await getDefaultNetwork();
@@ -61,7 +71,8 @@ const commandModule: CommandModule<Options, Options> = {
61
71
  }
62
72
  }
63
73
 
64
- await upgradeHandler(dubheConfig, dubheConfig.name, network, bumpVersion);
74
+ const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
75
+ await upgradeHandler(dubheConfig, dubheConfig.name, network, bumpVersion, fullnodeUrls);
65
76
  } catch (error: any) {
66
77
  logError(error);
67
78
  handlerExit(1);
@@ -4,10 +4,14 @@ import { initializeDubhe } from './utils';
4
4
  import { DubheCliError } from './errors';
5
5
  dotenv.config();
6
6
 
7
- export async function checkBalanceHandler(network: 'mainnet' | 'testnet' | 'devnet' | 'localnet') {
7
+ export async function checkBalanceHandler(
8
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
9
+ fullnodeUrls?: string[]
10
+ ) {
8
11
  try {
9
12
  const dubhe = initializeDubhe({
10
- network
13
+ network,
14
+ fullnodeUrls
11
15
  });
12
16
 
13
17
  const balance = await dubhe.getBalance();
@@ -4,13 +4,14 @@ import { getOldPackageId, saveMetadata } from './utils';
4
4
  export async function loadMetadataHandler(
5
5
  dubheConfig: DubheConfig,
6
6
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
7
- packageId?: string
7
+ packageId?: string,
8
+ fullnodeUrls?: string[]
8
9
  ) {
9
10
  if (packageId) {
10
- await saveMetadata(dubheConfig.name, network, packageId);
11
+ await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
11
12
  } else {
12
13
  const projectPath = `${process.cwd()}/src/${dubheConfig.name}`;
13
14
  const packageId = await getOldPackageId(projectPath, network);
14
- await saveMetadata(dubheConfig.name, network, packageId);
15
+ await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
15
16
  }
16
17
  }
@@ -11,6 +11,7 @@ import {
11
11
  saveMetadata,
12
12
  getOriginalDubhePackageId,
13
13
  updatePublishedToml,
14
+ syncDubheFrameworkAddress,
14
15
  updateEphemeralPubFile,
15
16
  getEphemeralPubFilePath,
16
17
  getPublishedTomlEntry,
@@ -246,7 +247,8 @@ async function publishContract(
246
247
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
247
248
  projectPath: string,
248
249
  gasBudget?: number,
249
- force?: boolean
250
+ force?: boolean,
251
+ fullnodeUrls?: string[]
250
252
  ) {
251
253
  console.log('\n🚀 Starting Contract Publication...');
252
254
  console.log(` ├─ Project: ${projectPath}`);
@@ -259,6 +261,14 @@ async function publishContract(
259
261
  await removeEnvContent(`${projectPath}/Move.lock`, network);
260
262
  console.log(` └─ Account: ${dubhe.getAddress()}`);
261
263
 
264
+ // Ensure src/dubhe/Published.toml references the canonical framework address
265
+ // for this network before building. This is a no-op when the address is
266
+ // already current, and automatically corrects stale entries whenever the
267
+ // framework is redeployed on testnet/mainnet without a manual file update.
268
+ if (dubheConfig.name !== 'dubhe') {
269
+ syncDubheFrameworkAddress(process.cwd(), network, chainId);
270
+ }
271
+
262
272
  console.log('\n📦 Building Contract...');
263
273
  // For localnet: pass the ephemeral pubfile so the build system can resolve
264
274
  // the dubhe dependency that was just published in publishDubheFramework().
@@ -506,7 +516,7 @@ async function publishContract(
506
516
  dappStorageId || undefined
507
517
  );
508
518
 
509
- await saveMetadata(dubheConfig.name, network, packageId);
519
+ await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
510
520
 
511
521
  // Insert package id to dubhe config
512
522
  let config = JSON.parse(fs.readFileSync(`${process.cwd()}/dubhe.config.json`, 'utf-8'));
@@ -717,12 +727,14 @@ export async function publishHandler(
717
727
  dubheConfig: DubheConfig,
718
728
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
719
729
  force: boolean,
720
- gasBudget?: number
730
+ gasBudget?: number,
731
+ fullnodeUrls?: string[]
721
732
  ) {
722
- await switchEnv(network);
733
+ await switchEnv(network, fullnodeUrls?.[0]);
723
734
 
724
735
  const dubhe = initializeDubhe({
725
- network
736
+ network,
737
+ fullnodeUrls
726
738
  });
727
739
 
728
740
  const path = process.cwd();
@@ -732,5 +744,5 @@ export async function publishHandler(
732
744
  await publishDubheFramework(dubhe, network);
733
745
  }
734
746
 
735
- await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget, force);
747
+ await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget, force, fullnodeUrls);
736
748
  }
@@ -16,6 +16,7 @@ import {
16
16
  getDappStorageId,
17
17
  getOriginalDubhePackageId,
18
18
  updatePublishedToml,
19
+ syncDubheFrameworkAddress,
19
20
  clearPublishedTomlEntry,
20
21
  restorePublishedTomlEntry,
21
22
  readPublishedToml,
@@ -76,14 +77,15 @@ export async function upgradeHandler(
76
77
  config: DubheConfig,
77
78
  name: string,
78
79
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
79
- bumpVersion: boolean = false
80
+ bumpVersion: boolean = false,
81
+ fullnodeUrls?: string[]
80
82
  ) {
81
- await switchEnv(network);
83
+ await switchEnv(network, fullnodeUrls?.[0]);
82
84
 
83
85
  const path = process.cwd();
84
86
  const projectPath = `${path}/src/${name}`;
85
87
 
86
- const dubhe = initializeDubhe({ network });
88
+ const dubhe = initializeDubhe({ network, fullnodeUrls });
87
89
 
88
90
  let oldVersion = Number(await getVersion(projectPath, network));
89
91
  let oldPackageId = await getOldPackageId(projectPath, network);
@@ -130,6 +132,15 @@ export async function upgradeHandler(
130
132
  const savedPublishedEntry =
131
133
  network !== 'localnet' ? clearPublishedTomlEntry(projectPath, network) : undefined;
132
134
 
135
+ // For testnet/mainnet: auto-sync src/dubhe/Published.toml to the canonical
136
+ // framework address from the SDK before building. Prevents
137
+ // VMVerificationOrDeserializationError when the framework was redeployed
138
+ // since the Published.toml was last written. Skip when upgrading dubhe itself.
139
+ if (name !== 'dubhe' && (network === 'testnet' || network === 'mainnet')) {
140
+ const chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
141
+ syncDubheFrameworkAddress(path, network, chainId);
142
+ }
143
+
133
144
  // For localnet upgrades: refresh Pub.localnet.toml with dubhe's current address
134
145
  // so that the build can resolve the dubhe dependency.
135
146
  // Skip this step when upgrading dubhe itself — dubhe has no local dubhe dependency,
@@ -250,13 +250,14 @@ export async function saveContractData(
250
250
  export async function saveMetadata(
251
251
  projectName: string,
252
252
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
253
- packageId: string
253
+ packageId: string,
254
+ fullnodeUrls?: string[]
254
255
  ) {
255
256
  const path = process.cwd();
256
257
 
257
258
  // Save metadata files
258
259
  try {
259
- const metadata = await loadMetadata(network, packageId);
260
+ const metadata = await loadMetadata(network, packageId, fullnodeUrls);
260
261
  if (metadata) {
261
262
  const metadataJson = JSON.stringify(metadata, null, 2);
262
263
 
@@ -396,6 +397,46 @@ export function getPublishedTomlEntry(
396
397
  return entries[network];
397
398
  }
398
399
 
400
+ /**
401
+ * Syncs the Dubhe framework address in `src/dubhe/Published.toml` with the
402
+ * canonical package ID from the SDK's `getDefaultConfig` for the given network.
403
+ *
404
+ * This prevents `VMVerificationOrDeserializationError` during `publish` and
405
+ * `upgrade` when the framework has been redeployed on testnet/mainnet but the
406
+ * local `Published.toml` still references the old address. The function is a
407
+ * no-op for localnet and devnet (no stable canonical address exists there).
408
+ *
409
+ * @param contractsRootDir - The contracts working directory (process.cwd() in CLI context)
410
+ * @param network - Target network
411
+ * @param chainId - Live chain identifier obtained from the node
412
+ */
413
+ export function syncDubheFrameworkAddress(
414
+ contractsRootDir: string,
415
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
416
+ chainId: string
417
+ ): void {
418
+ if (network === 'localnet' || network === 'devnet') return;
419
+
420
+ const frameworkPackageId = getDefaultConfig(network as NetworkType).frameworkPackageId;
421
+ if (!frameworkPackageId) return;
422
+
423
+ const dubhePath = pathJoin(contractsRootDir, 'src', 'dubhe');
424
+ if (!fs.existsSync(dubhePath)) return;
425
+
426
+ const existing = getPublishedTomlEntry(dubhePath, network);
427
+ if (existing?.publishedAt === frameworkPackageId) return;
428
+
429
+ updatePublishedToml(dubhePath, network, chainId, frameworkPackageId, frameworkPackageId, 1);
430
+ console.log(
431
+ chalk.gray(
432
+ ` ├─ Auto-synced dubhe framework address for ${network}: ${frameworkPackageId.slice(
433
+ 0,
434
+ 10
435
+ )}...`
436
+ )
437
+ );
438
+ }
439
+
399
440
  export function clearPublishedTomlEntry(
400
441
  packagePath: string,
401
442
  network: string
@@ -570,7 +611,8 @@ async function checkRpcAvailability(rpcUrl: string): Promise<boolean> {
570
611
  }
571
612
 
572
613
  export async function addEnv(
573
- network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
614
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
615
+ rpcUrl?: string
574
616
  ): Promise<void> {
575
617
  const rpcMap = {
576
618
  localnet: 'http://127.0.0.1:9000',
@@ -579,13 +621,13 @@ export async function addEnv(
579
621
  mainnet: 'https://fullnode.mainnet.sui.io:443/'
580
622
  };
581
623
 
582
- const rpcUrl = rpcMap[network];
624
+ const resolvedRpcUrl = rpcUrl || rpcMap[network];
583
625
 
584
626
  // Check RPC availability first
585
- const isRpcAvailable = await checkRpcAvailability(rpcUrl);
627
+ const isRpcAvailable = await checkRpcAvailability(resolvedRpcUrl);
586
628
  if (!isRpcAvailable) {
587
629
  throw new Error(
588
- `RPC endpoint ${rpcUrl} is not available. Please check your network connection or try again later.`
630
+ `RPC endpoint ${resolvedRpcUrl} is not available. Please check your network connection or try again later.`
589
631
  );
590
632
  }
591
633
 
@@ -595,7 +637,7 @@ export async function addEnv(
595
637
 
596
638
  const suiProcess = spawn(
597
639
  'sui',
598
- ['client', 'new-env', '--alias', network, '--rpc', rpcMap[network]],
640
+ ['client', 'new-env', '--alias', network, '--rpc', resolvedRpcUrl],
599
641
  {
600
642
  env: { ...process.env },
601
643
  stdio: 'pipe'
@@ -716,10 +758,13 @@ export async function getDefaultNetwork(): Promise<NetworkAlias> {
716
758
  return currentAlias as NetworkAlias;
717
759
  }
718
760
 
719
- export async function switchEnv(network: 'mainnet' | 'testnet' | 'devnet' | 'localnet') {
761
+ export async function switchEnv(
762
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
763
+ rpcUrl?: string
764
+ ) {
720
765
  try {
721
766
  // First, try to add the environment
722
- await addEnv(network);
767
+ await addEnv(network, rpcUrl);
723
768
 
724
769
  // Then switch to the specified environment
725
770
  return new Promise<void>((resolve, reject) => {
@@ -793,18 +838,21 @@ export function loadKey(): string {
793
838
  export function initializeDubhe({
794
839
  network,
795
840
  packageId,
796
- metadata
841
+ metadata,
842
+ fullnodeUrls
797
843
  }: {
798
844
  network: NetworkType;
799
845
  packageId?: string;
800
846
  metadata?: SuiMoveNormalizedModules;
847
+ fullnodeUrls?: string[];
801
848
  }): Dubhe {
802
849
  const privateKey = loadKey();
803
850
  return new Dubhe({
804
851
  networkType: network,
805
852
  secretKey: privateKey,
806
853
  packageId,
807
- metadata
854
+ metadata,
855
+ fullnodeUrls
808
856
  });
809
857
  }
810
858