@offckb/cli 0.1.0-rc3 → 0.1.0-rc5
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 +87 -16
- package/{docker → ckb}/devnet/specs/dev.toml +16 -2
- package/ckb/devnet/specs/spore-scripts/cluster +0 -0
- package/ckb/devnet/specs/spore-scripts/cluster_agent +0 -0
- package/ckb/devnet/specs/spore-scripts/cluster_proxy +0 -0
- package/ckb/devnet/specs/spore-scripts/spore +0 -0
- package/ckb/devnet/specs/spore-scripts/spore_extension_lua +0 -0
- package/dist/cfg/const.d.ts +1 -0
- package/dist/cfg/const.js +4 -3
- package/dist/cfg/select.d.ts +1 -0
- package/dist/cfg/select.js +51 -0
- package/dist/cli.js +34 -16
- package/dist/cmd/build-lumos-config.d.ts +2 -2
- package/dist/cmd/build-lumos-config.js +92 -69
- package/dist/cmd/clean.d.ts +1 -0
- package/dist/cmd/clean.js +21 -0
- package/dist/cmd/genkey.js +3 -7
- package/dist/cmd/init-chain.js +1 -43
- package/dist/cmd/init.d.ts +1 -1
- package/dist/cmd/init.js +10 -4
- package/dist/util.d.ts +5 -0
- package/dist/util.js +101 -1
- package/package.json +9 -9
- package/{template → templates}/config.json +8 -8
- package/templates/transfer/index.html +13 -0
- package/{template → templates/transfer}/index.tsx +38 -4
- package/{template → templates/xudt}/index.html +1 -1
- package/templates/xudt/index.tsx +106 -0
- package/templates/xudt/lib.ts +179 -0
- package/templates/xudt/package.json +29 -0
- package/templates/xudt/scheme.ts +27 -0
- package/templates/xudt/tsconfig.json +10 -0
- package/templates/xudt/yarn.lock +2315 -0
- package/docker/docker-compose.yml +0 -37
- /package/{docker → ckb}/devnet/check-ckb-started-successfully.sh +0 -0
- /package/{docker → ckb}/devnet/ckb-miner.toml +0 -0
- /package/{docker → ckb}/devnet/ckb.toml +0 -0
- /package/{docker → ckb}/devnet/default.db-options +0 -0
- /package/{docker → ckb}/devnet/specs/always_success +0 -0
- /package/{docker → ckb}/devnet/specs/anyone_can_pay +0 -0
- /package/{docker → ckb}/devnet/specs/omni_lock +0 -0
- /package/{docker → ckb}/devnet/specs/sudt +0 -0
- /package/{docker → ckb}/devnet/specs/xudt_rce +0 -0
- /package/{template → templates}/ckb.ts +0 -0
- /package/{template → templates/transfer}/lib.ts +0 -0
- /package/{template → templates/transfer}/package.json +0 -0
- /package/{template → templates/transfer}/tsconfig.json +0 -0
- /package/{template → templates/transfer}/yarn.lock +0 -0
package/dist/cmd/init.js
CHANGED
|
@@ -4,12 +4,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.init = void 0;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
7
|
const const_1 = require("../cfg/const");
|
|
9
8
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
|
|
9
|
+
const util_1 = require("../util");
|
|
10
|
+
function init(name, template) {
|
|
11
11
|
const targetPath = path_1.default.resolve(const_1.currentExecPath, name);
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const sourcePath = path_1.default.resolve(const_1.dappTemplatePath, template);
|
|
13
|
+
(0, util_1.copyFolderSync)(sourcePath, targetPath);
|
|
14
|
+
// add some common code files
|
|
15
|
+
const ckbDotTs = path_1.default.resolve(const_1.dappTemplatePath, 'ckb.ts');
|
|
16
|
+
const configJson = path_1.default.resolve(const_1.dappTemplatePath, 'config.json');
|
|
17
|
+
(0, util_1.copyFileSync)(ckbDotTs, targetPath);
|
|
18
|
+
(0, util_1.copyFileSync)(configJson, targetPath);
|
|
19
|
+
console.log(`init CKB dapp project: ${targetPath}`);
|
|
14
20
|
}
|
|
15
21
|
exports.init = init;
|
package/dist/util.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
1
|
export declare function isFolderExists(folderPath: string): boolean;
|
|
2
|
+
export declare function copyFolderSync(source: string, destination: string): void;
|
|
3
|
+
export declare function copyFileSync(source: string, target: string): void;
|
|
4
|
+
export declare function copyFilesWithExclusion(sourceDir: string, destinationDir: string, excludedFolders: string[]): Promise<void>;
|
|
5
|
+
export declare function copyRecursive(source: string, destination: string, excludedFolders: string[]): Promise<void>;
|
|
6
|
+
export declare function removeFolderSync(folderPath: string): void;
|
package/dist/util.js
CHANGED
|
@@ -22,9 +22,19 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
25
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.isFolderExists = void 0;
|
|
35
|
+
exports.removeFolderSync = exports.copyRecursive = exports.copyFilesWithExclusion = exports.copyFileSync = exports.copyFolderSync = exports.isFolderExists = void 0;
|
|
27
36
|
const fs = __importStar(require("fs"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
28
38
|
function isFolderExists(folderPath) {
|
|
29
39
|
try {
|
|
30
40
|
// Check if the path exists
|
|
@@ -39,3 +49,93 @@ function isFolderExists(folderPath) {
|
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
exports.isFolderExists = isFolderExists;
|
|
52
|
+
function copyFolderSync(source, destination) {
|
|
53
|
+
if (!fs.existsSync(destination)) {
|
|
54
|
+
fs.mkdirSync(destination);
|
|
55
|
+
}
|
|
56
|
+
const files = fs.readdirSync(source);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const currentPath = path.join(source, file);
|
|
59
|
+
const destinationPath = path.join(destination, file);
|
|
60
|
+
if (fs.statSync(currentPath).isDirectory()) {
|
|
61
|
+
copyFolderSync(currentPath, destinationPath);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
fs.copyFileSync(currentPath, destinationPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.copyFolderSync = copyFolderSync;
|
|
69
|
+
function copyFileSync(source, target) {
|
|
70
|
+
let targetFile = target;
|
|
71
|
+
// If target is a directory, a new file with the same name will be created
|
|
72
|
+
if (fs.existsSync(target)) {
|
|
73
|
+
if (fs.lstatSync(target).isDirectory()) {
|
|
74
|
+
targetFile = path.join(target, path.basename(source));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
fs.writeFileSync(targetFile, fs.readFileSync(source));
|
|
78
|
+
}
|
|
79
|
+
exports.copyFileSync = copyFileSync;
|
|
80
|
+
function copyFilesWithExclusion(sourceDir, destinationDir, excludedFolders) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
try {
|
|
83
|
+
// Ensure the destination directory exists
|
|
84
|
+
yield fs.promises.mkdir(destinationDir, { recursive: true });
|
|
85
|
+
// Start copying recursively from the source directory
|
|
86
|
+
yield copyRecursive(sourceDir, destinationDir, excludedFolders);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('An error occurred during copying files:', error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
exports.copyFilesWithExclusion = copyFilesWithExclusion;
|
|
94
|
+
// Function to recursively copy files and directories
|
|
95
|
+
function copyRecursive(source, destination, excludedFolders) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
// Get a list of all files and directories in the source directory
|
|
98
|
+
const files = yield fs.promises.readdir(source);
|
|
99
|
+
// Iterate through each file or directory
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const sourcePath = path.join(source, file);
|
|
102
|
+
const destPath = path.join(destination, file);
|
|
103
|
+
// Get the file's stats
|
|
104
|
+
const stats = yield fs.promises.stat(sourcePath);
|
|
105
|
+
// If it's a directory, recursively copy it (unless it's excluded)
|
|
106
|
+
if (stats.isDirectory()) {
|
|
107
|
+
if (excludedFolders.includes(file)) {
|
|
108
|
+
// Skipping directory: ${sourcePath}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Ensure destination directory exists before copying
|
|
112
|
+
yield fs.promises.mkdir(destPath, { recursive: true });
|
|
113
|
+
yield copyRecursive(sourcePath, destPath, excludedFolders);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Otherwise, copy the file
|
|
118
|
+
yield fs.promises.copyFile(sourcePath, destPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
exports.copyRecursive = copyRecursive;
|
|
124
|
+
function removeFolderSync(folderPath) {
|
|
125
|
+
if (fs.existsSync(folderPath)) {
|
|
126
|
+
fs.readdirSync(folderPath).forEach((file) => {
|
|
127
|
+
const curPath = path.join(folderPath, file);
|
|
128
|
+
if (fs.lstatSync(curPath).isDirectory()) {
|
|
129
|
+
// Recursive call for directories
|
|
130
|
+
removeFolderSync(curPath);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Delete files
|
|
134
|
+
fs.unlinkSync(curPath);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Remove the directory itself
|
|
138
|
+
fs.rmdirSync(folderPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.removeFolderSync = removeFolderSync;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@offckb/cli",
|
|
3
|
-
"version": "0.1.0-
|
|
4
|
-
"description": "ckb development
|
|
3
|
+
"version": "0.1.0-rc5",
|
|
4
|
+
"description": "ckb development network for your first try",
|
|
5
5
|
"author": "Retric Su <retric@cryptape.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"account",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"ckb",
|
|
19
|
+
"templates"
|
|
20
20
|
],
|
|
21
21
|
"private": false,
|
|
22
22
|
"publishConfig": {
|
|
@@ -26,10 +26,9 @@
|
|
|
26
26
|
"build": "tsc",
|
|
27
27
|
"start": "ts-node-dev --transpile-only src/cli.ts",
|
|
28
28
|
"clean": "rm -rf ./target",
|
|
29
|
-
"
|
|
30
|
-
"lint": "eslint \"{src,
|
|
31
|
-
"
|
|
32
|
-
"fmt": "prettier --write '{src,template,account}/**/*.{js,jsx,ts,tsx,md,json}'"
|
|
29
|
+
"lint": "eslint \"{src,templates}/**/*.ts\" \"{src,templates}/**/*.tsx\" --ignore-pattern 'node_modules/'",
|
|
30
|
+
"lint:fix": "eslint \"{src,templates}/**/*.ts\" \"{src,templates}/**/*.tsx\" --ignore-pattern 'node_modules/' --fix",
|
|
31
|
+
"fmt": "prettier --write '{src,templates,account}/**/*.{js,jsx,ts,tsx,md,json}'"
|
|
33
32
|
},
|
|
34
33
|
"husky": {
|
|
35
34
|
"hooks": {
|
|
@@ -37,7 +36,7 @@
|
|
|
37
36
|
}
|
|
38
37
|
},
|
|
39
38
|
"lint-staged": {
|
|
40
|
-
"{src,
|
|
39
|
+
"{src,templates,account}/**/*.{js,jsx,ts,tsx,md,json}": "prettier --ignore-unknown --write"
|
|
41
40
|
},
|
|
42
41
|
"eslintConfig": {
|
|
43
42
|
"extends": [
|
|
@@ -60,6 +59,7 @@
|
|
|
60
59
|
},
|
|
61
60
|
"dependencies": {
|
|
62
61
|
"@ckb-lumos/lumos": "0.21.1",
|
|
62
|
+
"@inquirer/prompts": "^4.1.0",
|
|
63
63
|
"adm-zip": "^0.5.10",
|
|
64
64
|
"axios": "^1.6.7",
|
|
65
65
|
"child_process": "^1.0.2",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SECP256K1_BLAKE160": {
|
|
5
5
|
"CODE_HASH": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
|
|
6
6
|
"HASH_TYPE": "type",
|
|
7
|
-
"TX_HASH": "
|
|
7
|
+
"TX_HASH": "0x75be96e1871693f030db27ddae47890a28ab180e88e36ebb3575d9f1377d3da7",
|
|
8
8
|
"INDEX": "0x0",
|
|
9
9
|
"DEP_TYPE": "depGroup",
|
|
10
10
|
"SHORT_ID": 1
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
"SECP256K1_BLAKE160_MULTISIG": {
|
|
13
13
|
"CODE_HASH": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8",
|
|
14
14
|
"HASH_TYPE": "type",
|
|
15
|
-
"TX_HASH": "
|
|
15
|
+
"TX_HASH": "0x75be96e1871693f030db27ddae47890a28ab180e88e36ebb3575d9f1377d3da7",
|
|
16
16
|
"INDEX": "0x1",
|
|
17
17
|
"DEP_TYPE": "depGroup"
|
|
18
18
|
},
|
|
19
19
|
"DAO": {
|
|
20
20
|
"CODE_HASH": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e",
|
|
21
21
|
"HASH_TYPE": "type",
|
|
22
|
-
"TX_HASH": "
|
|
22
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
23
23
|
"INDEX": "0x2",
|
|
24
24
|
"DEP_TYPE": "code",
|
|
25
25
|
"SHORT_ID": 2
|
|
@@ -27,35 +27,35 @@
|
|
|
27
27
|
"SUDT": {
|
|
28
28
|
"CODE_HASH": "0x6283a479a3cf5d4276cd93594de9f1827ab9b55c7b05b3d28e4c2e0a696cfefd",
|
|
29
29
|
"HASH_TYPE": "type",
|
|
30
|
-
"TX_HASH": "
|
|
30
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
31
31
|
"INDEX": "0x5",
|
|
32
32
|
"DEP_TYPE": "code"
|
|
33
33
|
},
|
|
34
34
|
"XUDT": {
|
|
35
35
|
"CODE_HASH": "0x1a1e4fef34f5982906f745b048fe7b1089647e82346074e0f32c2ece26cf6b1e",
|
|
36
36
|
"HASH_TYPE": "type",
|
|
37
|
-
"TX_HASH": "
|
|
37
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
38
38
|
"INDEX": "0x6",
|
|
39
39
|
"DEP_TYPE": "code"
|
|
40
40
|
},
|
|
41
41
|
"OMNILOCK": {
|
|
42
42
|
"CODE_HASH": "0x9c6933d977360f115a3e9cd5a2e0e475853681b80d775d93ad0f8969da343e56",
|
|
43
43
|
"HASH_TYPE": "type",
|
|
44
|
-
"TX_HASH": "
|
|
44
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
45
45
|
"INDEX": "0x7",
|
|
46
46
|
"DEP_TYPE": "code"
|
|
47
47
|
},
|
|
48
48
|
"ANYONE_CAN_PAY": {
|
|
49
49
|
"CODE_HASH": "0xe09352af0066f3162287763ce4ddba9af6bfaeab198dc7ab37f8c71c9e68bb5b",
|
|
50
50
|
"HASH_TYPE": "type",
|
|
51
|
-
"TX_HASH": "
|
|
51
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
52
52
|
"INDEX": "0x8",
|
|
53
53
|
"DEP_TYPE": "code"
|
|
54
54
|
},
|
|
55
55
|
"ALWAYS_SUCCESS": {
|
|
56
56
|
"CODE_HASH": "0xbb4469004225b39e983929db71fe2253cba1d49a76223e9e1d212cdca1f79f28",
|
|
57
57
|
"HASH_TYPE": "type",
|
|
58
|
-
"TX_HASH": "
|
|
58
|
+
"TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
|
|
59
59
|
"INDEX": "0x9",
|
|
60
60
|
"DEP_TYPE": "code"
|
|
61
61
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>View and Transfer Balance</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src="index.tsx" type="module"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -29,10 +29,35 @@ export function App() {
|
|
|
29
29
|
}
|
|
30
30
|
}, [privKey]);
|
|
31
31
|
|
|
32
|
+
const onInputPrivKey = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
// Regular expression to match a valid private key with "0x" prefix
|
|
34
|
+
const priv = e.target.value;
|
|
35
|
+
const privateKeyRegex = /^0x[0-9a-fA-F]{64}$/;
|
|
36
|
+
|
|
37
|
+
const isValid = privateKeyRegex.test(priv);
|
|
38
|
+
if (isValid) {
|
|
39
|
+
setPrivKey(priv);
|
|
40
|
+
} else {
|
|
41
|
+
alert(
|
|
42
|
+
`Invalid private key: must start with 0x and 32 bytes length. Ensure you're using a valid private key from the offckb accounts list.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const enabled = +amount > 6100000000 && +balance > +amount && toAddr.length > 0;
|
|
48
|
+
const amountTip =
|
|
49
|
+
amount.length > 0 && +amount < 6100000000 ? (
|
|
50
|
+
<span>
|
|
51
|
+
amount must larger than 6,100,000,000(61 CKB), see{' '}
|
|
52
|
+
<a href="https://medium.com/nervosnetwork/understanding-the-nervos-dao-and-cell-model-d68f38272c24">why</a>
|
|
53
|
+
</span>
|
|
54
|
+
) : null;
|
|
55
|
+
|
|
32
56
|
return (
|
|
33
57
|
<div>
|
|
58
|
+
<h1>View and Transfer Balance</h1>
|
|
34
59
|
<label htmlFor="private-key">Private Key: </label>
|
|
35
|
-
<input id="private-key" type="text" onChange={
|
|
60
|
+
<input id="private-key" type="text" onChange={onInputPrivKey} />
|
|
36
61
|
<ul>
|
|
37
62
|
<li>CKB Address: {fromAddr}</li>
|
|
38
63
|
<li>
|
|
@@ -40,16 +65,25 @@ export function App() {
|
|
|
40
65
|
<pre>{JSON.stringify(fromLock, null, 2)}</pre>
|
|
41
66
|
</li>
|
|
42
67
|
|
|
43
|
-
<li>Total capacity: {balance}</li>
|
|
68
|
+
<li>Total capacity: {(+balance).toLocaleString()}</li>
|
|
44
69
|
</ul>
|
|
45
70
|
<label htmlFor="to-address">Transfer to Address: </label>
|
|
46
71
|
<input id="to-address" type="text" onChange={(e) => setToAddr(e.target.value)} />
|
|
47
72
|
<br />
|
|
48
73
|
<label htmlFor="amount">Amount</label>
|
|
49
74
|
|
|
50
|
-
<input id="amount" type="
|
|
75
|
+
<input id="amount" type="number" onChange={(e) => setAmount(e.target.value)} />
|
|
76
|
+
<small>Tx fee: 100,000 (0.001 CKB)</small>
|
|
77
|
+
<br />
|
|
78
|
+
<small style={{ color: 'red' }}>{amountTip}</small>
|
|
79
|
+
<br />
|
|
51
80
|
<br />
|
|
52
|
-
<button
|
|
81
|
+
<button
|
|
82
|
+
disabled={!enabled}
|
|
83
|
+
onClick={() => transfer({ amount, from: fromAddr, to: toAddr, privKey }).catch(alert)}
|
|
84
|
+
>
|
|
85
|
+
Transfer
|
|
86
|
+
</button>
|
|
53
87
|
</div>
|
|
54
88
|
);
|
|
55
89
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>Issue Custom Token</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { Cell, Script } from '@ckb-lumos/lumos';
|
|
4
|
+
import {
|
|
5
|
+
capacityOf,
|
|
6
|
+
computeLockScriptHashFromPrivateKey,
|
|
7
|
+
generateAccountFromPrivateKey,
|
|
8
|
+
issueToken,
|
|
9
|
+
queryIssuedTokenCells,
|
|
10
|
+
readTokenAmount,
|
|
11
|
+
} from './lib';
|
|
12
|
+
|
|
13
|
+
const app = document.getElementById('root');
|
|
14
|
+
ReactDOM.render(<App />, app);
|
|
15
|
+
|
|
16
|
+
export function App() {
|
|
17
|
+
const [privKey, setPrivKey] = useState('');
|
|
18
|
+
const [fromAddr, setFromAddr] = useState('');
|
|
19
|
+
const [fromLock, setFromLock] = useState<Script>();
|
|
20
|
+
const [balance, setBalance] = useState('0');
|
|
21
|
+
|
|
22
|
+
const [amount, setAmount] = useState('');
|
|
23
|
+
const [cells, setCells] = useState<Cell[]>([]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const updateFromInfo = async () => {
|
|
27
|
+
const { lockScript, address } = generateAccountFromPrivateKey(privKey);
|
|
28
|
+
const capacity = await capacityOf(address);
|
|
29
|
+
setFromAddr(address);
|
|
30
|
+
setFromLock(lockScript);
|
|
31
|
+
setBalance(capacity.toString());
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (privKey) {
|
|
35
|
+
updateFromInfo();
|
|
36
|
+
}
|
|
37
|
+
}, [privKey]);
|
|
38
|
+
|
|
39
|
+
const onInputPrivKey = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
40
|
+
// Regular expression to match a valid private key with "0x" prefix
|
|
41
|
+
const priv = e.target.value;
|
|
42
|
+
const privateKeyRegex = /^0x[0-9a-fA-F]{64}$/;
|
|
43
|
+
|
|
44
|
+
const isValid = privateKeyRegex.test(priv);
|
|
45
|
+
if (isValid) {
|
|
46
|
+
setPrivKey(priv);
|
|
47
|
+
} else {
|
|
48
|
+
alert(
|
|
49
|
+
`Invalid private key: must start with 0x and 32 bytes length. Ensure you're using a valid private key from the offckb accounts list.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const enabledIssue = +amount > 0 && +balance > 6100000000;
|
|
55
|
+
const enabledCheck = privKey.length > 0;
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<h1>
|
|
59
|
+
Issue Custom Token{' '}
|
|
60
|
+
<small>
|
|
61
|
+
<a href="https://github.com/XuJiandong/rfcs/blob/xudt/rfcs/0052-extensible-udt/0052-extensible-udt.md#xudt-witness">
|
|
62
|
+
{'(xUDT specs)'}
|
|
63
|
+
</a>
|
|
64
|
+
</small>
|
|
65
|
+
</h1>
|
|
66
|
+
<p></p>
|
|
67
|
+
<label htmlFor="private-key">Private Key: </label>
|
|
68
|
+
<input id="private-key" type="text" onChange={onInputPrivKey} />
|
|
69
|
+
<ul>
|
|
70
|
+
<li>CKB Address: {fromAddr}</li>
|
|
71
|
+
<li>
|
|
72
|
+
Current lock script:
|
|
73
|
+
<pre>{JSON.stringify(fromLock, null, 2)}</pre>
|
|
74
|
+
</li>
|
|
75
|
+
|
|
76
|
+
<li>Total capacity: {(+balance).toLocaleString()}</li>
|
|
77
|
+
</ul>
|
|
78
|
+
<br />
|
|
79
|
+
<label htmlFor="amount">Token Amount</label>
|
|
80
|
+
|
|
81
|
+
<input id="amount" type="text" onChange={(e) => setAmount(e.target.value)} />
|
|
82
|
+
<br />
|
|
83
|
+
<button disabled={!enabledIssue} onClick={() => issueToken(privKey, amount).catch(alert)}>
|
|
84
|
+
Issue token
|
|
85
|
+
</button>
|
|
86
|
+
<br />
|
|
87
|
+
<br />
|
|
88
|
+
<hr />
|
|
89
|
+
<p>after issued token, click the below button to check it</p>
|
|
90
|
+
<button disabled={!enabledCheck} onClick={() => queryIssuedTokenCells(privKey).then(setCells).catch(alert)}>
|
|
91
|
+
Check issued token
|
|
92
|
+
</button>
|
|
93
|
+
{cells.length > 0 && <h3>Result: all the cells which hosted this issued token</h3>}
|
|
94
|
+
{cells.map((cell, index) => (
|
|
95
|
+
<div key={index}>
|
|
96
|
+
<p>Cell #{index}</p>
|
|
97
|
+
<p>token amount: {readTokenAmount(cell.data).toNumber()}</p>
|
|
98
|
+
<p>issuer lockScript Hash: {computeLockScriptHashFromPrivateKey(privKey)}</p>
|
|
99
|
+
<p>token xudt args: {cell.cellOutput.type.args}</p>
|
|
100
|
+
<p>token holder's lockScript args: {cell.cellOutput.lock.args}</p>
|
|
101
|
+
<hr />
|
|
102
|
+
</div>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { helpers, Address, Script, hd, config, Cell, commons, BI, utils, CellDep } from '@ckb-lumos/lumos';
|
|
2
|
+
import { values, blockchain } from '@ckb-lumos/base';
|
|
3
|
+
import { indexer, lumosConfig, rpc } from './ckb';
|
|
4
|
+
import { TransactionSkeletonType } from '@ckb-lumos/helpers';
|
|
5
|
+
import { bytes, number } from '@ckb-lumos/codec';
|
|
6
|
+
import { xudtWitnessType } from './scheme';
|
|
7
|
+
|
|
8
|
+
config.initializeConfig(lumosConfig);
|
|
9
|
+
|
|
10
|
+
type Account = {
|
|
11
|
+
lockScript: Script;
|
|
12
|
+
address: Address;
|
|
13
|
+
pubKey: string;
|
|
14
|
+
};
|
|
15
|
+
export const generateAccountFromPrivateKey = (privKey: string): Account => {
|
|
16
|
+
const pubKey = hd.key.privateToPublic(privKey);
|
|
17
|
+
const args = hd.key.publicKeyToBlake160(pubKey);
|
|
18
|
+
const template = lumosConfig.SCRIPTS['SECP256K1_BLAKE160']!;
|
|
19
|
+
const lockScript = {
|
|
20
|
+
codeHash: template.CODE_HASH,
|
|
21
|
+
hashType: template.HASH_TYPE,
|
|
22
|
+
args: args,
|
|
23
|
+
};
|
|
24
|
+
const address = helpers.encodeToAddress(lockScript, { config: lumosConfig });
|
|
25
|
+
return {
|
|
26
|
+
lockScript,
|
|
27
|
+
address,
|
|
28
|
+
pubKey,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const computeLockScriptHashFromPrivateKey = (privkey: string) => {
|
|
33
|
+
const { lockScript } = generateAccountFromPrivateKey(privkey);
|
|
34
|
+
return utils.computeScriptHash(lockScript);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export async function capacityOf(address: string): Promise<BI> {
|
|
38
|
+
const collector = indexer.collector({
|
|
39
|
+
lock: helpers.parseAddress(address, { config: lumosConfig }),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let balance = BI.from(0);
|
|
43
|
+
for await (const cell of collector.collect()) {
|
|
44
|
+
balance = balance.add(cell.cellOutput.capacity);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return balance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function addCellDep(txSkeleton: TransactionSkeletonType, newCellDep: CellDep): TransactionSkeletonType {
|
|
51
|
+
const cellDep = txSkeleton.get('cellDeps').find((cellDep) => {
|
|
52
|
+
return (
|
|
53
|
+
cellDep.depType === newCellDep.depType &&
|
|
54
|
+
new values.OutPointValue(cellDep.outPoint, { validate: false }).equals(
|
|
55
|
+
new values.OutPointValue(newCellDep.outPoint, { validate: false }),
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!cellDep) {
|
|
61
|
+
txSkeleton = txSkeleton.update('cellDeps', (cellDeps) => {
|
|
62
|
+
return cellDeps.push({
|
|
63
|
+
outPoint: newCellDep.outPoint,
|
|
64
|
+
depType: newCellDep.depType,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return txSkeleton;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function issueToken(privKey: string, amount: string) {
|
|
73
|
+
const { lockScript } = generateAccountFromPrivateKey(privKey);
|
|
74
|
+
const xudtDeps = lumosConfig.SCRIPTS.XUDT;
|
|
75
|
+
const lockDeps = lumosConfig.SCRIPTS.SECP256K1_BLAKE160;
|
|
76
|
+
|
|
77
|
+
const xudtArgs = utils.computeScriptHash(lockScript) + '00000000';
|
|
78
|
+
const typeScript = {
|
|
79
|
+
codeHash: xudtDeps.CODE_HASH,
|
|
80
|
+
hashType: xudtDeps.HASH_TYPE,
|
|
81
|
+
args: xudtArgs,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let txSkeleton = helpers.TransactionSkeleton();
|
|
85
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
86
|
+
outPoint: {
|
|
87
|
+
txHash: lockDeps.TX_HASH,
|
|
88
|
+
index: lockDeps.INDEX,
|
|
89
|
+
},
|
|
90
|
+
depType: lockDeps.DEP_TYPE,
|
|
91
|
+
});
|
|
92
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
93
|
+
outPoint: {
|
|
94
|
+
txHash: xudtDeps.TX_HASH,
|
|
95
|
+
index: xudtDeps.INDEX,
|
|
96
|
+
},
|
|
97
|
+
depType: xudtDeps.DEP_TYPE,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const targetOutput: Cell = {
|
|
101
|
+
cellOutput: {
|
|
102
|
+
capacity: '0x0',
|
|
103
|
+
lock: lockScript,
|
|
104
|
+
type: typeScript,
|
|
105
|
+
},
|
|
106
|
+
data: bytes.hexify(number.Uint128LE.pack(amount)),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// additional 0.001 ckb for tx fee
|
|
110
|
+
// the tx fee could calculated by tx size
|
|
111
|
+
// this is just a simple example
|
|
112
|
+
const capacity = helpers.minimalCellCapacity(targetOutput);
|
|
113
|
+
targetOutput.cellOutput.capacity = '0x' + capacity.toString(16);
|
|
114
|
+
const neededCapacity = BI.from(capacity.toString(10)).add(100000);
|
|
115
|
+
let collectedSum = BI.from(0);
|
|
116
|
+
const collected: Cell[] = [];
|
|
117
|
+
const collector = indexer.collector({ lock: lockScript, type: 'empty' });
|
|
118
|
+
for await (const cell of collector.collect()) {
|
|
119
|
+
collectedSum = collectedSum.add(cell.cellOutput.capacity);
|
|
120
|
+
collected.push(cell);
|
|
121
|
+
if (collectedSum >= neededCapacity) break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (collectedSum.lt(neededCapacity)) {
|
|
125
|
+
throw new Error(`Not enough CKB, ${collectedSum} < ${neededCapacity}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const changeOutput: Cell = {
|
|
129
|
+
cellOutput: {
|
|
130
|
+
capacity: collectedSum.sub(neededCapacity).toHexString(),
|
|
131
|
+
lock: lockScript,
|
|
132
|
+
},
|
|
133
|
+
data: '0x',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
txSkeleton = txSkeleton.update('inputs', (inputs) => inputs.push(...collected));
|
|
137
|
+
txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(targetOutput, changeOutput));
|
|
138
|
+
/* 65-byte zeros in hex */
|
|
139
|
+
const lockWitness =
|
|
140
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
|
|
141
|
+
const outputTypeWitness = xudtWitnessType.pack({ extension_data: [] });
|
|
142
|
+
const witnessArgs = blockchain.WitnessArgs.pack({ lock: lockWitness, outputType: outputTypeWitness });
|
|
143
|
+
const witness = bytes.hexify(witnessArgs);
|
|
144
|
+
txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(0, witness));
|
|
145
|
+
|
|
146
|
+
// signing
|
|
147
|
+
txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
|
|
148
|
+
const message = txSkeleton.get('signingEntries').get(0)?.message;
|
|
149
|
+
const Sig = hd.key.signRecoverable(message!, privKey);
|
|
150
|
+
const tx = helpers.sealTransaction(txSkeleton, [Sig]);
|
|
151
|
+
console.log(tx);
|
|
152
|
+
|
|
153
|
+
const hash = await rpc.sendTransaction(tx, 'passthrough');
|
|
154
|
+
console.log('The transaction hash is', hash);
|
|
155
|
+
alert(`The transaction hash is ${hash}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function queryIssuedTokenCells(privKey: string) {
|
|
159
|
+
const { lockScript } = generateAccountFromPrivateKey(privKey);
|
|
160
|
+
const xudtDeps = lumosConfig.SCRIPTS.XUDT;
|
|
161
|
+
|
|
162
|
+
const xudtArgs = utils.computeScriptHash(lockScript) + '00000000';
|
|
163
|
+
const typeScript = {
|
|
164
|
+
codeHash: xudtDeps.CODE_HASH,
|
|
165
|
+
hashType: xudtDeps.HASH_TYPE,
|
|
166
|
+
args: xudtArgs,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const collected: Cell[] = [];
|
|
170
|
+
const collector = indexer.collector({ type: typeScript });
|
|
171
|
+
for await (const cell of collector.collect()) {
|
|
172
|
+
collected.push(cell);
|
|
173
|
+
}
|
|
174
|
+
return collected;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function readTokenAmount(amount: string) {
|
|
178
|
+
return number.Uint128LE.unpack(amount);
|
|
179
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@offckb/xudt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "parcel index.html",
|
|
8
|
+
"lint": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@ckb-lumos/base": "0.21.1",
|
|
15
|
+
"@ckb-lumos/codec": "0.21.1",
|
|
16
|
+
"@ckb-lumos/lumos": "0.21.1",
|
|
17
|
+
"@types/react": "^18.0.25",
|
|
18
|
+
"@types/react-dom": "^18.0.9",
|
|
19
|
+
"react": "^18.2.0",
|
|
20
|
+
"react-dom": "^18.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"crypto-browserify": "^3.12.0",
|
|
24
|
+
"parcel": "^2.9.3",
|
|
25
|
+
"path-browserify": "^1.0.0",
|
|
26
|
+
"process": "^0.11.10",
|
|
27
|
+
"stream-browserify": "^3.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|