@btc-vision/cli 1.0.2 → 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 (37) hide show
  1. package/README.md +55 -23
  2. package/build/commands/AcceptCommand.js +62 -15
  3. package/build/commands/ConfigCommand.js +2 -27
  4. package/build/commands/DeprecateCommand.js +32 -11
  5. package/build/commands/InitCommand.js +0 -6
  6. package/build/commands/PublishCommand.js +82 -27
  7. package/build/commands/ScopeRegisterCommand.d.ts +7 -0
  8. package/build/commands/ScopeRegisterCommand.js +115 -0
  9. package/build/commands/TransferCommand.js +118 -30
  10. package/build/commands/UndeprecateCommand.js +31 -10
  11. package/build/index.js +2 -0
  12. package/build/lib/config.d.ts +1 -0
  13. package/build/lib/config.js +3 -2
  14. package/build/lib/ipfs.js +85 -76
  15. package/build/lib/registry.d.ts +1 -1
  16. package/build/lib/registry.js +3 -3
  17. package/build/lib/transaction.d.ts +27 -0
  18. package/build/lib/transaction.js +91 -0
  19. package/build/lib/wallet.d.ts +7 -7
  20. package/build/lib/wallet.js +3 -6
  21. package/build/types/index.d.ts +1 -0
  22. package/package.json +2 -1
  23. package/src/commands/AcceptCommand.ts +89 -16
  24. package/src/commands/ConfigCommand.ts +2 -29
  25. package/src/commands/DeprecateCommand.ts +48 -11
  26. package/src/commands/InitCommand.ts +0 -6
  27. package/src/commands/PublishCommand.ts +138 -28
  28. package/src/commands/ScopeRegisterCommand.ts +164 -0
  29. package/src/commands/TransferCommand.ts +159 -31
  30. package/src/commands/UndeprecateCommand.ts +43 -10
  31. package/src/index.ts +2 -0
  32. package/src/lib/config.ts +3 -2
  33. package/src/lib/ipfs.ts +113 -99
  34. package/src/lib/registry.ts +5 -2
  35. package/src/lib/transaction.ts +205 -0
  36. package/src/lib/wallet.ts +10 -19
  37. package/src/types/index.ts +3 -1
@@ -17,12 +17,24 @@ import {
17
17
  computePermissionsHash,
18
18
  encodeDependencies,
19
19
  getPackage,
20
+ getRegistryContract,
20
21
  getScope,
21
22
  mldsaLevelToRegistry,
22
23
  parsePackageName,
23
24
  pluginTypeToRegistry,
24
25
  } from '../lib/registry.js';
26
+ import {
27
+ buildTransactionParams,
28
+ checkBalance,
29
+ DEFAULT_FEE_RATE,
30
+ DEFAULT_MAX_SAT_TO_SPEND,
31
+ formatSats,
32
+ getWalletAddress,
33
+ waitForTransactionConfirmation,
34
+ } from '../lib/transaction.js';
25
35
  import { CLIMldsaLevel, NetworkName } from '../types/index.js';
36
+ import { PsbtOutputExtended } from '@btc-vision/bitcoin';
37
+ import { StrippedTransactionOutput, TransactionOutputFlags } from 'opnet';
26
38
 
27
39
  interface PublishOptions {
28
40
  network: string;
@@ -116,12 +128,13 @@ export class PublishCommand extends BaseCommand {
116
128
 
117
129
  // Check registry status
118
130
  this.logger.info('Checking registry status...');
119
- const { scope, name } = parsePackageName(meta.name);
131
+ const { scope } = parsePackageName(meta.name);
120
132
  const network = (options?.network || 'mainnet') as NetworkName;
121
133
 
122
134
  // Check if scoped package
123
135
  if (scope) {
124
136
  const scopeInfo = await getScope(scope, network);
137
+
125
138
  if (!scopeInfo) {
126
139
  this.logger.fail(`Scope @${scope} is not registered`);
127
140
  this.logger.warn(
@@ -180,44 +193,141 @@ export class PublishCommand extends BaseCommand {
180
193
  const permissionsHash = computePermissionsHash(meta.permissions);
181
194
  const dependencies = encodeDependencies(meta.dependencies || {});
182
195
 
196
+ // Check wallet balance
197
+ this.logger.info('Checking wallet balance...');
198
+ const { sufficient, balance } = await checkBalance(wallet, network);
199
+ if (!sufficient) {
200
+ this.logger.fail('Insufficient balance');
201
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
202
+ this.logger.error('Please fund your wallet before publishing.');
203
+ process.exit(1);
204
+ }
205
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
206
+
207
+ // Get contract with sender for write operations
208
+ const sender = getWalletAddress(wallet);
209
+ const contract = getRegistryContract(network, sender);
210
+
211
+ const treasuryAddress = await contract.getTreasuryAddress();
212
+
213
+ const extraUtxo: PsbtOutputExtended = {
214
+ address: treasuryAddress.properties.treasuryAddress,
215
+ value: 10_000,
216
+ };
217
+
218
+ let txParams = buildTransactionParams(
219
+ wallet,
220
+ network,
221
+ DEFAULT_MAX_SAT_TO_SPEND,
222
+ DEFAULT_FEE_RATE,
223
+ extraUtxo,
224
+ );
225
+
183
226
  // Register package if new
184
227
  if (isNewPackage) {
185
- this.logger.info('Registering package...');
186
- this.logger.warn('Package registration required.');
187
- this.logger.log(`Transaction would call: registerPackage("${meta.name}")`);
188
- this.logger.info('Package registration (transaction pending)');
228
+ this.logger.info('Registering new package...');
229
+
230
+ const outSimulation: StrippedTransactionOutput[] = [
231
+ {
232
+ index: 1,
233
+ to: treasuryAddress.properties.treasuryAddress,
234
+ value: 10_000n,
235
+ flags: TransactionOutputFlags.hasTo,
236
+ scriptPubKey: undefined,
237
+ },
238
+ ];
239
+
240
+ contract.setTransactionDetails({
241
+ inputs: [],
242
+ outputs: outSimulation,
243
+ });
244
+
245
+ const registerResult = await contract.registerPackage(meta.name);
246
+ if (registerResult.revert) {
247
+ this.logger.fail('Package registration would fail');
248
+ this.logger.error(`Reason: ${registerResult.revert}`);
249
+ process.exit(1);
250
+ }
251
+
252
+ if (registerResult.estimatedGas) {
253
+ this.logger.info(`Estimated gas: ${registerResult.estimatedGas} sats`);
254
+ }
255
+
256
+ const registerReceipt = await registerResult.sendTransaction(txParams);
257
+ this.logger.success('Package registration transaction sent');
258
+ this.logger.log(`Transaction ID: ${registerReceipt.transactionId}`);
259
+ this.logger.log('');
260
+
261
+ // Wait for registration transaction to be confirmed
262
+ const confirmationResult = await waitForTransactionConfirmation(
263
+ registerReceipt.transactionId,
264
+ network,
265
+ {
266
+ message: 'Waiting for package registration to confirm',
267
+ },
268
+ );
269
+
270
+ if (!confirmationResult.confirmed) {
271
+ if (confirmationResult.revert) {
272
+ this.logger.fail('Package registration failed');
273
+ this.logger.error(`Reason: ${confirmationResult.revert}`);
274
+ } else if (confirmationResult.error) {
275
+ this.logger.fail('Package registration not confirmed');
276
+ this.logger.error(confirmationResult.error);
277
+ }
278
+ process.exit(1);
279
+ }
280
+
281
+ this.logger.log('');
189
282
  }
190
283
 
284
+ txParams = buildTransactionParams(
285
+ wallet,
286
+ network,
287
+ DEFAULT_MAX_SAT_TO_SPEND,
288
+ DEFAULT_FEE_RATE,
289
+ );
290
+
191
291
  // Publish version
192
292
  this.logger.info('Publishing version...');
193
- this.logger.warn('Version publishing required.');
194
- this.logger.log('Transaction would call: publishVersion(');
195
- this.logger.log(` packageName: "${meta.name}",`);
196
- this.logger.log(` version: "${meta.version}",`);
197
- this.logger.log(` ipfsCid: "${pinResult.cid}",`);
198
- this.logger.log(` checksum: <32 bytes>,`);
199
- this.logger.log(` signature: <${parsed.signature.length} bytes>,`);
200
- this.logger.log(` mldsaLevel: ${mldsaLevelToRegistry(mldsaLevel)},`);
201
- this.logger.log(` opnetVersionRange: "${meta.opnetVersion}",`);
202
- this.logger.log(` pluginType: ${pluginTypeToRegistry(meta.pluginType)},`);
203
- this.logger.log(` permissionsHash: <32 bytes>,`);
204
- this.logger.log(` dependencies: <${dependencies.length} bytes>`);
205
- this.logger.log(')');
206
- this.logger.info('Version publishing (transaction pending)');
293
+
294
+ const publishResult = await contract.publishVersion(
295
+ meta.name,
296
+ meta.version,
297
+ pinResult.cid,
298
+ new Uint8Array(parsed.checksum),
299
+ new Uint8Array(parsed.signature),
300
+ mldsaLevelToRegistry(mldsaLevel),
301
+ meta.opnetVersion,
302
+ pluginTypeToRegistry(meta.pluginType),
303
+ permissionsHash,
304
+ dependencies,
305
+ );
306
+
307
+ if (publishResult.revert) {
308
+ this.logger.fail('Version publishing would fail');
309
+ this.logger.error(`Reason: ${publishResult.revert}`);
310
+ process.exit(1);
311
+ }
312
+
313
+ if (publishResult.estimatedGas) {
314
+ this.logger.info(`Estimated gas: ${publishResult.estimatedGas} sats`);
315
+ }
316
+
317
+ const publishReceipt = await publishResult.sendTransaction(txParams);
207
318
 
208
319
  this.logger.log('');
209
- this.logger.success('Plugin uploaded successfully!');
210
- this.logger.log('');
211
- this.logger.log(`IPFS CID: ${pinResult.cid}`);
212
- this.logger.log(`Gateway: https://ipfs.opnet.org/ipfs/${pinResult.cid}`);
320
+ this.logger.success('Plugin published successfully!');
213
321
  this.logger.log('');
214
- this.logger.warn('Note: Registry transaction support is coming soon.');
215
- this.logger.warn(
216
- 'The binary has been uploaded to IPFS and is ready for registry submission.',
217
- );
322
+ this.logger.log(`Package: ${meta.name}`);
323
+ this.logger.log(`Version: ${meta.version}`);
324
+ this.logger.log(`IPFS CID: ${pinResult.cid}`);
325
+ this.logger.log(`Transaction ID: ${publishReceipt.transactionId}`);
326
+ this.logger.log(`Fees paid: ${formatSats(publishReceipt.estimatedFees)}`);
327
+ this.logger.log(`Gateway: https://ipfs.opnet.org/ipfs/${pinResult.cid}`);
218
328
  this.logger.log('');
219
329
  } catch (error) {
220
- this.logger.fail('Publishing failed');
330
+ this.logger.fail(`Publishing failed`);
221
331
  if (this.isUserCancelled(error)) {
222
332
  this.logger.warn('Publishing cancelled.');
223
333
  process.exit(0);
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Scope Register command - Register a new scope in the registry
3
+ *
4
+ * @module commands/ScopeRegisterCommand
5
+ */
6
+
7
+ import { confirm } from '@inquirer/prompts';
8
+ import { BaseCommand } from './BaseCommand.js';
9
+ import { getRegistryContract, getScope, getScopePrice } from '../lib/registry.js';
10
+ import { canSign, loadCredentials } from '../lib/credentials.js';
11
+ import { CLIWallet } from '../lib/wallet.js';
12
+ import {
13
+ buildTransactionParams,
14
+ checkBalance,
15
+ formatSats,
16
+ getWalletAddress,
17
+ } from '../lib/transaction.js';
18
+ import { NetworkName } from '../types/index.js';
19
+
20
+ interface ScopeRegisterOptions {
21
+ network: string;
22
+ yes?: boolean;
23
+ }
24
+
25
+ export class ScopeRegisterCommand extends BaseCommand {
26
+ constructor() {
27
+ super('scope:register', 'Register a new scope in the registry');
28
+ }
29
+
30
+ protected configure(): void {
31
+ this.command
32
+ .argument('<name>', 'Scope name (without @)')
33
+ .option('-n, --network <network>', 'Network', 'mainnet')
34
+ .option('-y, --yes', 'Skip confirmation')
35
+ .action((name: string, options?: ScopeRegisterOptions) =>
36
+ this.execute(name, options || { network: 'mainnet' }),
37
+ );
38
+ }
39
+
40
+ private async execute(name: string, options?: ScopeRegisterOptions): Promise<void> {
41
+ try {
42
+ // Remove @ prefix if provided
43
+ const scopeName = name.startsWith('@') ? name.substring(1) : name;
44
+
45
+ // Validate scope name
46
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(scopeName) && !/^[a-z]$/.test(scopeName)) {
47
+ this.logger.fail('Invalid scope name');
48
+ this.logger.error('Scope name must:');
49
+ this.logger.error(' - Start with a lowercase letter');
50
+ this.logger.error(' - Contain only lowercase letters, numbers, and hyphens');
51
+ this.logger.error(' - End with a letter or number');
52
+ process.exit(1);
53
+ }
54
+
55
+ // Load credentials
56
+ this.logger.info('Loading wallet...');
57
+ const credentials = loadCredentials();
58
+ if (!credentials || !canSign(credentials)) {
59
+ this.logger.fail('No credentials configured');
60
+ this.logger.warn('Run `opnet login` to configure your wallet.');
61
+ process.exit(1);
62
+ }
63
+ const wallet = CLIWallet.fromCredentials(credentials);
64
+ this.logger.success('Wallet loaded');
65
+
66
+ const network = (options?.network || 'mainnet') as NetworkName;
67
+
68
+ // Check if scope already exists
69
+ this.logger.info(`Checking if @${scopeName} is available...`);
70
+ const existingScope = await getScope(scopeName, network);
71
+ if (existingScope) {
72
+ this.logger.fail('Scope already registered');
73
+ this.logger.error(`Scope @${scopeName} is already registered.`);
74
+ this.logger.log(`Owner: ${existingScope.owner}`);
75
+ process.exit(1);
76
+ }
77
+ this.logger.success(`Scope @${scopeName} is available`);
78
+
79
+ // Get scope registration price
80
+ this.logger.info('Fetching registration price...');
81
+ const scopePrice = await getScopePrice(network);
82
+ this.logger.success(`Registration price: ${formatSats(scopePrice)}`);
83
+
84
+ // Check wallet balance
85
+ this.logger.info('Checking wallet balance...');
86
+ const minRequired = scopePrice + 50_000n; // Price + estimated fees
87
+ const { sufficient, balance } = await checkBalance(wallet, network, minRequired);
88
+ if (!sufficient) {
89
+ this.logger.fail('Insufficient balance');
90
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
91
+ this.logger.error(`Required (approx): ${formatSats(minRequired)}`);
92
+ this.logger.error('Please fund your wallet before registering a scope.');
93
+ process.exit(1);
94
+ }
95
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
96
+
97
+ // Display summary
98
+ this.logger.log('');
99
+ this.logger.info('Scope Registration Summary');
100
+ this.logger.log('─'.repeat(50));
101
+ this.logger.log(`Scope: @${scopeName}`);
102
+ this.logger.log(`Price: ${formatSats(scopePrice)}`);
103
+ this.logger.log(`Network: ${options?.network}`);
104
+ this.logger.log(`Address: ${wallet.p2trAddress}`);
105
+ this.logger.log('');
106
+
107
+ // Confirmation
108
+ if (!options?.yes) {
109
+ const confirmed = await confirm({
110
+ message: `Register scope @${scopeName}?`,
111
+ default: true,
112
+ });
113
+
114
+ if (!confirmed) {
115
+ this.logger.warn('Registration cancelled.');
116
+ return;
117
+ }
118
+ }
119
+
120
+ // Execute registration
121
+ this.logger.info('Registering scope...');
122
+
123
+ const sender = getWalletAddress(wallet);
124
+ const contract = getRegistryContract(network, sender);
125
+ const txParams = buildTransactionParams(wallet, network);
126
+
127
+ const registerResult = await contract.registerScope(scopeName);
128
+
129
+ if (registerResult.revert) {
130
+ this.logger.fail('Registration would fail');
131
+ this.logger.error(`Reason: ${registerResult.revert}`);
132
+ process.exit(1);
133
+ }
134
+
135
+ if (registerResult.estimatedGas) {
136
+ this.logger.info(`Estimated gas: ${registerResult.estimatedGas} sats`);
137
+ }
138
+
139
+ const receipt = await registerResult.sendTransaction(txParams);
140
+
141
+ this.logger.log('');
142
+ this.logger.success('Scope registered successfully!');
143
+ this.logger.log('');
144
+ this.logger.log(`Scope: @${scopeName}`);
145
+ this.logger.log(`Owner: ${wallet.p2trAddress}`);
146
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
147
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
148
+ this.logger.log('');
149
+ this.logger.info(
150
+ 'You can now publish packages under this scope using: opnet publish',
151
+ );
152
+ this.logger.log('');
153
+ } catch (error) {
154
+ this.logger.fail('Scope registration failed');
155
+ if (this.isUserCancelled(error)) {
156
+ this.logger.warn('Registration cancelled.');
157
+ process.exit(0);
158
+ }
159
+ this.exitWithError(this.formatError(error));
160
+ }
161
+ }
162
+ }
163
+
164
+ export const scopeRegisterCommand = new ScopeRegisterCommand().getCommand();
@@ -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