@0xobelisk/sui-cli 1.2.0-pre.12 → 1.2.0-pre.121
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 +7 -7
- package/dist/dubhe.js +152 -51
- package/dist/dubhe.js.map +1 -1
- package/package.json +31 -19
- package/src/commands/build.ts +61 -18
- package/src/commands/call.ts +83 -83
- package/src/commands/checkBalance.ts +27 -12
- package/src/commands/convertJson.ts +85 -0
- package/src/commands/doctor.ts +1515 -0
- package/src/commands/faucet.ts +20 -10
- package/src/commands/generate.ts +61 -0
- package/src/commands/generateKey.ts +3 -2
- package/src/commands/index.ts +20 -11
- package/src/commands/info.ts +61 -0
- package/src/commands/loadMetadata.ts +68 -0
- package/src/commands/localnode.ts +22 -6
- package/src/commands/publish.ts +55 -7
- package/src/commands/query.ts +101 -101
- package/src/commands/shell.ts +208 -0
- package/src/commands/{configStore.ts → storeConfig.ts} +13 -5
- package/src/commands/switchEnv.ts +33 -0
- package/src/commands/test.ts +143 -31
- package/src/commands/upgrade.ts +46 -6
- package/src/commands/wait.ts +333 -22
- package/src/commands/watch.ts +9 -8
- package/src/dubhe.ts +12 -4
- package/src/utils/axios-downloader.ts +116 -0
- package/src/utils/callHandler.ts +118 -118
- package/src/utils/checkBalance.ts +6 -2
- package/src/utils/constants.ts +9 -0
- package/src/utils/generateAccount.ts +1 -1
- package/src/utils/index.ts +4 -3
- package/src/utils/metadataHandler.ts +17 -0
- package/src/utils/publishHandler.ts +408 -289
- package/src/utils/queryStorage.ts +141 -141
- package/src/utils/startNode.ts +115 -16
- package/src/utils/storeConfig.ts +50 -10
- package/src/utils/upgradeHandler.ts +218 -85
- package/src/utils/utils.ts +1041 -63
- package/src/commands/schemagen.ts +0 -40
|
@@ -3,19 +3,50 @@ 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
|
+
getDubheDappHubId,
|
|
10
|
+
initializeDubhe,
|
|
11
|
+
saveMetadata,
|
|
12
|
+
getOriginalDubhePackageId,
|
|
13
|
+
updatePublishedToml,
|
|
14
|
+
syncDubheFrameworkAddress,
|
|
15
|
+
updateEphemeralPubFile,
|
|
16
|
+
getEphemeralPubFilePath,
|
|
17
|
+
getPublishedTomlEntry,
|
|
18
|
+
clearPublishedTomlEntry,
|
|
19
|
+
restorePublishedTomlEntry
|
|
11
20
|
} from './utils';
|
|
12
21
|
import { DubheConfig } from '@0xobelisk/sui-common';
|
|
13
22
|
import * as fs from 'fs';
|
|
14
23
|
import * as path from 'path';
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Temporarily add localnet to Move.toml [environments] section before building.
|
|
27
|
+
* Sui CLI 1.40+ requires the active environment to be declared in Move.toml even
|
|
28
|
+
* when --build-env is specified. This patches the file and returns the original
|
|
29
|
+
* content so the caller can restore it in a finally block.
|
|
30
|
+
* Returns null if no changes were needed.
|
|
31
|
+
*/
|
|
32
|
+
function patchMoveTomlWithLocalnetEnv(moveTomlPath: string, chainId: string): string | null {
|
|
33
|
+
if (!fs.existsSync(moveTomlPath)) return null;
|
|
34
|
+
const content = fs.readFileSync(moveTomlPath, 'utf-8');
|
|
35
|
+
|
|
36
|
+
if (content.includes('localnet')) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let updatedContent: string;
|
|
41
|
+
if (content.includes('[environments]')) {
|
|
42
|
+
updatedContent = content.replace('[environments]', `[environments]\nlocalnet = "${chainId}"`);
|
|
43
|
+
} else {
|
|
44
|
+
updatedContent = content.trimEnd() + `\n\n[environments]\nlocalnet = "${chainId}"\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(moveTomlPath, updatedContent, 'utf-8');
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
19
50
|
|
|
20
51
|
async function removeEnvContent(
|
|
21
52
|
filePath: string,
|
|
@@ -127,21 +158,53 @@ published-version = "${config.publishedVersion}"
|
|
|
127
158
|
// return segments.length > 0 ? segments[segments.length - 1] : '';
|
|
128
159
|
// }
|
|
129
160
|
|
|
130
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Build a Move package and return [modules, dependencies] as base64 arrays.
|
|
163
|
+
*
|
|
164
|
+
* For localnet (ephemeral) networks:
|
|
165
|
+
* - Uses --build-env testnet so dependency addresses are resolved via testnet
|
|
166
|
+
* Published.toml (no need to add 'localnet' to Move.toml [environments]).
|
|
167
|
+
* - Optionally reads a Pub.localnet.toml pubfile for already-published local deps.
|
|
168
|
+
* - This matches the Sui docs approach:
|
|
169
|
+
* https://docs.sui.io/guides/developer/packages/move-package-management
|
|
170
|
+
*
|
|
171
|
+
* For persistent networks (testnet/mainnet/devnet): uses -e <network> as before.
|
|
172
|
+
*/
|
|
173
|
+
function buildContract(
|
|
174
|
+
projectPath: string,
|
|
175
|
+
network?: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
176
|
+
pubfilePath?: string
|
|
177
|
+
): string[][] {
|
|
131
178
|
let modules: any, dependencies: any;
|
|
132
179
|
try {
|
|
180
|
+
let buildEnvFlag: string;
|
|
181
|
+
if (network === 'localnet') {
|
|
182
|
+
// Ephemeral approach: resolve deps via testnet configuration.
|
|
183
|
+
// --pubfile-path supplies already-published local dep addresses (e.g. dubhe).
|
|
184
|
+
buildEnvFlag = ' --build-env testnet';
|
|
185
|
+
if (pubfilePath) {
|
|
186
|
+
buildEnvFlag += ` --pubfile-path ${pubfilePath}`;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
buildEnvFlag = network ? ` -e ${network}` : '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --no-tree-shaking avoids on-chain RPC calls during build.
|
|
133
193
|
const buildResult = JSON.parse(
|
|
134
|
-
execSync(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
194
|
+
execSync(
|
|
195
|
+
`sui move build --dump-bytecode-as-base64 --no-tree-shaking${buildEnvFlag} --path ${projectPath}`,
|
|
196
|
+
{
|
|
197
|
+
encoding: 'utf-8',
|
|
198
|
+
stdio: 'pipe'
|
|
199
|
+
}
|
|
200
|
+
)
|
|
138
201
|
);
|
|
139
202
|
modules = buildResult.modules;
|
|
140
203
|
dependencies = buildResult.dependencies;
|
|
141
204
|
} catch (error: any) {
|
|
142
205
|
console.error(chalk.red(' └─ Build failed'));
|
|
143
|
-
console.error(error.stdout);
|
|
144
|
-
|
|
206
|
+
console.error(error.stdout || error.stderr);
|
|
207
|
+
throw new Error(`Build failed: ${error.stdout || error.stderr || error.message}`);
|
|
145
208
|
}
|
|
146
209
|
return [modules, dependencies];
|
|
147
210
|
}
|
|
@@ -154,132 +217,28 @@ interface ObjectChange {
|
|
|
154
217
|
}
|
|
155
218
|
|
|
156
219
|
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++;
|
|
220
|
+
const chainId = await dubhe.suiInteractor.currentClient.getChainIdentifier();
|
|
221
|
+
console.log(` ├─ ChainId: ${chainId}`);
|
|
222
|
+
const address = dubhe.getAddress();
|
|
223
|
+
const coins = await dubhe.suiInteractor.currentClient.getCoins({
|
|
224
|
+
owner: address,
|
|
225
|
+
coinType: '0x2::sui::SUI'
|
|
226
|
+
});
|
|
269
227
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
228
|
+
if (coins.data.length > 0) {
|
|
229
|
+
const balance = coins.data.reduce((sum, coin) => sum + Number(coin.balance), 0);
|
|
230
|
+
if (balance > 0) {
|
|
231
|
+
console.log(` ├─ Deployer balance: ${balance / 10 ** 9} SUI`);
|
|
232
|
+
return chainId;
|
|
233
|
+
} else {
|
|
234
|
+
console.log(
|
|
235
|
+
chalk.yellow(
|
|
236
|
+
` ├─ Deployer balance: 0 SUI, please ensure your account has sufficient SUI balance`
|
|
237
|
+
)
|
|
238
|
+
);
|
|
273
239
|
}
|
|
274
|
-
} finally {
|
|
275
|
-
process.removeListener('SIGINT', handleInterrupt);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (isInterrupted) {
|
|
279
|
-
process.exit(0);
|
|
280
240
|
}
|
|
281
|
-
|
|
282
|
-
throw new Error('Failed to connect to node');
|
|
241
|
+
return chainId;
|
|
283
242
|
}
|
|
284
243
|
|
|
285
244
|
async function publishContract(
|
|
@@ -287,7 +246,9 @@ async function publishContract(
|
|
|
287
246
|
dubheConfig: DubheConfig,
|
|
288
247
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
289
248
|
projectPath: string,
|
|
290
|
-
gasBudget?: number
|
|
249
|
+
gasBudget?: number,
|
|
250
|
+
force?: boolean,
|
|
251
|
+
fullnodeUrls?: string[]
|
|
291
252
|
) {
|
|
292
253
|
console.log('\n🚀 Starting Contract Publication...');
|
|
293
254
|
console.log(` ├─ Project: ${projectPath}`);
|
|
@@ -300,8 +261,116 @@ async function publishContract(
|
|
|
300
261
|
await removeEnvContent(`${projectPath}/Move.lock`, network);
|
|
301
262
|
console.log(` └─ Account: ${dubhe.getAddress()}`);
|
|
302
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
|
+
|
|
303
272
|
console.log('\n📦 Building Contract...');
|
|
304
|
-
|
|
273
|
+
// For localnet: pass the ephemeral pubfile so the build system can resolve
|
|
274
|
+
// the dubhe dependency that was just published in publishDubheFramework().
|
|
275
|
+
// If the file was written for a different chain (node restarted with
|
|
276
|
+
// --force-regenesis), discard it to avoid a chain-id mismatch build error.
|
|
277
|
+
let pubfilePath =
|
|
278
|
+
network === 'localnet' ? getEphemeralPubFilePath(process.cwd(), network) : undefined;
|
|
279
|
+
if (pubfilePath && fs.existsSync(pubfilePath)) {
|
|
280
|
+
const pubfileContent = fs.readFileSync(pubfilePath, 'utf-8');
|
|
281
|
+
const chainIdMatch = pubfileContent.match(/^chain-id\s*=\s*"([^"]*)"/m);
|
|
282
|
+
const pubfileChainId = chainIdMatch ? chainIdMatch[1] : '';
|
|
283
|
+
if (pubfileChainId && pubfileChainId !== chainId) {
|
|
284
|
+
console.log(
|
|
285
|
+
chalk.yellow(
|
|
286
|
+
` ├─ Stale Pub.localnet.toml (chain ${pubfileChainId} → ${chainId}), discarding`
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
fs.unlinkSync(pubfilePath);
|
|
290
|
+
pubfilePath = undefined;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Move.toml paths — declared early so both the Published.toml handling block and
|
|
295
|
+
// the localnet env-patching block can reference them.
|
|
296
|
+
const contractMoveTomlPath = `${projectPath}/Move.toml`;
|
|
297
|
+
const dubheMoveTomlPath = path.join(path.dirname(projectPath), 'dubhe', 'Move.toml');
|
|
298
|
+
let savedContractMoveToml: string | null = null;
|
|
299
|
+
let savedDubheMoveToml: string | null = null;
|
|
300
|
+
|
|
301
|
+
// So the build uses package address 0x0: for localnet always remove the contract's
|
|
302
|
+
// Published.toml; for testnet/mainnet/devnet only when --force (clear current network entry).
|
|
303
|
+
// Otherwise Sui CLI bakes the existing [published.<network>] address into the bytecode and
|
|
304
|
+
// the chain rejects with PublishErrorNonZeroAddress.
|
|
305
|
+
const contractPublishedTomlPath = `${projectPath}/Published.toml`;
|
|
306
|
+
let savedContractPublishedToml: string | null = null;
|
|
307
|
+
let savedContractPublishedEntry: {
|
|
308
|
+
network: string;
|
|
309
|
+
entry: Exclude<ReturnType<typeof getPublishedTomlEntry>, undefined>;
|
|
310
|
+
} | null = null;
|
|
311
|
+
if (network === 'localnet' && fs.existsSync(contractPublishedTomlPath)) {
|
|
312
|
+
savedContractPublishedToml = fs.readFileSync(contractPublishedTomlPath, 'utf-8');
|
|
313
|
+
fs.unlinkSync(contractPublishedTomlPath);
|
|
314
|
+
} else if (network === 'testnet' || network === 'mainnet' || network === 'devnet') {
|
|
315
|
+
const entry = getPublishedTomlEntry(projectPath, network);
|
|
316
|
+
if (entry && force) {
|
|
317
|
+
// Existing entry + --force: clear it so the build uses 0x0 instead of the old address.
|
|
318
|
+
savedContractPublishedEntry = { network, entry };
|
|
319
|
+
clearPublishedTomlEntry(projectPath, network);
|
|
320
|
+
} else if (!entry) {
|
|
321
|
+
// No Published.toml entry for this network (first-time deploy to this network).
|
|
322
|
+
// The Sui CLI has no per-network override and falls back to Move.toml's [addresses]
|
|
323
|
+
// value, which may be non-zero from a previous deployment on a different network,
|
|
324
|
+
// causing PublishErrorNonZeroAddress.
|
|
325
|
+
// Temporarily zero out Move.toml before building so the self-address is 0x0.
|
|
326
|
+
savedContractMoveToml = fs.readFileSync(contractMoveTomlPath, 'utf-8');
|
|
327
|
+
updateMoveTomlAddress(projectPath, '0x0');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// For localnet: also temporarily remove dubhe's Published.toml when building the
|
|
332
|
+
// contract package. After publishDubheFramework restores dubhe/Published.toml, its
|
|
333
|
+
// testnet entry's original-id may be "0x0", which collides with the contract package's
|
|
334
|
+
// own address (also 0x0 before publish), triggering a spurious cyclic-dependency error.
|
|
335
|
+
const dubhePublishedTomlPath = path.join(path.dirname(projectPath), 'dubhe', 'Published.toml');
|
|
336
|
+
let savedDubhePublishedToml: string | null = null;
|
|
337
|
+
if (network === 'localnet' && fs.existsSync(dubhePublishedTomlPath)) {
|
|
338
|
+
savedDubhePublishedToml = fs.readFileSync(dubhePublishedTomlPath, 'utf-8');
|
|
339
|
+
fs.unlinkSync(dubhePublishedTomlPath);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
343
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments]
|
|
344
|
+
// for both the contract and its dubhe dependency.
|
|
345
|
+
if (network === 'localnet') {
|
|
346
|
+
savedContractMoveToml = patchMoveTomlWithLocalnetEnv(contractMoveTomlPath, chainId);
|
|
347
|
+
savedDubheMoveToml = patchMoveTomlWithLocalnetEnv(dubheMoveTomlPath, chainId);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let modules: any, dependencies: any;
|
|
351
|
+
try {
|
|
352
|
+
[modules, dependencies] = buildContract(projectPath, network, pubfilePath);
|
|
353
|
+
} finally {
|
|
354
|
+
if (savedContractPublishedToml !== null) {
|
|
355
|
+
fs.writeFileSync(contractPublishedTomlPath, savedContractPublishedToml, 'utf-8');
|
|
356
|
+
}
|
|
357
|
+
if (savedContractPublishedEntry !== null) {
|
|
358
|
+
restorePublishedTomlEntry(
|
|
359
|
+
projectPath,
|
|
360
|
+
savedContractPublishedEntry.network,
|
|
361
|
+
savedContractPublishedEntry.entry
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (savedDubhePublishedToml !== null) {
|
|
365
|
+
fs.writeFileSync(dubhePublishedTomlPath, savedDubhePublishedToml, 'utf-8');
|
|
366
|
+
}
|
|
367
|
+
if (savedContractMoveToml !== null) {
|
|
368
|
+
fs.writeFileSync(contractMoveTomlPath, savedContractMoveToml, 'utf-8');
|
|
369
|
+
}
|
|
370
|
+
if (savedDubheMoveToml !== null) {
|
|
371
|
+
fs.writeFileSync(dubheMoveTomlPath, savedDubheMoveToml, 'utf-8');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
305
374
|
|
|
306
375
|
console.log('\n🔄 Publishing Contract...');
|
|
307
376
|
const tx = new Transaction();
|
|
@@ -311,63 +380,38 @@ async function publishContract(
|
|
|
311
380
|
const [upgradeCap] = tx.publish({ modules, dependencies });
|
|
312
381
|
tx.transferObjects([upgradeCap], dubhe.getAddress());
|
|
313
382
|
|
|
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
|
-
|
|
383
|
+
let result;
|
|
328
384
|
try {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
}
|
|
385
|
+
result = await dubhe.signAndSendTxn({ tx });
|
|
386
|
+
} catch (error: any) {
|
|
387
|
+
console.error(chalk.red(' └─ Publication failed'));
|
|
388
|
+
console.error(error.message);
|
|
389
|
+
if (
|
|
390
|
+
!force &&
|
|
391
|
+
(network === 'testnet' || network === 'mainnet' || network === 'devnet') &&
|
|
392
|
+
/PublishErrorNonZeroAddress/i.test(String(error?.message))
|
|
393
|
+
) {
|
|
394
|
+
console.error(
|
|
395
|
+
chalk.yellow(
|
|
396
|
+
' Tip: This package may already be published on this network. Use --force to clear the stored address and publish as new, or use "dubhe upgrade" to update the existing package.'
|
|
397
|
+
)
|
|
398
|
+
);
|
|
349
399
|
}
|
|
350
|
-
|
|
351
|
-
process.removeListener('SIGINT', handleInterrupt);
|
|
400
|
+
throw new Error(`Contract publication failed: ${error.message}`);
|
|
352
401
|
}
|
|
353
402
|
|
|
354
|
-
if (isInterrupted) {
|
|
355
|
-
process.exit(0);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
359
|
-
|
|
360
403
|
if (!result || result.effects?.status.status === 'failure') {
|
|
361
|
-
|
|
362
|
-
process.exit(1);
|
|
404
|
+
throw new Error('Contract publication transaction failed');
|
|
363
405
|
}
|
|
364
406
|
|
|
365
407
|
console.log(' ├─ Processing publication results...');
|
|
366
408
|
let version = 1;
|
|
367
409
|
let packageId = '';
|
|
368
|
-
let
|
|
369
|
-
let
|
|
410
|
+
let dappHubId = '';
|
|
411
|
+
let resources = dubheConfig.resources ?? {};
|
|
412
|
+
let enums = dubheConfig.enums;
|
|
370
413
|
let upgradeCapId = '';
|
|
414
|
+
let startCheckpoint = '';
|
|
371
415
|
|
|
372
416
|
let printObjects: any[] = [];
|
|
373
417
|
|
|
@@ -384,6 +428,13 @@ async function publishContract(
|
|
|
384
428
|
console.log(` ├─ Upgrade Cap: ${object.objectId}`);
|
|
385
429
|
upgradeCapId = object.objectId || '';
|
|
386
430
|
}
|
|
431
|
+
if (
|
|
432
|
+
object.type === 'created' &&
|
|
433
|
+
object.objectType &&
|
|
434
|
+
object.objectType.includes('dapp_service::DappHub')
|
|
435
|
+
) {
|
|
436
|
+
dappHubId = object.objectId || '';
|
|
437
|
+
}
|
|
387
438
|
if (object.type === 'created') {
|
|
388
439
|
printObjects.push(object);
|
|
389
440
|
}
|
|
@@ -392,19 +443,25 @@ async function publishContract(
|
|
|
392
443
|
console.log(` └─ Transaction: ${result.digest}`);
|
|
393
444
|
|
|
394
445
|
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
446
|
+
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
395
447
|
|
|
396
448
|
console.log('\n⚡ Executing Deploy Hook...');
|
|
397
449
|
await delay(5000);
|
|
398
450
|
|
|
451
|
+
startCheckpoint = await dubhe.suiInteractor.currentClient.getLatestCheckpointSequenceNumber();
|
|
452
|
+
|
|
399
453
|
const deployHookTx = new Transaction();
|
|
400
454
|
let args = [];
|
|
455
|
+
let frameworkDappHubId =
|
|
456
|
+
dubheConfig.name === 'dubhe' ? dappHubId : await getDubheDappHubId(network);
|
|
457
|
+
args.push(deployHookTx.object(frameworkDappHubId));
|
|
458
|
+
// Dubhe framework genesis::run(dapp_hub, ctx) does not take clock.
|
|
459
|
+
// DApp genesis::run(dapp_hub, clock, ctx) still takes clock.
|
|
401
460
|
if (dubheConfig.name !== 'dubhe') {
|
|
402
|
-
|
|
403
|
-
args.push(deployHookTx.object(dubheSchemaId));
|
|
461
|
+
args.push(deployHookTx.object('0x6'));
|
|
404
462
|
}
|
|
405
|
-
args.push(deployHookTx.object('0x6'));
|
|
406
463
|
deployHookTx.moveCall({
|
|
407
|
-
target: `${packageId}
|
|
464
|
+
target: `${packageId}::genesis::run`,
|
|
408
465
|
arguments: args
|
|
409
466
|
});
|
|
410
467
|
|
|
@@ -414,53 +471,82 @@ async function publishContract(
|
|
|
414
471
|
} catch (error: any) {
|
|
415
472
|
console.error(chalk.red(' └─ Deploy hook execution failed'));
|
|
416
473
|
console.error(error.message);
|
|
417
|
-
|
|
474
|
+
throw new Error(`genesis::run failed: ${error.message}`);
|
|
418
475
|
}
|
|
419
476
|
|
|
420
477
|
if (deployHookResult.effects?.status.status === 'success') {
|
|
421
478
|
console.log(' ├─ Hook execution successful');
|
|
422
479
|
console.log(` ├─ Transaction: ${deployHookResult.digest}`);
|
|
423
480
|
|
|
424
|
-
|
|
425
|
-
|
|
481
|
+
// Capture the DappStorage object created by genesis::run so we can persist
|
|
482
|
+
// its ID for later use in migrate_to_vN transactions during upgrades.
|
|
483
|
+
let dappStorageId = '';
|
|
484
|
+
deployHookResult.objectChanges!.map((object: ObjectChange) => {
|
|
426
485
|
if (
|
|
427
486
|
object.type === 'created' &&
|
|
428
487
|
object.objectType &&
|
|
429
|
-
object.objectType.includes('
|
|
488
|
+
object.objectType.includes('dapp_service::DappStorage')
|
|
430
489
|
) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (
|
|
434
|
-
object.type === 'created' &&
|
|
435
|
-
object.objectType &&
|
|
436
|
-
object.objectType.includes('schema') &&
|
|
437
|
-
!object.objectType.includes('dynamic_field')
|
|
438
|
-
) {
|
|
439
|
-
printObjects.push(object);
|
|
490
|
+
dappStorageId = object.objectId || '';
|
|
491
|
+
console.log(` ├─ DappStorage: ${dappStorageId}`);
|
|
440
492
|
}
|
|
441
493
|
});
|
|
442
494
|
|
|
495
|
+
console.log('\n📋 Created Objects:');
|
|
443
496
|
printObjects.map((object: ObjectChange) => {
|
|
444
|
-
console.log(` ├─
|
|
445
|
-
console.log(` └─
|
|
497
|
+
console.log(` ├─ ID: ${object.objectId}`);
|
|
498
|
+
console.log(` └─ Type: ${object.objectType}`);
|
|
446
499
|
});
|
|
447
500
|
|
|
448
|
-
saveContractData(
|
|
501
|
+
await saveContractData(
|
|
449
502
|
dubheConfig.name,
|
|
450
503
|
network,
|
|
504
|
+
startCheckpoint,
|
|
451
505
|
packageId,
|
|
452
|
-
|
|
506
|
+
packageId, // originalPackageId: first publish, so original == current
|
|
507
|
+
frameworkDappHubId,
|
|
453
508
|
upgradeCapId,
|
|
454
509
|
version,
|
|
455
|
-
|
|
510
|
+
resources,
|
|
511
|
+
enums,
|
|
512
|
+
// localnet: persist the locally deployed framework ID so the SDK can be
|
|
513
|
+
// initialised without hardcoding it. testnet/mainnet use a well-known
|
|
514
|
+
// constant already embedded in the SDK defaults, so we store undefined.
|
|
515
|
+
network === 'localnet' ? await getOriginalDubhePackageId(network) : undefined,
|
|
516
|
+
dappStorageId || undefined
|
|
456
517
|
);
|
|
518
|
+
|
|
519
|
+
await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
|
|
520
|
+
|
|
521
|
+
// Insert package id to dubhe config
|
|
522
|
+
let config = JSON.parse(fs.readFileSync(`${process.cwd()}/dubhe.config.json`, 'utf-8'));
|
|
523
|
+
config.original_package_id = packageId;
|
|
524
|
+
// Initialize the trusted package address list. The indexer uses this list to verify
|
|
525
|
+
// event.type_.address so that forged events from other contracts are rejected even
|
|
526
|
+
// when they embed the same dapp_key string.
|
|
527
|
+
config.package_ids = [packageId];
|
|
528
|
+
config.dubhe_object_id = frameworkDappHubId;
|
|
529
|
+
// When deploying the dubhe framework itself, the "original dubhe package ID" is
|
|
530
|
+
// the package we just published. For user packages, look up the well-known
|
|
531
|
+
// framework address for the target network from the client config.
|
|
532
|
+
config.original_dubhe_package_id =
|
|
533
|
+
dubheConfig.name === 'dubhe' ? packageId : await getOriginalDubhePackageId(network);
|
|
534
|
+
config.start_checkpoint = startCheckpoint;
|
|
535
|
+
// Canonical dapp_key type string: stable across upgrades, no "0x" prefix, padded to 64 hex chars.
|
|
536
|
+
// Matches the Move type_name::with_defining_ids<DappKey>().into_string() format.
|
|
537
|
+
const pkgHex = packageId.replace(/^0x/i, '').padStart(64, '0');
|
|
538
|
+
config.dapp_key = `${pkgHex}::dapp_key::DappKey`;
|
|
539
|
+
// Persist the DappStorage object ID so store-config can include it in deployment.ts
|
|
540
|
+
// and upgrade transactions can reference it without reading from .history.
|
|
541
|
+
if (dappStorageId) {
|
|
542
|
+
config.dapp_storage_id = dappStorageId;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
fs.writeFileSync(`${process.cwd()}/dubhe.config.json`, JSON.stringify(config, null, 2));
|
|
546
|
+
|
|
457
547
|
console.log('\n✅ Contract Publication Complete\n');
|
|
458
548
|
} else {
|
|
459
|
-
|
|
460
|
-
console.log(chalk.yellow(' Please republish or manually call deploy_hook::run'));
|
|
461
|
-
console.log(chalk.yellow(' Please check the transaction digest:'));
|
|
462
|
-
console.log(chalk.yellow(` ${deployHookResult.digest}`));
|
|
463
|
-
process.exit(1);
|
|
549
|
+
throw new Error(`genesis::run transaction failed. Digest: ${deployHookResult.digest}`);
|
|
464
550
|
}
|
|
465
551
|
}
|
|
466
552
|
|
|
@@ -469,15 +555,13 @@ async function checkDubheFramework(projectPath: string): Promise<boolean> {
|
|
|
469
555
|
console.log(chalk.yellow('\nℹ️ Dubhe Framework Files Not Found'));
|
|
470
556
|
console.log(chalk.yellow(' ├─ Expected Path:'), projectPath);
|
|
471
557
|
console.log(chalk.yellow(' ├─ To set up Dubhe Framework:'));
|
|
472
|
-
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p
|
|
558
|
+
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p src/dubhe'));
|
|
473
559
|
console.log(
|
|
474
560
|
chalk.yellow(
|
|
475
|
-
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe
|
|
561
|
+
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe src/dubhe'
|
|
476
562
|
)
|
|
477
563
|
);
|
|
478
|
-
console.log(
|
|
479
|
-
chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe')
|
|
480
|
-
);
|
|
564
|
+
console.log(chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe'));
|
|
481
565
|
console.log(chalk.yellow(' └─ After setup, restart the local node'));
|
|
482
566
|
return false;
|
|
483
567
|
}
|
|
@@ -488,8 +572,8 @@ export async function publishDubheFramework(
|
|
|
488
572
|
dubhe: Dubhe,
|
|
489
573
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
|
|
490
574
|
) {
|
|
491
|
-
const
|
|
492
|
-
const projectPath = `${
|
|
575
|
+
const cwd = process.cwd();
|
|
576
|
+
const projectPath = `${cwd}/src/dubhe`;
|
|
493
577
|
|
|
494
578
|
if (!(await checkDubheFramework(projectPath))) {
|
|
495
579
|
return;
|
|
@@ -501,66 +585,72 @@ export async function publishDubheFramework(
|
|
|
501
585
|
const chainId = await waitForNode(dubhe);
|
|
502
586
|
|
|
503
587
|
await removeEnvContent(`${projectPath}/Move.lock`, network);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
588
|
+
if (network === 'localnet') {
|
|
589
|
+
// When building with --build-env testnet, Sui CLI reads Move.lock's [env.testnet] section
|
|
590
|
+
// and bakes its original-published-id (non-zero for a previously published dubhe) into the
|
|
591
|
+
// bytecode as the package self-address. Publishing then fails with PublishErrorNonZeroAddress
|
|
592
|
+
// because Sui requires the self-address to be 0x0 for a first-time publish.
|
|
593
|
+
// Fix: clear the testnet env section before building so the CLI uses 0x0 from Move.toml.
|
|
594
|
+
await removeEnvContent(`${projectPath}/Move.lock`, 'testnet');
|
|
595
|
+
}
|
|
596
|
+
await updateMoveTomlAddress(projectPath, '0x0');
|
|
597
|
+
|
|
598
|
+
const startCheckpoint =
|
|
599
|
+
await dubhe.suiInteractor.currentClient.getLatestCheckpointSequenceNumber();
|
|
600
|
+
|
|
601
|
+
// For localnet: --build-env testnet is used to resolve git dependencies, but the
|
|
602
|
+
// Move CLI will also read Published.toml and use any existing testnet address for
|
|
603
|
+
// dubhe — causing PublishErrorNonZeroAddress if a testnet entry already exists.
|
|
604
|
+
// Fix: temporarily remove Published.toml before the build, then restore it.
|
|
605
|
+
// This ensures the dubhe package compiles with address 0x0 (from Move.toml).
|
|
606
|
+
const publishedTomlPath = `${projectPath}/Published.toml`;
|
|
607
|
+
let savedPublishedTomlContent: string | null = null;
|
|
608
|
+
if (network === 'localnet' && fs.existsSync(publishedTomlPath)) {
|
|
609
|
+
savedPublishedTomlContent = fs.readFileSync(publishedTomlPath, 'utf-8');
|
|
610
|
+
fs.unlinkSync(publishedTomlPath);
|
|
611
|
+
}
|
|
508
612
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
isInterrupted = true;
|
|
517
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
518
|
-
console.log('\n └─ Operation cancelled by user');
|
|
519
|
-
process.exit(0);
|
|
520
|
-
};
|
|
521
|
-
process.on('SIGINT', handleInterrupt);
|
|
613
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
614
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments].
|
|
615
|
+
const moveTomlPath = `${projectPath}/Move.toml`;
|
|
616
|
+
let savedMoveTomlContent: string | null = null;
|
|
617
|
+
if (network === 'localnet') {
|
|
618
|
+
savedMoveTomlContent = patchMoveTomlWithLocalnetEnv(moveTomlPath, chainId);
|
|
619
|
+
}
|
|
522
620
|
|
|
621
|
+
let modules: any, dependencies: any;
|
|
523
622
|
try {
|
|
524
|
-
|
|
525
|
-
try {
|
|
526
|
-
result = await dubhe.signAndSendTxn({ tx });
|
|
527
|
-
} catch (error) {
|
|
528
|
-
if (isInterrupted) break;
|
|
529
|
-
|
|
530
|
-
retryCount++;
|
|
531
|
-
if (retryCount === MAX_RETRIES) {
|
|
532
|
-
console.log(chalk.red(` └─ Publication failed after ${MAX_RETRIES} attempts`));
|
|
533
|
-
console.log(chalk.red(' └─ Please check your network connection and try again later.'));
|
|
534
|
-
process.exit(1);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
|
|
538
|
-
const spinner = SPINNER[spinnerIndex % SPINNER.length];
|
|
539
|
-
spinnerIndex++;
|
|
540
|
-
|
|
541
|
-
process.stdout.write(`\r ├─ ${spinner} Retrying... (${elapsedTime}s)`);
|
|
542
|
-
await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
|
|
543
|
-
}
|
|
544
|
-
}
|
|
623
|
+
[modules, dependencies] = buildContract(projectPath, network);
|
|
545
624
|
} finally {
|
|
546
|
-
|
|
625
|
+
// Always restore Published.toml and Move.toml (successful build or error)
|
|
626
|
+
if (savedPublishedTomlContent !== null) {
|
|
627
|
+
fs.writeFileSync(publishedTomlPath, savedPublishedTomlContent, 'utf-8');
|
|
628
|
+
}
|
|
629
|
+
if (savedMoveTomlContent !== null) {
|
|
630
|
+
fs.writeFileSync(moveTomlPath, savedMoveTomlContent, 'utf-8');
|
|
631
|
+
}
|
|
547
632
|
}
|
|
548
633
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
634
|
+
const tx = new Transaction();
|
|
635
|
+
const [upgradeCap] = tx.publish({ modules, dependencies });
|
|
636
|
+
tx.transferObjects([upgradeCap], dubhe.getAddress());
|
|
552
637
|
|
|
553
|
-
|
|
638
|
+
let result;
|
|
639
|
+
try {
|
|
640
|
+
result = await dubhe.signAndSendTxn({ tx });
|
|
641
|
+
} catch (error: any) {
|
|
642
|
+
console.error(chalk.red(' └─ Publication failed'));
|
|
643
|
+
console.error(error.message);
|
|
644
|
+
throw new Error(`Dubhe framework publication failed: ${error.message}`);
|
|
645
|
+
}
|
|
554
646
|
|
|
555
647
|
if (!result || result.effects?.status.status === 'failure') {
|
|
556
|
-
|
|
557
|
-
process.exit(1);
|
|
648
|
+
throw new Error('Dubhe framework publication transaction failed');
|
|
558
649
|
}
|
|
559
650
|
|
|
560
651
|
let version = 1;
|
|
561
652
|
let packageId = '';
|
|
562
|
-
let
|
|
563
|
-
let schemas: Record<string, string> = {};
|
|
653
|
+
let dappHubId = '';
|
|
564
654
|
let upgradeCapId = '';
|
|
565
655
|
|
|
566
656
|
result.objectChanges!.map((object: ObjectChange) => {
|
|
@@ -574,14 +664,21 @@ export async function publishDubheFramework(
|
|
|
574
664
|
) {
|
|
575
665
|
upgradeCapId = object.objectId || '';
|
|
576
666
|
}
|
|
667
|
+
if (
|
|
668
|
+
object.type === 'created' &&
|
|
669
|
+
object.objectType &&
|
|
670
|
+
object.objectType.includes('dapp_service::DappHub')
|
|
671
|
+
) {
|
|
672
|
+
dappHubId = object.objectId || '';
|
|
673
|
+
}
|
|
577
674
|
});
|
|
578
675
|
|
|
579
676
|
await delay(3000);
|
|
580
|
-
|
|
581
677
|
const deployHookTx = new Transaction();
|
|
678
|
+
// Dubhe framework genesis::run(dapp_hub, ctx) — clock no longer required.
|
|
582
679
|
deployHookTx.moveCall({
|
|
583
|
-
target: `${packageId}::
|
|
584
|
-
arguments: [deployHookTx.object(
|
|
680
|
+
target: `${packageId}::genesis::run`,
|
|
681
|
+
arguments: [deployHookTx.object(dappHubId)]
|
|
585
682
|
});
|
|
586
683
|
|
|
587
684
|
let deployHookResult;
|
|
@@ -590,44 +687,66 @@ export async function publishDubheFramework(
|
|
|
590
687
|
} catch (error: any) {
|
|
591
688
|
console.error(chalk.red(' └─ Deploy hook execution failed'));
|
|
592
689
|
console.error(error.message);
|
|
593
|
-
|
|
690
|
+
throw new Error(`Dubhe genesis::run failed: ${error.message}`);
|
|
594
691
|
}
|
|
595
692
|
|
|
596
|
-
if (deployHookResult.effects?.status.status
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
object.type === 'created' &&
|
|
600
|
-
object.objectType &&
|
|
601
|
-
object.objectType.includes('dubhe_schema::Schema')
|
|
602
|
-
) {
|
|
603
|
-
schemaId = object.objectId || '';
|
|
604
|
-
}
|
|
605
|
-
});
|
|
693
|
+
if (deployHookResult.effects?.status.status !== 'success') {
|
|
694
|
+
throw new Error('Deploy hook execution failed');
|
|
606
695
|
}
|
|
607
696
|
|
|
608
|
-
|
|
697
|
+
await updateMoveTomlAddress(projectPath, packageId);
|
|
698
|
+
await saveContractData(
|
|
699
|
+
'dubhe',
|
|
700
|
+
network,
|
|
701
|
+
startCheckpoint,
|
|
702
|
+
packageId,
|
|
703
|
+
packageId, // originalPackageId: first publish, original == current
|
|
704
|
+
dappHubId,
|
|
705
|
+
upgradeCapId,
|
|
706
|
+
version,
|
|
707
|
+
{},
|
|
708
|
+
{},
|
|
709
|
+
// Store the localnet framework package ID so other packages can read it
|
|
710
|
+
// from deployment JSON and pass it to the Dubhe client constructor.
|
|
711
|
+
network === 'localnet' ? packageId : undefined
|
|
712
|
+
);
|
|
609
713
|
|
|
610
714
|
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
611
|
-
|
|
715
|
+
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
716
|
+
|
|
717
|
+
// For localnet: write dubhe's published address to Pub.localnet.toml so that
|
|
718
|
+
// the counter package build (next step) can resolve the dubhe dependency.
|
|
719
|
+
if (network === 'localnet') {
|
|
720
|
+
const pubfilePath = getEphemeralPubFilePath(cwd, network);
|
|
721
|
+
updateEphemeralPubFile(pubfilePath, chainId, 'testnet', {
|
|
722
|
+
source: projectPath,
|
|
723
|
+
publishedAt: packageId,
|
|
724
|
+
originalId: packageId,
|
|
725
|
+
upgradeCap: upgradeCapId
|
|
726
|
+
});
|
|
727
|
+
}
|
|
612
728
|
}
|
|
613
729
|
|
|
614
730
|
export async function publishHandler(
|
|
615
731
|
dubheConfig: DubheConfig,
|
|
616
732
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
617
|
-
|
|
733
|
+
force: boolean,
|
|
734
|
+
gasBudget?: number,
|
|
735
|
+
fullnodeUrls?: string[]
|
|
618
736
|
) {
|
|
619
|
-
await switchEnv(network);
|
|
737
|
+
await switchEnv(network, fullnodeUrls?.[0]);
|
|
620
738
|
|
|
621
739
|
const dubhe = initializeDubhe({
|
|
622
|
-
network
|
|
740
|
+
network,
|
|
741
|
+
fullnodeUrls
|
|
623
742
|
});
|
|
624
743
|
|
|
625
|
-
|
|
744
|
+
const path = process.cwd();
|
|
745
|
+
const projectPath = `${path}/src/${dubheConfig.name}`;
|
|
746
|
+
|
|
747
|
+
if (network === 'localnet' && dubheConfig.name !== 'dubhe') {
|
|
626
748
|
await publishDubheFramework(dubhe, network);
|
|
627
749
|
}
|
|
628
750
|
|
|
629
|
-
|
|
630
|
-
const projectPath = `${path}/src/${dubheConfig.name}`;
|
|
631
|
-
await updateDubheDependency(`${projectPath}/Move.toml`, network);
|
|
632
|
-
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget);
|
|
751
|
+
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget, force, fullnodeUrls);
|
|
633
752
|
}
|