@ckb-firewall/cli 0.1.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 +21 -0
- package/README.md +108 -0
- package/dist/commands/add.d.ts +13 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +247 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/execute.d.ts +12 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +229 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/inspect.d.ts +9 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +77 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/proposals.d.ts +4 -0
- package/dist/commands/proposals.d.ts.map +1 -0
- package/dist/commands/proposals.js +77 -0
- package/dist/commands/proposals.js.map +1 -0
- package/dist/commands/propose.d.ts +12 -0
- package/dist/commands/propose.d.ts.map +1 -0
- package/dist/commands/propose.js +240 -0
- package/dist/commands/propose.js.map +1 -0
- package/dist/commands/remove.d.ts +12 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +236 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/sign.d.ts +7 -0
- package/dist/commands/sign.d.ts.map +1 -0
- package/dist/commands/sign.js +178 -0
- package/dist/commands/sign.js.map +1 -0
- package/dist/commands/vote.d.ts +7 -0
- package/dist/commands/vote.d.ts.map +1 -0
- package/dist/commands/vote.js +106 -0
- package/dist/commands/vote.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/blkl.d.ts +8 -0
- package/dist/lib/blkl.d.ts.map +1 -0
- package/dist/lib/blkl.js +62 -0
- package/dist/lib/blkl.js.map +1 -0
- package/dist/lib/defaults.d.ts +22 -0
- package/dist/lib/defaults.d.ts.map +1 -0
- package/dist/lib/defaults.js +28 -0
- package/dist/lib/defaults.js.map +1 -0
- package/dist/lib/hints.d.ts +7 -0
- package/dist/lib/hints.d.ts.map +1 -0
- package/dist/lib/hints.js +67 -0
- package/dist/lib/hints.js.map +1 -0
- package/dist/lib/proposals.d.ts +61 -0
- package/dist/lib/proposals.d.ts.map +1 -0
- package/dist/lib/proposals.js +89 -0
- package/dist/lib/proposals.js.map +1 -0
- package/dist/lib/rpc.d.ts +24 -0
- package/dist/lib/rpc.d.ts.map +1 -0
- package/dist/lib/rpc.js +55 -0
- package/dist/lib/rpc.js.map +1 -0
- package/dist/lib/witness.d.ts +22 -0
- package/dist/lib/witness.d.ts.map +1 -0
- package/dist/lib/witness.js +82 -0
- package/dist/lib/witness.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 digitaldrreamer and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @ckb-firewall/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ckb-firewall/cli)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Command-line tooling for inspecting and governing the [CKB Transaction Firewall](https://github.com/digitaldrreamer/ckb-transaction-firewall) blacklist registry.
|
|
7
|
+
|
|
8
|
+
Node 20+. ESM only.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ckb-firewall/cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or use the one-line installer (handles Node version checks and PATH setup):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
curl -fsSL https://raw.githubusercontent.com/digitaldrreamer/ckb-transaction-firewall/main/scripts/install-cli.sh | bash
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
### `ckb-firewall inspect`
|
|
25
|
+
|
|
26
|
+
Display the current blacklist entries from the live testnet registry cell.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
ckb-firewall inspect
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Options: `--rpc-url`, `--registry-tx`, `--registry-index` (defaults point at the canonical testnet cell).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### Quick path — testnet/dev
|
|
37
|
+
|
|
38
|
+
These commands produce a signed transaction file ready for `ckb-cli`. They use placeholder governance witnesses and are intended for testnet experimentation and development.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Add a lock-args to the blacklist
|
|
42
|
+
ckb-firewall add --lock-args 0xabc123...
|
|
43
|
+
|
|
44
|
+
# Remove a lock-args from the blacklist
|
|
45
|
+
ckb-firewall remove --lock-args 0xabc123...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Both commands are interactive when flags are omitted. Both accept `--sign` to sign and submit in one step via `ckb-cli`.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### Full governance flow
|
|
53
|
+
|
|
54
|
+
The full flow creates an auditable, community-reviewed proposal with a 72-hour review window, validator voting, and 3-of-5 multisig signing before anything is executed on-chain.
|
|
55
|
+
|
|
56
|
+
**1. Create a proposal**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ckb-firewall propose
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Prompts for action (add/remove), lock args, threat classification, severity, evidence, rationale, and proposer identity. Stores the proposal locally (`~/.ckb-firewall/proposals/`) and prints the proposal ID.
|
|
63
|
+
|
|
64
|
+
**2. Vote on proposals**
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ckb-firewall vote --proposal <id> --vote yes --validator alice
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Interactive when flags are omitted. Requires a minimum of 3 yes votes before signing is allowed.
|
|
71
|
+
|
|
72
|
+
**3. List proposals**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ckb-firewall proposals
|
|
76
|
+
ckb-firewall proposals --status voting
|
|
77
|
+
ckb-firewall proposals --status approved
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Shows a table with status, vote tally, signature count, and review window countdown.
|
|
81
|
+
|
|
82
|
+
**4. Sign an approved proposal**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
ckb-firewall sign --proposal <id> --signer-index 0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Prompts for a 32-byte secp256k1 private key (password-masked). Falls back to a deterministic dev key for testnet use. Signing is only allowed after the 72-hour review window has passed and the vote threshold is met.
|
|
89
|
+
|
|
90
|
+
**5. Execute on-chain**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ckb-firewall execute --proposal <id>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Builds and writes a governance transaction JSON file. Add `--sign` to sign and submit via `ckb-cli` in one step.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Testnet defaults
|
|
101
|
+
|
|
102
|
+
All commands default to the canonical testnet registry cell. See [`docs/deployments/testnet.registry.json`](https://github.com/digitaldrreamer/ckb-transaction-firewall/blob/main/docs/deployments/testnet.registry.json) for the exact cell outpoint and script identity.
|
|
103
|
+
|
|
104
|
+
## More
|
|
105
|
+
|
|
106
|
+
- [CKB Transaction Firewall](https://github.com/digitaldrreamer/ckb-transaction-firewall) — contracts, TypeScript SDK, Rust SDK, governance docs, testnet deployment
|
|
107
|
+
- [Governance model](https://github.com/digitaldrreamer/ckb-transaction-firewall/blob/main/docs/governance.md)
|
|
108
|
+
- [Architecture and trust model](https://github.com/digitaldrreamer/ckb-transaction-firewall/blob/main/docs/architecture.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AddOptions {
|
|
2
|
+
lockArgs?: string;
|
|
3
|
+
expiresAt: string;
|
|
4
|
+
rpcUrl: string;
|
|
5
|
+
registryTx: string;
|
|
6
|
+
registryIndex: string;
|
|
7
|
+
txOut: string;
|
|
8
|
+
sign: boolean;
|
|
9
|
+
fromAccount: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function addCommand(opts: AddOptions): Promise<void>;
|
|
12
|
+
export declare function addDefaults(): Partial<AddOptions>;
|
|
13
|
+
//# sourceMappingURL=add.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AA0DD,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAwMhE;AAED,wBAAgB,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,CAUjD"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import logSymbols from "log-symbols";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import { parseRegistryPayload } from "@ckb-firewall/sdk";
|
|
8
|
+
import { getLiveCell } from "../lib/rpc.js";
|
|
9
|
+
import { encodeRegistryPayload, insertSorted, bytesToHex, hexToBytes, strip0x, } from "../lib/blkl.js";
|
|
10
|
+
import { ckbBlake2b, buildGov1Witness, buildWitnessArgs, placeholderSigners, } from "../lib/witness.js";
|
|
11
|
+
import { TESTNET_RPC_URL, TESTNET_REGISTRY_CELL, TESTNET_CONTRACT_OUTPOINTS, SECP256K1_DEP_GROUP, } from "../lib/defaults.js";
|
|
12
|
+
import { printHints } from "../lib/hints.js";
|
|
13
|
+
function isValidHex(value) {
|
|
14
|
+
const clean = strip0x(value);
|
|
15
|
+
return clean.length > 0 && /^[0-9a-fA-F]+$/.test(clean) && clean.length % 2 === 0;
|
|
16
|
+
}
|
|
17
|
+
function normaliseHex(value) {
|
|
18
|
+
return `0x${strip0x(value).toLowerCase()}`;
|
|
19
|
+
}
|
|
20
|
+
async function promptLockArgs() {
|
|
21
|
+
const { lockArgs } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: "input",
|
|
24
|
+
name: "lockArgs",
|
|
25
|
+
message: "Lock args to blacklist (0x-prefixed hex):",
|
|
26
|
+
validate(input) {
|
|
27
|
+
if (!input.trim())
|
|
28
|
+
return "Lock args cannot be empty.";
|
|
29
|
+
if (!isValidHex(input.trim()))
|
|
30
|
+
return "Must be valid even-length hex (e.g. 0xabc123...).";
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
return lockArgs.trim();
|
|
36
|
+
}
|
|
37
|
+
async function promptExpiresAt() {
|
|
38
|
+
const { choice } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: "list",
|
|
41
|
+
name: "choice",
|
|
42
|
+
message: "Expiry:",
|
|
43
|
+
choices: [
|
|
44
|
+
{ name: "Never (permanent listing)", value: "never" },
|
|
45
|
+
{ name: "Custom unix timestamp", value: "custom" },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
if (choice === "never")
|
|
50
|
+
return "0";
|
|
51
|
+
const { ts } = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: "input",
|
|
54
|
+
name: "ts",
|
|
55
|
+
message: "Expiry (unix timestamp in seconds):",
|
|
56
|
+
validate(input) {
|
|
57
|
+
const n = Number(input.trim());
|
|
58
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
59
|
+
return "Enter a positive integer.";
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
return ts.trim();
|
|
65
|
+
}
|
|
66
|
+
export async function addCommand(opts) {
|
|
67
|
+
const rpcUrl = opts.rpcUrl;
|
|
68
|
+
const txHash = opts.registryTx;
|
|
69
|
+
const index = Number.parseInt(opts.registryIndex, 10);
|
|
70
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
71
|
+
console.error(logSymbols.error, chalk.red("--registry-index must be a non-negative integer."));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
// ── interactive prompts when args are missing ────────────────────────────
|
|
75
|
+
let rawLockArgs = opts.lockArgs?.trim() ?? "";
|
|
76
|
+
if (!rawLockArgs) {
|
|
77
|
+
console.log();
|
|
78
|
+
rawLockArgs = await promptLockArgs();
|
|
79
|
+
}
|
|
80
|
+
else if (!isValidHex(rawLockArgs)) {
|
|
81
|
+
console.error(logSymbols.error, chalk.red(`Invalid --lock-args: "${rawLockArgs}". Must be even-length hex (e.g. 0xabc123...).`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const lockArgs = normaliseHex(rawLockArgs);
|
|
85
|
+
// If expiresAt is still the default "0", optionally prompt (skip if running non-interactively).
|
|
86
|
+
let expiresAtStr = opts.expiresAt;
|
|
87
|
+
if (expiresAtStr === "0" && !opts.lockArgs && process.stdin.isTTY) {
|
|
88
|
+
expiresAtStr = await promptExpiresAt();
|
|
89
|
+
}
|
|
90
|
+
if (!/^\d+$/.test(expiresAtStr.trim())) {
|
|
91
|
+
console.error(logSymbols.error, chalk.red("--expires-at must be a non-negative integer (unix timestamp, or 0 for never)."));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const expiresAt = BigInt(expiresAtStr.trim());
|
|
95
|
+
// ── fetch registry ───────────────────────────────────────────────────────
|
|
96
|
+
const spinner = ora("Fetching current registry cell").start();
|
|
97
|
+
let cell;
|
|
98
|
+
try {
|
|
99
|
+
cell = await getLiveCell(rpcUrl, txHash, index);
|
|
100
|
+
spinner.succeed("Registry cell loaded");
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
spinner.fail("Could not fetch the registry cell");
|
|
104
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
+
console.error(chalk.red(`\n ${msg}`));
|
|
106
|
+
console.error(chalk.dim(" Check that your RPC node is reachable and the cell outpoint is correct."));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
// ── parse current payload ────────────────────────────────────────────────
|
|
110
|
+
let currentPayload;
|
|
111
|
+
try {
|
|
112
|
+
currentPayload = parseRegistryPayload(cell.data);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
console.error(logSymbols.error, chalk.red("The registry cell does not contain a valid BLKL v1 payload."));
|
|
116
|
+
console.error(chalk.dim(" Pass --registry-tx and --registry-index to point at the correct cell."));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
// ── duplicate check ──────────────────────────────────────────────────────
|
|
120
|
+
if (currentPayload.entries.some((e) => strip0x(e.identifier).toLowerCase() === strip0x(lockArgs).toLowerCase())) {
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(logSymbols.warning, chalk.yellow(`${lockArgs} is already in the registry. Nothing to do.`));
|
|
123
|
+
console.log();
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
// ── build new payload ────────────────────────────────────────────────────
|
|
127
|
+
const newEntries = insertSorted(currentPayload.entries, { identifier: lockArgs, expiresAt });
|
|
128
|
+
const newPayload = { version: currentPayload.version, entries: newEntries };
|
|
129
|
+
const newBlkl = encodeRegistryPayload(newPayload);
|
|
130
|
+
const oldBlkl = hexToBytes(cell.data);
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.bold("Proposed change:"));
|
|
133
|
+
console.log(` ${logSymbols.success} Add ${chalk.green(lockArgs)}`);
|
|
134
|
+
console.log(` Expires: ${expiresAt === 0n ? "never" : new Date(Number(expiresAt) * 1000).toISOString()}`);
|
|
135
|
+
console.log(` Registry size: ${currentPayload.entries.length} → ${newEntries.length} entries`);
|
|
136
|
+
console.log();
|
|
137
|
+
// ── build governance witness ─────────────────────────────────────────────
|
|
138
|
+
const proposalData = new TextEncoder().encode(`add:${lockArgs}:${expiresAt}:${Date.now()}`);
|
|
139
|
+
const proposalIdHash = ckbBlake2b(proposalData);
|
|
140
|
+
const voteDigestHash = ckbBlake2b(proposalIdHash);
|
|
141
|
+
const oldRoot = ckbBlake2b(oldBlkl);
|
|
142
|
+
const newRoot = ckbBlake2b(newBlkl);
|
|
143
|
+
const gov1 = buildGov1Witness({
|
|
144
|
+
proposalIdHash,
|
|
145
|
+
voteDigestHash,
|
|
146
|
+
oldRoot,
|
|
147
|
+
newRoot,
|
|
148
|
+
signers: placeholderSigners(3),
|
|
149
|
+
});
|
|
150
|
+
const witnessBytes = buildWitnessArgs({
|
|
151
|
+
lock: new Uint8Array(65),
|
|
152
|
+
inputType: gov1,
|
|
153
|
+
});
|
|
154
|
+
// ── build transaction ────────────────────────────────────────────────────
|
|
155
|
+
const txJson = {
|
|
156
|
+
transaction: {
|
|
157
|
+
version: "0x0",
|
|
158
|
+
cell_deps: [
|
|
159
|
+
{ out_point: { tx_hash: SECP256K1_DEP_GROUP.txHash, index: "0x0" }, dep_type: "dep_group" },
|
|
160
|
+
{
|
|
161
|
+
out_point: {
|
|
162
|
+
tx_hash: TESTNET_CONTRACT_OUTPOINTS.blacklistRegistry.txHash,
|
|
163
|
+
index: `0x${TESTNET_CONTRACT_OUTPOINTS.blacklistRegistry.index.toString(16)}`,
|
|
164
|
+
},
|
|
165
|
+
dep_type: "code",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
header_deps: [],
|
|
169
|
+
inputs: [
|
|
170
|
+
{ since: "0x0", previous_output: { tx_hash: cell.txHash, index: `0x${cell.index.toString(16)}` } },
|
|
171
|
+
],
|
|
172
|
+
outputs: [{ capacity: cell.capacity, lock: cell.lock, type: cell.type }],
|
|
173
|
+
outputs_data: [bytesToHex(newBlkl)],
|
|
174
|
+
witnesses: [bytesToHex(witnessBytes)],
|
|
175
|
+
},
|
|
176
|
+
multisig_configs: {},
|
|
177
|
+
signatures: {},
|
|
178
|
+
};
|
|
179
|
+
const txOut = opts.txOut;
|
|
180
|
+
writeFileSync(txOut, JSON.stringify(txJson, null, 2) + "\n");
|
|
181
|
+
console.log(logSymbols.success, `Transaction written to ${chalk.bold(txOut)}`);
|
|
182
|
+
console.log();
|
|
183
|
+
// ── sign / submit ────────────────────────────────────────────────────────
|
|
184
|
+
if (!opts.sign) {
|
|
185
|
+
console.log("Sign and submit with ckb-cli:");
|
|
186
|
+
console.log(chalk.dim(` ckb-cli wallet sign-txs --tx-file ${txOut} --from-account <your-address>\n` +
|
|
187
|
+
` ckb-cli wallet apply-txs --tx-file ${txOut}`));
|
|
188
|
+
console.log();
|
|
189
|
+
printHints("add");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let fromAccount = opts.fromAccount;
|
|
193
|
+
if (!fromAccount && process.stdin.isTTY) {
|
|
194
|
+
const { account } = await inquirer.prompt([
|
|
195
|
+
{
|
|
196
|
+
type: "input",
|
|
197
|
+
name: "account",
|
|
198
|
+
message: "Governance account address (ckb-cli --from-account):",
|
|
199
|
+
validate: (v) => v.trim().length > 0 || "Account address is required.",
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
fromAccount = account.trim();
|
|
203
|
+
}
|
|
204
|
+
if (!fromAccount) {
|
|
205
|
+
console.error(logSymbols.error, chalk.red("--from-account is required when using --sign."));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const { proceed } = await inquirer.prompt([
|
|
209
|
+
{
|
|
210
|
+
type: "confirm",
|
|
211
|
+
name: "proceed",
|
|
212
|
+
message: `Sign and submit the transaction using account ${fromAccount}?`,
|
|
213
|
+
default: false,
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
if (!proceed) {
|
|
217
|
+
console.log("Aborted.");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const signSpinner = ora("Signing with ckb-cli").start();
|
|
221
|
+
try {
|
|
222
|
+
execFileSync("ckb-cli", ["wallet", "sign-txs", "--tx-file", txOut, "--from-account", fromAccount], { stdio: "inherit" });
|
|
223
|
+
signSpinner.succeed("Signed");
|
|
224
|
+
const submitSpinner = ora("Submitting").start();
|
|
225
|
+
execFileSync("ckb-cli", ["wallet", "apply-txs", "--tx-file", txOut], { stdio: "inherit" });
|
|
226
|
+
submitSpinner.succeed("Submitted");
|
|
227
|
+
printHints("add");
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
signSpinner.fail("ckb-cli failed");
|
|
231
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
232
|
+
console.error(chalk.dim(` You can sign manually: ckb-cli wallet sign-txs --tx-file ${txOut} --from-account ${fromAccount}`));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function addDefaults() {
|
|
237
|
+
return {
|
|
238
|
+
expiresAt: "0",
|
|
239
|
+
rpcUrl: TESTNET_RPC_URL,
|
|
240
|
+
registryTx: TESTNET_REGISTRY_CELL.txHash,
|
|
241
|
+
registryIndex: String(TESTNET_REGISTRY_CELL.index),
|
|
242
|
+
txOut: "gov_add_tx.json",
|
|
243
|
+
sign: false,
|
|
244
|
+
fromAccount: "",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ExecuteOptions {
|
|
2
|
+
proposal?: string;
|
|
3
|
+
rpcUrl: string;
|
|
4
|
+
registryTx: string;
|
|
5
|
+
registryIndex: string;
|
|
6
|
+
txOut: string;
|
|
7
|
+
sign: boolean;
|
|
8
|
+
fromAccount: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function executeCommand(opts: ExecuteOptions): Promise<void>;
|
|
11
|
+
export declare function executeDefaults(): Partial<ExecuteOptions>;
|
|
12
|
+
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/commands/execute.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAuOxE;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,cAAc,CAAC,CASzD"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import logSymbols from "log-symbols";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import { parseRegistryPayload } from "@ckb-firewall/sdk";
|
|
8
|
+
import { getLiveCell } from "../lib/rpc.js";
|
|
9
|
+
import { loadProposal, saveProposal, listProposals, isReviewWindowPassed, isVoteApproved, isReadyToExecute, SIG_THRESHOLD, } from "../lib/proposals.js";
|
|
10
|
+
import { encodeRegistryPayload, insertSorted, removeEntry, bytesToHex, hexToBytes, strip0x, } from "../lib/blkl.js";
|
|
11
|
+
import { ckbBlake2b, buildGov1Witness, buildWitnessArgs, } from "../lib/witness.js";
|
|
12
|
+
import { TESTNET_RPC_URL, TESTNET_REGISTRY_CELL, TESTNET_CONTRACT_OUTPOINTS, SECP256K1_DEP_GROUP, } from "../lib/defaults.js";
|
|
13
|
+
import { printHints } from "../lib/hints.js";
|
|
14
|
+
export async function executeCommand(opts) {
|
|
15
|
+
// ── select proposal ──────────────────────────────────────────────────────
|
|
16
|
+
let proposalId = opts.proposal?.trim() ?? "";
|
|
17
|
+
if (!proposalId) {
|
|
18
|
+
const ready = listProposals().filter(isReadyToExecute);
|
|
19
|
+
if (ready.length === 0) {
|
|
20
|
+
console.log(logSymbols.warning, chalk.yellow("No proposals are ready to execute."));
|
|
21
|
+
console.log(chalk.dim(" A proposal needs: review window passed + vote threshold + 3 signatures."));
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
const { chosen } = await inquirer.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: "list",
|
|
27
|
+
name: "chosen",
|
|
28
|
+
message: "Select proposal to execute:",
|
|
29
|
+
choices: ready.map((p) => ({
|
|
30
|
+
name: `${chalk.bold(p.id)} ${p.action} ${p.lockArgs.slice(0, 24)}…`,
|
|
31
|
+
value: p.id,
|
|
32
|
+
})),
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
proposalId = chosen;
|
|
36
|
+
}
|
|
37
|
+
const proposal = loadProposal(proposalId);
|
|
38
|
+
// ── checks ───────────────────────────────────────────────────────────────
|
|
39
|
+
if (proposal.status === "executed") {
|
|
40
|
+
console.log(logSymbols.success, chalk.green(`Already executed — tx: ${proposal.txHash ?? "unknown"}`));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
if (!isReviewWindowPassed(proposal)) {
|
|
44
|
+
const ms = new Date(proposal.reviewWindowEndsAt).getTime() - Date.now();
|
|
45
|
+
const h = Math.floor(ms / 3_600_000);
|
|
46
|
+
console.log(logSymbols.error, chalk.red(`Review window not passed — ${h}h remaining.`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (!isVoteApproved(proposal)) {
|
|
50
|
+
console.log(logSymbols.error, chalk.red("Vote threshold not met."));
|
|
51
|
+
console.log(chalk.dim(` Use: ckb-firewall vote --proposal ${proposal.id}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
if (proposal.signatures.length < SIG_THRESHOLD) {
|
|
55
|
+
console.log(logSymbols.error, chalk.red(`Only ${proposal.signatures.length}/${SIG_THRESHOLD} signatures — need more.`));
|
|
56
|
+
console.log(chalk.dim(` Use: ckb-firewall sign --proposal ${proposal.id}`));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
// ── fetch current registry cell ──────────────────────────────────────────
|
|
60
|
+
const registryIndex = Number.parseInt(opts.registryIndex, 10);
|
|
61
|
+
if (!Number.isInteger(registryIndex) || registryIndex < 0) {
|
|
62
|
+
console.error(logSymbols.error, chalk.red("--registry-index must be a non-negative integer."));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const spinner = ora("Fetching current registry cell").start();
|
|
66
|
+
let cell;
|
|
67
|
+
try {
|
|
68
|
+
cell = await getLiveCell(opts.rpcUrl, opts.registryTx, registryIndex);
|
|
69
|
+
spinner.succeed("Registry cell loaded");
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
spinner.fail("Could not fetch registry cell");
|
|
73
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
let currentPayload;
|
|
77
|
+
try {
|
|
78
|
+
currentPayload = parseRegistryPayload(cell.data);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
console.error(logSymbols.error, chalk.red("Registry cell does not contain a valid BLKL payload."));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// ── build new BLKL payload ────────────────────────────────────────────────
|
|
85
|
+
const oldBlkl = hexToBytes(cell.data);
|
|
86
|
+
let newEntries;
|
|
87
|
+
if (proposal.action === "add") {
|
|
88
|
+
if (currentPayload.entries.some((e) => strip0x(e.identifier).toLowerCase() === strip0x(proposal.lockArgs).toLowerCase())) {
|
|
89
|
+
console.log(logSymbols.warning, chalk.yellow(`${proposal.lockArgs} is already in the registry.`));
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
newEntries = insertSorted(currentPayload.entries, {
|
|
93
|
+
identifier: proposal.lockArgs,
|
|
94
|
+
expiresAt: BigInt(proposal.expiresAt),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
newEntries = removeEntry(currentPayload.entries, proposal.lockArgs);
|
|
99
|
+
if (newEntries.length === currentPayload.entries.length) {
|
|
100
|
+
console.log(logSymbols.warning, chalk.yellow(`${proposal.lockArgs} is not in the registry.`));
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const newPayload = { version: currentPayload.version, entries: newEntries };
|
|
105
|
+
const newBlkl = encodeRegistryPayload(newPayload);
|
|
106
|
+
// ── build GOV1 witness with real signatures ───────────────────────────────
|
|
107
|
+
const proposalIdBytes = hexToBytes(proposal.proposalIdHash);
|
|
108
|
+
const voteDigestBytes = hexToBytes(proposal.voteDigestHash);
|
|
109
|
+
const oldRoot = ckbBlake2b(oldBlkl);
|
|
110
|
+
const newRoot = ckbBlake2b(newBlkl);
|
|
111
|
+
const signers = proposal.signatures.slice(0, SIG_THRESHOLD).map((s) => ({
|
|
112
|
+
index: s.signerIndex,
|
|
113
|
+
sig: hexToBytes(s.signature),
|
|
114
|
+
}));
|
|
115
|
+
const gov1 = buildGov1Witness({
|
|
116
|
+
proposalIdHash: proposalIdBytes,
|
|
117
|
+
voteDigestHash: voteDigestBytes,
|
|
118
|
+
oldRoot,
|
|
119
|
+
newRoot,
|
|
120
|
+
signers,
|
|
121
|
+
});
|
|
122
|
+
const witnessBytes = buildWitnessArgs({
|
|
123
|
+
lock: new Uint8Array(65),
|
|
124
|
+
inputType: gov1,
|
|
125
|
+
});
|
|
126
|
+
// ── summary ───────────────────────────────────────────────────────────────
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(chalk.bold("Executing proposal:"), proposal.id);
|
|
129
|
+
console.log(` Action: ${proposal.action === "add" ? chalk.green("add") : chalk.red("remove")} ${proposal.lockArgs}`);
|
|
130
|
+
console.log(` Proposer: ${proposal.proposer}`);
|
|
131
|
+
console.log(` Signers: ${signers.map((s) => `#${s.index}`).join(", ")}`);
|
|
132
|
+
console.log(` Old → new: ${currentPayload.entries.length} → ${newEntries.length} entries`);
|
|
133
|
+
console.log();
|
|
134
|
+
// ── build tx JSON ────────────────────────────────────────────────────────
|
|
135
|
+
const txJson = {
|
|
136
|
+
transaction: {
|
|
137
|
+
version: "0x0",
|
|
138
|
+
cell_deps: [
|
|
139
|
+
{ out_point: { tx_hash: SECP256K1_DEP_GROUP.txHash, index: "0x0" }, dep_type: "dep_group" },
|
|
140
|
+
{
|
|
141
|
+
out_point: {
|
|
142
|
+
tx_hash: TESTNET_CONTRACT_OUTPOINTS.blacklistRegistry.txHash,
|
|
143
|
+
index: `0x${TESTNET_CONTRACT_OUTPOINTS.blacklistRegistry.index.toString(16)}`,
|
|
144
|
+
},
|
|
145
|
+
dep_type: "code",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
header_deps: [],
|
|
149
|
+
inputs: [
|
|
150
|
+
{ since: "0x0", previous_output: { tx_hash: cell.txHash, index: `0x${cell.index.toString(16)}` } },
|
|
151
|
+
],
|
|
152
|
+
outputs: [{ capacity: cell.capacity, lock: cell.lock, type: cell.type }],
|
|
153
|
+
outputs_data: [bytesToHex(newBlkl)],
|
|
154
|
+
witnesses: [bytesToHex(witnessBytes)],
|
|
155
|
+
},
|
|
156
|
+
multisig_configs: {},
|
|
157
|
+
signatures: {},
|
|
158
|
+
};
|
|
159
|
+
const txOut = opts.txOut;
|
|
160
|
+
writeFileSync(txOut, JSON.stringify(txJson, null, 2) + "\n");
|
|
161
|
+
console.log(logSymbols.success, `Transaction written to ${chalk.bold(txOut)}`);
|
|
162
|
+
console.log();
|
|
163
|
+
// ── sign / submit ────────────────────────────────────────────────────────
|
|
164
|
+
if (!opts.sign) {
|
|
165
|
+
console.log("Sign and submit with ckb-cli:");
|
|
166
|
+
console.log(chalk.dim(` ckb-cli wallet sign-txs --tx-file ${txOut} --from-account <address>\n` +
|
|
167
|
+
` ckb-cli wallet apply-txs --tx-file ${txOut}`));
|
|
168
|
+
console.log();
|
|
169
|
+
printHints("execute");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
let fromAccount = opts.fromAccount;
|
|
173
|
+
if (!fromAccount && process.stdin.isTTY) {
|
|
174
|
+
const { account } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: "input",
|
|
177
|
+
name: "account",
|
|
178
|
+
message: "Governance account address (ckb-cli --from-account):",
|
|
179
|
+
validate: (v) => v.trim().length > 0 || "Required.",
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
fromAccount = account.trim();
|
|
183
|
+
}
|
|
184
|
+
if (!fromAccount) {
|
|
185
|
+
console.error(logSymbols.error, chalk.red("--from-account is required when using --sign."));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const { proceed } = await inquirer.prompt([
|
|
189
|
+
{ type: "confirm", name: "proceed", message: "Sign and submit?", default: false },
|
|
190
|
+
]);
|
|
191
|
+
if (!proceed) {
|
|
192
|
+
console.log("Aborted.");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const signSpinner = ora("Signing with ckb-cli").start();
|
|
196
|
+
try {
|
|
197
|
+
execFileSync("ckb-cli", ["wallet", "sign-txs", "--tx-file", txOut, "--from-account", fromAccount], { stdio: "inherit" });
|
|
198
|
+
signSpinner.succeed("Signed");
|
|
199
|
+
const submitSpinner = ora("Submitting").start();
|
|
200
|
+
const output = execFileSync("ckb-cli", ["wallet", "apply-txs", "--tx-file", txOut], { encoding: "utf8" });
|
|
201
|
+
submitSpinner.succeed("Submitted");
|
|
202
|
+
const txHash = output.match(/0x[a-fA-F0-9]{64}/)?.[0];
|
|
203
|
+
if (txHash) {
|
|
204
|
+
proposal.status = "executed";
|
|
205
|
+
proposal.txHash = txHash;
|
|
206
|
+
saveProposal(proposal);
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(logSymbols.success, chalk.green("Registry updated on-chain."));
|
|
209
|
+
console.log(` Tx: ${chalk.bold(txHash)}`);
|
|
210
|
+
printHints("execute");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
signSpinner.fail("ckb-cli failed");
|
|
215
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function executeDefaults() {
|
|
220
|
+
return {
|
|
221
|
+
rpcUrl: TESTNET_RPC_URL,
|
|
222
|
+
registryTx: TESTNET_REGISTRY_CELL.txHash,
|
|
223
|
+
registryIndex: String(TESTNET_REGISTRY_CELL.index),
|
|
224
|
+
txOut: "gov_execute_tx.json",
|
|
225
|
+
sign: false,
|
|
226
|
+
fromAccount: "",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=execute.js.map
|