@0xobelisk/sui-cli 1.2.0-pre.98 → 2.0.0
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/LICENSE +92 -0
- package/README.md +5 -5
- package/dist/dubhe.js +121 -102
- package/dist/dubhe.js.map +1 -1
- package/package.json +24 -24
- package/src/commands/build.ts +15 -3
- package/src/commands/checkBalance.ts +17 -9
- package/src/commands/convertJson.ts +39 -16
- package/src/commands/faucet.ts +9 -3
- package/src/commands/{schemagen.ts → generate.ts} +16 -6
- package/src/commands/index.ts +4 -4
- package/src/commands/info.ts +8 -2
- package/src/commands/loadMetadata.ts +13 -2
- package/src/commands/publish.ts +39 -5
- package/src/commands/shell.ts +15 -5
- package/src/commands/{configStore.ts → storeConfig.ts} +2 -1
- package/src/commands/switchEnv.ts +13 -6
- package/src/commands/test.ts +106 -37
- package/src/commands/upgrade.ts +36 -3
- package/src/commands/watch.ts +7 -7
- package/src/dubhe.ts +0 -0
- package/src/utils/checkBalance.ts +6 -2
- package/src/utils/constants.ts +8 -4
- package/src/utils/metadataHandler.ts +4 -3
- package/src/utils/publishHandler.ts +213 -142
- package/src/utils/storeConfig.ts +55 -9
- package/src/utils/upgradeHandler.ts +127 -98
- package/src/utils/utils.ts +405 -123
|
@@ -6,127 +6,48 @@ import {
|
|
|
6
6
|
updateMoveTomlAddress,
|
|
7
7
|
switchEnv,
|
|
8
8
|
delay,
|
|
9
|
-
|
|
9
|
+
getDubheDappHubId,
|
|
10
10
|
initializeDubhe,
|
|
11
11
|
saveMetadata,
|
|
12
12
|
getOriginalDubhePackageId,
|
|
13
13
|
updatePublishedToml,
|
|
14
|
+
syncDubheFrameworkAddress,
|
|
14
15
|
updateEphemeralPubFile,
|
|
15
|
-
getEphemeralPubFilePath
|
|
16
|
+
getEphemeralPubFilePath,
|
|
17
|
+
getPublishedTomlEntry,
|
|
18
|
+
clearPublishedTomlEntry,
|
|
19
|
+
restorePublishedTomlEntry,
|
|
20
|
+
removeEnvFromMoveLock
|
|
16
21
|
} from './utils';
|
|
17
22
|
import { DubheConfig } from '@0xobelisk/sui-common';
|
|
18
23
|
import * as fs from 'fs';
|
|
19
24
|
import * as path from 'path';
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
fs.writeFileSync(filePath, updatedContent, 'utf-8');
|
|
32
|
-
}
|
|
26
|
+
/**
|
|
27
|
+
* Temporarily add localnet to Move.toml [environments] section before building.
|
|
28
|
+
* Sui CLI 1.40+ requires the active environment to be declared in Move.toml even
|
|
29
|
+
* when --build-env is specified. This patches the file and returns the original
|
|
30
|
+
* content so the caller can restore it in a finally block.
|
|
31
|
+
* Returns null if no changes were needed.
|
|
32
|
+
*/
|
|
33
|
+
function patchMoveTomlWithLocalnetEnv(moveTomlPath: string, chainId: string): string | null {
|
|
34
|
+
if (!fs.existsSync(moveTomlPath)) return null;
|
|
35
|
+
const content = fs.readFileSync(moveTomlPath, 'utf-8');
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
latestPublishedId: string;
|
|
38
|
-
publishedVersion: number;
|
|
39
|
-
}
|
|
37
|
+
if (content.includes('localnet')) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
operation: 'publish' | 'upgrade',
|
|
45
|
-
chainId: string,
|
|
46
|
-
publishedId: string
|
|
47
|
-
): void {
|
|
48
|
-
const envFilePath = path.resolve(filePath);
|
|
49
|
-
const envContent = fs.readFileSync(envFilePath, 'utf-8');
|
|
50
|
-
const envLines = envContent.split('\n');
|
|
51
|
-
|
|
52
|
-
const networkSectionIndex = envLines.findIndex((line) => line.trim() === `[env.${networkType}]`);
|
|
53
|
-
const config: EnvConfig = {
|
|
54
|
-
chainId: chainId,
|
|
55
|
-
originalPublishedId: '',
|
|
56
|
-
latestPublishedId: '',
|
|
57
|
-
publishedVersion: 0
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (networkSectionIndex === -1) {
|
|
61
|
-
// If network section is not found, add a new section
|
|
62
|
-
if (operation === 'publish') {
|
|
63
|
-
config.originalPublishedId = publishedId;
|
|
64
|
-
config.latestPublishedId = publishedId;
|
|
65
|
-
config.publishedVersion = 1;
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`Network type [env.${networkType}] not found in the file and cannot upgrade.`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
41
|
+
let updatedContent: string;
|
|
42
|
+
if (content.includes('[environments]')) {
|
|
43
|
+
updatedContent = content.replace('[environments]', `[environments]\nlocalnet = "${chainId}"`);
|
|
71
44
|
} else {
|
|
72
|
-
|
|
73
|
-
const line = envLines[i].trim();
|
|
74
|
-
if (line.startsWith('[')) break; // End of the current network section
|
|
75
|
-
|
|
76
|
-
const [key, value] = line.split('=').map((part) => part.trim().replace(/"/g, ''));
|
|
77
|
-
switch (key) {
|
|
78
|
-
case 'original-published-id':
|
|
79
|
-
config.originalPublishedId = value;
|
|
80
|
-
break;
|
|
81
|
-
case 'latest-published-id':
|
|
82
|
-
config.latestPublishedId = value;
|
|
83
|
-
break;
|
|
84
|
-
case 'published-version':
|
|
85
|
-
config.publishedVersion = parseInt(value, 10);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (operation === 'publish') {
|
|
91
|
-
config.originalPublishedId = publishedId;
|
|
92
|
-
config.latestPublishedId = publishedId;
|
|
93
|
-
config.publishedVersion = 1;
|
|
94
|
-
} else if (operation === 'upgrade') {
|
|
95
|
-
config.latestPublishedId = publishedId;
|
|
96
|
-
config.publishedVersion += 1;
|
|
97
|
-
}
|
|
45
|
+
updatedContent = content.trimEnd() + `\n\n[environments]\nlocalnet = "${chainId}"\n`;
|
|
98
46
|
}
|
|
99
47
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
chain-id = "${config.chainId}"
|
|
103
|
-
original-published-id = "${config.originalPublishedId}"
|
|
104
|
-
latest-published-id = "${config.latestPublishedId}"
|
|
105
|
-
published-version = "${config.publishedVersion}"
|
|
106
|
-
`;
|
|
107
|
-
|
|
108
|
-
const newEnvContent =
|
|
109
|
-
networkSectionIndex === -1
|
|
110
|
-
? envContent + updatedSection
|
|
111
|
-
: envLines.slice(0, networkSectionIndex).join('\n') + updatedSection;
|
|
112
|
-
|
|
113
|
-
fs.writeFileSync(envFilePath, newEnvContent, 'utf-8');
|
|
48
|
+
fs.writeFileSync(moveTomlPath, updatedContent, 'utf-8');
|
|
49
|
+
return content;
|
|
114
50
|
}
|
|
115
|
-
// function capitalizeAndRemoveUnderscores(input: string): string {
|
|
116
|
-
// return input
|
|
117
|
-
// .split('_')
|
|
118
|
-
// .map((word, index) => {
|
|
119
|
-
// return index === 0
|
|
120
|
-
// ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
121
|
-
// : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
122
|
-
// })
|
|
123
|
-
// .join('');
|
|
124
|
-
// }
|
|
125
|
-
//
|
|
126
|
-
// function getLastSegment(input: string): string {
|
|
127
|
-
// const segments = input.split('::');
|
|
128
|
-
// return segments.length > 0 ? segments[segments.length - 1] : '';
|
|
129
|
-
// }
|
|
130
51
|
|
|
131
52
|
/**
|
|
132
53
|
* Build a Move package and return [modules, dependencies] as base64 arrays.
|
|
@@ -216,7 +137,9 @@ async function publishContract(
|
|
|
216
137
|
dubheConfig: DubheConfig,
|
|
217
138
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
218
139
|
projectPath: string,
|
|
219
|
-
gasBudget?: number
|
|
140
|
+
gasBudget?: number,
|
|
141
|
+
force?: boolean,
|
|
142
|
+
fullnodeUrls?: string[]
|
|
220
143
|
) {
|
|
221
144
|
console.log('\n🚀 Starting Contract Publication...');
|
|
222
145
|
console.log(` ├─ Project: ${projectPath}`);
|
|
@@ -226,23 +149,74 @@ async function publishContract(
|
|
|
226
149
|
const chainId = await waitForNode(dubhe);
|
|
227
150
|
console.log(' ├─ Validating Environment...');
|
|
228
151
|
|
|
229
|
-
|
|
152
|
+
removeEnvFromMoveLock(`${projectPath}/Move.lock`, network);
|
|
230
153
|
console.log(` └─ Account: ${dubhe.getAddress()}`);
|
|
231
154
|
|
|
155
|
+
// Ensure src/dubhe/Published.toml references the canonical framework address
|
|
156
|
+
// for this network before building. This is a no-op when the address is
|
|
157
|
+
// already current, and automatically corrects stale entries whenever the
|
|
158
|
+
// framework is redeployed on testnet/mainnet without a manual file update.
|
|
159
|
+
if (dubheConfig.name !== 'dubhe') {
|
|
160
|
+
syncDubheFrameworkAddress(process.cwd(), network, chainId);
|
|
161
|
+
}
|
|
162
|
+
|
|
232
163
|
console.log('\n📦 Building Contract...');
|
|
233
164
|
// For localnet: pass the ephemeral pubfile so the build system can resolve
|
|
234
165
|
// the dubhe dependency that was just published in publishDubheFramework().
|
|
235
|
-
|
|
166
|
+
// If the file was written for a different chain (node restarted with
|
|
167
|
+
// --force-regenesis), discard it to avoid a chain-id mismatch build error.
|
|
168
|
+
let pubfilePath =
|
|
236
169
|
network === 'localnet' ? getEphemeralPubFilePath(process.cwd(), network) : undefined;
|
|
170
|
+
if (pubfilePath && fs.existsSync(pubfilePath)) {
|
|
171
|
+
const pubfileContent = fs.readFileSync(pubfilePath, 'utf-8');
|
|
172
|
+
const chainIdMatch = pubfileContent.match(/^chain-id\s*=\s*"([^"]*)"/m);
|
|
173
|
+
const pubfileChainId = chainIdMatch ? chainIdMatch[1] : '';
|
|
174
|
+
if (pubfileChainId && pubfileChainId !== chainId) {
|
|
175
|
+
console.log(
|
|
176
|
+
chalk.yellow(
|
|
177
|
+
` ├─ Stale Pub.localnet.toml (chain ${pubfileChainId} → ${chainId}), discarding`
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
fs.unlinkSync(pubfilePath);
|
|
181
|
+
pubfilePath = undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
237
184
|
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
185
|
+
// Move.toml paths — declared early so both the Published.toml handling block and
|
|
186
|
+
// the localnet env-patching block can reference them.
|
|
187
|
+
const contractMoveTomlPath = `${projectPath}/Move.toml`;
|
|
188
|
+
const dubheMoveTomlPath = path.join(path.dirname(projectPath), 'dubhe', 'Move.toml');
|
|
189
|
+
let savedContractMoveToml: string | null = null;
|
|
190
|
+
let savedDubheMoveToml: string | null = null;
|
|
191
|
+
|
|
192
|
+
// So the build uses package address 0x0: for localnet always remove the contract's
|
|
193
|
+
// Published.toml; for testnet/mainnet/devnet only when --force (clear current network entry).
|
|
194
|
+
// Otherwise Sui CLI bakes the existing [published.<network>] address into the bytecode and
|
|
195
|
+
// the chain rejects with PublishErrorNonZeroAddress.
|
|
241
196
|
const contractPublishedTomlPath = `${projectPath}/Published.toml`;
|
|
242
197
|
let savedContractPublishedToml: string | null = null;
|
|
198
|
+
let savedContractPublishedEntry: {
|
|
199
|
+
network: string;
|
|
200
|
+
entry: Exclude<ReturnType<typeof getPublishedTomlEntry>, undefined>;
|
|
201
|
+
} | null = null;
|
|
243
202
|
if (network === 'localnet' && fs.existsSync(contractPublishedTomlPath)) {
|
|
244
203
|
savedContractPublishedToml = fs.readFileSync(contractPublishedTomlPath, 'utf-8');
|
|
245
204
|
fs.unlinkSync(contractPublishedTomlPath);
|
|
205
|
+
} else if (network === 'testnet' || network === 'mainnet' || network === 'devnet') {
|
|
206
|
+
const entry = getPublishedTomlEntry(projectPath, network);
|
|
207
|
+
if (entry && force) {
|
|
208
|
+
// Existing entry + --force: clear it so the build uses 0x0 instead of the old address.
|
|
209
|
+
savedContractPublishedEntry = { network, entry };
|
|
210
|
+
clearPublishedTomlEntry(projectPath, network);
|
|
211
|
+
} else if (!entry) {
|
|
212
|
+
// No Published.toml entry for this network (first-time deploy to this network).
|
|
213
|
+
// The Sui CLI has no per-network override and falls back to Move.toml's [addresses]
|
|
214
|
+
// value, which may be non-zero from a previous deployment on a different network,
|
|
215
|
+
// causing PublishErrorNonZeroAddress.
|
|
216
|
+
// Temporarily zero out Move.toml before building so the self-address is 0x0.
|
|
217
|
+
savedContractMoveToml = fs.readFileSync(contractMoveTomlPath, 'utf-8');
|
|
218
|
+
updateMoveTomlAddress(projectPath, '0x0');
|
|
219
|
+
}
|
|
246
220
|
}
|
|
247
221
|
|
|
248
222
|
// For localnet: also temporarily remove dubhe's Published.toml when building the
|
|
@@ -256,6 +230,14 @@ async function publishContract(
|
|
|
256
230
|
fs.unlinkSync(dubhePublishedTomlPath);
|
|
257
231
|
}
|
|
258
232
|
|
|
233
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
234
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments]
|
|
235
|
+
// for both the contract and its dubhe dependency.
|
|
236
|
+
if (network === 'localnet') {
|
|
237
|
+
savedContractMoveToml = patchMoveTomlWithLocalnetEnv(contractMoveTomlPath, chainId);
|
|
238
|
+
savedDubheMoveToml = patchMoveTomlWithLocalnetEnv(dubheMoveTomlPath, chainId);
|
|
239
|
+
}
|
|
240
|
+
|
|
259
241
|
let modules: any, dependencies: any;
|
|
260
242
|
try {
|
|
261
243
|
[modules, dependencies] = buildContract(projectPath, network, pubfilePath);
|
|
@@ -263,9 +245,22 @@ async function publishContract(
|
|
|
263
245
|
if (savedContractPublishedToml !== null) {
|
|
264
246
|
fs.writeFileSync(contractPublishedTomlPath, savedContractPublishedToml, 'utf-8');
|
|
265
247
|
}
|
|
248
|
+
if (savedContractPublishedEntry !== null) {
|
|
249
|
+
restorePublishedTomlEntry(
|
|
250
|
+
projectPath,
|
|
251
|
+
savedContractPublishedEntry.network,
|
|
252
|
+
savedContractPublishedEntry.entry
|
|
253
|
+
);
|
|
254
|
+
}
|
|
266
255
|
if (savedDubhePublishedToml !== null) {
|
|
267
256
|
fs.writeFileSync(dubhePublishedTomlPath, savedDubhePublishedToml, 'utf-8');
|
|
268
257
|
}
|
|
258
|
+
if (savedContractMoveToml !== null) {
|
|
259
|
+
fs.writeFileSync(contractMoveTomlPath, savedContractMoveToml, 'utf-8');
|
|
260
|
+
}
|
|
261
|
+
if (savedDubheMoveToml !== null) {
|
|
262
|
+
fs.writeFileSync(dubheMoveTomlPath, savedDubheMoveToml, 'utf-8');
|
|
263
|
+
}
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
console.log('\n🔄 Publishing Contract...');
|
|
@@ -282,6 +277,17 @@ async function publishContract(
|
|
|
282
277
|
} catch (error: any) {
|
|
283
278
|
console.error(chalk.red(' └─ Publication failed'));
|
|
284
279
|
console.error(error.message);
|
|
280
|
+
if (
|
|
281
|
+
!force &&
|
|
282
|
+
(network === 'testnet' || network === 'mainnet' || network === 'devnet') &&
|
|
283
|
+
/PublishErrorNonZeroAddress/i.test(String(error?.message))
|
|
284
|
+
) {
|
|
285
|
+
console.error(
|
|
286
|
+
chalk.yellow(
|
|
287
|
+
' 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.'
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
285
291
|
throw new Error(`Contract publication failed: ${error.message}`);
|
|
286
292
|
}
|
|
287
293
|
|
|
@@ -292,9 +298,8 @@ async function publishContract(
|
|
|
292
298
|
console.log(' ├─ Processing publication results...');
|
|
293
299
|
let version = 1;
|
|
294
300
|
let packageId = '';
|
|
295
|
-
let
|
|
296
|
-
let
|
|
297
|
-
let resources = dubheConfig.resources;
|
|
301
|
+
let dappHubId = '';
|
|
302
|
+
let resources = dubheConfig.resources ?? {};
|
|
298
303
|
let enums = dubheConfig.enums;
|
|
299
304
|
let upgradeCapId = '';
|
|
300
305
|
let startCheckpoint = '';
|
|
@@ -319,7 +324,7 @@ async function publishContract(
|
|
|
319
324
|
object.objectType &&
|
|
320
325
|
object.objectType.includes('dapp_service::DappHub')
|
|
321
326
|
) {
|
|
322
|
-
|
|
327
|
+
dappHubId = object.objectId || '';
|
|
323
328
|
}
|
|
324
329
|
if (object.type === 'created') {
|
|
325
330
|
printObjects.push(object);
|
|
@@ -328,7 +333,6 @@ async function publishContract(
|
|
|
328
333
|
|
|
329
334
|
console.log(` └─ Transaction: ${result.digest}`);
|
|
330
335
|
|
|
331
|
-
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
332
336
|
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
333
337
|
|
|
334
338
|
console.log('\n⚡ Executing Deploy Hook...');
|
|
@@ -338,9 +342,14 @@ async function publishContract(
|
|
|
338
342
|
|
|
339
343
|
const deployHookTx = new Transaction();
|
|
340
344
|
let args = [];
|
|
341
|
-
let
|
|
342
|
-
|
|
343
|
-
args.push(deployHookTx.object(
|
|
345
|
+
let frameworkDappHubId =
|
|
346
|
+
dubheConfig.name === 'dubhe' ? dappHubId : await getDubheDappHubId(network);
|
|
347
|
+
args.push(deployHookTx.object(frameworkDappHubId));
|
|
348
|
+
// Dubhe framework genesis::run(dapp_hub, ctx) does not take clock.
|
|
349
|
+
// DApp genesis::run(dapp_hub, clock, ctx) still takes clock.
|
|
350
|
+
if (dubheConfig.name !== 'dubhe') {
|
|
351
|
+
args.push(deployHookTx.object('0x6'));
|
|
352
|
+
}
|
|
344
353
|
deployHookTx.moveCall({
|
|
345
354
|
target: `${packageId}::genesis::run`,
|
|
346
355
|
arguments: args
|
|
@@ -359,6 +368,20 @@ async function publishContract(
|
|
|
359
368
|
console.log(' ├─ Hook execution successful');
|
|
360
369
|
console.log(` ├─ Transaction: ${deployHookResult.digest}`);
|
|
361
370
|
|
|
371
|
+
// Capture the DappStorage object created by genesis::run so we can persist
|
|
372
|
+
// its ID for later use in migrate_to_vN transactions during upgrades.
|
|
373
|
+
let dappStorageId = '';
|
|
374
|
+
deployHookResult.objectChanges!.map((object: ObjectChange) => {
|
|
375
|
+
if (
|
|
376
|
+
object.type === 'created' &&
|
|
377
|
+
object.objectType &&
|
|
378
|
+
object.objectType.includes('dapp_service::DappStorage')
|
|
379
|
+
) {
|
|
380
|
+
dappStorageId = object.objectId || '';
|
|
381
|
+
console.log(` ├─ DappStorage: ${dappStorageId}`);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
362
385
|
console.log('\n📋 Created Objects:');
|
|
363
386
|
printObjects.map((object: ObjectChange) => {
|
|
364
387
|
console.log(` ├─ ID: ${object.objectId}`);
|
|
@@ -370,22 +393,48 @@ async function publishContract(
|
|
|
370
393
|
network,
|
|
371
394
|
startCheckpoint,
|
|
372
395
|
packageId,
|
|
373
|
-
|
|
396
|
+
packageId, // originalPackageId: first publish, so original == current
|
|
397
|
+
frameworkDappHubId,
|
|
374
398
|
upgradeCapId,
|
|
375
399
|
version,
|
|
376
|
-
components,
|
|
377
400
|
resources,
|
|
378
|
-
enums
|
|
401
|
+
enums,
|
|
402
|
+
// localnet: persist the locally deployed framework ID so the SDK can be
|
|
403
|
+
// initialised without hardcoding it. testnet/mainnet use a well-known
|
|
404
|
+
// constant already embedded in the SDK defaults, so we store undefined.
|
|
405
|
+
network === 'localnet' ? await getOriginalDubhePackageId(network) : undefined,
|
|
406
|
+
dappStorageId || undefined
|
|
379
407
|
);
|
|
380
408
|
|
|
381
|
-
await saveMetadata(dubheConfig.name, network, packageId);
|
|
409
|
+
await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
|
|
382
410
|
|
|
383
411
|
// Insert package id to dubhe config
|
|
384
412
|
let config = JSON.parse(fs.readFileSync(`${process.cwd()}/dubhe.config.json`, 'utf-8'));
|
|
385
413
|
config.original_package_id = packageId;
|
|
386
|
-
|
|
387
|
-
|
|
414
|
+
// Initialize the trusted package address list. The indexer uses this list to verify
|
|
415
|
+
// event.type_.address so that forged events from other contracts are rejected even
|
|
416
|
+
// when they embed the same dapp_key string.
|
|
417
|
+
config.package_ids = [packageId];
|
|
418
|
+
config.dubhe_object_id = frameworkDappHubId;
|
|
419
|
+
// When deploying the dubhe framework itself, the "original dubhe package ID" is
|
|
420
|
+
// the package we just published. For user packages, look up the well-known
|
|
421
|
+
// framework address for the target network from the client config.
|
|
422
|
+
// devnet has no fixed framework deployment so we skip this field.
|
|
423
|
+
if (dubheConfig.name === 'dubhe') {
|
|
424
|
+
config.original_dubhe_package_id = packageId;
|
|
425
|
+
} else if (network !== 'devnet') {
|
|
426
|
+
config.original_dubhe_package_id = await getOriginalDubhePackageId(network);
|
|
427
|
+
}
|
|
388
428
|
config.start_checkpoint = startCheckpoint;
|
|
429
|
+
// Canonical dapp_key type string: stable across upgrades, no "0x" prefix, padded to 64 hex chars.
|
|
430
|
+
// Matches the Move type_name::with_defining_ids<DappKey>().into_string() format.
|
|
431
|
+
const pkgHex = packageId.replace(/^0x/i, '').padStart(64, '0');
|
|
432
|
+
config.dapp_key = `${pkgHex}::dapp_key::DappKey`;
|
|
433
|
+
// Persist the DappStorage object ID so store-config can include it in deployment.ts
|
|
434
|
+
// and upgrade transactions can reference it without reading from .history.
|
|
435
|
+
if (dappStorageId) {
|
|
436
|
+
config.dapp_storage_id = dappStorageId;
|
|
437
|
+
}
|
|
389
438
|
|
|
390
439
|
fs.writeFileSync(`${process.cwd()}/dubhe.config.json`, JSON.stringify(config, null, 2));
|
|
391
440
|
|
|
@@ -400,10 +449,10 @@ async function checkDubheFramework(projectPath: string): Promise<boolean> {
|
|
|
400
449
|
console.log(chalk.yellow('\nℹ️ Dubhe Framework Files Not Found'));
|
|
401
450
|
console.log(chalk.yellow(' ├─ Expected Path:'), projectPath);
|
|
402
451
|
console.log(chalk.yellow(' ├─ To set up Dubhe Framework:'));
|
|
403
|
-
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p
|
|
452
|
+
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p src/dubhe'));
|
|
404
453
|
console.log(
|
|
405
454
|
chalk.yellow(
|
|
406
|
-
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe
|
|
455
|
+
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe src/dubhe'
|
|
407
456
|
)
|
|
408
457
|
);
|
|
409
458
|
console.log(chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe'));
|
|
@@ -429,7 +478,15 @@ export async function publishDubheFramework(
|
|
|
429
478
|
|
|
430
479
|
const chainId = await waitForNode(dubhe);
|
|
431
480
|
|
|
432
|
-
|
|
481
|
+
removeEnvFromMoveLock(`${projectPath}/Move.lock`, network);
|
|
482
|
+
if (network === 'localnet') {
|
|
483
|
+
// When building with --build-env testnet, Sui CLI reads Move.lock's [env.testnet] section
|
|
484
|
+
// and bakes its original-published-id (non-zero for a previously published dubhe) into the
|
|
485
|
+
// bytecode as the package self-address. Publishing then fails with PublishErrorNonZeroAddress
|
|
486
|
+
// because Sui requires the self-address to be 0x0 for a first-time publish.
|
|
487
|
+
// Fix: clear the testnet env section before building so the CLI uses 0x0 from Move.toml.
|
|
488
|
+
removeEnvFromMoveLock(`${projectPath}/Move.lock`, 'testnet');
|
|
489
|
+
}
|
|
433
490
|
await updateMoveTomlAddress(projectPath, '0x0');
|
|
434
491
|
|
|
435
492
|
const startCheckpoint =
|
|
@@ -447,16 +504,25 @@ export async function publishDubheFramework(
|
|
|
447
504
|
fs.unlinkSync(publishedTomlPath);
|
|
448
505
|
}
|
|
449
506
|
|
|
507
|
+
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
508
|
+
// even when --build-env is specified. Temporarily inject localnet into [environments].
|
|
509
|
+
const moveTomlPath = `${projectPath}/Move.toml`;
|
|
510
|
+
let savedMoveTomlContent: string | null = null;
|
|
511
|
+
if (network === 'localnet') {
|
|
512
|
+
savedMoveTomlContent = patchMoveTomlWithLocalnetEnv(moveTomlPath, chainId);
|
|
513
|
+
}
|
|
514
|
+
|
|
450
515
|
let modules: any, dependencies: any;
|
|
451
516
|
try {
|
|
452
|
-
// For localnet: use --build-env testnet (no pubfile needed — dubhe has no local deps).
|
|
453
|
-
// For testnet/mainnet: use -e <network> as usual.
|
|
454
517
|
[modules, dependencies] = buildContract(projectPath, network);
|
|
455
518
|
} finally {
|
|
456
|
-
// Always restore Published.toml (successful build or error)
|
|
519
|
+
// Always restore Published.toml and Move.toml (successful build or error)
|
|
457
520
|
if (savedPublishedTomlContent !== null) {
|
|
458
521
|
fs.writeFileSync(publishedTomlPath, savedPublishedTomlContent, 'utf-8');
|
|
459
522
|
}
|
|
523
|
+
if (savedMoveTomlContent !== null) {
|
|
524
|
+
fs.writeFileSync(moveTomlPath, savedMoveTomlContent, 'utf-8');
|
|
525
|
+
}
|
|
460
526
|
}
|
|
461
527
|
|
|
462
528
|
const tx = new Transaction();
|
|
@@ -478,7 +544,7 @@ export async function publishDubheFramework(
|
|
|
478
544
|
|
|
479
545
|
let version = 1;
|
|
480
546
|
let packageId = '';
|
|
481
|
-
let
|
|
547
|
+
let dappHubId = '';
|
|
482
548
|
let upgradeCapId = '';
|
|
483
549
|
|
|
484
550
|
result.objectChanges!.map((object: ObjectChange) => {
|
|
@@ -497,15 +563,16 @@ export async function publishDubheFramework(
|
|
|
497
563
|
object.objectType &&
|
|
498
564
|
object.objectType.includes('dapp_service::DappHub')
|
|
499
565
|
) {
|
|
500
|
-
|
|
566
|
+
dappHubId = object.objectId || '';
|
|
501
567
|
}
|
|
502
568
|
});
|
|
503
569
|
|
|
504
570
|
await delay(3000);
|
|
505
571
|
const deployHookTx = new Transaction();
|
|
572
|
+
// Dubhe framework genesis::run(dapp_hub, ctx) — clock no longer required.
|
|
506
573
|
deployHookTx.moveCall({
|
|
507
574
|
target: `${packageId}::genesis::run`,
|
|
508
|
-
arguments: [deployHookTx.object(
|
|
575
|
+
arguments: [deployHookTx.object(dappHubId)]
|
|
509
576
|
});
|
|
510
577
|
|
|
511
578
|
let deployHookResult;
|
|
@@ -527,15 +594,17 @@ export async function publishDubheFramework(
|
|
|
527
594
|
network,
|
|
528
595
|
startCheckpoint,
|
|
529
596
|
packageId,
|
|
530
|
-
|
|
597
|
+
packageId, // originalPackageId: first publish, original == current
|
|
598
|
+
dappHubId,
|
|
531
599
|
upgradeCapId,
|
|
532
600
|
version,
|
|
533
601
|
{},
|
|
534
602
|
{},
|
|
535
|
-
|
|
603
|
+
// Store the localnet framework package ID so other packages can read it
|
|
604
|
+
// from deployment JSON and pass it to the Dubhe client constructor.
|
|
605
|
+
network === 'localnet' ? packageId : undefined
|
|
536
606
|
);
|
|
537
607
|
|
|
538
|
-
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
539
608
|
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
540
609
|
|
|
541
610
|
// For localnet: write dubhe's published address to Pub.localnet.toml so that
|
|
@@ -554,13 +623,15 @@ export async function publishDubheFramework(
|
|
|
554
623
|
export async function publishHandler(
|
|
555
624
|
dubheConfig: DubheConfig,
|
|
556
625
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
557
|
-
|
|
558
|
-
gasBudget?: number
|
|
626
|
+
force: boolean,
|
|
627
|
+
gasBudget?: number,
|
|
628
|
+
fullnodeUrls?: string[]
|
|
559
629
|
) {
|
|
560
|
-
await switchEnv(network);
|
|
630
|
+
await switchEnv(network, fullnodeUrls?.[0]);
|
|
561
631
|
|
|
562
632
|
const dubhe = initializeDubhe({
|
|
563
|
-
network
|
|
633
|
+
network,
|
|
634
|
+
fullnodeUrls
|
|
564
635
|
});
|
|
565
636
|
|
|
566
637
|
const path = process.cwd();
|
|
@@ -570,5 +641,5 @@ export async function publishHandler(
|
|
|
570
641
|
await publishDubheFramework(dubhe, network);
|
|
571
642
|
}
|
|
572
643
|
|
|
573
|
-
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget);
|
|
644
|
+
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget, force, fullnodeUrls);
|
|
574
645
|
}
|
package/src/utils/storeConfig.ts
CHANGED
|
@@ -1,16 +1,53 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
3
|
import { DubheConfig } from '@0xobelisk/sui-common';
|
|
4
|
-
import { getDeploymentJson,
|
|
4
|
+
import { getDeploymentJson, getDubheDappHubId, getOriginalDubhePackageId } from './utils';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/** Derive the stable dapp_key type string from the original (genesis) package ID.
|
|
7
|
+
* Matches the Move-side `type_name::with_defining_ids<DappKey>().into_string()` output:
|
|
8
|
+
* "<hex64>::dapp_key::DappKey" (no "0x" prefix, address zero-padded to 64 hex chars).
|
|
9
|
+
*/
|
|
10
|
+
function buildDappKey(originalPackageId: string): string {
|
|
11
|
+
const hex = originalPackageId.replace(/^0x/i, '').padStart(64, '0');
|
|
12
|
+
return `${hex}::dapp_key::DappKey`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function storeConfig(
|
|
16
|
+
network: string,
|
|
17
|
+
packageId: string,
|
|
18
|
+
originalPackageId: string,
|
|
19
|
+
dappStorageId: string,
|
|
20
|
+
outputPath: string
|
|
21
|
+
) {
|
|
22
|
+
const dappHubId = await getDubheDappHubId(network);
|
|
23
|
+
|
|
24
|
+
// Mirror getDubheDappHubId: for localnet the framework is deployed ephemerally so we
|
|
25
|
+
// read its package ID from src/dubhe/.history/sui_localnet/latest.json.
|
|
26
|
+
// For testnet/mainnet the hardcoded ID from @0xobelisk/sui-client defaultConfig is used.
|
|
27
|
+
// devnet has no fixed framework deployment, so frameworkPackageId stays undefined.
|
|
28
|
+
let frameworkPackageId: string | undefined;
|
|
29
|
+
if (network !== 'devnet') {
|
|
30
|
+
frameworkPackageId = await getOriginalDubhePackageId(network);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const frameworkIdLine =
|
|
34
|
+
frameworkPackageId !== undefined
|
|
35
|
+
? `\n// Published package ID of the dubhe framework — required for proxy operations.\nexport const FrameworkPackageId: string | undefined = '${frameworkPackageId}';\n`
|
|
36
|
+
: `\n// Published package ID of the dubhe framework — required for proxy operations.\n// Not available for devnet (no fixed framework deployment).\nexport const FrameworkPackageId: string | undefined = undefined;\n`;
|
|
37
|
+
|
|
38
|
+
const dappKey = buildDappKey(originalPackageId);
|
|
39
|
+
|
|
40
|
+
const code = `type NetworkType = 'testnet' | 'mainnet' | 'devnet' | 'localnet';
|
|
9
41
|
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
42
|
+
export const Network: NetworkType = '${network}';
|
|
43
|
+
export const PackageId = '${packageId}';
|
|
44
|
+
/** The first-published (original) package ID — stable across upgrades. Used for dapp_key and indexer filtering. */
|
|
45
|
+
export const OriginalPackageId = '${originalPackageId}';
|
|
46
|
+
/** Canonical dapp_key type string derived from OriginalPackageId. Pass to the Dubhe SDK and GraphQL queries. */
|
|
47
|
+
export const DappKey = '${dappKey}';
|
|
48
|
+
export const DappHubId = '${dappHubId}';
|
|
49
|
+
export const DappStorageId = '${dappStorageId}';
|
|
50
|
+
${frameworkIdLine}`;
|
|
14
51
|
|
|
15
52
|
writeOutput(code, outputPath, 'storeConfig');
|
|
16
53
|
}
|
|
@@ -36,5 +73,14 @@ export async function storeConfigHandler(
|
|
|
36
73
|
const path = process.cwd();
|
|
37
74
|
const contractPath = `${path}/src/${dubheConfig.name}`;
|
|
38
75
|
const deployment = await getDeploymentJson(contractPath, network);
|
|
39
|
-
|
|
76
|
+
// Prefer the persisted originalPackageId; fall back to packageId for old deployments
|
|
77
|
+
// that were created before this field was introduced.
|
|
78
|
+
const originalPackageId = deployment.originalPackageId ?? deployment.packageId;
|
|
79
|
+
await storeConfig(
|
|
80
|
+
deployment.network,
|
|
81
|
+
deployment.packageId,
|
|
82
|
+
originalPackageId,
|
|
83
|
+
deployment.dappStorageId ?? '',
|
|
84
|
+
outputPath
|
|
85
|
+
);
|
|
40
86
|
}
|