@offckb/cli 0.3.0-rc2 → 0.3.0-rc4

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 CHANGED
@@ -27,6 +27,7 @@ There are BREAKING CHANGES between v0.2.x and v0.3.x, make sure to read the [mig
27
27
  - [Get started](#get-started)
28
28
  - [Running CKB](#running-ckb)
29
29
  - [List scripts info](#list-scripts-info)
30
+ - [Tweak Devnet Config](#tweak-devnet-config)
30
31
  - [Create a full-stack Project](#create-a-full-stack-project)
31
32
  - [Create a script-only Project](#create-a-script-only-project)
32
33
  - [Build and Deploy a script](#build-and-deploy-a-script)
@@ -49,7 +50,7 @@ There are BREAKING CHANGES between v0.2.x and v0.3.x, make sure to read the [mig
49
50
  npm install -g @offckb/cli
50
51
  ```
51
52
 
52
- *We recommand using [LTS](https://nodejs.org/en/download/package-manager) version of Node to run `offckb`*
53
+ _We recommand using [LTS](https://nodejs.org/en/download/package-manager) version of Node to run `offckb`_
53
54
 
54
55
  ## Usage
55
56
 
@@ -83,7 +84,7 @@ Commands:
83
84
  help [command] display help for command
84
85
  ```
85
86
 
86
- *Use `offckb [command] -h` to learn more about a specific command.*
87
+ _Use `offckb [command] -h` to learn more about a specific command._
87
88
 
88
89
  ## Get started
89
90
 
@@ -98,7 +99,7 @@ offckb node
98
99
  Or specify a CKB version:
99
100
 
100
101
  ```sh
101
- offckb node 0.117.0
102
+ offckb node 0.117.0
102
103
  ```
103
104
 
104
105
  Or set the default CKB version:
@@ -142,6 +143,39 @@ Or print the scripts info in a CCC style:
142
143
  offckb system-scripts --export-style ccc
143
144
  ```
144
145
 
146
+ ### Tweak Devnet Config
147
+
148
+ By default, offckb use a fixed devnet config for the local blockchain. You can tweak the config to customize the devnet:
149
+
150
+ First, start a default CKB devnet and locate your devnet folder
151
+
152
+ ```sh
153
+ offckb node
154
+ # after starting, press ctrl-c to kill the node
155
+ # then get the config
156
+ offckb config list
157
+ ```
158
+
159
+ Result:
160
+
161
+ ```json
162
+ {
163
+ "devnet": {
164
+ "rpcUrl": "http://localhost:8114",
165
+ "configPath": "~/Library/Application Support/offckb-nodejs/devnet",
166
+ "dataPath": "~/Library/Application Support/offckb-nodejs/devnet/data"
167
+ }
168
+ }
169
+ ```
170
+
171
+ Pay attention to the `devnet.configPath` and `devnet.dataPath`. They are the ones we need.
172
+
173
+ 1. `cd` into the `devnet.configPath`, this is the config folder for the local blockchain. Modify the config in the folder to better customize the devnet. For customization, see [Custom Devnet Setup](https://docs.nervos.org/docs/node/run-devnet-node#custom-devnet-setup) and [Configure CKB](https://github.com/nervosnetwork/ckb/blob/develop/docs/configure.md) for better explanation of the config files.
174
+ 2. After modifications, remove everything in the `devnet.dataPath` folder. This will clean the chain data.
175
+ 3. Restart local blockchain by running `offckb node`
176
+
177
+ Done.
178
+
145
179
  ### Create a full-stack Project
146
180
 
147
181
  Create a new project from predefined boilerplates.
@@ -166,7 +200,7 @@ Note: you need to have rust/cargo/cargo-generate/clang 16+ installed in your env
166
200
 
167
201
  The fullstack boilerplate project is a monorepo, which contains a script project and a frontend project.
168
202
 
169
- To build the script, in the root of the project, run:
203
+ To build the script, in the root of the project, run:
170
204
 
171
205
  ```sh
172
206
  make build
@@ -178,6 +212,12 @@ To deploy the script, cd into the frontend folder and run:
178
212
  cd frontend && offckb deploy --network <devnet/testnet>
179
213
  ```
180
214
 
215
+ Pass `--type-id` option if you want Scripts to be upgradable
216
+
217
+ ```sh
218
+ cd frontend && offckb deploy --type-id --network <devnet/testnet>
219
+ ```
220
+
181
221
  Once the deployment is done, you can use the following command to check the deployed scripts:
182
222
 
183
223
  ```sh
@@ -1,4 +1,4 @@
1
- name = "offckb"
1
+ name = "ckb_dev"
2
2
 
3
3
  [genesis]
4
4
  version = 0
package/dist/cli.js CHANGED
@@ -123,6 +123,7 @@ program
123
123
  .description('Deploy contracts to different networks, only supports devnet and testnet')
124
124
  .option('--network <network>', 'Specify the network to deploy to', 'devnet')
125
125
  .option('--target <target>', 'Specify the relative bin target folder to deploy to')
126
+ .option('-t, --type-id', 'Specify if use upgradable type id to deploy the script')
126
127
  .option('--privkey <privkey>', 'Specify the private key to deploy scripts')
127
128
  .action((options) => (0, deploy_1.deploy)(options));
128
129
  program
@@ -2,5 +2,6 @@ import { NetworkOption } from '../util/type';
2
2
  export interface DeployOptions extends NetworkOption {
3
3
  target: string | null | undefined;
4
4
  privkey?: string | null;
5
+ typeId?: boolean;
5
6
  }
6
7
  export declare function deploy(opt?: DeployOptions): Promise<void>;
@@ -13,36 +13,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.deploy = void 0;
16
- const lumos_1 = require("@ckb-lumos/lumos");
17
- const fs_1 = __importDefault(require("fs"));
18
16
  const type_1 = require("../util/type");
19
17
  const path_1 = __importDefault(require("path"));
20
- const ckb_1 = require("../util/ckb");
21
18
  const account_1 = require("../cfg/account");
22
- const fs_2 = require("../util/fs");
19
+ const fs_1 = require("../util/fs");
23
20
  const validator_1 = require("../util/validator");
24
- const toml_1 = require("../deploy/toml");
25
- const migration_1 = require("../deploy/migration");
26
- const utils_1 = require("@ckb-lumos/lumos/utils");
27
- const gen_1 = require("../scripts/gen");
28
- const offckb_config_1 = require("../template/offckb-config");
29
- function deploy(opt = { network: type_1.Network.devnet, target: null }) {
21
+ const deploy_1 = require("../deploy");
22
+ const ckb_1 = require("../sdk/ckb");
23
+ function deploy(opt = { network: type_1.Network.devnet, typeId: false, target: null }) {
24
+ var _a;
30
25
  return __awaiter(this, void 0, void 0, function* () {
31
26
  const network = opt.network;
32
27
  (0, validator_1.validateNetworkOpt)(network);
33
- const ckb = new ckb_1.CKB(network);
28
+ const ckb = new ckb_1.CKB({ network });
34
29
  // we use deployerAccount to deploy contract by default
35
30
  const privateKey = opt.privkey || account_1.deployerAccount.privkey;
36
- const lumosConfig = ckb.getLumosConfig();
37
- const from = ckb_1.CKB.generateAccountFromPrivateKey(privateKey, lumosConfig);
31
+ const enableTypeId = (_a = opt.typeId) !== null && _a !== void 0 ? _a : false;
38
32
  const targetFolder = opt.target;
39
33
  if (targetFolder) {
40
- const binFolder = (0, fs_2.isAbsolutePath)(targetFolder) ? targetFolder : path_1.default.resolve(process.cwd(), targetFolder);
41
- const bins = (0, fs_2.listBinaryFilesInFolder)(binFolder);
34
+ const binFolder = (0, fs_1.isAbsolutePath)(targetFolder) ? targetFolder : path_1.default.resolve(process.cwd(), targetFolder);
35
+ const bins = (0, fs_1.listBinaryFilesInFolder)(binFolder);
42
36
  const binPaths = bins.map((bin) => path_1.default.resolve(binFolder, bin));
43
- const results = yield deployBinaries(binPaths, from, ckb);
37
+ const results = yield (0, deploy_1.deployBinaries)(binPaths, privateKey, enableTypeId, ckb);
44
38
  // record the deployed contract infos
45
- recordDeployResult(results, network, false); // we don't update my-scripts.json since we don't know where the file is
39
+ (0, deploy_1.recordDeployResult)(results, network, false); // we don't update my-scripts.json since we don't know where the file is
46
40
  return;
47
41
  }
48
42
  // check if target workspace is valid
@@ -53,105 +47,10 @@ function deploy(opt = { network: type_1.Network.devnet, target: null }) {
53
47
  return console.debug('Not a valid offCKB dapp workspace.');
54
48
  }
55
49
  // read contract bin folder
56
- const bins = getToDeployBinsPath();
57
- const results = yield deployBinaries(bins, from, ckb);
50
+ const bins = (0, deploy_1.getToDeployBinsPath)();
51
+ const results = yield (0, deploy_1.deployBinaries)(bins, privateKey, enableTypeId, ckb);
58
52
  // record the deployed contract infos
59
- recordDeployResult(results, network);
53
+ (0, deploy_1.recordDeployResult)(results, network);
60
54
  });
61
55
  }
62
56
  exports.deploy = deploy;
63
- function getToDeployBinsPath() {
64
- const userOffCKBConfigPath = path_1.default.resolve(process.cwd(), 'offckb.config.ts');
65
- const fileContent = fs_1.default.readFileSync(userOffCKBConfigPath, 'utf-8');
66
- const match = fileContent.match(/contractBinFolder:\s*['"]([^'"]+)['"]/);
67
- if (match && match[1]) {
68
- const contractBinFolderValue = match[1];
69
- const binFolderPath = (0, fs_2.isAbsolutePath)(contractBinFolderValue)
70
- ? contractBinFolderValue
71
- : path_1.default.resolve(process.cwd(), contractBinFolderValue);
72
- const bins = (0, fs_2.listBinaryFilesInFolder)(binFolderPath);
73
- return bins.map((bin) => path_1.default.resolve(binFolderPath, bin));
74
- }
75
- else {
76
- console.log('contractBinFolder value not found in offckb.config.ts');
77
- return [];
78
- }
79
- }
80
- function recordDeployResult(results, network, updateMyScriptsJsonFile = true) {
81
- return __awaiter(this, void 0, void 0, function* () {
82
- if (results.length === 0) {
83
- return;
84
- }
85
- for (const result of results) {
86
- (0, toml_1.generateDeploymentToml)(result.deploymentOptions, network);
87
- (0, migration_1.generateDeploymentRecipeJsonFile)(result.deploymentOptions.name, result.deploymentRecipe, network);
88
- }
89
- // update my-scripts.json
90
- if (updateMyScriptsJsonFile) {
91
- const userOffCKBConfigPath = path_1.default.resolve(process.cwd(), 'offckb.config.ts');
92
- const folder = offckb_config_1.OffCKBConfigFile.readContractInfoFolder(userOffCKBConfigPath);
93
- if (folder) {
94
- const myScriptsFilePath = path_1.default.resolve(folder, 'my-scripts.json');
95
- (0, gen_1.genMyScriptsJsonFile)(myScriptsFilePath);
96
- }
97
- }
98
- console.log('done.');
99
- });
100
- }
101
- function deployBinaries(binPaths, from, ckb) {
102
- return __awaiter(this, void 0, void 0, function* () {
103
- if (binPaths.length === 0) {
104
- console.log('No binary to deploy.');
105
- }
106
- const results = [];
107
- for (const bin of binPaths) {
108
- const result = yield deployBinary(bin, from, ckb);
109
- results.push(result);
110
- }
111
- return results;
112
- });
113
- }
114
- function deployBinary(binPath, from, ckb) {
115
- return __awaiter(this, void 0, void 0, function* () {
116
- const bin = yield (0, fs_2.readFileToUint8Array)(binPath);
117
- const contractName = path_1.default.basename(binPath);
118
- const result = yield lumos_1.commons.deploy.generateDeployWithTypeIdTx({
119
- cellProvider: ckb.indexer,
120
- fromInfo: from.address,
121
- scriptBinary: bin,
122
- config: ckb.getLumosConfig(),
123
- });
124
- // send deploy tx
125
- let txSkeleton = result.txSkeleton;
126
- txSkeleton = lumos_1.commons.common.prepareSigningEntries(txSkeleton);
127
- const message = txSkeleton.get('signingEntries').get(0).message;
128
- const Sig = lumos_1.hd.key.signRecoverable(message, from.privKey);
129
- const tx = lumos_1.helpers.sealTransaction(txSkeleton, [Sig]);
130
- const res = yield ckb.rpc.sendTransaction(tx, 'passthrough');
131
- console.log(`contract ${contractName} deployed, tx hash:`, res);
132
- console.log('wait 4 blocks..');
133
- yield ckb.indexer.waitForSync(-4); // why negative 4? a bug in ckb-lumos
134
- // todo: handle multiple cell recipes?
135
- return {
136
- deploymentOptions: {
137
- name: contractName,
138
- binFilePath: binPath,
139
- enableTypeId: true,
140
- lockScript: tx.outputs[+result.scriptConfig.INDEX].lock,
141
- },
142
- deploymentRecipe: {
143
- cellRecipes: [
144
- {
145
- name: contractName,
146
- txHash: result.scriptConfig.TX_HASH,
147
- index: result.scriptConfig.INDEX,
148
- occupiedCapacity: '0x' + BigInt(tx.outputsData[+result.scriptConfig.INDEX].slice(2).length / 2).toString(16),
149
- dataHash: (0, utils_1.ckbHash)(tx.outputsData[+result.scriptConfig.INDEX]),
150
- typeId: (0, utils_1.computeScriptHash)(result.typeId),
151
- },
152
- ],
153
- depGroupRecipes: [],
154
- },
155
- };
156
- });
157
- }
@@ -65,18 +65,22 @@ function getSystemScriptsFromListHashes() {
65
65
  const listHashesString = (0, list_hashes_1.getListHashes)(settings.bins.defaultCKBVersion);
66
66
  if (listHashesString) {
67
67
  const listHashes = toml_1.default.parse(listHashesString);
68
- const systemScriptArray = listHashes.offckb.system_cells
68
+ const chainSpecHashes = Object.values(listHashes)[0];
69
+ if (chainSpecHashes == null) {
70
+ throw new Error(`invalid chain spec hashes file ${listHashesString}`);
71
+ }
72
+ const systemScriptArray = chainSpecHashes.system_cells
69
73
  .map((cell) => {
70
74
  var _a;
71
75
  // Extract the file name
72
76
  const name = ((_a = cell.path.split('/').pop()) === null || _a === void 0 ? void 0 : _a.replace(')', '')) || 'unknown script';
73
- const depGroupIndex = listHashes.offckb.dep_groups.findIndex((depGroup) => depGroup.included_cells.includes(`Bundled(specs/cells/${name})`));
77
+ const depGroupIndex = chainSpecHashes.dep_groups.findIndex((depGroup) => depGroup.included_cells.includes(`Bundled(specs/cells/${name})`));
74
78
  const depType = depGroupIndex === -1 ? 'code' : 'depGroup';
75
79
  const depGroup = depGroupIndex === -1
76
80
  ? undefined
77
81
  : {
78
- txHash: listHashes.offckb.dep_groups[depGroupIndex].tx_hash,
79
- index: listHashes.offckb.dep_groups[depGroupIndex].index,
82
+ txHash: chainSpecHashes.dep_groups[depGroupIndex].tx_hash,
83
+ index: chainSpecHashes.dep_groups[depGroupIndex].index,
80
84
  };
81
85
  const scriptInfo = systemCellToScriptInfo(cell, depType, depGroup);
82
86
  return {
@@ -0,0 +1,18 @@
1
+ import { DeploymentOptions } from '../deploy/toml';
2
+ import { DeploymentRecipe } from '../deploy/migration';
3
+ import { HexString } from '@ckb-lumos/lumos';
4
+ import { Network } from '../util/type';
5
+ import { CKB } from '../sdk/ckb';
6
+ export type DeployBinaryReturnType = ReturnType<typeof deployBinary>;
7
+ export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
8
+ export type DeployedInterfaceType = UnwrapPromise<DeployBinaryReturnType>;
9
+ export declare function getToDeployBinsPath(): string[];
10
+ export declare function recordDeployResult(results: DeployedInterfaceType[], network: Network, isUpdateMyScriptsJsonFile?: boolean): Promise<void>;
11
+ export declare function deployBinaries(binPaths: string[], privateKey: HexString, enableTypeId: boolean, ckb: CKB): Promise<{
12
+ deploymentRecipe: DeploymentRecipe;
13
+ deploymentOptions: DeploymentOptions;
14
+ }[]>;
15
+ export declare function deployBinary(binPath: string, privateKey: HexString, enableTypeId: boolean, ckb: CKB): Promise<{
16
+ deploymentRecipe: DeploymentRecipe;
17
+ deploymentOptions: DeploymentOptions;
18
+ }>;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.deployBinary = exports.deployBinaries = exports.recordDeployResult = exports.getToDeployBinsPath = void 0;
16
+ const toml_1 = require("../deploy/toml");
17
+ const migration_1 = require("../deploy/migration");
18
+ const utils_1 = require("@ckb-lumos/lumos/utils");
19
+ const gen_1 = require("../scripts/gen");
20
+ const offckb_config_1 = require("../template/offckb-config");
21
+ const fs_1 = require("../util/fs");
22
+ const path_1 = __importDefault(require("path"));
23
+ const fs_2 = __importDefault(require("fs"));
24
+ function getToDeployBinsPath() {
25
+ const userOffCKBConfigPath = path_1.default.resolve(process.cwd(), 'offckb.config.ts');
26
+ const fileContent = fs_2.default.readFileSync(userOffCKBConfigPath, 'utf-8');
27
+ const match = fileContent.match(/contractBinFolder:\s*['"]([^'"]+)['"]/);
28
+ if (match && match[1]) {
29
+ const contractBinFolderValue = match[1];
30
+ const binFolderPath = (0, fs_1.isAbsolutePath)(contractBinFolderValue)
31
+ ? contractBinFolderValue
32
+ : path_1.default.resolve(process.cwd(), contractBinFolderValue);
33
+ const bins = (0, fs_1.listBinaryFilesInFolder)(binFolderPath);
34
+ return bins.map((bin) => path_1.default.resolve(binFolderPath, bin));
35
+ }
36
+ else {
37
+ console.log('contractBinFolder value not found in offckb.config.ts');
38
+ return [];
39
+ }
40
+ }
41
+ exports.getToDeployBinsPath = getToDeployBinsPath;
42
+ function recordDeployResult(results, network, isUpdateMyScriptsJsonFile = true) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ if (results.length === 0) {
45
+ return;
46
+ }
47
+ for (const result of results) {
48
+ (0, toml_1.generateDeploymentToml)(result.deploymentOptions, network);
49
+ (0, migration_1.generateDeploymentMigrationFile)(result.deploymentOptions.name, result.deploymentRecipe, network);
50
+ }
51
+ // update my-scripts.json
52
+ if (isUpdateMyScriptsJsonFile) {
53
+ const userOffCKBConfigPath = path_1.default.resolve(process.cwd(), 'offckb.config.ts');
54
+ const folder = offckb_config_1.OffCKBConfigFile.readContractInfoFolder(userOffCKBConfigPath);
55
+ if (folder) {
56
+ const myScriptsFilePath = path_1.default.resolve(folder, 'my-scripts.json');
57
+ (0, gen_1.genMyScriptsJsonFile)(myScriptsFilePath);
58
+ }
59
+ }
60
+ console.log('done.');
61
+ });
62
+ }
63
+ exports.recordDeployResult = recordDeployResult;
64
+ function deployBinaries(binPaths, privateKey, enableTypeId, ckb) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ if (binPaths.length === 0) {
67
+ console.log('No binary to deploy.');
68
+ }
69
+ const results = [];
70
+ for (const bin of binPaths) {
71
+ const result = yield deployBinary(bin, privateKey, enableTypeId, ckb);
72
+ results.push(result);
73
+ }
74
+ return results;
75
+ });
76
+ }
77
+ exports.deployBinaries = deployBinaries;
78
+ function deployBinary(binPath, privateKey, enableTypeId, ckb) {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ const bin = yield (0, fs_1.readFileToUint8Array)(binPath);
81
+ const contractName = path_1.default.basename(binPath);
82
+ const result = !enableTypeId
83
+ ? yield ckb.deployScript(bin, privateKey)
84
+ : migration_1.Migration.isDeployedWithTypeId(contractName, ckb.network)
85
+ ? yield ckb.upgradeTypeIdScript(contractName, bin, privateKey)
86
+ : yield ckb.deployNewTypeIDScript(bin, privateKey);
87
+ console.log(`contract ${contractName} deployed, tx hash:`, result.txHash);
88
+ console.log('wait for tx confirmed on-chain...');
89
+ yield ckb.waitForTxConfirm(result.txHash);
90
+ console.log('tx committed.');
91
+ const txHash = result.txHash;
92
+ const typeIdScript = result.typeId;
93
+ const index = result.scriptOutputCellIndex;
94
+ const tx = result.tx;
95
+ const dataByteLen = BigInt(tx.outputsData[+index].slice(2).length / 2);
96
+ const dataShannonLen = dataByteLen * BigInt('100000000');
97
+ const occupiedCapacity = '0x' + dataShannonLen.toString(16);
98
+ if (enableTypeId && typeIdScript == null) {
99
+ throw new Error('type id script is null while enableTypeId is true.');
100
+ }
101
+ const typeIdScriptHash = enableTypeId ? (0, utils_1.computeScriptHash)(typeIdScript) : undefined;
102
+ // todo: handle multiple cell recipes?
103
+ return {
104
+ deploymentOptions: {
105
+ name: contractName,
106
+ binFilePath: binPath,
107
+ enableTypeId: enableTypeId,
108
+ lockScript: tx.outputs[+index].lock,
109
+ },
110
+ deploymentRecipe: {
111
+ cellRecipes: [
112
+ {
113
+ name: contractName,
114
+ txHash,
115
+ index: '0x' + index.toString(16),
116
+ occupiedCapacity,
117
+ dataHash: (0, utils_1.ckbHash)(tx.outputsData[+index]),
118
+ typeId: typeIdScriptHash,
119
+ },
120
+ ],
121
+ depGroupRecipes: [],
122
+ },
123
+ };
124
+ });
125
+ }
126
+ exports.deployBinary = deployBinary;
@@ -19,27 +19,32 @@ export interface DeploymentRecipe {
19
19
  cellRecipes: CellRecipe[];
20
20
  depGroupRecipes: DepGroupRecipe[];
21
21
  }
22
- export interface DeploymentRecipeJson {
22
+ export interface MigrationJson {
23
23
  cell_recipes: {
24
24
  name: string;
25
25
  tx_hash: string;
26
- index: HexNumber;
27
- occupied_capacity: HexNumber;
26
+ index: number;
27
+ occupied_capacity: number;
28
28
  data_hash: string;
29
29
  type_id?: string;
30
30
  }[];
31
31
  dep_group_recipes: {
32
32
  name: string;
33
33
  tx_hash: string;
34
- index: HexNumber;
34
+ index: number;
35
35
  data_hash: string;
36
- occupied_capacity: HexNumber;
36
+ occupied_capacity: number;
37
37
  }[];
38
38
  }
39
- export declare function generateDeploymentRecipeJsonFile(name: string, deploymentRecipe: DeploymentRecipe, network?: Network): void;
40
- export declare function readDeploymentRecipeJsonFile(filePath: string): DeploymentRecipe;
39
+ export declare class Migration {
40
+ static find(scriptName: string, network?: Network): DeploymentRecipe | null;
41
+ static isDeployed(scriptName: string, network?: Network): boolean;
42
+ static isDeployedWithTypeId(scriptName: string, network?: Network): boolean;
43
+ }
44
+ export declare function generateDeploymentMigrationFile(name: string, deploymentRecipe: DeploymentRecipe, network?: Network): void;
45
+ export declare function readDeploymentMigrationFile(filePath: string): DeploymentRecipe;
41
46
  export declare function getFormattedMigrationDate(): string;
42
47
  export declare function getMigrationFolderPath(scriptName: string, network: Network): string;
43
- export declare function getNewestMigrationFile(folderPath: string): string | undefined;
44
- export declare function deploymentRecipeToJson(recipe: DeploymentRecipe): DeploymentRecipeJson;
45
- export declare function deploymentRecipeFromJson(json: DeploymentRecipeJson): DeploymentRecipe;
48
+ export declare function getNewestMigrationFile(folderPath: string): string | null;
49
+ export declare function deploymentRecipeToJson(recipe: DeploymentRecipe): MigrationJson;
50
+ export declare function deploymentRecipeFromJson(json: MigrationJson): DeploymentRecipe;
@@ -26,12 +26,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.deploymentRecipeFromJson = exports.deploymentRecipeToJson = exports.getNewestMigrationFile = exports.getMigrationFolderPath = exports.getFormattedMigrationDate = exports.readDeploymentRecipeJsonFile = exports.generateDeploymentRecipeJsonFile = void 0;
29
+ exports.deploymentRecipeFromJson = exports.deploymentRecipeToJson = exports.getNewestMigrationFile = exports.getMigrationFolderPath = exports.getFormattedMigrationDate = exports.readDeploymentMigrationFile = exports.generateDeploymentMigrationFile = exports.Migration = void 0;
30
30
  const type_1 = require("../util/type");
31
31
  const path_1 = __importStar(require("path"));
32
32
  const fs_1 = __importDefault(require("fs"));
33
33
  const util_1 = require("./util");
34
- function generateDeploymentRecipeJsonFile(name, deploymentRecipe, network = type_1.Network.devnet) {
34
+ class Migration {
35
+ static find(scriptName, network = type_1.Network.devnet) {
36
+ const filePath = getMigrationFolderPath(scriptName, network);
37
+ const migrationFile = getNewestMigrationFile(filePath);
38
+ if (migrationFile == null)
39
+ return null;
40
+ return readDeploymentMigrationFile(migrationFile);
41
+ }
42
+ static isDeployed(scriptName, network = type_1.Network.devnet) {
43
+ const deploymentReceipt = Migration.find(scriptName, network);
44
+ if (deploymentReceipt == null)
45
+ return false;
46
+ return true;
47
+ }
48
+ static isDeployedWithTypeId(scriptName, network = type_1.Network.devnet) {
49
+ const isDeployed = this.isDeployed(scriptName, network);
50
+ if (isDeployed === false)
51
+ return false;
52
+ const deploymentReceipt = Migration.find(scriptName, network);
53
+ const typeId = deploymentReceipt.cellRecipes[0].typeId;
54
+ if (typeId == null)
55
+ return false;
56
+ return true;
57
+ }
58
+ }
59
+ exports.Migration = Migration;
60
+ function generateDeploymentMigrationFile(name, deploymentRecipe, network = type_1.Network.devnet) {
35
61
  const cellRecipes = deploymentRecipe.cellRecipes;
36
62
  const depGroupRecipes = deploymentRecipe.depGroupRecipes;
37
63
  const jsonString = JSON.stringify(deploymentRecipeToJson({ cellRecipes, depGroupRecipes }), null, 2);
@@ -44,13 +70,13 @@ function generateDeploymentRecipeJsonFile(name, deploymentRecipe, network = type
44
70
  console.log(`${name} migration json file ${outputFilePath} generated successfully.`);
45
71
  }
46
72
  }
47
- exports.generateDeploymentRecipeJsonFile = generateDeploymentRecipeJsonFile;
48
- function readDeploymentRecipeJsonFile(filePath) {
73
+ exports.generateDeploymentMigrationFile = generateDeploymentMigrationFile;
74
+ function readDeploymentMigrationFile(filePath) {
49
75
  const jsonString = fs_1.default.readFileSync(filePath, 'utf-8');
50
76
  const data = JSON.parse(jsonString);
51
77
  return deploymentRecipeFromJson(data);
52
78
  }
53
- exports.readDeploymentRecipeJsonFile = readDeploymentRecipeJsonFile;
79
+ exports.readDeploymentMigrationFile = readDeploymentMigrationFile;
54
80
  function getFormattedMigrationDate() {
55
81
  const now = new Date();
56
82
  const year = now.getFullYear();
@@ -68,6 +94,9 @@ function getMigrationFolderPath(scriptName, network) {
68
94
  }
69
95
  exports.getMigrationFolderPath = getMigrationFolderPath;
70
96
  function getNewestMigrationFile(folderPath) {
97
+ if (!fs_1.default.existsSync(folderPath) || !fs_1.default.lstatSync(folderPath).isDirectory()) {
98
+ return null;
99
+ }
71
100
  const files = fs_1.default
72
101
  .readdirSync(folderPath)
73
102
  .filter((file) => file.endsWith('.json')) // Ensure only JSON files are considered
@@ -78,28 +107,36 @@ function getNewestMigrationFile(folderPath) {
78
107
  return timestampA.localeCompare(timestampB);
79
108
  });
80
109
  // Return the full path of the newest file (last in sorted array) or undefined if no files
81
- return files.length > 0 ? path_1.default.join(folderPath, files[files.length - 1]) : undefined;
110
+ return files.length > 0 ? path_1.default.join(folderPath, files[files.length - 1]) : null;
82
111
  }
83
112
  exports.getNewestMigrationFile = getNewestMigrationFile;
84
113
  function deploymentRecipeToJson(recipe) {
85
114
  return {
86
115
  cell_recipes: recipe.cellRecipes.map((val) => {
116
+ if (BigInt(val.occupiedCapacity) > BigInt(Number.MAX_SAFE_INTEGER)) {
117
+ // CKB blocksize limit is 500k, so it should be impossible to have a cell occupied data larger than Number.MAX_SAFE_INTEGER which is 9007,1992,5474,0991
118
+ console.error(`invalid occupiedCapacity: ${val.occupiedCapacity}, the cell_recipes json might be incorrect for cell outpoint ${val.txHash}:${+val.index}`);
119
+ }
87
120
  return {
88
121
  name: val.name,
89
122
  tx_hash: val.txHash,
90
- index: val.index,
91
- occupied_capacity: val.occupiedCapacity,
123
+ index: +val.index,
124
+ occupied_capacity: +BigInt(val.occupiedCapacity).toString(10),
92
125
  data_hash: val.dataHash,
93
126
  type_id: val.typeId,
94
127
  };
95
128
  }),
96
129
  dep_group_recipes: recipe.depGroupRecipes.map((val) => {
130
+ if (BigInt(val.occupiedCapacity) > BigInt(Number.MAX_SAFE_INTEGER)) {
131
+ // CKB blocksize limit is 500k, so it should be impossible to have a cell occupied data larger than Number.MAX_SAFE_INTEGER which is 9007,1992,5474,0991
132
+ console.error(`invalid occupiedCapacity: ${val.occupiedCapacity}, the dep_group_recipes json might be incorrect for cell outpoint ${val.txHash}:${+val.index}`);
133
+ }
97
134
  return {
98
135
  name: val.name,
99
136
  tx_hash: val.txHash,
100
- index: val.index,
137
+ index: +val.index,
101
138
  data_hash: val.dataHash,
102
- occupied_capacity: val.occupiedCapacity,
139
+ occupied_capacity: +BigInt(val.occupiedCapacity).toString(10),
103
140
  };
104
141
  }),
105
142
  };
@@ -111,8 +148,8 @@ function deploymentRecipeFromJson(json) {
111
148
  return {
112
149
  name: val.name,
113
150
  txHash: val.tx_hash,
114
- index: val.index,
115
- occupiedCapacity: val.occupied_capacity,
151
+ index: '0x' + val.index.toString(16),
152
+ occupiedCapacity: '0x' + val.occupied_capacity.toString(16),
116
153
  dataHash: val.data_hash,
117
154
  typeId: val.type_id,
118
155
  };
@@ -121,9 +158,9 @@ function deploymentRecipeFromJson(json) {
121
158
  return {
122
159
  name: val.name,
123
160
  txHash: val.tx_hash,
124
- index: val.index,
161
+ index: '0x' + val.index.toString(16),
125
162
  dataHash: val.data_hash,
126
- occupiedCapacity: val.occupied_capacity,
163
+ occupiedCapacity: '0x' + val.occupied_capacity.toString(16),
127
164
  };
128
165
  }),
129
166
  };
@@ -10,7 +10,7 @@ export interface DeploymentOptions {
10
10
  export interface DeploymentToml {
11
11
  cells: {
12
12
  name: string;
13
- enable_type_id: 'true' | 'false';
13
+ enable_type_id: boolean;
14
14
  location: {
15
15
  file: string;
16
16
  };
@@ -13,7 +13,7 @@ function generateDeploymentToml(options, network) {
13
13
  cells: [
14
14
  {
15
15
  name: options.name,
16
- enable_type_id: options.enableTypeId ? 'true' : 'false',
16
+ enable_type_id: options.enableTypeId,
17
17
  location: {
18
18
  file: options.binFilePath,
19
19
  },
@@ -44,7 +44,7 @@ function readDeploymentToml(scriptName, network) {
44
44
  cells: [
45
45
  {
46
46
  name: data.cells[0].name,
47
- enableTypeId: data.cells[0].enable_type_id === 'true' ? true : false,
47
+ enableTypeId: data.cells[0].enable_type_id,
48
48
  location: {
49
49
  file: data.cells[0].location.file,
50
50
  },
@@ -0,0 +1,4 @@
1
+ import { ccc } from '@ckb-ccc/core';
2
+ export declare function buildCCCDevnetKnownScripts(): Record<ccc.KnownScript, Pick<ccc.Script, "codeHash" | "hashType"> & {
3
+ cellDeps: ccc.CellDepInfoLike[];
4
+ }>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCCCDevnetKnownScripts = void 0;
4
+ //todo: extract getSystemScriptsFromListHashes/toCCCKnownScripts from cmd folder
5
+ const system_scripts_1 = require("../cmd/system-scripts");
6
+ function buildCCCDevnetKnownScripts() {
7
+ const devnetSystemScripts = (0, system_scripts_1.getSystemScriptsFromListHashes)();
8
+ if (devnetSystemScripts == null) {
9
+ throw new Error('can not getSystemScriptsFromListHashes in devnet');
10
+ }
11
+ const devnetKnownScripts = (0, system_scripts_1.toCCCKnownScripts)(devnetSystemScripts);
12
+ return devnetKnownScripts;
13
+ }
14
+ exports.buildCCCDevnetKnownScripts = buildCCCDevnetKnownScripts;
@@ -42,7 +42,7 @@ function readUserDeployedScriptsInfo(network) {
42
42
  if (newestFilePath) {
43
43
  try {
44
44
  // Read the file content
45
- const recipe = (0, migration_1.readDeploymentRecipeJsonFile)(newestFilePath);
45
+ const recipe = (0, migration_1.readDeploymentMigrationFile)(newestFilePath);
46
46
  // todo: handle multiple cell recipes?
47
47
  const firstCell = recipe.cellRecipes[0];
48
48
  const isDepCode = recipe.depGroupRecipes.length > 0;
@@ -0,0 +1,24 @@
1
+ import { ccc, Script } from '@ckb-ccc/core';
2
+ import { Network } from '../util/type';
3
+ import { HexString } from '@ckb-lumos/lumos';
4
+ export declare class CKBProps {
5
+ network?: Network;
6
+ isEnableProxyRpc?: boolean;
7
+ }
8
+ export interface DeploymentResult {
9
+ txHash: HexString;
10
+ tx: ccc.Transaction;
11
+ scriptOutputCellIndex: number;
12
+ isTypeId: boolean;
13
+ typeId?: Script;
14
+ }
15
+ export declare class CKB {
16
+ network: Network;
17
+ private client;
18
+ constructor({ network, isEnableProxyRpc }: CKBProps);
19
+ private buildSigner;
20
+ waitForTxConfirm(txHash: HexString, timeout?: number): Promise<void>;
21
+ deployScript(scriptBinBytes: Uint8Array, privateKey: string): Promise<DeploymentResult>;
22
+ deployNewTypeIDScript(scriptBinBytes: Uint8Array, privateKey: string): Promise<DeploymentResult>;
23
+ upgradeTypeIdScript(scriptName: string, newScriptBinBytes: Uint8Array, privateKey: HexString): Promise<DeploymentResult>;
24
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // this is a rewrite for util/ckb.ts
3
+ // to replace lumos with ccc
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.CKB = exports.CKBProps = void 0;
15
+ const core_1 = require("@ckb-ccc/core");
16
+ const type_1 = require("../util/type");
17
+ const validator_1 = require("../util/validator");
18
+ const network_1 = require("./network");
19
+ const private_1 = require("../scripts/private");
20
+ const migration_1 = require("../deploy/migration");
21
+ class CKBProps {
22
+ }
23
+ exports.CKBProps = CKBProps;
24
+ class CKB {
25
+ constructor({ network = type_1.Network.devnet, isEnableProxyRpc = false }) {
26
+ if (!(0, validator_1.isValidNetworkString)(network)) {
27
+ throw new Error('invalid network option');
28
+ }
29
+ this.network = network;
30
+ if (isEnableProxyRpc === true) {
31
+ this.client =
32
+ network === 'mainnet'
33
+ ? new core_1.ccc.ClientPublicMainnet({ url: network_1.networks.mainnet.proxy_rpc_url })
34
+ : network === 'testnet'
35
+ ? new core_1.ccc.ClientPublicTestnet({ url: network_1.networks.testnet.proxy_rpc_url })
36
+ : new core_1.ccc.ClientPublicTestnet({
37
+ url: network_1.networks.devnet.proxy_rpc_url,
38
+ scripts: (0, private_1.buildCCCDevnetKnownScripts)(),
39
+ });
40
+ }
41
+ else {
42
+ this.client =
43
+ network === 'mainnet'
44
+ ? new core_1.ccc.ClientPublicMainnet()
45
+ : network === 'testnet'
46
+ ? new core_1.ccc.ClientPublicTestnet()
47
+ : new core_1.ccc.ClientPublicTestnet({
48
+ url: network_1.networks.devnet.rpc_url,
49
+ scripts: (0, private_1.buildCCCDevnetKnownScripts)(),
50
+ });
51
+ }
52
+ }
53
+ buildSigner(privateKey) {
54
+ const signer = new core_1.ccc.SignerCkbPrivateKey(this.client, privateKey);
55
+ return signer;
56
+ }
57
+ waitForTxConfirm(txHash, timeout = 60000) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const query = () => __awaiter(this, void 0, void 0, function* () {
60
+ const res = yield this.client.getTransactionNoCache(txHash);
61
+ if (res && res.status === 'committed') {
62
+ return true;
63
+ }
64
+ else {
65
+ return false;
66
+ }
67
+ });
68
+ return waitFor(query, timeout, 5000);
69
+ });
70
+ }
71
+ deployScript(scriptBinBytes, privateKey) {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ const signer = this.buildSigner(privateKey);
74
+ const signerSecp256k1Address = yield signer.getAddressObjSecp256k1();
75
+ const tx = core_1.ccc.Transaction.from({
76
+ outputs: [
77
+ {
78
+ lock: signerSecp256k1Address.script,
79
+ },
80
+ ],
81
+ outputsData: [scriptBinBytes],
82
+ });
83
+ yield tx.completeInputsByCapacity(signer);
84
+ yield tx.completeFeeBy(signer, 1000);
85
+ const txHash = yield signer.sendTransaction(tx);
86
+ return { txHash, tx, scriptOutputCellIndex: 0, isTypeId: false };
87
+ });
88
+ }
89
+ deployNewTypeIDScript(scriptBinBytes, privateKey) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ const signer = this.buildSigner(privateKey);
92
+ const signerSecp256k1Address = yield signer.getAddressObjSecp256k1();
93
+ const typeIdTx = core_1.ccc.Transaction.from({
94
+ outputs: [
95
+ {
96
+ lock: signerSecp256k1Address.script,
97
+ type: yield core_1.ccc.Script.fromKnownScript(this.client, core_1.ccc.KnownScript.TypeId, '00'.repeat(32)),
98
+ },
99
+ ],
100
+ outputsData: [scriptBinBytes],
101
+ });
102
+ yield typeIdTx.completeInputsByCapacity(signer);
103
+ if (!typeIdTx.outputs[0].type) {
104
+ throw new Error('Unexpected disappeared output');
105
+ }
106
+ typeIdTx.outputs[0].type.args = core_1.ccc.hashTypeId(typeIdTx.inputs[0], 0);
107
+ yield typeIdTx.completeFeeBy(signer, 1000);
108
+ const txHash = yield signer.sendTransaction(typeIdTx);
109
+ return { txHash, tx: typeIdTx, scriptOutputCellIndex: 0, isTypeId: true, typeId: typeIdTx.outputs[0].type };
110
+ });
111
+ }
112
+ upgradeTypeIdScript(scriptName, newScriptBinBytes, privateKey) {
113
+ var _a;
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ const deploymentReceipt = migration_1.Migration.find(scriptName, this.network);
116
+ if (deploymentReceipt == null)
117
+ throw new Error("no migration file, can't be updated.");
118
+ const outpoint = {
119
+ txHash: deploymentReceipt.cellRecipes[0].txHash,
120
+ index: deploymentReceipt.cellRecipes[0].index,
121
+ };
122
+ const typeId = deploymentReceipt.cellRecipes[0].typeId;
123
+ if (typeId == null)
124
+ throw new Error("type id in migration file is null, can't be updated.");
125
+ const cell = yield this.client.getCell(outpoint);
126
+ if (cell == null) {
127
+ throw new Error('type id cell not found!');
128
+ }
129
+ const typeIdArgs = (_a = cell.cellOutput.type) === null || _a === void 0 ? void 0 : _a.args;
130
+ if (typeIdArgs == null) {
131
+ throw new Error("type id args is null, can't be updated");
132
+ }
133
+ const typeIdFromLiveCell = core_1.ccc.Script.from(cell.cellOutput.type).hash();
134
+ if (typeId !== typeIdFromLiveCell) {
135
+ throw new Error(`type id not matched! migration file type id: ${typeId}, live cell type id: ${typeIdFromLiveCell}`);
136
+ }
137
+ const cellInput = core_1.ccc.CellInput.from({ previousOutput: cell.outPoint, since: 0 });
138
+ const signer = this.buildSigner(privateKey);
139
+ const signerSecp256k1Address = yield signer.getAddressObjSecp256k1();
140
+ const typeIdTx = core_1.ccc.Transaction.from({
141
+ inputs: [cellInput],
142
+ outputs: [
143
+ {
144
+ lock: signerSecp256k1Address.script,
145
+ type: yield core_1.ccc.Script.fromKnownScript(this.client, core_1.ccc.KnownScript.TypeId, '00'.repeat(32)),
146
+ },
147
+ ],
148
+ outputsData: [newScriptBinBytes],
149
+ });
150
+ yield typeIdTx.completeInputsByCapacity(signer);
151
+ if (!typeIdTx.outputs[0].type) {
152
+ throw new Error('Unexpected disappeared output');
153
+ }
154
+ typeIdTx.outputs[0].type.args = typeIdArgs;
155
+ yield typeIdTx.completeFeeBy(signer, 1000);
156
+ const txHash = yield signer.sendTransaction(typeIdTx);
157
+ return { txHash, tx: typeIdTx, scriptOutputCellIndex: 0, isTypeId: true, typeId: typeIdTx.outputs[0].type };
158
+ });
159
+ }
160
+ }
161
+ exports.CKB = CKB;
162
+ function waitFor(query, timeout, interval) {
163
+ return __awaiter(this, void 0, void 0, function* () {
164
+ const startTime = Date.now();
165
+ while (true) {
166
+ if (Date.now() - startTime > timeout) {
167
+ throw new Error('Operation timed out');
168
+ }
169
+ try {
170
+ const result = yield query();
171
+ if (result)
172
+ break;
173
+ }
174
+ catch (error) {
175
+ console.debug(error.message);
176
+ }
177
+ yield new Promise((resolve) => setTimeout(resolve, interval));
178
+ }
179
+ });
180
+ }
@@ -0,0 +1,17 @@
1
+ export declare const networks: {
2
+ devnet: {
3
+ addr_prefix: string;
4
+ rpc_url: string;
5
+ proxy_rpc_url: string;
6
+ };
7
+ testnet: {
8
+ addr_prefix: string;
9
+ rpc_url: string;
10
+ proxy_rpc_url: string;
11
+ };
12
+ mainnet: {
13
+ addr_prefix: string;
14
+ rpc_url: string;
15
+ proxy_rpc_url: string;
16
+ };
17
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.networks = void 0;
4
+ const setting_1 = require("../cfg/setting");
5
+ const config = (0, setting_1.readSettings)();
6
+ exports.networks = {
7
+ devnet: {
8
+ addr_prefix: 'ckt',
9
+ rpc_url: config.devnet.rpcUrl,
10
+ proxy_rpc_url: `http://127.0.0.1:${config.rpc.proxyPort}`,
11
+ },
12
+ testnet: {
13
+ addr_prefix: 'ckt',
14
+ rpc_url: config.testnet.rpcUrl,
15
+ proxy_rpc_url: `http://127.0.0.1:${config.rpc.proxyPort}`,
16
+ },
17
+ mainnet: {
18
+ addr_prefix: 'ckb',
19
+ rpc_url: config.mainnet.rpcUrl,
20
+ proxy_rpc_url: `http://127.0.0.1:${config.rpc.proxyPort}`,
21
+ },
22
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offckb/cli",
3
- "version": "0.3.0-rc2",
3
+ "version": "0.3.0-rc4",
4
4
  "description": "ckb development network for your first try",
5
5
  "author": "CKB EcoFund",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "typescript": "^5.3.3"
60
60
  },
61
61
  "dependencies": {
62
- "@ckb-ccc/core": "^0.0.11-alpha.3",
62
+ "@ckb-ccc/core": "^0.0.16-alpha.3",
63
63
  "@ckb-lumos/lumos": "0.23.0",
64
64
  "@iarna/toml": "^2.2.5",
65
65
  "@inquirer/prompts": "^4.1.0",