@0xobelisk/sui-cli 1.2.0-pre.99 → 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 +117 -103
- 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 +168 -152
- package/src/utils/storeConfig.ts +55 -9
- package/src/utils/upgradeHandler.ts +127 -98
- package/src/utils/utils.ts +405 -123
|
@@ -6,13 +6,18 @@ 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';
|
|
@@ -44,116 +49,6 @@ function patchMoveTomlWithLocalnetEnv(moveTomlPath: string, chainId: string): st
|
|
|
44
49
|
return content;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
async function removeEnvContent(
|
|
48
|
-
filePath: string,
|
|
49
|
-
networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
|
|
50
|
-
): Promise<void> {
|
|
51
|
-
if (!fs.existsSync(filePath)) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
-
const regex = new RegExp(`\\[env\\.${networkType}\\][\\s\\S]*?(?=\\[|$)`, 'g');
|
|
56
|
-
const updatedContent = content.replace(regex, '');
|
|
57
|
-
fs.writeFileSync(filePath, updatedContent, 'utf-8');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface EnvConfig {
|
|
61
|
-
chainId: string;
|
|
62
|
-
originalPublishedId: string;
|
|
63
|
-
latestPublishedId: string;
|
|
64
|
-
publishedVersion: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function updateEnvFile(
|
|
68
|
-
filePath: string,
|
|
69
|
-
networkType: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
70
|
-
operation: 'publish' | 'upgrade',
|
|
71
|
-
chainId: string,
|
|
72
|
-
publishedId: string
|
|
73
|
-
): void {
|
|
74
|
-
const envFilePath = path.resolve(filePath);
|
|
75
|
-
const envContent = fs.readFileSync(envFilePath, 'utf-8');
|
|
76
|
-
const envLines = envContent.split('\n');
|
|
77
|
-
|
|
78
|
-
const networkSectionIndex = envLines.findIndex((line) => line.trim() === `[env.${networkType}]`);
|
|
79
|
-
const config: EnvConfig = {
|
|
80
|
-
chainId: chainId,
|
|
81
|
-
originalPublishedId: '',
|
|
82
|
-
latestPublishedId: '',
|
|
83
|
-
publishedVersion: 0
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
if (networkSectionIndex === -1) {
|
|
87
|
-
// If network section is not found, add a new section
|
|
88
|
-
if (operation === 'publish') {
|
|
89
|
-
config.originalPublishedId = publishedId;
|
|
90
|
-
config.latestPublishedId = publishedId;
|
|
91
|
-
config.publishedVersion = 1;
|
|
92
|
-
} else {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`Network type [env.${networkType}] not found in the file and cannot upgrade.`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
for (let i = networkSectionIndex + 1; i < envLines.length; i++) {
|
|
99
|
-
const line = envLines[i].trim();
|
|
100
|
-
if (line.startsWith('[')) break; // End of the current network section
|
|
101
|
-
|
|
102
|
-
const [key, value] = line.split('=').map((part) => part.trim().replace(/"/g, ''));
|
|
103
|
-
switch (key) {
|
|
104
|
-
case 'original-published-id':
|
|
105
|
-
config.originalPublishedId = value;
|
|
106
|
-
break;
|
|
107
|
-
case 'latest-published-id':
|
|
108
|
-
config.latestPublishedId = value;
|
|
109
|
-
break;
|
|
110
|
-
case 'published-version':
|
|
111
|
-
config.publishedVersion = parseInt(value, 10);
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (operation === 'publish') {
|
|
117
|
-
config.originalPublishedId = publishedId;
|
|
118
|
-
config.latestPublishedId = publishedId;
|
|
119
|
-
config.publishedVersion = 1;
|
|
120
|
-
} else if (operation === 'upgrade') {
|
|
121
|
-
config.latestPublishedId = publishedId;
|
|
122
|
-
config.publishedVersion += 1;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const updatedSection = `
|
|
127
|
-
[env.${networkType}]
|
|
128
|
-
chain-id = "${config.chainId}"
|
|
129
|
-
original-published-id = "${config.originalPublishedId}"
|
|
130
|
-
latest-published-id = "${config.latestPublishedId}"
|
|
131
|
-
published-version = "${config.publishedVersion}"
|
|
132
|
-
`;
|
|
133
|
-
|
|
134
|
-
const newEnvContent =
|
|
135
|
-
networkSectionIndex === -1
|
|
136
|
-
? envContent + updatedSection
|
|
137
|
-
: envLines.slice(0, networkSectionIndex).join('\n') + updatedSection;
|
|
138
|
-
|
|
139
|
-
fs.writeFileSync(envFilePath, newEnvContent, 'utf-8');
|
|
140
|
-
}
|
|
141
|
-
// function capitalizeAndRemoveUnderscores(input: string): string {
|
|
142
|
-
// return input
|
|
143
|
-
// .split('_')
|
|
144
|
-
// .map((word, index) => {
|
|
145
|
-
// return index === 0
|
|
146
|
-
// ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
147
|
-
// : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
148
|
-
// })
|
|
149
|
-
// .join('');
|
|
150
|
-
// }
|
|
151
|
-
//
|
|
152
|
-
// function getLastSegment(input: string): string {
|
|
153
|
-
// const segments = input.split('::');
|
|
154
|
-
// return segments.length > 0 ? segments[segments.length - 1] : '';
|
|
155
|
-
// }
|
|
156
|
-
|
|
157
52
|
/**
|
|
158
53
|
* Build a Move package and return [modules, dependencies] as base64 arrays.
|
|
159
54
|
*
|
|
@@ -242,7 +137,9 @@ async function publishContract(
|
|
|
242
137
|
dubheConfig: DubheConfig,
|
|
243
138
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
244
139
|
projectPath: string,
|
|
245
|
-
gasBudget?: number
|
|
140
|
+
gasBudget?: number,
|
|
141
|
+
force?: boolean,
|
|
142
|
+
fullnodeUrls?: string[]
|
|
246
143
|
) {
|
|
247
144
|
console.log('\n🚀 Starting Contract Publication...');
|
|
248
145
|
console.log(` ├─ Project: ${projectPath}`);
|
|
@@ -252,23 +149,74 @@ async function publishContract(
|
|
|
252
149
|
const chainId = await waitForNode(dubhe);
|
|
253
150
|
console.log(' ├─ Validating Environment...');
|
|
254
151
|
|
|
255
|
-
|
|
152
|
+
removeEnvFromMoveLock(`${projectPath}/Move.lock`, network);
|
|
256
153
|
console.log(` └─ Account: ${dubhe.getAddress()}`);
|
|
257
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
|
+
|
|
258
163
|
console.log('\n📦 Building Contract...');
|
|
259
164
|
// For localnet: pass the ephemeral pubfile so the build system can resolve
|
|
260
165
|
// the dubhe dependency that was just published in publishDubheFramework().
|
|
261
|
-
|
|
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 =
|
|
262
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
|
+
}
|
|
263
184
|
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
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.
|
|
267
196
|
const contractPublishedTomlPath = `${projectPath}/Published.toml`;
|
|
268
197
|
let savedContractPublishedToml: string | null = null;
|
|
198
|
+
let savedContractPublishedEntry: {
|
|
199
|
+
network: string;
|
|
200
|
+
entry: Exclude<ReturnType<typeof getPublishedTomlEntry>, undefined>;
|
|
201
|
+
} | null = null;
|
|
269
202
|
if (network === 'localnet' && fs.existsSync(contractPublishedTomlPath)) {
|
|
270
203
|
savedContractPublishedToml = fs.readFileSync(contractPublishedTomlPath, 'utf-8');
|
|
271
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
|
+
}
|
|
272
220
|
}
|
|
273
221
|
|
|
274
222
|
// For localnet: also temporarily remove dubhe's Published.toml when building the
|
|
@@ -285,10 +233,6 @@ async function publishContract(
|
|
|
285
233
|
// Sui CLI 1.40+ checks that the active environment is declared in Move.toml
|
|
286
234
|
// even when --build-env is specified. Temporarily inject localnet into [environments]
|
|
287
235
|
// 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
236
|
if (network === 'localnet') {
|
|
293
237
|
savedContractMoveToml = patchMoveTomlWithLocalnetEnv(contractMoveTomlPath, chainId);
|
|
294
238
|
savedDubheMoveToml = patchMoveTomlWithLocalnetEnv(dubheMoveTomlPath, chainId);
|
|
@@ -301,6 +245,13 @@ async function publishContract(
|
|
|
301
245
|
if (savedContractPublishedToml !== null) {
|
|
302
246
|
fs.writeFileSync(contractPublishedTomlPath, savedContractPublishedToml, 'utf-8');
|
|
303
247
|
}
|
|
248
|
+
if (savedContractPublishedEntry !== null) {
|
|
249
|
+
restorePublishedTomlEntry(
|
|
250
|
+
projectPath,
|
|
251
|
+
savedContractPublishedEntry.network,
|
|
252
|
+
savedContractPublishedEntry.entry
|
|
253
|
+
);
|
|
254
|
+
}
|
|
304
255
|
if (savedDubhePublishedToml !== null) {
|
|
305
256
|
fs.writeFileSync(dubhePublishedTomlPath, savedDubhePublishedToml, 'utf-8');
|
|
306
257
|
}
|
|
@@ -326,6 +277,17 @@ async function publishContract(
|
|
|
326
277
|
} catch (error: any) {
|
|
327
278
|
console.error(chalk.red(' └─ Publication failed'));
|
|
328
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
|
+
}
|
|
329
291
|
throw new Error(`Contract publication failed: ${error.message}`);
|
|
330
292
|
}
|
|
331
293
|
|
|
@@ -336,9 +298,8 @@ async function publishContract(
|
|
|
336
298
|
console.log(' ├─ Processing publication results...');
|
|
337
299
|
let version = 1;
|
|
338
300
|
let packageId = '';
|
|
339
|
-
let
|
|
340
|
-
let
|
|
341
|
-
let resources = dubheConfig.resources;
|
|
301
|
+
let dappHubId = '';
|
|
302
|
+
let resources = dubheConfig.resources ?? {};
|
|
342
303
|
let enums = dubheConfig.enums;
|
|
343
304
|
let upgradeCapId = '';
|
|
344
305
|
let startCheckpoint = '';
|
|
@@ -363,7 +324,7 @@ async function publishContract(
|
|
|
363
324
|
object.objectType &&
|
|
364
325
|
object.objectType.includes('dapp_service::DappHub')
|
|
365
326
|
) {
|
|
366
|
-
|
|
327
|
+
dappHubId = object.objectId || '';
|
|
367
328
|
}
|
|
368
329
|
if (object.type === 'created') {
|
|
369
330
|
printObjects.push(object);
|
|
@@ -372,7 +333,6 @@ async function publishContract(
|
|
|
372
333
|
|
|
373
334
|
console.log(` └─ Transaction: ${result.digest}`);
|
|
374
335
|
|
|
375
|
-
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
376
336
|
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
377
337
|
|
|
378
338
|
console.log('\n⚡ Executing Deploy Hook...');
|
|
@@ -382,9 +342,14 @@ async function publishContract(
|
|
|
382
342
|
|
|
383
343
|
const deployHookTx = new Transaction();
|
|
384
344
|
let args = [];
|
|
385
|
-
let
|
|
386
|
-
|
|
387
|
-
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
|
+
}
|
|
388
353
|
deployHookTx.moveCall({
|
|
389
354
|
target: `${packageId}::genesis::run`,
|
|
390
355
|
arguments: args
|
|
@@ -403,6 +368,20 @@ async function publishContract(
|
|
|
403
368
|
console.log(' ├─ Hook execution successful');
|
|
404
369
|
console.log(` ├─ Transaction: ${deployHookResult.digest}`);
|
|
405
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
|
+
|
|
406
385
|
console.log('\n📋 Created Objects:');
|
|
407
386
|
printObjects.map((object: ObjectChange) => {
|
|
408
387
|
console.log(` ├─ ID: ${object.objectId}`);
|
|
@@ -414,22 +393,48 @@ async function publishContract(
|
|
|
414
393
|
network,
|
|
415
394
|
startCheckpoint,
|
|
416
395
|
packageId,
|
|
417
|
-
|
|
396
|
+
packageId, // originalPackageId: first publish, so original == current
|
|
397
|
+
frameworkDappHubId,
|
|
418
398
|
upgradeCapId,
|
|
419
399
|
version,
|
|
420
|
-
components,
|
|
421
400
|
resources,
|
|
422
|
-
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
|
|
423
407
|
);
|
|
424
408
|
|
|
425
|
-
await saveMetadata(dubheConfig.name, network, packageId);
|
|
409
|
+
await saveMetadata(dubheConfig.name, network, packageId, fullnodeUrls);
|
|
426
410
|
|
|
427
411
|
// Insert package id to dubhe config
|
|
428
412
|
let config = JSON.parse(fs.readFileSync(`${process.cwd()}/dubhe.config.json`, 'utf-8'));
|
|
429
413
|
config.original_package_id = packageId;
|
|
430
|
-
|
|
431
|
-
|
|
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
|
+
}
|
|
432
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
|
+
}
|
|
433
438
|
|
|
434
439
|
fs.writeFileSync(`${process.cwd()}/dubhe.config.json`, JSON.stringify(config, null, 2));
|
|
435
440
|
|
|
@@ -444,10 +449,10 @@ async function checkDubheFramework(projectPath: string): Promise<boolean> {
|
|
|
444
449
|
console.log(chalk.yellow('\nℹ️ Dubhe Framework Files Not Found'));
|
|
445
450
|
console.log(chalk.yellow(' ├─ Expected Path:'), projectPath);
|
|
446
451
|
console.log(chalk.yellow(' ├─ To set up Dubhe Framework:'));
|
|
447
|
-
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p
|
|
452
|
+
console.log(chalk.yellow(' │ 1. Create directory: mkdir -p src/dubhe'));
|
|
448
453
|
console.log(
|
|
449
454
|
chalk.yellow(
|
|
450
|
-
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe
|
|
455
|
+
' │ 2. Clone repository: git clone https://github.com/0xobelisk/dubhe src/dubhe'
|
|
451
456
|
)
|
|
452
457
|
);
|
|
453
458
|
console.log(chalk.yellow(' │ 3. Or download from: https://github.com/0xobelisk/dubhe'));
|
|
@@ -473,7 +478,15 @@ export async function publishDubheFramework(
|
|
|
473
478
|
|
|
474
479
|
const chainId = await waitForNode(dubhe);
|
|
475
480
|
|
|
476
|
-
|
|
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
|
+
}
|
|
477
490
|
await updateMoveTomlAddress(projectPath, '0x0');
|
|
478
491
|
|
|
479
492
|
const startCheckpoint =
|
|
@@ -501,8 +514,6 @@ export async function publishDubheFramework(
|
|
|
501
514
|
|
|
502
515
|
let modules: any, dependencies: any;
|
|
503
516
|
try {
|
|
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
517
|
[modules, dependencies] = buildContract(projectPath, network);
|
|
507
518
|
} finally {
|
|
508
519
|
// Always restore Published.toml and Move.toml (successful build or error)
|
|
@@ -533,7 +544,7 @@ export async function publishDubheFramework(
|
|
|
533
544
|
|
|
534
545
|
let version = 1;
|
|
535
546
|
let packageId = '';
|
|
536
|
-
let
|
|
547
|
+
let dappHubId = '';
|
|
537
548
|
let upgradeCapId = '';
|
|
538
549
|
|
|
539
550
|
result.objectChanges!.map((object: ObjectChange) => {
|
|
@@ -552,15 +563,16 @@ export async function publishDubheFramework(
|
|
|
552
563
|
object.objectType &&
|
|
553
564
|
object.objectType.includes('dapp_service::DappHub')
|
|
554
565
|
) {
|
|
555
|
-
|
|
566
|
+
dappHubId = object.objectId || '';
|
|
556
567
|
}
|
|
557
568
|
});
|
|
558
569
|
|
|
559
570
|
await delay(3000);
|
|
560
571
|
const deployHookTx = new Transaction();
|
|
572
|
+
// Dubhe framework genesis::run(dapp_hub, ctx) — clock no longer required.
|
|
561
573
|
deployHookTx.moveCall({
|
|
562
574
|
target: `${packageId}::genesis::run`,
|
|
563
|
-
arguments: [deployHookTx.object(
|
|
575
|
+
arguments: [deployHookTx.object(dappHubId)]
|
|
564
576
|
});
|
|
565
577
|
|
|
566
578
|
let deployHookResult;
|
|
@@ -582,15 +594,17 @@ export async function publishDubheFramework(
|
|
|
582
594
|
network,
|
|
583
595
|
startCheckpoint,
|
|
584
596
|
packageId,
|
|
585
|
-
|
|
597
|
+
packageId, // originalPackageId: first publish, original == current
|
|
598
|
+
dappHubId,
|
|
586
599
|
upgradeCapId,
|
|
587
600
|
version,
|
|
588
601
|
{},
|
|
589
602
|
{},
|
|
590
|
-
|
|
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
|
|
591
606
|
);
|
|
592
607
|
|
|
593
|
-
updateEnvFile(`${projectPath}/Move.lock`, network, 'publish', chainId, packageId);
|
|
594
608
|
updatePublishedToml(projectPath, network, chainId, packageId, packageId, 1);
|
|
595
609
|
|
|
596
610
|
// For localnet: write dubhe's published address to Pub.localnet.toml so that
|
|
@@ -609,13 +623,15 @@ export async function publishDubheFramework(
|
|
|
609
623
|
export async function publishHandler(
|
|
610
624
|
dubheConfig: DubheConfig,
|
|
611
625
|
network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
|
|
612
|
-
|
|
613
|
-
gasBudget?: number
|
|
626
|
+
force: boolean,
|
|
627
|
+
gasBudget?: number,
|
|
628
|
+
fullnodeUrls?: string[]
|
|
614
629
|
) {
|
|
615
|
-
await switchEnv(network);
|
|
630
|
+
await switchEnv(network, fullnodeUrls?.[0]);
|
|
616
631
|
|
|
617
632
|
const dubhe = initializeDubhe({
|
|
618
|
-
network
|
|
633
|
+
network,
|
|
634
|
+
fullnodeUrls
|
|
619
635
|
});
|
|
620
636
|
|
|
621
637
|
const path = process.cwd();
|
|
@@ -625,5 +641,5 @@ export async function publishHandler(
|
|
|
625
641
|
await publishDubheFramework(dubhe, network);
|
|
626
642
|
}
|
|
627
643
|
|
|
628
|
-
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget);
|
|
644
|
+
await publishContract(dubhe, dubheConfig, network, projectPath, gasBudget, force, fullnodeUrls);
|
|
629
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
|
}
|