@0xobelisk/sui-cli 1.2.0-pre.12 → 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.
Files changed (40) hide show
  1. package/README.md +7 -7
  2. package/dist/dubhe.js +152 -51
  3. package/dist/dubhe.js.map +1 -1
  4. package/package.json +31 -19
  5. package/src/commands/build.ts +61 -18
  6. package/src/commands/call.ts +83 -83
  7. package/src/commands/checkBalance.ts +27 -12
  8. package/src/commands/convertJson.ts +84 -0
  9. package/src/commands/doctor.ts +1515 -0
  10. package/src/commands/faucet.ts +20 -10
  11. package/src/commands/generate.ts +61 -0
  12. package/src/commands/generateKey.ts +3 -2
  13. package/src/commands/index.ts +20 -11
  14. package/src/commands/info.ts +61 -0
  15. package/src/commands/loadMetadata.ts +68 -0
  16. package/src/commands/localnode.ts +22 -6
  17. package/src/commands/publish.ts +55 -7
  18. package/src/commands/query.ts +101 -101
  19. package/src/commands/shell.ts +208 -0
  20. package/src/commands/{configStore.ts → storeConfig.ts} +13 -5
  21. package/src/commands/switchEnv.ts +33 -0
  22. package/src/commands/test.ts +143 -31
  23. package/src/commands/upgrade.ts +46 -6
  24. package/src/commands/wait.ts +333 -22
  25. package/src/commands/watch.ts +9 -8
  26. package/src/dubhe.ts +12 -4
  27. package/src/utils/axios-downloader.ts +116 -0
  28. package/src/utils/callHandler.ts +118 -118
  29. package/src/utils/checkBalance.ts +6 -2
  30. package/src/utils/constants.ts +9 -0
  31. package/src/utils/generateAccount.ts +1 -1
  32. package/src/utils/index.ts +4 -3
  33. package/src/utils/metadataHandler.ts +17 -0
  34. package/src/utils/publishHandler.ts +404 -289
  35. package/src/utils/queryStorage.ts +141 -141
  36. package/src/utils/startNode.ts +115 -16
  37. package/src/utils/storeConfig.ts +50 -10
  38. package/src/utils/upgradeHandler.ts +210 -86
  39. package/src/utils/utils.ts +1025 -63
  40. package/src/commands/schemagen.ts +0 -40
@@ -7,49 +7,32 @@ import {
7
7
  getVersion,
8
8
  getUpgradeCap,
9
9
  saveContractData,
10
- getOnchainSchemas,
11
10
  switchEnv,
12
- getSchemaId,
13
- getDubheSchemaId,
14
- initializeDubhe
11
+ initializeDubhe,
12
+ getOnchainResources,
13
+ getStartCheckpoint,
14
+ appendMigrateFunction,
15
+ getDubheDappHubId,
16
+ getDappStorageId,
17
+ getOriginalDubhePackageId,
18
+ updatePublishedToml,
19
+ syncDubheFrameworkAddress,
20
+ clearPublishedTomlEntry,
21
+ restorePublishedTomlEntry,
22
+ readPublishedToml,
23
+ updateEphemeralPubFile,
24
+ getEphemeralPubFilePath,
25
+ updateMoveTomlAddress
15
26
  } from './utils';
16
27
  import * as fs from 'fs';
17
28
  import * as path from 'path';
18
29
  import { DubheConfig } from '@0xobelisk/sui-common';
19
30
 
20
31
  type Migration = {
21
- schemaName: string;
22
- fields: string;
32
+ name: string;
33
+ fields: any;
23
34
  };
24
35
 
25
- function updateMigrateMethod(projectPath: string, migrations: Migration[]): void {
26
- let filePath = `${projectPath}/sources/codegen/core/schema.move`;
27
- const fileContent = fs.readFileSync(filePath, 'utf-8');
28
- const migrateMethodRegex = new RegExp(
29
- `public fun migrate\\(_schema: &mut Schema, _ctx: &mut TxContext\\) {[^}]*}`
30
- );
31
- const newMigrateMethod = `
32
- public fun migrate(_schema: &mut Schema, _ctx: &mut TxContext) {
33
- ${migrations
34
- .map((migration) => {
35
- let storage_type = '';
36
- if (migration.fields.includes('StorageValue')) {
37
- storage_type = `storage_value::new(b"${migration.schemaName}", _ctx)`;
38
- } else if (migration.fields.includes('StorageMap')) {
39
- storage_type = `storage_map::new(b"${migration.schemaName}", _ctx)`;
40
- } else if (migration.fields.includes('StorageDoubleMap')) {
41
- storage_type = `storage_double_map::new(b"${migration.schemaName}", _ctx)`;
42
- }
43
- return `storage::add_field<${migration.fields}>(&mut _schema.id, b"${migration.schemaName}", ${storage_type});`;
44
- })
45
- .join('')}
46
- }
47
- `;
48
-
49
- const updatedContent = fileContent.replace(migrateMethodRegex, newMigrateMethod);
50
- fs.writeFileSync(filePath, updatedContent, 'utf-8');
51
- }
52
-
53
36
  function replaceEnvField(
54
37
  filePath: string,
55
38
  networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
@@ -89,24 +72,51 @@ function replaceEnvField(
89
72
 
90
73
  return previousValue;
91
74
  }
75
+
92
76
  export async function upgradeHandler(
93
77
  config: DubheConfig,
94
78
  name: string,
95
- network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
79
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
80
+ bumpVersion: boolean = false,
81
+ fullnodeUrls?: string[]
96
82
  ) {
97
- await switchEnv(network);
83
+ await switchEnv(network, fullnodeUrls?.[0]);
98
84
 
99
85
  const path = process.cwd();
100
86
  const projectPath = `${path}/src/${name}`;
101
87
 
102
- const dubhe = initializeDubhe({
103
- network
104
- });
88
+ const dubhe = initializeDubhe({ network, fullnodeUrls });
105
89
 
106
90
  let oldVersion = Number(await getVersion(projectPath, network));
107
91
  let oldPackageId = await getOldPackageId(projectPath, network);
108
92
  let upgradeCap = await getUpgradeCap(projectPath, network);
109
- let schemaId = await getSchemaId(projectPath, network);
93
+ let startCheckpoint = await getStartCheckpoint(projectPath, network);
94
+ let dappHubId = await getDubheDappHubId(network);
95
+ let dappStorageId = await getDappStorageId(projectPath, network);
96
+ // For localnet the framework is deployed ephemerally; preserve its package ID in .history.
97
+ const frameworkPackageId =
98
+ network === 'localnet' ? await getOriginalDubhePackageId(network) : undefined;
99
+ let onchainResources = await getOnchainResources(projectPath, network);
100
+
101
+ let pendingMigration: Migration[] = [];
102
+ Object.entries(config.resources ?? {}).forEach(([key, value]) => {
103
+ if (!onchainResources.hasOwnProperty(key)) {
104
+ pendingMigration.push({ name: key, fields: value });
105
+ }
106
+ });
107
+
108
+ const tables = pendingMigration.map((migration) => migration.name);
109
+ // Trigger migration when new resources were detected (schema change) OR when the
110
+ // caller explicitly requested a version bump (--bump-version flag). The latter
111
+ // covers breaking logic changes and security fixes that must invalidate old clients
112
+ // even though no new resources were added.
113
+ const needsMigration = tables.length > 0 || bumpVersion;
114
+ if (needsMigration) {
115
+ if (bumpVersion && tables.length === 0) {
116
+ console.log(chalk.yellow('--bump-version: forcing version bump with no schema changes.'));
117
+ }
118
+ appendMigrateFunction(projectPath, config.name, oldVersion + 1);
119
+ }
110
120
 
111
121
  const original_published_id = replaceEnvField(
112
122
  `${projectPath}/Move.lock`,
@@ -115,33 +125,96 @@ export async function upgradeHandler(
115
125
  '0x0000000000000000000000000000000000000000000000000000000000000000'
116
126
  );
117
127
 
118
- let pendingMigration: Migration[] = [];
119
- let schemas = await getOnchainSchemas(projectPath, network);
120
- Object.entries(config.schemas).forEach(([key, value]) => {
121
- if (!schemas.hasOwnProperty(key)) {
122
- pendingMigration.push({ schemaName: key, fields: value });
128
+ // For persistent networks (testnet/mainnet): zero out Published.toml so the
129
+ // package compiles with address 0x0 for upgrade.
130
+ // For localnet: we use --build-env testnet + Pub.localnet.toml, so Published.toml
131
+ // is not consulted during the build and does not need to be cleared.
132
+ const savedPublishedEntry =
133
+ network !== 'localnet' ? clearPublishedTomlEntry(projectPath, network) : undefined;
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
+
144
+ // For localnet upgrades: refresh Pub.localnet.toml with dubhe's current address
145
+ // so that the build can resolve the dubhe dependency.
146
+ // Skip this step when upgrading dubhe itself — dubhe has no local dubhe dependency,
147
+ // and adding its own address to the pubfile causes PublishErrorNonZeroAddress because
148
+ // the Sui CLI treats the root package's pubfile entry as a non-zero self-address.
149
+ const cwd = process.cwd();
150
+ if (network === 'localnet' && name !== 'dubhe') {
151
+ const dubheProjectPath = `${cwd}/src/dubhe`;
152
+ const dubheEntries = readPublishedToml(dubheProjectPath);
153
+ const dubheEntry = dubheEntries['localnet'];
154
+ if (dubheEntry) {
155
+ const pubfilePath = getEphemeralPubFilePath(cwd, network);
156
+ const dubheUpgradeCap = await getUpgradeCap(dubheProjectPath, network).catch(() => '');
157
+ updateEphemeralPubFile(pubfilePath, dubheEntry.chainId, 'testnet', {
158
+ source: dubheProjectPath,
159
+ publishedAt: dubheEntry.publishedAt,
160
+ originalId: dubheEntry.originalId,
161
+ upgradeCap: dubheUpgradeCap
162
+ });
123
163
  }
124
- });
125
- updateMigrateMethod(projectPath, pendingMigration);
164
+ }
126
165
 
127
166
  try {
128
167
  let modules: any, dependencies: any, digest: any;
168
+ // When upgrading dubhe itself on localnet, Move.toml has a non-zero 'dubhe' address
169
+ // (the mainnet/testnet deploy address). This gets baked into the upgrade bytecode as the
170
+ // self-address, causing PublishErrorNonZeroAddress. Zero it out before the build and
171
+ // restore it afterwards — mirroring what publishDubheFramework does.
172
+ let savedDubheMoveTomlContent: string | null = null;
173
+ if (network === 'localnet' && name === 'dubhe') {
174
+ const moveTomlPath = `${projectPath}/Move.toml`;
175
+ savedDubheMoveTomlContent = fs.readFileSync(moveTomlPath, 'utf-8');
176
+ updateMoveTomlAddress(projectPath, '0x0');
177
+ }
129
178
  try {
179
+ // For localnet: use --build-env testnet --pubfile-path Pub.localnet.toml
180
+ // so the package compiles with address 0x0 (not in pubfile) and links
181
+ // against the already-published dubhe dependency (from pubfile).
182
+ // Exception: when upgrading dubhe itself, skip the pubfile — dubhe has no
183
+ // local dubhe dependency and including itself in the pubfile causes
184
+ // PublishErrorNonZeroAddress.
185
+ // For testnet/mainnet: use -e <network> as usual.
186
+ let buildCmd: string;
187
+ if (network === 'localnet') {
188
+ if (name !== 'dubhe') {
189
+ const pubfilePath = getEphemeralPubFilePath(cwd, network);
190
+ buildCmd = `sui move build --dump-bytecode-as-base64 --no-tree-shaking --build-env testnet --pubfile-path ${pubfilePath} --path ${path}/src/${name}`;
191
+ } else {
192
+ buildCmd = `sui move build --dump-bytecode-as-base64 --no-tree-shaking --build-env testnet --path ${path}/src/${name}`;
193
+ }
194
+ } else {
195
+ buildCmd = `sui move build --dump-bytecode-as-base64 --no-tree-shaking -e ${network} --path ${path}/src/${name}`;
196
+ }
197
+
130
198
  const {
131
199
  modules: extractedModules,
132
200
  dependencies: extractedDependencies,
133
201
  digest: extractedDigest
134
- } = JSON.parse(
135
- execSync(`sui move build --dump-bytecode-as-base64 --path ${path}/src/${name}`, {
136
- encoding: 'utf-8'
137
- })
138
- );
202
+ } = JSON.parse(execSync(buildCmd, { encoding: 'utf-8', stdio: 'pipe' }));
139
203
 
140
204
  modules = extractedModules;
141
205
  dependencies = extractedDependencies;
142
206
  digest = extractedDigest;
143
207
  } catch (error: any) {
144
- throw new UpgradeError(error.stdout);
208
+ // Restore Published.toml before throwing (only for persistent networks)
209
+ if (savedPublishedEntry) {
210
+ restorePublishedTomlEntry(projectPath, network, savedPublishedEntry);
211
+ }
212
+ throw new UpgradeError(error.stdout || error.stderr || error.message);
213
+ } finally {
214
+ // Always restore dubhe Move.toml after build (success or error)
215
+ if (savedDubheMoveTomlContent !== null) {
216
+ fs.writeFileSync(`${projectPath}/Move.toml`, savedDubheMoveTomlContent, 'utf-8');
217
+ }
145
218
  }
146
219
 
147
220
  console.log('\nšŸš€ Starting Upgrade Process...');
@@ -200,51 +273,102 @@ export async function upgradeHandler(
200
273
  replaceEnvField(`${projectPath}/Move.lock`, network, 'latest-published-id', newPackageId);
201
274
  replaceEnvField(`${projectPath}/Move.lock`, network, 'published-version', oldVersion + 1 + '');
202
275
 
276
+ // Update Published.toml with the new package ID after upgrade.
277
+ // For localnet: savedPublishedEntry is undefined (we skip clearPublishedTomlEntry),
278
+ // so fall back to reading the current Published.toml entry for chainId/originalId.
279
+ const existingEntry = readPublishedToml(projectPath)[network];
280
+ const chainId = savedPublishedEntry?.chainId ?? existingEntry?.chainId ?? '';
281
+ updatePublishedToml(
282
+ projectPath,
283
+ network,
284
+ chainId,
285
+ newPackageId,
286
+ savedPublishedEntry?.originalId ?? existingEntry?.originalId ?? original_published_id,
287
+ oldVersion + 1
288
+ );
289
+
203
290
  saveContractData(
204
291
  name,
205
292
  network,
293
+ startCheckpoint,
206
294
  newPackageId,
207
- schemaId,
295
+ original_published_id, // originalPackageId: stable across all upgrades
296
+ dappHubId,
208
297
  upgradeCap,
209
298
  oldVersion + 1,
210
- config.schemas
299
+ config.resources ?? {},
300
+ config.enums,
301
+ frameworkPackageId,
302
+ dappStorageId || undefined
211
303
  );
212
304
 
213
- console.log(`\nšŸš€ Starting Migration Process...`);
214
- pendingMigration.forEach((migration) => {
215
- console.log('šŸ“‹ Added Fields:', JSON.stringify(migration, null, 2));
216
- });
217
- await new Promise((resolve) => setTimeout(resolve, 5000));
218
-
219
- const migrateTx = new Transaction();
220
- const newVersion = oldVersion + 1;
221
- let args = [];
222
- if (name !== 'dubhe') {
223
- let dubheSchemaId = await getDubheSchemaId(network);
224
- args.push(migrateTx.object(dubheSchemaId));
225
- }
226
- args.push(migrateTx.object(schemaId));
227
- args.push(migrateTx.pure.address(newPackageId));
228
- args.push(migrateTx.pure.u32(newVersion));
229
- migrateTx.moveCall({
230
- target: `${newPackageId}::${name}_migrate::migrate_to_v${newVersion}`,
231
- arguments: args
232
- });
305
+ // Only run the migration transaction if there are pending schema changes or a
306
+ // forced version bump was requested via --bump-version.
307
+ // A pure "bug-fix" upgrade with no new fields and no --bump-version flag does
308
+ // not need a migration call — old and new package can coexist safely.
309
+ if (needsMigration) {
310
+ if (pendingMigration.length > 0) {
311
+ console.log(`\nšŸš€ Starting Migration Process...`);
312
+ pendingMigration.forEach((migration) => {
313
+ console.log('šŸ“‹ Added Fields:', JSON.stringify(migration, null, 2));
314
+ });
315
+ } else {
316
+ console.log(`\nšŸš€ Starting Migration Process (forced version bump)...`);
317
+ }
233
318
 
234
- await dubhe.signAndSendTxn({
235
- tx: migrateTx,
236
- onSuccess: (result) => {
237
- console.log(chalk.green(`Migration Transaction Digest: ${result.digest}`));
238
- },
239
- onError: (error) => {
240
- console.log(
241
- chalk.red('Migration Transaction failed!, Please execute the migration manually.')
319
+ // On localnet the indexer may lag behind the chain; give it time to register
320
+ // the newly upgraded package before the migration transaction references it.
321
+ // On testnet/mainnet the validator processes state immediately — no delay needed.
322
+ if (network === 'localnet') {
323
+ await new Promise((resolve) => setTimeout(resolve, 5000));
324
+ }
325
+
326
+ if (!dappStorageId) {
327
+ console.warn(
328
+ chalk.yellow(
329
+ 'Warning: dappStorageId not found in latest.json. ' +
330
+ 'Re-publish the contract to capture it, or pass DappStorage manually.'
331
+ )
242
332
  );
243
- console.error(error);
244
333
  }
245
- });
334
+
335
+ const migrateTx = new Transaction();
336
+ const newVersion = oldVersion + 1;
337
+ migrateTx.moveCall({
338
+ target: `${newPackageId}::migrate::migrate_to_v${newVersion}`,
339
+ arguments: [
340
+ migrateTx.object(dappHubId),
341
+ migrateTx.object(dappStorageId),
342
+ migrateTx.pure.address(newPackageId)
343
+ ]
344
+ });
345
+
346
+ await dubhe.signAndSendTxn({
347
+ tx: migrateTx,
348
+ onSuccess: (result) => {
349
+ console.log(chalk.green(`Migration Transaction Digest: ${result.digest}`));
350
+ },
351
+ onError: (error) => {
352
+ console.log(
353
+ chalk.red('Migration Transaction failed!, Please execute the migration manually.')
354
+ );
355
+ console.error(error);
356
+ }
357
+ });
358
+ } else {
359
+ console.log(`\nāœ… No schema changes — migration step skipped.`);
360
+ // Brief delay to allow localnet to index the upgraded package before
361
+ // subsequent on-chain queries.
362
+ if (network === 'localnet') {
363
+ await new Promise((resolve) => setTimeout(resolve, 2000));
364
+ }
365
+ }
246
366
  } catch (error: any) {
367
+ // Restore Published.toml to original state on failure (persistent networks only)
368
+ if (savedPublishedEntry) {
369
+ restorePublishedTomlEntry(projectPath, network, savedPublishedEntry);
370
+ }
247
371
  console.log(chalk.red('upgrade handler execution failed!'));
248
- console.error(error.message);
372
+ throw error;
249
373
  }
250
374
  }