@0xobelisk/sui-cli 1.2.0-pre.1 → 1.2.0-pre.100
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/README.md +3 -3
- package/dist/dubhe.js +125 -66
- package/dist/dubhe.js.map +1 -1
- package/package.json +31 -19
- package/src/commands/build.ts +47 -16
- package/src/commands/call.ts +83 -83
- package/src/commands/checkBalance.ts +12 -5
- package/src/commands/configStore.ts +12 -4
- package/src/commands/convertJson.ts +70 -0
- package/src/commands/doctor.ts +1515 -0
- package/src/commands/faucet.ts +11 -7
- package/src/commands/generateKey.ts +3 -2
- package/src/commands/index.ts +16 -7
- package/src/commands/info.ts +55 -0
- package/src/commands/loadMetadata.ts +57 -0
- package/src/commands/localnode.ts +22 -12
- package/src/commands/publish.ts +21 -7
- package/src/commands/query.ts +101 -101
- package/src/commands/schemagen.ts +15 -4
- package/src/commands/shell.ts +198 -0
- package/src/commands/switchEnv.ts +26 -0
- package/src/commands/test.ts +54 -11
- package/src/commands/upgrade.ts +11 -4
- package/src/commands/wait.ts +333 -22
- package/src/commands/watch.ts +2 -1
- package/src/dubhe.ts +12 -4
- package/src/utils/axios-downloader.ts +116 -0
- package/src/utils/callHandler.ts +118 -118
- package/src/utils/constants.ts +5 -0
- package/src/utils/generateAccount.ts +1 -1
- package/src/utils/index.ts +4 -3
- package/src/utils/metadataHandler.ts +16 -0
- package/src/utils/publishHandler.ts +295 -290
- package/src/utils/queryStorage.ts +141 -141
- package/src/utils/startNode.ts +165 -108
- package/src/utils/storeConfig.ts +6 -12
- package/src/utils/upgradeHandler.ts +147 -86
- package/src/utils/utils.ts +771 -54
|
@@ -3,19 +3,46 @@ import { execSync } from 'child_process';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import {
|
|
5
5
|
saveContractData,
|
|
6
|
-
|
|
6
|
+
updateMoveTomlAddress,
|
|
7
7
|
switchEnv,
|
|
8
8
|
delay,
|
|
9
|
-
|
|
10
|
-
initializeDubhe
|
|
9
|
+
getDubheDappHub,
|
|
10
|
+
initializeDubhe,
|
|
11
|
+
saveMetadata,
|
|
12
|
+
getOriginalDubhePackageId,
|
|
13
|
+
updatePublishedToml,
|
|
14
|
+
updateEphemeralPubFile,
|
|
15
|
+
getEphemeralPubFilePath
|
|
11
16
|
} from './utils';
|
|
12
17
|
import { DubheConfig } from '@0xobelisk/sui-common';
|
|
13
18
|
import * as fs from 'fs';
|
|
14
19
|
import * as path from 'path';
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Temporarily add localnet to Move.toml [environments] section before building.
|
|
23
|
+
* Sui CLI 1.40+ requires the active environment to be declared in Move.toml even
|
|
24
|
+
* when --build-env is specified. This patches the file and returns the original
|
|
25
|
+
* content so the caller can restore it in a finally block.
|
|
26
|
+
* Returns null if no changes were needed.
|
|
27
|
+
*/
|
|
28
|
+
function patchMoveTomlWithLocalnetEnv(moveTomlPath: string, chainId: string): string | null {
|
|
29
|
+
if (!fs.existsSync(moveTomlPath)) return null;
|
|
30
|
+
const content = fs.readFileSync(moveTomlPath, 'utf-8');
|
|
31
|
+
|
|
32
|
+
if (content.includes('localnet')) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let updatedContent: string;
|
|
37
|
+
if (content.includes('[environments]')) {
|
|
38
|
+
updatedContent = content.replace('[environments]', `[environments]\nlocalnet = "${chainId}"`);
|
|
39
|
+
} else {
|
|
40
|
+
updatedContent = content.trimEnd() + `\n\n[environments]\nlocalnet = "${chainId}"\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(moveTomlPath, updatedContent, 'utf-8');
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
19
46
|
|
|
20
47
|
async function removeEnvContent(
|
|
21
48
|
filePath: string,
|
|
@@ -127,21 +154,53 @@ published-version = "${config.publishedVersion}"
|
|
|
127
154
|
// return segments.length > 0 ? segments[segments.length - 1] : '';
|
|
128
155
|
// }
|
|
129
156
|
|
|
130
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Build a Move package and return [modules, dependencies] as base64 arrays.
|
|
159
|
+
*
|
|
160
|
+
* For localnet (ephemeral) networks:
|
|
161
|
+
* - Uses --build-env testnet so dependency addresses are resolved via testnet
|
|
162
|
+
* Published.toml (no need to add 'localnet' to Move.toml [environments]).
|
|
163
|
+
* - Optionally reads a Pub.localnet.toml pubfile for already-published local deps.
|
|
164
|
+
* - This matches the Sui docs approach:
|
|
165
|
+
* https://docs.sui.io/guides/developer/packages/move-package-management
|
|
166
|
+
*
|
|
167
|
+
* For persistent networks (testnet/mainnet/devnet): uses -e <network> as before.
|
|
168
|
+
*/
|
|
169
|
+
function buildContract(
|
|
170
|
+
projectPath: string,
|
|
171
|
+
network?: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
172
|
+
pubfilePath?: string
|
|
173
|
+
): string[][] {
|
|
131
174
|
let modules: any, dependencies: any;
|
|
132
175
|
try {
|
|
176
|
+
let buildEnvFlag: string;
|
|
177
|
+
if (network === 'localnet') {
|
|
178
|
+
// Ephemeral approach: resolve deps via testnet configuration.
|
|
179
|
+
// --pubfile-path supplies already-published local dep addresses (e.g. dubhe).
|
|
180
|
+
buildEnvFlag = ' --build-env testnet';
|
|
181
|
+
if (pubfilePath) {
|
|
182
|
+
buildEnvFlag += ` --pubfile-path ${pubfilePath}`;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
buildEnvFlag = network ? ` -e ${network}` : '';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// --no-tree-shaking avoids on-chain RPC calls during build.
|
|
133
189
|
const buildResult = JSON.parse(
|
|
134
|
-
execSync(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
execSync(
|
|
191
|
+
`sui move build --dump-bytecode-as-base64 --no-tree-shaking${buildEnvFlag} --path ${projectPath}`,
|
|
192
|
+
{
|
|
193
|
+
encoding: 'utf-8',
|
|
194
|
+
stdio: 'pipe'
|
|
195
|
+
}
|
|
196
|
+
)
|
|
138
197
|
);
|
|
139
198
|
modules = buildResult.modules;
|
|
140
199
|
dependencies = buildResult.dependencies;
|
|
141
200
|
} catch (error: any) {
|
|
142
201
|
console.error(chalk.red(' └─ Build failed'));
|
|
143
|
-
console.error(error.stdout);
|
|
144
|
-
|
|
202
|
+
console.error(error.stdout || error.stderr);
|
|
203
|
+
throw new Error(`Build failed: ${error.stdout || error.stderr || error.message}`);
|
|
145
204
|
}
|
|
146
205
|
return [modules, dependencies];
|
|
147
206
|
}
|
|
@@ -154,132 +213,28 @@ interface ObjectChange {
|
|
|
154
213
|
}
|
|
155
214
|
|
|
156
215
|
async function waitForNode(dubhe: Dubhe): Promise<string> {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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++;
|
|
216
|
+
const chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
|
|
217
|
+
console.log(` ├─ ChainId: ${chainId}`);
|
|
218
|
+
const address = dubhe.getAddress();
|
|
219
|
+
const coins = await dubhe.suiInteractor.currentClient.getCoins({
|
|
220
|
+
owner: address,
|
|
221
|
+
coinType: '0x2::sui::SUI'
|
|
222
|
+
});
|
|
269
223
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
224
|
+
if (coins.data.length > 0) {
|
|
225
|
+
const balance = coins.data.reduce((sum, coin) => sum + Number(coin.balance), 0);
|
|
226
|
+
if (balance > 0) {
|
|
227
|
+
console.log(` ├─ Deployer balance: ${balance / 10 ** 9} SUI`);
|
|
228
|
+
return chainId;
|
|
229
|
+
} else {
|
|
230
|
+
console.log(
|
|
231
|
+
chalk.yellow(
|
|
232
|
+
` ├─ Deployer balance: 0 SUI, please ensure your account has sufficient SUI balance`
|
|
233
|
+
)
|
|
234
|
+
);
|
|
273
235
|
}
|
|
274
|
-
} finally {
|
|
275
|
-
process.removeListener('SIGINT', handleInterrupt);
|
|
276
236
|
}
|
|
277
|
-
|
|
278
|
-
if (isInterrupted) {
|
|
279
|
-
process.exit(0);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
throw new Error('Failed to connect to node');
|
|
237
|
+
return chainId;
|
|
283
238
|
}
|
|
284
239
|
|
|
285
240
|
async function publishContract(
|
|
@@ -301,7 +256,61 @@ async function publishContract(
|
|
|
301
256
|
console.log(` └─ Account: ${dubhe.getAddress()}`);
|
|
302
257
|
|
|
303
258
|
console.log('\n📦 Building Contract...');
|
|
304
|
-
|
|
259
|
+
// For localnet: pass the ephemeral pubfile so the build system can resolve
|
|
260
|
+
// the dubhe dependency that was just published in publishDubheFramework().
|
|
261
|
+
const pubfilePath =
|
|
262
|
+
network === 'localnet' ? getEphemeralPubFilePath(process.cwd(), network) : undefined;
|
|
263
|
+
|
|
264
|
+
// For localnet: temporarily remove Published.toml to prevent the testnet address
|
|
265
|
+
// (if it exists) from being picked up by --build-env testnet, which would cause
|
|
266
|
+
// PublishErrorNonZeroAddress. The pubfile supplies dubhe's localnet address instead.
|
|
267
|
+
const contractPublishedTomlPath = `${projectPath}/Published.toml`;
|
|
268
|
+
let savedContractPublishedToml: string | null = null;
|
|
269
|
+
if (network === 'localnet' && fs.existsSync(contractPublishedTomlPath)) {
|
|
270
|
+
savedContractPublishedToml = fs.readFileSync(contractPublishedTomlPath, 'utf-8');
|
|
271
|
+
fs.unlinkSync(contractPublishedTomlPath);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For localnet: also temporarily remove dubhe's Published.toml when building the
|
|
275
|
+
// contract package. After publishDubheFramework restores dubhe/Published.toml, its
|
|
276
|
+
// testnet entry's original-id may be "0x0", which collides with the contract package's
|
|
277
|
+
// own address (also 0x0 before publish), triggering a spurious cyclic-dependency error.
|
|
278
|
+
const dubhePublishedTomlPath = path.join(path.dirname(projectPath), 'dubhe', 'Published.toml');
|
|
279
|
+
let savedDubhePublishedToml: string | null = null;
|
|
280
|
+
if (network === 'localnet' && fs.existsSync(dubhePublishedTomlPath)) {
|
|
281
|
+
savedDubhePublishedToml = fs.readFileSync(dubhePublishedTomlPath, 'utf-8');
|
|
282
|
+
fs.unlinkSync(dubhePublishedTomlPath);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
286
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments]
|
|
287
|
+
// for both the contract and its dubhe dependency.
|
|
288
|
+
const contractMoveTomlPath = `${projectPath}/Move.toml`;
|
|
289
|
+
const dubheMoveTomlPath = path.join(path.dirname(projectPath), 'dubhe', 'Move.toml');
|
|
290
|
+
let savedContractMoveToml: string | null = null;
|
|
291
|
+
let savedDubheMoveToml: string | null = null;
|
|
292
|
+
if (network === 'localnet') {
|
|
293
|
+
savedContractMoveToml = patchMoveTomlWithLocalnetEnv(contractMoveTomlPath, chainId);
|
|
294
|
+
savedDubheMoveToml = patchMoveTomlWithLocalnetEnv(dubheMoveTomlPath, chainId);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let modules: any, dependencies: any;
|
|
298
|
+
try {
|
|
299
|
+
[modules, dependencies] = buildContract(projectPath, network, pubfilePath);
|
|
300
|
+
} finally {
|
|
301
|
+
if (savedContractPublishedToml !== null) {
|
|
302
|
+
fs.writeFileSync(contractPublishedTomlPath, savedContractPublishedToml, 'utf-8');
|
|
303
|
+
}
|
|
304
|
+
if (savedDubhePublishedToml !== null) {
|
|
305
|
+
fs.writeFileSync(dubhePublishedTomlPath, savedDubhePublishedToml, 'utf-8');
|
|
306
|
+
}
|
|
307
|
+
if (savedContractMoveToml !== null) {
|
|
308
|
+
fs.writeFileSync(contractMoveTomlPath, savedContractMoveToml, 'utf-8');
|
|
309
|
+
}
|
|
310
|
+
if (savedDubheMoveToml !== null) {
|
|
311
|
+
fs.writeFileSync(dubheMoveTomlPath, savedDubheMoveToml, 'utf-8');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
305
314
|
|
|
306
315
|
console.log('\n🔄 Publishing Contract...');
|
|
307
316
|
const tx = new Transaction();
|
|
@@ -311,63 +320,30 @@ async function publishContract(
|
|
|
311
320
|
const [upgradeCap] = tx.publish({ modules, dependencies });
|
|
312
321
|
tx.transferObjects([upgradeCap], dubhe.getAddress());
|
|
313
322
|
|
|
314
|
-
let result
|
|
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
|
-
|
|
323
|
+
let result;
|
|
328
324
|
try {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (isInterrupted) {
|
|
355
|
-
process.exit(0);
|
|
325
|
+
result = await dubhe.signAndSendTxn({ tx });
|
|
326
|
+
} catch (error: any) {
|
|
327
|
+
console.error(chalk.red(' └─ Publication failed'));
|
|
328
|
+
console.error(error.message);
|
|
329
|
+
throw new Error(`Contract publication failed: ${error.message}`);
|
|
356
330
|
}
|
|
357
331
|
|
|
358
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
359
|
-
|
|
360
332
|
if (!result || result.effects?.status.status === 'failure') {
|
|
361
|
-
|
|
362
|
-
process.exit(1);
|
|
333
|
+
throw new Error('Contract publication transaction failed');
|
|
363
334
|
}
|
|
364
335
|
|
|
365
336
|
console.log(' ├─ Processing publication results...');
|
|
366
337
|
let version = 1;
|
|
367
338
|
let packageId = '';
|
|
368
|
-
let
|
|
369
|
-
let
|
|
339
|
+
let dappHub = '';
|
|
340
|
+
let components = dubheConfig.components;
|
|
341
|
+
let resources = dubheConfig.resources;
|
|
342
|
+
let enums = dubheConfig.enums;
|
|
370
343
|
let upgradeCapId = '';
|
|
344
|
+
let startCheckpoint = '';
|
|
345
|
+
|
|
346
|
+
let printObjects: any[] = [];
|
|
371
347
|
|
|
372
348
|
result.objectChanges!.map((object: ObjectChange) => {
|
|
373
349
|
if (object.type === 'published') {
|
|
@@ -382,24 +358,35 @@ async function publishContract(
|
|
|
382
358
|
console.log(` ├─ Upgrade Cap: ${object.objectId}`);
|
|
383
359
|
upgradeCapId = object.objectId || '';
|
|
384
360
|
}
|
|
361
|
+
if (
|
|
362
|
+
object.type === 'created' &&
|
|
363
|
+
object.objectType &&
|
|
364
|
+
object.objectType.includes('dapp_service::DappHub')
|
|
365
|
+
) {
|
|
366
|
+
dappHub = object.objectId || '';
|
|
367
|
+
}
|
|
368
|
+
if (object.type === 'created') {
|
|
369
|
+
printObjects.push(object);
|
|
370
|
+
}
|
|
385
371
|
});
|
|
386
372
|
|
|
387
373
|
console.log(` └─ Transaction: ${result.digest}`);
|
|
388
374
|
|
|
389
375
|
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
376
|
+
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
390
377
|
|
|
391
378
|
console.log('\n⚡ Executing Deploy Hook...');
|
|
392
379
|
await delay(5000);
|
|
393
380
|
|
|
381
|
+
startCheckpoint = await dubhe.suiInteractor.currentClient.getLatestCheckpointSequenceNumber();
|
|
382
|
+
|
|
394
383
|
const deployHookTx = new Transaction();
|
|
395
384
|
let args = [];
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
args.push(deployHookTx.object(dubheSchemaId));
|
|
399
|
-
}
|
|
385
|
+
let dubheDappHub = dubheConfig.name === 'dubhe' ? dappHub : await getDubheDappHub(network);
|
|
386
|
+
args.push(deployHookTx.object(dubheDappHub));
|
|
400
387
|
args.push(deployHookTx.object('0x6'));
|
|
401
388
|
deployHookTx.moveCall({
|
|
402
|
-
target: `${packageId}
|
|
389
|
+
target: `${packageId}::genesis::run`,
|
|
403
390
|
arguments: args
|
|
404
391
|
});
|
|
405
392
|
|
|
@@ -409,49 +396,46 @@ async function publishContract(
|
|
|
409
396
|
} catch (error: any) {
|
|
410
397
|
console.error(chalk.red(' └─ Deploy hook execution failed'));
|
|
411
398
|
console.error(error.message);
|
|
412
|
-
|
|
399
|
+
throw new Error(`genesis::run failed: ${error.message}`);
|
|
413
400
|
}
|
|
414
401
|
|
|
415
402
|
if (deployHookResult.effects?.status.status === 'success') {
|
|
416
403
|
console.log(' ├─ Hook execution successful');
|
|
417
404
|
console.log(` ├─ Transaction: ${deployHookResult.digest}`);
|
|
418
405
|
|
|
419
|
-
console.log('\n📋 Created
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
object.objectType &&
|
|
424
|
-
object.objectType.includes('schema::Schema')
|
|
425
|
-
) {
|
|
426
|
-
schemaId = object.objectId || '';
|
|
427
|
-
}
|
|
428
|
-
if (
|
|
429
|
-
object.type === 'created' &&
|
|
430
|
-
object.objectType &&
|
|
431
|
-
object.objectType.includes('schema') &&
|
|
432
|
-
!object.objectType.includes('dynamic_field')
|
|
433
|
-
) {
|
|
434
|
-
console.log(` ├─ Type: ${object.objectType}`);
|
|
435
|
-
console.log(` └─ ID: ${object.objectId}`);
|
|
436
|
-
}
|
|
406
|
+
console.log('\n📋 Created Objects:');
|
|
407
|
+
printObjects.map((object: ObjectChange) => {
|
|
408
|
+
console.log(` ├─ ID: ${object.objectId}`);
|
|
409
|
+
console.log(` └─ Type: ${object.objectType}`);
|
|
437
410
|
});
|
|
438
411
|
|
|
439
|
-
saveContractData(
|
|
412
|
+
await saveContractData(
|
|
440
413
|
dubheConfig.name,
|
|
441
414
|
network,
|
|
415
|
+
startCheckpoint,
|
|
442
416
|
packageId,
|
|
443
|
-
|
|
417
|
+
dubheDappHub,
|
|
444
418
|
upgradeCapId,
|
|
445
419
|
version,
|
|
446
|
-
|
|
420
|
+
components,
|
|
421
|
+
resources,
|
|
422
|
+
enums
|
|
447
423
|
);
|
|
424
|
+
|
|
425
|
+
await saveMetadata(dubheConfig.name, network, packageId);
|
|
426
|
+
|
|
427
|
+
// Insert package id to dubhe config
|
|
428
|
+
let config = JSON.parse(fs.readFileSync(`${process.cwd()}/dubhe.config.json`, 'utf-8'));
|
|
429
|
+
config.original_package_id = packageId;
|
|
430
|
+
config.dubhe_object_id = dubheDappHub;
|
|
431
|
+
config.original_dubhe_package_id = await getOriginalDubhePackageId(network);
|
|
432
|
+
config.start_checkpoint = startCheckpoint;
|
|
433
|
+
|
|
434
|
+
fs.writeFileSync(`${process.cwd()}/dubhe.config.json`, JSON.stringify(config, null, 2));
|
|
435
|
+
|
|
448
436
|
console.log('\n✅ Contract Publication Complete\n');
|
|
449
437
|
} else {
|
|
450
|
-
|
|
451
|
-
console.log(chalk.yellow(' Please republish or manually call deploy_hook::run'));
|
|
452
|
-
console.log(chalk.yellow(' Please check the transaction digest:'));
|
|
453
|
-
console.log(chalk.yellow(` ${deployHookResult.digest}`));
|
|
454
|
-
process.exit(1);
|
|
438
|
+
throw new Error(`genesis::run transaction failed. Digest: ${deployHookResult.digest}`);
|
|
455
439
|
}
|
|
456
440
|
}
|
|
457
441
|
|
|
@@ -460,15 +444,13 @@ async function checkDubheFramework(projectPath: string): Promise<boolean> {
|
|
|
460
444
|
console.log(chalk.yellow('\nℹ️ Dubhe Framework Files Not Found'));
|
|
461
445
|
console.log(chalk.yellow(' ├─ Expected Path:'), projectPath);
|
|
462
446
|
console.log(chalk.yellow(' ├─ To set up Dubhe Framework:'));
|
|
463
|
-
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p contracts/dubhe
|
|
447
|
+
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p contracts/dubhe'));
|
|
464
448
|
console.log(
|
|
465
449
|
chalk.yellow(
|
|
466
|
-
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe
|
|
450
|
+
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe contracts/dubhe'
|
|
467
451
|
)
|
|
468
452
|
);
|
|
469
|
-
console.log(
|
|
470
|
-
chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe-framework')
|
|
471
|
-
);
|
|
453
|
+
console.log(chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe'));
|
|
472
454
|
console.log(chalk.yellow(' └─ After setup, restart the local node'));
|
|
473
455
|
return false;
|
|
474
456
|
}
|
|
@@ -479,8 +461,8 @@ export async function publishDubheFramework(
|
|
|
479
461
|
dubhe: Dubhe,
|
|
480
462
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
|
|
481
463
|
) {
|
|
482
|
-
const
|
|
483
|
-
const projectPath = `${
|
|
464
|
+
const cwd = process.cwd();
|
|
465
|
+
const projectPath = `${cwd}/src/dubhe`;
|
|
484
466
|
|
|
485
467
|
if (!(await checkDubheFramework(projectPath))) {
|
|
486
468
|
return;
|
|
@@ -492,66 +474,66 @@ export async function publishDubheFramework(
|
|
|
492
474
|
const chainId = await waitForNode(dubhe);
|
|
493
475
|
|
|
494
476
|
await removeEnvContent(`${projectPath}/Move.lock`, network);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
477
|
+
await updateMoveTomlAddress(projectPath, '0x0');
|
|
478
|
+
|
|
479
|
+
const startCheckpoint =
|
|
480
|
+
await dubhe.suiInteractor.currentClient.getLatestCheckpointSequenceNumber();
|
|
481
|
+
|
|
482
|
+
// For localnet: --build-env testnet is used to resolve git dependencies, but the
|
|
483
|
+
// Move CLI will also read Published.toml and use any existing testnet address for
|
|
484
|
+
// dubhe — causing PublishErrorNonZeroAddress if a testnet entry already exists.
|
|
485
|
+
// Fix: temporarily remove Published.toml before the build, then restore it.
|
|
486
|
+
// This ensures the dubhe package compiles with address 0x0 (from Move.toml).
|
|
487
|
+
const publishedTomlPath = `${projectPath}/Published.toml`;
|
|
488
|
+
let savedPublishedTomlContent: string | null = null;
|
|
489
|
+
if (network === 'localnet' && fs.existsSync(publishedTomlPath)) {
|
|
490
|
+
savedPublishedTomlContent = fs.readFileSync(publishedTomlPath, 'utf-8');
|
|
491
|
+
fs.unlinkSync(publishedTomlPath);
|
|
492
|
+
}
|
|
499
493
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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);
|
|
494
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
495
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments].
|
|
496
|
+
const moveTomlPath = `${projectPath}/Move.toml`;
|
|
497
|
+
let savedMoveTomlContent: string | null = null;
|
|
498
|
+
if (network === 'localnet') {
|
|
499
|
+
savedMoveTomlContent = patchMoveTomlWithLocalnetEnv(moveTomlPath, chainId);
|
|
500
|
+
}
|
|
513
501
|
|
|
502
|
+
let modules: any, dependencies: any;
|
|
514
503
|
try {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
}
|
|
504
|
+
// For localnet: use --build-env testnet (no pubfile needed — dubhe has no local deps).
|
|
505
|
+
// For testnet/mainnet: use -e <network> as usual.
|
|
506
|
+
[modules, dependencies] = buildContract(projectPath, network);
|
|
536
507
|
} finally {
|
|
537
|
-
|
|
508
|
+
// Always restore Published.toml and Move.toml (successful build or error)
|
|
509
|
+
if (savedPublishedTomlContent !== null) {
|
|
510
|
+
fs.writeFileSync(publishedTomlPath, savedPublishedTomlContent, 'utf-8');
|
|
511
|
+
}
|
|
512
|
+
if (savedMoveTomlContent !== null) {
|
|
513
|
+
fs.writeFileSync(moveTomlPath, savedMoveTomlContent, 'utf-8');
|
|
514
|
+
}
|
|
538
515
|
}
|
|
539
516
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
517
|
+
const tx = new Transaction();
|
|
518
|
+
const [upgradeCap] = tx.publish({ modules, dependencies });
|
|
519
|
+
tx.transferObjects([upgradeCap], dubhe.getAddress());
|
|
543
520
|
|
|
544
|
-
|
|
521
|
+
let result;
|
|
522
|
+
try {
|
|
523
|
+
result = await dubhe.signAndSendTxn({ tx });
|
|
524
|
+
} catch (error: any) {
|
|
525
|
+
console.error(chalk.red(' └─ Publication failed'));
|
|
526
|
+
console.error(error.message);
|
|
527
|
+
throw new Error(`Dubhe framework publication failed: ${error.message}`);
|
|
528
|
+
}
|
|
545
529
|
|
|
546
530
|
if (!result || result.effects?.status.status === 'failure') {
|
|
547
|
-
|
|
548
|
-
process.exit(1);
|
|
531
|
+
throw new Error('Dubhe framework publication transaction failed');
|
|
549
532
|
}
|
|
550
533
|
|
|
551
534
|
let version = 1;
|
|
552
535
|
let packageId = '';
|
|
553
|
-
let
|
|
554
|
-
let schemas: Record<string, string> = {};
|
|
536
|
+
let dappHub = '';
|
|
555
537
|
let upgradeCapId = '';
|
|
556
538
|
|
|
557
539
|
result.objectChanges!.map((object: ObjectChange) => {
|
|
@@ -565,14 +547,20 @@ export async function publishDubheFramework(
|
|
|
565
547
|
) {
|
|
566
548
|
upgradeCapId = object.objectId || '';
|
|
567
549
|
}
|
|
550
|
+
if (
|
|
551
|
+
object.type === 'created' &&
|
|
552
|
+
object.objectType &&
|
|
553
|
+
object.objectType.includes('dapp_service::DappHub')
|
|
554
|
+
) {
|
|
555
|
+
dappHub = object.objectId || '';
|
|
556
|
+
}
|
|
568
557
|
});
|
|
569
558
|
|
|
570
559
|
await delay(3000);
|
|
571
|
-
|
|
572
560
|
const deployHookTx = new Transaction();
|
|
573
561
|
deployHookTx.moveCall({
|
|
574
|
-
target: `${packageId}::
|
|
575
|
-
arguments: [deployHookTx.object('0x6')]
|
|
562
|
+
target: `${packageId}::genesis::run`,
|
|
563
|
+
arguments: [deployHookTx.object(dappHub), deployHookTx.object('0x6')]
|
|
576
564
|
});
|
|
577
565
|
|
|
578
566
|
let deployHookResult;
|
|
@@ -581,30 +569,47 @@ export async function publishDubheFramework(
|
|
|
581
569
|
} catch (error: any) {
|
|
582
570
|
console.error(chalk.red(' └─ Deploy hook execution failed'));
|
|
583
571
|
console.error(error.message);
|
|
584
|
-
|
|
572
|
+
throw new Error(`Dubhe genesis::run failed: ${error.message}`);
|
|
585
573
|
}
|
|
586
574
|
|
|
587
|
-
if (deployHookResult.effects?.status.status
|
|
588
|
-
|
|
589
|
-
if (
|
|
590
|
-
object.type === 'created' &&
|
|
591
|
-
object.objectType &&
|
|
592
|
-
object.objectType.includes('dubhe_schema::Schema')
|
|
593
|
-
) {
|
|
594
|
-
schemaId = object.objectId || '';
|
|
595
|
-
}
|
|
596
|
-
});
|
|
575
|
+
if (deployHookResult.effects?.status.status !== 'success') {
|
|
576
|
+
throw new Error('Deploy hook execution failed');
|
|
597
577
|
}
|
|
598
578
|
|
|
599
|
-
|
|
579
|
+
await updateMoveTomlAddress(projectPath, packageId);
|
|
580
|
+
await saveContractData(
|
|
581
|
+
'dubhe',
|
|
582
|
+
network,
|
|
583
|
+
startCheckpoint,
|
|
584
|
+
packageId,
|
|
585
|
+
dappHub,
|
|
586
|
+
upgradeCapId,
|
|
587
|
+
version,
|
|
588
|
+
{},
|
|
589
|
+
{},
|
|
590
|
+
{}
|
|
591
|
+
);
|
|
600
592
|
|
|
601
593
|
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
602
|
-
|
|
594
|
+
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
595
|
+
|
|
596
|
+
// For localnet: write dubhe's published address to Pub.localnet.toml so that
|
|
597
|
+
// the counter package build (next step) can resolve the dubhe dependency.
|
|
598
|
+
if (network === 'localnet') {
|
|
599
|
+
const pubfilePath = getEphemeralPubFilePath(cwd, network);
|
|
600
|
+
updateEphemeralPubFile(pubfilePath, chainId, 'testnet', {
|
|
601
|
+
source: projectPath,
|
|
602
|
+
publishedAt: packageId,
|
|
603
|
+
originalId: packageId,
|
|
604
|
+
upgradeCap: upgradeCapId
|
|
605
|
+
});
|
|
606
|
+
}
|
|
603
607
|
}
|
|
604
608
|
|
|
605
609
|
export async function publishHandler(
|
|
606
610
|
dubheConfig: DubheConfig,
|
|
607
611
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
612
|
+
_force: boolean,
|
|
608
613
|
gasBudget?: number
|
|
609
614
|
) {
|
|
610
615
|
await switchEnv(network);
|
|
@@ -613,12 +618,12 @@ export async function publishHandler(
|
|
|
613
618
|
network
|
|
614
619
|
});
|
|
615
620
|
|
|
616
|
-
|
|
621
|
+
const path = process.cwd();
|
|
622
|
+
const projectPath = `${path}/src/${dubheConfig.name}`;
|
|
623
|
+
|
|
624
|
+
if (network === 'localnet' && dubheConfig.name !== 'dubhe') {
|
|
617
625
|
await publishDubheFramework(dubhe, network);
|
|
618
626
|
}
|
|
619
627
|
|
|
620
|
-
const path = process.cwd();
|
|
621
|
-
const projectPath = `${path}/contracts/${dubheConfig.name}`;
|
|
622
|
-
await updateDubheDependency(`${projectPath}/Move.toml`, network);
|
|
623
628
|
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget);
|
|
624
629
|
}
|