@btc-vision/cli 1.0.0 → 1.0.2

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.
Files changed (41) hide show
  1. package/build/commands/AcceptCommand.js +2 -2
  2. package/build/commands/CompileCommand.js +11 -12
  3. package/build/commands/ConfigCommand.js +3 -7
  4. package/build/commands/DeprecateCommand.js +2 -2
  5. package/build/commands/InfoCommand.js +2 -2
  6. package/build/commands/InitCommand.d.ts +2 -0
  7. package/build/commands/InitCommand.js +114 -20
  8. package/build/commands/InstallCommand.js +1 -1
  9. package/build/commands/KeygenCommand.js +1 -1
  10. package/build/commands/ListCommand.js +6 -2
  11. package/build/commands/LoginCommand.js +22 -10
  12. package/build/commands/LogoutCommand.js +1 -1
  13. package/build/commands/PublishCommand.js +3 -3
  14. package/build/commands/SearchCommand.js +1 -1
  15. package/build/commands/SignCommand.js +11 -11
  16. package/build/commands/TransferCommand.js +3 -3
  17. package/build/commands/UndeprecateCommand.js +1 -1
  18. package/build/commands/VerifyCommand.js +5 -2
  19. package/build/commands/WhoamiCommand.js +1 -1
  20. package/build/lib/PackageRegistry.abi.js +1 -1
  21. package/build/lib/binary.d.ts +5 -2
  22. package/build/lib/binary.js +18 -10
  23. package/build/lib/config.d.ts +1 -1
  24. package/build/lib/credentials.d.ts +1 -1
  25. package/build/lib/ipfs.js +3 -2
  26. package/build/lib/manifest.d.ts +1 -1
  27. package/build/lib/manifest.js +23 -12
  28. package/build/lib/provider.js +1 -1
  29. package/build/lib/registry.d.ts +1 -1
  30. package/build/lib/wallet.d.ts +5 -5
  31. package/build/lib/wallet.js +31 -31
  32. package/build/types/PackageRegistry.d.ts +1 -1
  33. package/build/types/index.js +1 -1
  34. package/package.json +3 -3
  35. package/src/commands/CompileCommand.ts +13 -14
  36. package/src/commands/InitCommand.ts +87 -5
  37. package/src/commands/SignCommand.ts +9 -21
  38. package/src/lib/binary.ts +24 -22
  39. package/src/lib/config.ts +3 -4
  40. package/src/lib/ipfs.ts +0 -1
  41. package/src/lib/manifest.ts +31 -8
@@ -1,5 +1,5 @@
1
1
  import * as crypto from 'crypto';
2
- import { PLUGIN_MAGIC_BYTES, PLUGIN_FORMAT_VERSION, MLDSA_PUBLIC_KEY_SIZES, MLDSA_SIGNATURE_SIZES, } from '@btc-vision/plugin-sdk';
2
+ import { MLDSA_PUBLIC_KEY_SIZES, MLDSA_SIGNATURE_SIZES, PLUGIN_FORMAT_VERSION, PLUGIN_MAGIC_BYTES, } from '@btc-vision/plugin-sdk';
3
3
  import { cliLevelToMLDSALevel, mldsaLevelToCLI } from '../types/index.js';
4
4
  export function parseOpnetBinary(data) {
5
5
  let offset = 0;
@@ -91,27 +91,35 @@ export function verifyChecksum(parsed) {
91
91
  return computed.equals(parsed.checksum);
92
92
  }
93
93
  export function buildOpnetBinary(options) {
94
- const { mldsaLevel, publicKey, signature, metadata, bytecode, proto = Buffer.alloc(0) } = options;
94
+ const { mldsaLevel, publicKey, metadata, bytecode, proto = Buffer.alloc(0), signFn } = options;
95
95
  const sdkLevel = cliLevelToMLDSALevel(mldsaLevel);
96
96
  const expectedPkSize = MLDSA_PUBLIC_KEY_SIZES[sdkLevel];
97
97
  const expectedSigSize = MLDSA_SIGNATURE_SIZES[sdkLevel];
98
98
  if (publicKey.length !== expectedPkSize) {
99
99
  throw new Error(`Public key size mismatch: expected ${expectedPkSize}, got ${publicKey.length}`);
100
100
  }
101
+ const tempMetadata = { ...metadata, checksum: '' };
102
+ const tempMetadataBytes = Buffer.from(JSON.stringify(tempMetadata), 'utf-8');
103
+ const checksum = computeChecksum(tempMetadataBytes, bytecode, proto);
104
+ const checksumHex = `sha256:${checksum.toString('hex')}`;
105
+ const finalMetadata = { ...metadata, checksum: checksumHex };
106
+ const metadataBytes = Buffer.from(JSON.stringify(finalMetadata), 'utf-8');
107
+ const finalChecksum = computeChecksum(metadataBytes, bytecode, proto);
108
+ const signature = signFn ? signFn(finalChecksum) : Buffer.alloc(expectedSigSize);
101
109
  if (signature.length !== expectedSigSize) {
102
110
  throw new Error(`Signature size mismatch: expected ${expectedSigSize}, got ${signature.length}`);
103
111
  }
104
- const metadataStr = JSON.stringify(metadata);
105
- const metadataBytes = Buffer.from(metadataStr, 'utf-8');
106
- const checksum = computeChecksum(metadataBytes, bytecode, proto);
107
112
  const totalSize = 8 +
108
113
  4 +
109
114
  1 +
110
115
  publicKey.length +
111
116
  signature.length +
112
- 4 + metadataBytes.length +
113
- 4 + bytecode.length +
114
- 4 + proto.length +
117
+ 4 +
118
+ metadataBytes.length +
119
+ 4 +
120
+ bytecode.length +
121
+ 4 +
122
+ proto.length +
115
123
  32;
116
124
  const buffer = Buffer.alloc(totalSize);
117
125
  let offset = 0;
@@ -137,8 +145,8 @@ export function buildOpnetBinary(options) {
137
145
  offset += 4;
138
146
  proto.copy(buffer, offset);
139
147
  offset += proto.length;
140
- checksum.copy(buffer, offset);
141
- return buffer;
148
+ finalChecksum.copy(buffer, offset);
149
+ return { binary: buffer, checksum: finalChecksum };
142
150
  }
143
151
  export function extractMetadata(data) {
144
152
  try {
@@ -1,4 +1,4 @@
1
- import { CLIConfig, NetworkName, CLIMldsaLevel } from '../types/index.js';
1
+ import { CLIConfig, CLIMldsaLevel, NetworkName } from '../types/index.js';
2
2
  export declare function ensureConfigDir(): void;
3
3
  export declare function loadConfig(): CLIConfig;
4
4
  export declare function saveConfig(config: CLIConfig): void;
@@ -1,4 +1,4 @@
1
- import { CLICredentials, NetworkName, CLIMldsaLevel } from '../types/index.js';
1
+ import { CLICredentials, CLIMldsaLevel, NetworkName } from '../types/index.js';
2
2
  export declare function loadCredentials(): CLICredentials | null;
3
3
  export declare function saveCredentials(credentials: CLICredentials): void;
4
4
  export declare function deleteCredentials(): boolean;
package/build/lib/ipfs.js CHANGED
@@ -62,7 +62,9 @@ export async function pinToIPFS(data, name) {
62
62
  'Content-Length': body.length.toString(),
63
63
  };
64
64
  if (config.ipfsPinningAuthHeader) {
65
- const [headerName, headerValue] = config.ipfsPinningAuthHeader.split(':').map((s) => s.trim());
65
+ const [headerName, headerValue] = config.ipfsPinningAuthHeader
66
+ .split(':')
67
+ .map((s) => s.trim());
66
68
  if (headerName && headerValue) {
67
69
  headers[headerName] = headerValue;
68
70
  }
@@ -139,7 +141,6 @@ export async function fetchFromIPFS(cid) {
139
141
  }
140
142
  catch (error) {
141
143
  lastError = error instanceof Error ? error : new Error(String(error));
142
- continue;
143
144
  }
144
145
  }
145
146
  throw new Error(`Failed to fetch from all gateways: ${lastError?.message}`);
@@ -10,5 +10,5 @@ export declare function createManifest(options: {
10
10
  email?: string;
11
11
  description?: string;
12
12
  pluginType: 'standalone' | 'library';
13
- }): IPluginMetadata;
13
+ }): Omit<IPluginMetadata, 'checksum'>;
14
14
  export declare function getManifestPath(dir?: string): string;
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { validateManifest as sdkValidateManifest, PLUGIN_NAME_REGEX, PLUGIN_MANIFEST_FILENAME, DEFAULT_PERMISSIONS, DEFAULT_RESOURCES, DEFAULT_LIFECYCLE, } from '@btc-vision/plugin-sdk';
3
+ import { DEFAULT_LIFECYCLE, DEFAULT_PERMISSIONS, DEFAULT_RESOURCES, PLUGIN_MANIFEST_FILENAME, PLUGIN_NAME_REGEX, validateManifest as sdkValidateManifest, } from '@btc-vision/plugin-sdk';
4
4
  const SCOPED_NAME_PATTERN = /^@[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/;
5
5
  export function validatePluginName(name) {
6
6
  const errors = [];
@@ -29,8 +29,7 @@ export function validateManifest(manifest) {
29
29
  export function validatePermissions(permissions) {
30
30
  const errors = [];
31
31
  if (permissions.database?.enabled) {
32
- if (!permissions.database.collections ||
33
- !Array.isArray(permissions.database.collections)) {
32
+ if (!permissions.database.collections || !Array.isArray(permissions.database.collections)) {
34
33
  errors.push('database.collections must be an array when database is enabled');
35
34
  }
36
35
  }
@@ -49,14 +48,27 @@ export function loadManifest(manifestPath) {
49
48
  catch (e) {
50
49
  throw new Error(`Failed to parse manifest: ${e instanceof Error ? e.message : String(e)}`);
51
50
  }
52
- const result = validateManifest(manifest);
53
- if (!result.valid) {
54
- const errorList = result.errors
55
- .map((e) => ` - ${e.path}: ${e.message}`)
56
- .join('\n');
57
- throw new Error(`Invalid manifest:\n${errorList}`);
51
+ const errors = [];
52
+ if (!manifest.name || typeof manifest.name !== 'string') {
53
+ errors.push('name is required');
54
+ }
55
+ if (!manifest.version || typeof manifest.version !== 'string') {
56
+ errors.push('version is required');
57
+ }
58
+ if (!manifest.author || typeof manifest.author !== 'object') {
59
+ errors.push('author is required');
60
+ }
61
+ if (!manifest.pluginType || !['standalone', 'library'].includes(manifest.pluginType)) {
62
+ errors.push('pluginType must be "standalone" or "library"');
63
+ }
64
+ if (errors.length > 0) {
65
+ throw new Error(`Invalid manifest:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
66
+ }
67
+ const result = manifest;
68
+ if (!result.checksum) {
69
+ result.checksum = '';
58
70
  }
59
- return manifest;
71
+ return result;
60
72
  }
61
73
  export function saveManifest(manifestPath, manifest) {
62
74
  const content = JSON.stringify(manifest, null, 4);
@@ -66,11 +78,10 @@ export function createManifest(options) {
66
78
  return {
67
79
  name: options.name,
68
80
  version: '1.0.0',
69
- opnetVersion: '^1.0.0',
81
+ opnetVersion: '>=0.0.1',
70
82
  main: 'dist/index.jsc',
71
83
  target: 'bytenode',
72
84
  type: 'plugin',
73
- checksum: '',
74
85
  author: {
75
86
  name: options.author,
76
87
  email: options.email,
@@ -1,5 +1,5 @@
1
1
  import { JSONRpcProvider } from 'opnet';
2
- import { getRpcUrl, getRegistryAddress, loadConfig } from './config.js';
2
+ import { getRegistryAddress, getRpcUrl, loadConfig } from './config.js';
3
3
  import { getNetwork } from './wallet.js';
4
4
  const DEFAULT_TIMEOUT = 30000;
5
5
  const providerCache = new Map();
@@ -1,5 +1,5 @@
1
1
  import { Address } from '@btc-vision/transaction';
2
- import { NetworkName, CLIMldsaLevel, RegistryPluginType } from '../types/index.js';
2
+ import { CLIMldsaLevel, NetworkName, RegistryPluginType } from '../types/index.js';
3
3
  import { IPluginPermissions } from '@btc-vision/plugin-sdk';
4
4
  import { IPackageRegistry } from '../types/PackageRegistry.js';
5
5
  export interface ScopeInfo {
@@ -1,7 +1,7 @@
1
1
  import { Network } from '@btc-vision/bitcoin';
2
2
  import { MLDSASecurityLevel } from '@btc-vision/bip32';
3
- import { Wallet, EcKeyPair } from '@btc-vision/transaction';
4
- import { CLICredentials, NetworkName, CLIMldsaLevel } from '../types/index.js';
3
+ import { EcKeyPair, Wallet } from '@btc-vision/transaction';
4
+ import { CLICredentials, CLIMldsaLevel, NetworkName } from '../types/index.js';
5
5
  export declare function getNetwork(networkName: NetworkName): Network;
6
6
  export declare function getMLDSASecurityLevel(level: CLIMldsaLevel): MLDSASecurityLevel;
7
7
  export declare class CLIWallet {
@@ -9,8 +9,6 @@ export declare class CLIWallet {
9
9
  private readonly network;
10
10
  private readonly mldsaLevel;
11
11
  private constructor();
12
- static fromCredentials(credentials: CLICredentials): CLIWallet;
13
- static load(): CLIWallet;
14
12
  get p2trAddress(): string;
15
13
  get underlyingWallet(): Wallet;
16
14
  get keypair(): EcKeyPair;
@@ -19,9 +17,11 @@ export declare class CLIWallet {
19
17
  get mldsaPublicKeyHash(): string;
20
18
  get securityLevel(): CLIMldsaLevel;
21
19
  get walletNetwork(): Network;
20
+ static fromCredentials(credentials: CLICredentials): CLIWallet;
21
+ static load(): CLIWallet;
22
+ static verifyMLDSA(data: Buffer, signature: Buffer, publicKey: Buffer, level: CLIMldsaLevel): boolean;
22
23
  signMLDSA(data: Buffer): Buffer;
23
24
  verifyMLDSA(data: Buffer, signature: Buffer): boolean;
24
- static verifyMLDSA(data: Buffer, signature: Buffer, publicKey: Buffer, level: CLIMldsaLevel): boolean;
25
25
  }
26
26
  export declare function generateMLDSAKeypair(level: CLIMldsaLevel): {
27
27
  privateKey: Buffer;
@@ -1,8 +1,8 @@
1
1
  import { networks } from '@btc-vision/bitcoin';
2
2
  import { MLDSASecurityLevel, QuantumBIP32Factory } from '@btc-vision/bip32';
3
- import { Mnemonic, Wallet, EcKeyPair, MessageSigner } from '@btc-vision/transaction';
3
+ import { EcKeyPair, MessageSigner, Mnemonic, Wallet } from '@btc-vision/transaction';
4
4
  import * as crypto from 'crypto';
5
- import { loadCredentials, canSign } from './credentials.js';
5
+ import { canSign, loadCredentials } from './credentials.js';
6
6
  export function getNetwork(networkName) {
7
7
  switch (networkName) {
8
8
  case 'mainnet':
@@ -32,30 +32,6 @@ export class CLIWallet {
32
32
  this.network = network;
33
33
  this.mldsaLevel = mldsaLevel;
34
34
  }
35
- static fromCredentials(credentials) {
36
- const network = getNetwork(credentials.network);
37
- const securityLevel = getMLDSASecurityLevel(credentials.mldsaLevel);
38
- if (credentials.mnemonic) {
39
- const mnemonic = new Mnemonic(credentials.mnemonic, '', network, securityLevel);
40
- const wallet = mnemonic.deriveUnisat();
41
- return new CLIWallet(wallet, network, credentials.mldsaLevel);
42
- }
43
- if (credentials.wif && credentials.mldsaPrivateKey) {
44
- const wallet = Wallet.fromWif(credentials.wif, credentials.mldsaPrivateKey, network, securityLevel);
45
- return new CLIWallet(wallet, network, credentials.mldsaLevel);
46
- }
47
- throw new Error('Invalid credentials: requires either mnemonic or both WIF and MLDSA key');
48
- }
49
- static load() {
50
- const credentials = loadCredentials();
51
- if (!credentials) {
52
- throw new Error('No credentials found. Run `opnet login` to configure your wallet.');
53
- }
54
- if (!canSign(credentials)) {
55
- throw new Error('Credentials incomplete for signing. Run `opnet login` to reconfigure.');
56
- }
57
- return CLIWallet.fromCredentials(credentials);
58
- }
59
35
  get p2trAddress() {
60
36
  return this.wallet.p2tr;
61
37
  }
@@ -81,12 +57,29 @@ export class CLIWallet {
81
57
  get walletNetwork() {
82
58
  return this.network;
83
59
  }
84
- signMLDSA(data) {
85
- const result = MessageSigner.signMLDSAMessage(this.wallet.mldsaKeypair, data);
86
- return Buffer.from(result.signature);
60
+ static fromCredentials(credentials) {
61
+ const network = getNetwork(credentials.network);
62
+ const securityLevel = getMLDSASecurityLevel(credentials.mldsaLevel);
63
+ if (credentials.mnemonic) {
64
+ const mnemonic = new Mnemonic(credentials.mnemonic, '', network, securityLevel);
65
+ const wallet = mnemonic.deriveUnisat();
66
+ return new CLIWallet(wallet, network, credentials.mldsaLevel);
67
+ }
68
+ if (credentials.wif && credentials.mldsaPrivateKey) {
69
+ const wallet = Wallet.fromWif(credentials.wif, credentials.mldsaPrivateKey, network, securityLevel);
70
+ return new CLIWallet(wallet, network, credentials.mldsaLevel);
71
+ }
72
+ throw new Error('Invalid credentials: requires either mnemonic or both WIF and MLDSA key');
87
73
  }
88
- verifyMLDSA(data, signature) {
89
- return MessageSigner.verifyMLDSASignature(this.wallet.mldsaKeypair, data, signature);
74
+ static load() {
75
+ const credentials = loadCredentials();
76
+ if (!credentials) {
77
+ throw new Error('No credentials found. Run `opnet login` to configure your wallet.');
78
+ }
79
+ if (!canSign(credentials)) {
80
+ throw new Error('Credentials incomplete for signing. Run `opnet login` to reconfigure.');
81
+ }
82
+ return CLIWallet.fromCredentials(credentials);
90
83
  }
91
84
  static verifyMLDSA(data, signature, publicKey, level) {
92
85
  const securityLevel = getMLDSASecurityLevel(level);
@@ -94,6 +87,13 @@ export class CLIWallet {
94
87
  const keypair = QuantumBIP32Factory.fromPublicKey(publicKey, dummyChainCode, networks.bitcoin, securityLevel);
95
88
  return keypair.verify(data, signature);
96
89
  }
90
+ signMLDSA(data) {
91
+ const result = MessageSigner.signMLDSAMessage(this.wallet.mldsaKeypair, data);
92
+ return Buffer.from(result.signature);
93
+ }
94
+ verifyMLDSA(data, signature) {
95
+ return MessageSigner.verifyMLDSASignature(this.wallet.mldsaKeypair, data, signature);
96
+ }
97
97
  }
98
98
  export function generateMLDSAKeypair(level) {
99
99
  const securityLevel = getMLDSASecurityLevel(level);
@@ -1,5 +1,5 @@
1
1
  import { Address } from '@btc-vision/transaction';
2
- import { CallResult, OPNetEvent, IOP_NETContract } from 'opnet';
2
+ import { CallResult, IOP_NETContract, OPNetEvent } from 'opnet';
3
3
  export type TreasuryAddressChangedEvent = {
4
4
  readonly previousAddressHash: bigint;
5
5
  readonly newAddressHash: bigint;
@@ -1,4 +1,4 @@
1
- import { MLDSALevel, MLDSA_PUBLIC_KEY_SIZES, MLDSA_SIGNATURE_SIZES } from '@btc-vision/plugin-sdk';
1
+ import { MLDSA_PUBLIC_KEY_SIZES, MLDSA_SIGNATURE_SIZES, MLDSALevel } from '@btc-vision/plugin-sdk';
2
2
  export function cliLevelToMLDSALevel(level) {
3
3
  switch (level) {
4
4
  case 44:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "CLI for the OPNet plugin ecosystem - scaffolding, compilation, signing, and registry interaction",
6
6
  "author": "OP_NET",
@@ -53,12 +53,12 @@
53
53
  "bytenode": "^1.5.7",
54
54
  "commander": "^14.0.0",
55
55
  "esbuild": "^0.25.5",
56
- "opnet": "^1.7.17",
56
+ "opnet": "^1.7.18",
57
57
  "ora": "^8.2.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@eslint/js": "^9.39.1",
61
- "@types/node": "^24.3.0",
61
+ "@types/node": "^25.0.2",
62
62
  "eslint": "^9.39.1",
63
63
  "gulp": "^5.0.1",
64
64
  "gulp-cached": "^1.1.1",
@@ -10,7 +10,7 @@ import * as esbuild from 'esbuild';
10
10
  import bytenode from 'bytenode';
11
11
  import { BaseCommand } from './BaseCommand.js';
12
12
  import { getManifestPath, loadManifest } from '../lib/manifest.js';
13
- import { buildOpnetBinary, computeChecksum, formatFileSize } from '../lib/binary.js';
13
+ import { buildOpnetBinary, formatFileSize } from '../lib/binary.js';
14
14
  import { CLIWallet } from '../lib/wallet.js';
15
15
  import { canSign, loadCredentials } from '../lib/credentials.js';
16
16
  import { CLIMldsaLevel } from '../types/index.js';
@@ -105,8 +105,8 @@ export class CompileCommand extends BaseCommand {
105
105
 
106
106
  // Prepare signing
107
107
  let publicKey: Buffer;
108
- let signature: Buffer;
109
108
  let mldsaLevel: CLIMldsaLevel;
109
+ let signFn: ((checksum: Buffer) => Buffer) | undefined;
110
110
 
111
111
  if (options.sign) {
112
112
  this.logger.info('Loading wallet for signing...');
@@ -125,33 +125,31 @@ export class CompileCommand extends BaseCommand {
125
125
 
126
126
  this.logger.success(`Wallet loaded (MLDSA-${mldsaLevel})`);
127
127
 
128
- // Compute checksum and sign
129
- this.logger.info('Signing plugin...');
130
- const metadataBytes = Buffer.from(JSON.stringify(manifest), 'utf-8');
131
- const checksum = computeChecksum(metadataBytes, bytecode, proto);
132
-
133
- signature = wallet.signMLDSA(checksum);
134
- this.logger.success(
135
- `Plugin signed (${formatFileSize(signature.length)} signature)`,
136
- );
128
+ // Create signing function that will be called with the final checksum
129
+ signFn = (checksum: Buffer) => wallet.signMLDSA(checksum);
137
130
  } else {
138
131
  this.logger.warn('Skipping signing (--no-sign)');
139
132
  // Use dummy values for unsigned binary
140
133
  mldsaLevel = 44;
141
134
  publicKey = Buffer.alloc(1312); // MLDSA-44 public key size
142
- signature = Buffer.alloc(2420); // MLDSA-44 signature size
143
135
  }
144
136
 
145
137
  // Build .opnet binary
146
138
  this.logger.info('Assembling .opnet binary...');
147
- const binary = buildOpnetBinary({
139
+ const { binary, checksum } = buildOpnetBinary({
148
140
  mldsaLevel,
149
141
  publicKey,
150
- signature,
151
142
  metadata: manifest,
152
143
  bytecode,
153
144
  proto,
145
+ signFn,
154
146
  });
147
+
148
+ if (options.sign) {
149
+ this.logger.success(
150
+ `Plugin signed (checksum: sha256:${checksum.toString('hex').substring(0, 16)}...)`,
151
+ );
152
+ }
155
153
  this.logger.success(`Binary assembled (${formatFileSize(binary.length)})`);
156
154
 
157
155
  // Write output
@@ -178,6 +176,7 @@ export class CompileCommand extends BaseCommand {
178
176
  this.logger.log(`Plugin: ${manifest.name}@${manifest.version}`);
179
177
  this.logger.log(`Type: ${manifest.pluginType}`);
180
178
  this.logger.log(`MLDSA Level: ${mldsaLevel}`);
179
+ this.logger.log(`Checksum: sha256:${checksum.toString('hex')}`);
181
180
  this.logger.log(`Signed: ${options.sign ? 'Yes' : 'No'}`);
182
181
  this.logger.log('');
183
182
 
@@ -164,6 +164,12 @@ export class InitCommand extends BaseCommand {
164
164
 
165
165
  // Create README.md
166
166
  this.createReadme(projectDir, config, force);
167
+
168
+ // Create ESLint config
169
+ this.createEslintConfig(projectDir, force);
170
+
171
+ // Create Prettier config
172
+ this.createPrettierConfig(projectDir, force);
167
173
  }
168
174
 
169
175
  private createPluginJson(
@@ -179,11 +185,10 @@ export class InitCommand extends BaseCommand {
179
185
  const manifest: Record<string, unknown> = {
180
186
  name: config.pluginName,
181
187
  version: '1.0.0',
182
- opnetVersion: '^1.0.0',
188
+ opnetVersion: '>=0.0.1',
183
189
  main: 'dist/index.jsc',
184
190
  target: 'bytenode',
185
191
  type: 'plugin',
186
- checksum: '',
187
192
  author: config.authorEmail
188
193
  ? { name: config.authorName, email: config.authorEmail }
189
194
  : { name: config.authorName },
@@ -277,9 +282,10 @@ export class InitCommand extends BaseCommand {
277
282
  main: 'dist/index.js',
278
283
  scripts: {
279
284
  build: 'tsc',
280
- compile: 'opnet compile',
281
- verify: 'opnet verify',
285
+ compile: 'npx opnet compile',
286
+ verify: 'npx opnet verify',
282
287
  lint: 'eslint src/',
288
+ format: 'prettier --write src/',
283
289
  },
284
290
  author: config.authorEmail
285
291
  ? `${config.authorName} <${config.authorEmail}>`
@@ -287,8 +293,12 @@ export class InitCommand extends BaseCommand {
287
293
  license: 'Apache-2.0',
288
294
  dependencies: { '@btc-vision/plugin-sdk': '^1.0.0' },
289
295
  devDependencies: {
290
- '@types/node': '^22.0.0',
296
+ '@eslint/js': '^9.39.0',
297
+ '@types/node': '^25.0.0',
298
+ eslint: '^9.39.0',
299
+ prettier: '^3.6.0',
291
300
  typescript: '^5.8.0',
301
+ 'typescript-eslint': '^8.39.0',
292
302
  '@btc-vision/cli': '^1.0.0',
293
303
  },
294
304
  };
@@ -454,6 +464,78 @@ Apache-2.0
454
464
  this.logger.success(' Created README.md');
455
465
  }
456
466
 
467
+ private createEslintConfig(projectDir: string, force?: boolean): void {
468
+ const eslintPath = path.join(projectDir, 'eslint.config.js');
469
+ if (fs.existsSync(eslintPath) && !force) return;
470
+
471
+ const content = `// @ts-check
472
+
473
+ import eslint from '@eslint/js';
474
+ import tseslint from 'typescript-eslint';
475
+
476
+ export default tseslint.config(
477
+ eslint.configs.recommended,
478
+ ...tseslint.configs.strictTypeChecked,
479
+ {
480
+ languageOptions: {
481
+ parserOptions: {
482
+ projectService: true,
483
+ tsconfigDirName: import.meta.dirname,
484
+ },
485
+ },
486
+ rules: {
487
+ 'no-undef': 'off',
488
+ '@typescript-eslint/no-unused-vars': 'off',
489
+ 'no-empty': 'off',
490
+ '@typescript-eslint/restrict-template-expressions': 'off',
491
+ '@typescript-eslint/only-throw-error': 'off',
492
+ '@typescript-eslint/no-unnecessary-condition': 'off',
493
+ '@typescript-eslint/unbound-method': 'warn',
494
+ '@typescript-eslint/no-confusing-void-expression': 'off',
495
+ '@typescript-eslint/no-extraneous-class': 'off',
496
+ 'no-async-promise-executor': 'off',
497
+ '@typescript-eslint/no-misused-promises': 'off',
498
+ '@typescript-eslint/no-unnecessary-type-parameters': 'off',
499
+ '@typescript-eslint/no-duplicate-enum-values': 'off',
500
+ 'prefer-spread': 'off',
501
+ '@typescript-eslint/no-empty-object-type': 'off',
502
+ '@typescript-eslint/no-base-to-string': 'off',
503
+ '@typescript-eslint/no-dynamic-delete': 'off',
504
+ '@typescript-eslint/no-redundant-type-constituents': 'off',
505
+ },
506
+ },
507
+ {
508
+ files: ['**/*.js'],
509
+ ...tseslint.configs.disableTypeChecked,
510
+ },
511
+ );
512
+ `;
513
+
514
+ fs.writeFileSync(eslintPath, content);
515
+ this.logger.success(' Created eslint.config.js');
516
+ }
517
+
518
+ private createPrettierConfig(projectDir: string, force?: boolean): void {
519
+ const prettierPath = path.join(projectDir, '.prettierrc.json');
520
+ if (fs.existsSync(prettierPath) && !force) return;
521
+
522
+ const config = {
523
+ printWidth: 100,
524
+ trailingComma: 'all',
525
+ tabWidth: 4,
526
+ semi: true,
527
+ singleQuote: true,
528
+ quoteProps: 'as-needed',
529
+ bracketSpacing: true,
530
+ bracketSameLine: true,
531
+ arrowParens: 'always',
532
+ singleAttributePerLine: true,
533
+ };
534
+
535
+ fs.writeFileSync(prettierPath, JSON.stringify(config, null, 4));
536
+ this.logger.success(' Created .prettierrc.json');
537
+ }
538
+
457
539
  private toPascalCase(str: string): string {
458
540
  return str
459
541
  .split(/[-_]/)
@@ -7,12 +7,7 @@
7
7
  import * as fs from 'fs';
8
8
  import * as crypto from 'crypto';
9
9
  import { BaseCommand } from './BaseCommand.js';
10
- import {
11
- buildOpnetBinary,
12
- computeChecksum,
13
- formatFileSize,
14
- parseOpnetBinary,
15
- } from '../lib/binary.js';
10
+ import { buildOpnetBinary, formatFileSize, parseOpnetBinary } from '../lib/binary.js';
16
11
  import { CLIWallet } from '../lib/wallet.js';
17
12
  import { canSign, loadCredentials } from '../lib/credentials.js';
18
13
 
@@ -77,27 +72,20 @@ export class SignCommand extends BaseCommand {
77
72
  process.exit(1);
78
73
  }
79
74
 
80
- // Compute new signature
81
- this.logger.info('Signing...');
82
- const metadataBytes = Buffer.from(parsed.rawMetadata, 'utf-8');
83
- const checksum = computeChecksum(
84
- metadataBytes,
85
- parsed.bytecode,
86
- parsed.proto ?? Buffer.alloc(0),
87
- );
88
- const signature = wallet.signMLDSA(checksum);
89
- this.logger.success(`Signed (${formatFileSize(signature.length)} signature)`);
90
-
91
- // Rebuild binary
92
- this.logger.info('Rebuilding binary...');
93
- const newBinary = buildOpnetBinary({
75
+ // Rebuild binary with signing
76
+ this.logger.info('Signing and rebuilding binary...');
77
+ const signFn = (checksum: Buffer) => wallet.signMLDSA(checksum);
78
+ const { binary: newBinary, checksum } = buildOpnetBinary({
94
79
  mldsaLevel: wallet.securityLevel,
95
80
  publicKey: wallet.mldsaPublicKey,
96
- signature,
97
81
  metadata: parsed.metadata,
98
82
  bytecode: parsed.bytecode,
99
83
  proto: parsed.proto ?? Buffer.alloc(0),
84
+ signFn,
100
85
  });
86
+ this.logger.success(
87
+ `Signed (checksum: sha256:${checksum.toString('hex').substring(0, 16)}...)`,
88
+ );
101
89
  this.logger.success(`Binary rebuilt (${formatFileSize(newBinary.length)})`);
102
90
 
103
91
  // Write output