@btc-vision/cli 1.0.1 → 1.0.3

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 (46) hide show
  1. package/README.md +55 -23
  2. package/build/commands/AcceptCommand.js +62 -15
  3. package/build/commands/CompileCommand.js +9 -10
  4. package/build/commands/ConfigCommand.js +2 -27
  5. package/build/commands/DeprecateCommand.js +32 -11
  6. package/build/commands/InitCommand.js +1 -7
  7. package/build/commands/PublishCommand.js +82 -27
  8. package/build/commands/ScopeRegisterCommand.d.ts +7 -0
  9. package/build/commands/ScopeRegisterCommand.js +115 -0
  10. package/build/commands/SignCommand.js +6 -9
  11. package/build/commands/TransferCommand.js +118 -30
  12. package/build/commands/UndeprecateCommand.js +31 -10
  13. package/build/index.js +2 -0
  14. package/build/lib/binary.d.ts +5 -2
  15. package/build/lib/binary.js +11 -6
  16. package/build/lib/config.d.ts +1 -0
  17. package/build/lib/config.js +3 -2
  18. package/build/lib/ipfs.js +85 -76
  19. package/build/lib/manifest.js +1 -1
  20. package/build/lib/registry.d.ts +1 -1
  21. package/build/lib/registry.js +3 -3
  22. package/build/lib/transaction.d.ts +27 -0
  23. package/build/lib/transaction.js +91 -0
  24. package/build/lib/wallet.d.ts +7 -7
  25. package/build/lib/wallet.js +3 -6
  26. package/build/types/index.d.ts +1 -0
  27. package/package.json +2 -1
  28. package/src/commands/AcceptCommand.ts +89 -16
  29. package/src/commands/CompileCommand.ts +13 -14
  30. package/src/commands/ConfigCommand.ts +2 -29
  31. package/src/commands/DeprecateCommand.ts +48 -11
  32. package/src/commands/InitCommand.ts +1 -7
  33. package/src/commands/PublishCommand.ts +138 -28
  34. package/src/commands/ScopeRegisterCommand.ts +164 -0
  35. package/src/commands/SignCommand.ts +9 -21
  36. package/src/commands/TransferCommand.ts +159 -31
  37. package/src/commands/UndeprecateCommand.ts +43 -10
  38. package/src/index.ts +2 -0
  39. package/src/lib/binary.ts +24 -22
  40. package/src/lib/config.ts +3 -2
  41. package/src/lib/ipfs.ts +113 -99
  42. package/src/lib/manifest.ts +1 -1
  43. package/src/lib/registry.ts +5 -2
  44. package/src/lib/transaction.ts +205 -0
  45. package/src/lib/wallet.ts +10 -19
  46. package/src/types/index.ts +3 -1
@@ -5,15 +5,23 @@
5
5
  */
6
6
 
7
7
  import { confirm, input } from '@inquirer/prompts';
8
+ import { Address } from '@btc-vision/transaction';
8
9
  import { BaseCommand } from './BaseCommand.js';
9
10
  import {
10
11
  getPackage,
11
12
  getPendingScopeTransfer,
12
13
  getPendingTransfer,
14
+ getRegistryContract,
13
15
  getScope,
14
16
  } from '../lib/registry.js';
15
17
  import { canSign, loadCredentials } from '../lib/credentials.js';
16
18
  import { CLIWallet } from '../lib/wallet.js';
19
+ import {
20
+ buildTransactionParams,
21
+ checkBalance,
22
+ formatSats,
23
+ getWalletAddress,
24
+ } from '../lib/transaction.js';
17
25
  import { NetworkName } from '../types/index.js';
18
26
 
19
27
  interface TransferOptions {
@@ -53,7 +61,7 @@ export class TransferCommand extends BaseCommand {
53
61
  this.logger.warn('Run `opnet login` to configure your wallet.');
54
62
  process.exit(1);
55
63
  }
56
- CLIWallet.fromCredentials(credentials);
64
+ const wallet = CLIWallet.fromCredentials(credentials);
57
65
  this.logger.success('Wallet loaded');
58
66
 
59
67
  const network = (options?.network || 'mainnet') as NetworkName;
@@ -129,28 +137,82 @@ export class TransferCommand extends BaseCommand {
129
137
  }
130
138
  }
131
139
 
140
+ // Check wallet balance
141
+ this.logger.info('Checking wallet balance...');
142
+ const { sufficient, balance } = await checkBalance(wallet, network);
143
+ if (!sufficient) {
144
+ this.logger.fail('Insufficient balance');
145
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
146
+ this.logger.error('Please fund your wallet before initiating transfer.');
147
+ process.exit(1);
148
+ }
149
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
150
+
132
151
  // Execute transfer
133
152
  this.logger.info('Initiating transfer...');
134
153
 
154
+ const sender = getWalletAddress(wallet);
155
+ const contract = getRegistryContract(network, sender);
156
+ const txParams = buildTransactionParams(wallet, network);
157
+ const newOwnerAddress = Address.fromString(targetOwner);
158
+
135
159
  if (isScope) {
136
160
  const scopeName = name.substring(1);
137
- this.logger.warn('Transfer transaction required.');
138
- this.logger.log('Transaction would call: initiateScopeTransfer(');
139
- this.logger.log(` scopeName: "${scopeName}",`);
140
- this.logger.log(` newOwner: "${targetOwner}"`);
141
- this.logger.log(')');
161
+ const transferResult = await contract.initiateScopeTransfer(
162
+ scopeName,
163
+ newOwnerAddress,
164
+ );
165
+
166
+ if (transferResult.revert) {
167
+ this.logger.fail('Transfer initiation would fail');
168
+ this.logger.error(`Reason: ${transferResult.revert}`);
169
+ process.exit(1);
170
+ }
171
+
172
+ if (transferResult.estimatedGas) {
173
+ this.logger.info(`Estimated gas: ${transferResult.estimatedGas} sats`);
174
+ }
175
+
176
+ const receipt = await transferResult.sendTransaction(txParams);
177
+
178
+ this.logger.log('');
179
+ this.logger.success('Scope transfer initiated successfully!');
180
+ this.logger.log('');
181
+ this.logger.log(`Scope: ${name}`);
182
+ this.logger.log(`New Owner: ${targetOwner}`);
183
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
184
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
185
+ this.logger.log('');
186
+ this.logger.warn(
187
+ 'Note: The new owner must call `opnet accept` to complete the transfer.',
188
+ );
142
189
  } else {
143
- this.logger.warn('Transfer transaction required.');
144
- this.logger.log('Transaction would call: initiateTransfer(');
145
- this.logger.log(` packageName: "${name}",`);
146
- this.logger.log(` newOwner: "${targetOwner}"`);
147
- this.logger.log(')');
148
- }
149
- this.logger.info('Transfer (transaction pending)');
190
+ const transferResult = await contract.initiateTransfer(name, newOwnerAddress);
150
191
 
151
- this.logger.log('');
152
- this.logger.success('Transfer initiated!');
153
- this.logger.warn('Note: Registry transaction support is coming soon.');
192
+ if (transferResult.revert) {
193
+ this.logger.fail('Transfer initiation would fail');
194
+ this.logger.error(`Reason: ${transferResult.revert}`);
195
+ process.exit(1);
196
+ }
197
+
198
+ if (transferResult.estimatedGas) {
199
+ this.logger.info(`Estimated gas: ${transferResult.estimatedGas} sats`);
200
+ }
201
+
202
+ const receipt = await transferResult.sendTransaction(txParams);
203
+
204
+ this.logger.log('');
205
+ this.logger.success('Package transfer initiated successfully!');
206
+ this.logger.log('');
207
+ this.logger.log(`Package: ${name}`);
208
+ this.logger.log(`New Owner: ${targetOwner}`);
209
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
210
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
211
+ this.logger.log('');
212
+ this.logger.warn(
213
+ 'Note: The new owner must call `opnet accept` to complete the transfer.',
214
+ );
215
+ }
154
216
  this.logger.log('');
155
217
  } catch (error) {
156
218
  this.logger.fail('Transfer failed');
@@ -170,6 +232,15 @@ export class TransferCommand extends BaseCommand {
170
232
  ): Promise<void> {
171
233
  this.logger.info('Checking pending transfer...');
172
234
 
235
+ // Load wallet for cancellation
236
+ const credentials = loadCredentials();
237
+ if (!credentials || !canSign(credentials)) {
238
+ this.logger.fail('No credentials configured');
239
+ this.logger.warn('Run `opnet login` to configure your wallet.');
240
+ process.exit(1);
241
+ }
242
+ const wallet = CLIWallet.fromCredentials(credentials);
243
+
173
244
  if (isScope) {
174
245
  const scopeName = name.substring(1);
175
246
  const pending = await getPendingScopeTransfer(scopeName, network);
@@ -191,12 +262,43 @@ export class TransferCommand extends BaseCommand {
191
262
  }
192
263
  }
193
264
 
265
+ // Check wallet balance
266
+ this.logger.info('Checking wallet balance...');
267
+ const { sufficient, balance } = await checkBalance(wallet, network);
268
+ if (!sufficient) {
269
+ this.logger.fail('Insufficient balance');
270
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
271
+ process.exit(1);
272
+ }
273
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
274
+
194
275
  this.logger.info('Cancelling transfer...');
195
- this.logger.warn('Cancellation transaction required.');
196
- this.logger.log('Transaction would call: cancelScopeTransfer(');
197
- this.logger.log(` scopeName: "${scopeName}"`);
198
- this.logger.log(')');
199
- this.logger.info('Cancellation (transaction pending)');
276
+
277
+ const sender = getWalletAddress(wallet);
278
+ const contract = getRegistryContract(network, sender);
279
+ const txParams = buildTransactionParams(wallet, network);
280
+
281
+ const cancelResult = await contract.cancelScopeTransfer(scopeName);
282
+
283
+ if (cancelResult.revert) {
284
+ this.logger.fail('Cancellation would fail');
285
+ this.logger.error(`Reason: ${cancelResult.revert}`);
286
+ process.exit(1);
287
+ }
288
+
289
+ if (cancelResult.estimatedGas) {
290
+ this.logger.info(`Estimated gas: ${cancelResult.estimatedGas} sats`);
291
+ }
292
+
293
+ const receipt = await cancelResult.sendTransaction(txParams);
294
+
295
+ this.logger.log('');
296
+ this.logger.success('Scope transfer cancelled successfully!');
297
+ this.logger.log('');
298
+ this.logger.log(`Scope: ${name}`);
299
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
300
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
301
+ this.logger.log('');
200
302
  } else {
201
303
  const pending = await getPendingTransfer(name, network);
202
304
  if (!pending) {
@@ -217,18 +319,44 @@ export class TransferCommand extends BaseCommand {
217
319
  }
218
320
  }
219
321
 
322
+ // Check wallet balance
323
+ this.logger.info('Checking wallet balance...');
324
+ const { sufficient, balance } = await checkBalance(wallet, network);
325
+ if (!sufficient) {
326
+ this.logger.fail('Insufficient balance');
327
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
328
+ process.exit(1);
329
+ }
330
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
331
+
220
332
  this.logger.info('Cancelling transfer...');
221
- this.logger.warn('Cancellation transaction required.');
222
- this.logger.log('Transaction would call: cancelTransfer(');
223
- this.logger.log(` packageName: "${name}"`);
224
- this.logger.log(')');
225
- this.logger.info('Cancellation (transaction pending)');
226
- }
227
333
 
228
- this.logger.log('');
229
- this.logger.success('Transfer cancellation submitted!');
230
- this.logger.warn('Note: Registry transaction support is coming soon.');
231
- this.logger.log('');
334
+ const sender = getWalletAddress(wallet);
335
+ const contract = getRegistryContract(network, sender);
336
+ const txParams = buildTransactionParams(wallet, network);
337
+
338
+ const cancelResult = await contract.cancelTransfer(name);
339
+
340
+ if (cancelResult.revert) {
341
+ this.logger.fail('Cancellation would fail');
342
+ this.logger.error(`Reason: ${cancelResult.revert}`);
343
+ process.exit(1);
344
+ }
345
+
346
+ if (cancelResult.estimatedGas) {
347
+ this.logger.info(`Estimated gas: ${cancelResult.estimatedGas} sats`);
348
+ }
349
+
350
+ const receipt = await cancelResult.sendTransaction(txParams);
351
+
352
+ this.logger.log('');
353
+ this.logger.success('Package transfer cancelled successfully!');
354
+ this.logger.log('');
355
+ this.logger.log(`Package: ${name}`);
356
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
357
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
358
+ this.logger.log('');
359
+ }
232
360
  }
233
361
  }
234
362
 
@@ -6,9 +6,15 @@
6
6
 
7
7
  import { confirm } from '@inquirer/prompts';
8
8
  import { BaseCommand } from './BaseCommand.js';
9
- import { getPackage, getVersion, isVersionImmutable } from '../lib/registry.js';
9
+ import { getPackage, getRegistryContract, getVersion, isVersionImmutable } from '../lib/registry.js';
10
10
  import { canSign, loadCredentials } from '../lib/credentials.js';
11
11
  import { CLIWallet } from '../lib/wallet.js';
12
+ import {
13
+ buildTransactionParams,
14
+ checkBalance,
15
+ formatSats,
16
+ getWalletAddress,
17
+ } from '../lib/transaction.js';
12
18
  import { NetworkName } from '../types/index.js';
13
19
 
14
20
  interface UndeprecateOptions {
@@ -46,7 +52,7 @@ export class UndeprecateCommand extends BaseCommand {
46
52
  this.logger.warn('Run `opnet login` to configure your wallet.');
47
53
  process.exit(1);
48
54
  }
49
- CLIWallet.fromCredentials(credentials);
55
+ const wallet = CLIWallet.fromCredentials(credentials);
50
56
  this.logger.success('Wallet loaded');
51
57
 
52
58
  // Get package info
@@ -107,18 +113,45 @@ export class UndeprecateCommand extends BaseCommand {
107
113
  }
108
114
  }
109
115
 
116
+ // Check wallet balance
117
+ this.logger.info('Checking wallet balance...');
118
+ const { sufficient, balance } = await checkBalance(wallet, network);
119
+ if (!sufficient) {
120
+ this.logger.fail('Insufficient balance');
121
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
122
+ this.logger.error('Please fund your wallet before undeprecating.');
123
+ process.exit(1);
124
+ }
125
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
126
+
110
127
  // Execute undeprecation
111
128
  this.logger.info('Removing deprecation...');
112
- this.logger.warn('Undeprecation transaction required.');
113
- this.logger.log('Transaction would call: undeprecateVersion(');
114
- this.logger.log(` packageName: "${packageName}",`);
115
- this.logger.log(` version: "${version}"`);
116
- this.logger.log(')');
117
- this.logger.info('Undeprecation (transaction pending)');
118
129
 
130
+ const sender = getWalletAddress(wallet);
131
+ const contract = getRegistryContract(network, sender);
132
+ const txParams = buildTransactionParams(wallet, network);
133
+
134
+ const undeprecateResult = await contract.undeprecateVersion(packageName, version);
135
+
136
+ if (undeprecateResult.revert) {
137
+ this.logger.fail('Undeprecation would fail');
138
+ this.logger.error(`Reason: ${undeprecateResult.revert}`);
139
+ process.exit(1);
140
+ }
141
+
142
+ if (undeprecateResult.estimatedGas) {
143
+ this.logger.info(`Estimated gas: ${undeprecateResult.estimatedGas} sats`);
144
+ }
145
+
146
+ const receipt = await undeprecateResult.sendTransaction(txParams);
147
+
148
+ this.logger.log('');
149
+ this.logger.success('Deprecation removed successfully!');
119
150
  this.logger.log('');
120
- this.logger.success('Undeprecation submitted!');
121
- this.logger.warn('Note: Registry transaction support is coming soon.');
151
+ this.logger.log(`Package: ${packageName}`);
152
+ this.logger.log(`Version: ${version}`);
153
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
154
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
122
155
  this.logger.log('');
123
156
  } catch (error) {
124
157
  this.logger.fail('Undeprecation failed');
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ import { deprecateCommand } from './commands/DeprecateCommand.js';
24
24
  import { undeprecateCommand } from './commands/UndeprecateCommand.js';
25
25
  import { transferCommand } from './commands/TransferCommand.js';
26
26
  import { acceptCommand } from './commands/AcceptCommand.js';
27
+ import { scopeRegisterCommand } from './commands/ScopeRegisterCommand.js';
27
28
  import { installCommand } from './commands/InstallCommand.js';
28
29
  import { updateCommand } from './commands/UpdateCommand.js';
29
30
  import { listCommand } from './commands/ListCommand.js';
@@ -57,6 +58,7 @@ program.addCommand(deprecateCommand);
57
58
  program.addCommand(undeprecateCommand);
58
59
  program.addCommand(transferCommand);
59
60
  program.addCommand(acceptCommand);
61
+ program.addCommand(scopeRegisterCommand);
60
62
  program.addCommand(installCommand);
61
63
  program.addCommand(updateCommand);
62
64
  program.addCommand(listCommand);
package/src/lib/binary.ts CHANGED
@@ -184,28 +184,21 @@ export function verifyChecksum(parsed: IParsedPluginFile): boolean {
184
184
  * Build a .opnet binary file
185
185
  *
186
186
  * @param options - Build options
187
- * @returns The assembled binary
187
+ * @returns The assembled binary and the checksum that was signed
188
188
  */
189
189
  export function buildOpnetBinary(options: {
190
190
  mldsaLevel: CLIMldsaLevel;
191
191
  publicKey: Buffer;
192
- signature: Buffer;
193
192
  metadata: IPluginMetadata;
194
193
  bytecode: Buffer;
195
194
  proto?: Buffer;
196
- }): Buffer {
197
- const {
198
- mldsaLevel,
199
- publicKey,
200
- signature,
201
- metadata,
202
- bytecode,
203
- proto = Buffer.alloc(0),
204
- } = options;
195
+ signFn?: (checksum: Buffer) => Buffer;
196
+ }): { binary: Buffer; checksum: Buffer } {
197
+ const { mldsaLevel, publicKey, metadata, bytecode, proto = Buffer.alloc(0), signFn } = options;
205
198
 
206
199
  const sdkLevel = cliLevelToMLDSALevel(mldsaLevel);
207
200
 
208
- // Validate sizes
201
+ // Validate public key size
209
202
  const expectedPkSize = MLDSA_PUBLIC_KEY_SIZES[sdkLevel];
210
203
  const expectedSigSize = MLDSA_SIGNATURE_SIZES[sdkLevel];
211
204
 
@@ -215,19 +208,28 @@ export function buildOpnetBinary(options: {
215
208
  );
216
209
  }
217
210
 
211
+ // First pass: compute checksum without the checksum field set
212
+ const tempMetadata = { ...metadata, checksum: '' };
213
+ const tempMetadataBytes = Buffer.from(JSON.stringify(tempMetadata), 'utf-8');
214
+ const checksum = computeChecksum(tempMetadataBytes, bytecode, proto);
215
+ const checksumHex = `sha256:${checksum.toString('hex')}`;
216
+
217
+ // Second pass: serialize metadata with checksum included
218
+ const finalMetadata = { ...metadata, checksum: checksumHex };
219
+ const metadataBytes = Buffer.from(JSON.stringify(finalMetadata), 'utf-8');
220
+
221
+ // Recompute checksum with the final metadata (includes checksum field)
222
+ const finalChecksum = computeChecksum(metadataBytes, bytecode, proto);
223
+
224
+ // Sign the final checksum (this is what gets verified)
225
+ const signature = signFn ? signFn(finalChecksum) : Buffer.alloc(expectedSigSize);
226
+
218
227
  if (signature.length !== expectedSigSize) {
219
228
  throw new Error(
220
229
  `Signature size mismatch: expected ${expectedSigSize}, got ${signature.length}`,
221
230
  );
222
231
  }
223
232
 
224
- // Serialize metadata
225
- const metadataStr = JSON.stringify(metadata);
226
- const metadataBytes = Buffer.from(metadataStr, 'utf-8');
227
-
228
- // Compute checksum
229
- const checksum = computeChecksum(metadataBytes, bytecode, proto);
230
-
231
233
  // Calculate total size
232
234
  const totalSize =
233
235
  8 + // magic
@@ -291,10 +293,10 @@ export function buildOpnetBinary(options: {
291
293
  proto.copy(buffer, offset);
292
294
  offset += proto.length;
293
295
 
294
- // Checksum
295
- checksum.copy(buffer, offset);
296
+ // Checksum (use finalChecksum which was computed with metadata containing checksum hex)
297
+ finalChecksum.copy(buffer, offset);
296
298
 
297
- return buffer;
299
+ return { binary: buffer, checksum: finalChecksum };
298
300
  }
299
301
 
300
302
  /**
package/src/lib/config.ts CHANGED
@@ -19,7 +19,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
19
19
  /**
20
20
  * Default CLI configuration
21
21
  */
22
- const DEFAULT_CONFIG: CLIConfig = {
22
+ export const DEFAULT_CONFIG: CLIConfig = {
23
23
  defaultNetwork: 'regtest',
24
24
  rpcUrls: {
25
25
  mainnet: 'https://api.opnet.org',
@@ -35,11 +35,12 @@ const DEFAULT_CONFIG: CLIConfig = {
35
35
  ],
36
36
  ipfsPinningEndpoint: 'https://ipfs.opnet.org/api/v0/add',
37
37
  ipfsPinningApiKey: '',
38
+ ipfsPinningSecret: '',
38
39
  ipfsPinningAuthHeader: 'Authorization',
39
40
  registryAddresses: {
40
41
  mainnet: '', // TODO: Set once deployed
41
42
  testnet: '', // TODO: Set once deployed
42
- regtest: '', // TODO: Set once deployed
43
+ regtest: '0x0737d17d0eff9915208f3c20ed7659587889bc94d25972672b3a6c03ff4ddbcc', // TODO: Set once deployed
43
44
  },
44
45
  defaultMldsaLevel: 44,
45
46
  indexerUrl: 'https://indexer.opnet.org',