@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
package/build/lib/ipfs.js CHANGED
@@ -42,86 +42,95 @@ async function httpRequest(url, options) {
42
42
  });
43
43
  }
44
44
  export async function pinToIPFS(data, name) {
45
- const config = loadConfig();
46
- const endpoint = config.ipfsPinningEndpoint;
47
- if (!endpoint) {
48
- throw new Error('IPFS pinning endpoint not configured. Run `opnet config set ipfsPinningEndpoint <url>`');
49
- }
50
- const boundary = '----FormBoundary' + Math.random().toString(36).substring(2);
51
- const fileName = name || 'plugin.opnet';
52
- const formParts = [];
53
- formParts.push(Buffer.from(`--${boundary}\r\n` +
54
- `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
55
- `Content-Type: application/octet-stream\r\n\r\n`));
56
- formParts.push(data);
57
- formParts.push(Buffer.from('\r\n'));
58
- formParts.push(Buffer.from(`--${boundary}--\r\n`));
59
- const body = Buffer.concat(formParts);
60
- const headers = {
61
- 'Content-Type': `multipart/form-data; boundary=${boundary}`,
62
- 'Content-Length': body.length.toString(),
63
- };
64
- if (config.ipfsPinningAuthHeader) {
65
- const [headerName, headerValue] = config.ipfsPinningAuthHeader
66
- .split(':')
67
- .map((s) => s.trim());
68
- if (headerName && headerValue) {
69
- headers[headerName] = headerValue;
45
+ try {
46
+ const config = loadConfig();
47
+ const endpoint = config.ipfsPinningEndpoint;
48
+ if (!endpoint) {
49
+ throw new Error('IPFS pinning endpoint not configured. Run `opnet config set ipfsPinningEndpoint <url>`');
70
50
  }
71
- }
72
- else if (config.ipfsPinningApiKey) {
73
- headers['Authorization'] = `Bearer ${config.ipfsPinningApiKey}`;
74
- }
75
- const url = new URL(endpoint);
76
- let requestUrl;
77
- if (url.hostname.includes('ipfs.opnet.org')) {
78
- requestUrl = endpoint;
79
- }
80
- else if (url.hostname.includes('pinata')) {
81
- requestUrl = 'https://api.pinata.cloud/pinning/pinFileToIPFS';
82
- if (config.ipfsPinningApiKey) {
83
- headers['pinata_api_key'] = config.ipfsPinningApiKey;
51
+ const boundary = '----FormBoundary' + Math.random().toString(36).substring(2);
52
+ const fileName = name || 'plugin.opnet';
53
+ const formParts = [];
54
+ formParts.push(Buffer.from(`--${boundary}\r\n` +
55
+ `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
56
+ `Content-Type: application/octet-stream\r\n\r\n`));
57
+ formParts.push(data);
58
+ formParts.push(Buffer.from('\r\n'));
59
+ formParts.push(Buffer.from(`--${boundary}--\r\n`));
60
+ const body = Buffer.concat(formParts);
61
+ const headers = {
62
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
63
+ 'Content-Length': body.length.toString(),
64
+ };
65
+ if (config.ipfsPinningAuthHeader) {
66
+ const [headerName, headerValue] = config.ipfsPinningAuthHeader
67
+ .split(':')
68
+ .map((s) => s.trim());
69
+ if (headerName && headerValue) {
70
+ headers[headerName] = headerValue;
71
+ }
84
72
  }
73
+ else if (config.ipfsPinningApiKey) {
74
+ headers['Authorization'] = `Bearer ${config.ipfsPinningApiKey}`;
75
+ }
76
+ const url = new URL(endpoint);
77
+ let requestUrl;
78
+ if (url.hostname.includes('ipfs.opnet.org')) {
79
+ requestUrl = endpoint;
80
+ }
81
+ else if (url.hostname.includes('pinata')) {
82
+ requestUrl = 'https://api.pinata.cloud/pinning/pinFileToIPFS';
83
+ if (config.ipfsPinningApiKey && !config.ipfsPinningApiKey.startsWith('eyJ')) {
84
+ headers['pinata_api_key'] = config.ipfsPinningApiKey;
85
+ if (config.ipfsPinningSecret) {
86
+ headers['pinata_secret_api_key'] = config.ipfsPinningSecret;
87
+ }
88
+ }
89
+ }
90
+ else if (url.hostname.includes('web3.storage') || url.hostname.includes('w3s.link')) {
91
+ requestUrl = endpoint.endsWith('/') ? endpoint + 'upload' : endpoint + '/upload';
92
+ }
93
+ else if (url.hostname.includes('nft.storage')) {
94
+ requestUrl = 'https://api.nft.storage/upload';
95
+ }
96
+ else if (url.pathname.includes('/api/v0/')) {
97
+ requestUrl = endpoint;
98
+ }
99
+ else {
100
+ requestUrl = endpoint.endsWith('/') ? endpoint + 'pins' : endpoint + '/pins';
101
+ }
102
+ const response = await httpRequest(requestUrl, {
103
+ method: 'POST',
104
+ headers,
105
+ body,
106
+ timeout: 120000,
107
+ });
108
+ const result = JSON.parse(response.toString());
109
+ let cid;
110
+ if (typeof result.IpfsHash === 'string') {
111
+ cid = result.IpfsHash;
112
+ }
113
+ else if (typeof result.cid === 'string') {
114
+ cid = result.cid;
115
+ }
116
+ else if (typeof result.Hash === 'string') {
117
+ cid = result.Hash;
118
+ }
119
+ else if (result.value &&
120
+ typeof result.value.cid === 'string') {
121
+ cid = result.value.cid;
122
+ }
123
+ if (!cid) {
124
+ throw new Error(`Failed to extract CID from pinning response: ${JSON.stringify(result)}`);
125
+ }
126
+ return {
127
+ cid,
128
+ size: data.length,
129
+ };
85
130
  }
86
- else if (url.hostname.includes('web3.storage') || url.hostname.includes('w3s.link')) {
87
- requestUrl = endpoint.endsWith('/') ? endpoint + 'upload' : endpoint + '/upload';
88
- }
89
- else if (url.hostname.includes('nft.storage')) {
90
- requestUrl = 'https://api.nft.storage/upload';
91
- }
92
- else if (url.pathname.includes('/api/v0/')) {
93
- requestUrl = endpoint;
94
- }
95
- else {
96
- requestUrl = endpoint.endsWith('/') ? endpoint + 'pins' : endpoint + '/pins';
97
- }
98
- const response = await httpRequest(requestUrl, {
99
- method: 'POST',
100
- headers,
101
- body,
102
- timeout: 120000,
103
- });
104
- const result = JSON.parse(response.toString());
105
- let cid;
106
- if (typeof result.IpfsHash === 'string') {
107
- cid = result.IpfsHash;
108
- }
109
- else if (typeof result.cid === 'string') {
110
- cid = result.cid;
111
- }
112
- else if (typeof result.Hash === 'string') {
113
- cid = result.Hash;
114
- }
115
- else if (result.value && typeof result.value.cid === 'string') {
116
- cid = result.value.cid;
117
- }
118
- if (!cid) {
119
- throw new Error(`Failed to extract CID from pinning response: ${JSON.stringify(result)}`);
131
+ catch (e) {
132
+ throw new Error(`IPFS pinning failed: ${e instanceof Error ? e.message : String(e)}`);
120
133
  }
121
- return {
122
- cid,
123
- size: data.length,
124
- };
125
134
  }
126
135
  export async function fetchFromIPFS(cid) {
127
136
  const config = loadConfig();
@@ -78,7 +78,7 @@ export function createManifest(options) {
78
78
  return {
79
79
  name: options.name,
80
80
  version: '1.0.0',
81
- opnetVersion: '^1.0.0',
81
+ opnetVersion: '>=0.0.1',
82
82
  main: 'dist/index.jsc',
83
83
  target: 'bytenode',
84
84
  type: 'plugin',
@@ -32,7 +32,7 @@ export interface PendingTransferInfo {
32
32
  pendingOwner: Address;
33
33
  initiatedAt: bigint;
34
34
  }
35
- export declare function getRegistryContract(network?: NetworkName): IPackageRegistry;
35
+ export declare function getRegistryContract(network?: NetworkName, sender?: Address): IPackageRegistry;
36
36
  export declare function clearRegistryCache(): void;
37
37
  export declare function getScope(scopeName: string, network?: NetworkName): Promise<ScopeInfo | null>;
38
38
  export declare function getScopeOwner(scopeName: string, network?: NetworkName): Promise<Address | null>;
@@ -5,10 +5,10 @@ import { getNetwork } from './wallet.js';
5
5
  import { loadConfig } from './config.js';
6
6
  import { PACKAGE_REGISTRY_ABI } from './PackageRegistry.abi.js';
7
7
  const registryCache = new Map();
8
- export function getRegistryContract(network) {
8
+ export function getRegistryContract(network, sender) {
9
9
  const config = loadConfig();
10
10
  const targetNetwork = network || config.defaultNetwork;
11
- const cacheKey = targetNetwork;
11
+ const cacheKey = sender ? `${targetNetwork}:${sender.toHex()}` : targetNetwork;
12
12
  const cached = registryCache.get(cacheKey);
13
13
  if (cached) {
14
14
  return cached;
@@ -16,7 +16,7 @@ export function getRegistryContract(network) {
16
16
  const provider = getProvider(targetNetwork);
17
17
  const registryAddress = getRegistryContractAddress(targetNetwork);
18
18
  const bitcoinNetwork = getNetwork(targetNetwork);
19
- const contract = getContract(registryAddress, PACKAGE_REGISTRY_ABI, provider, bitcoinNetwork);
19
+ const contract = getContract(registryAddress, PACKAGE_REGISTRY_ABI, provider, bitcoinNetwork, sender);
20
20
  registryCache.set(cacheKey, contract);
21
21
  return contract;
22
22
  }
@@ -0,0 +1,27 @@
1
+ import { Address } from '@btc-vision/transaction';
2
+ import { TransactionParameters } from 'opnet';
3
+ import { CLIWallet } from './wallet.js';
4
+ import { NetworkName } from '../types/index.js';
5
+ import { PsbtOutputExtended } from '@btc-vision/bitcoin';
6
+ export declare const DEFAULT_MAX_SAT_TO_SPEND = 100000n;
7
+ export declare const DEFAULT_FEE_RATE = 6;
8
+ export declare function buildTransactionParams(wallet: CLIWallet, network: NetworkName, maxSatToSpend?: bigint, feeRate?: number, extra?: PsbtOutputExtended): TransactionParameters;
9
+ export declare function getWalletAddress(wallet: CLIWallet): Address;
10
+ export declare function formatSats(sats: bigint): string;
11
+ export declare function checkBalance(wallet: CLIWallet, network: NetworkName, minBalance?: bigint): Promise<{
12
+ sufficient: boolean;
13
+ balance: bigint;
14
+ }>;
15
+ export declare const DEFAULT_POLL_INTERVAL = 10000;
16
+ export declare const DEFAULT_MAX_WAIT_TIME = 600000;
17
+ export interface TransactionConfirmationResult {
18
+ confirmed: boolean;
19
+ blockNumber?: bigint;
20
+ revert?: string;
21
+ error?: string;
22
+ }
23
+ export declare function waitForTransactionConfirmation(txHash: string, network: NetworkName, options?: {
24
+ pollInterval?: number;
25
+ maxWaitTime?: number;
26
+ message?: string;
27
+ }): Promise<TransactionConfirmationResult>;
@@ -0,0 +1,91 @@
1
+ import ora from 'ora';
2
+ import { getNetwork } from './wallet.js';
3
+ import { getProvider } from './provider.js';
4
+ export const DEFAULT_MAX_SAT_TO_SPEND = 100000n;
5
+ export const DEFAULT_FEE_RATE = 6;
6
+ export function buildTransactionParams(wallet, network, maxSatToSpend = DEFAULT_MAX_SAT_TO_SPEND, feeRate = DEFAULT_FEE_RATE, extra) {
7
+ const bitcoinNetwork = getNetwork(network);
8
+ return {
9
+ signer: wallet.keypair,
10
+ mldsaSigner: wallet.mldsaKeypair,
11
+ refundTo: wallet.p2trAddress,
12
+ maximumAllowedSatToSpend: maxSatToSpend,
13
+ network: bitcoinNetwork,
14
+ feeRate,
15
+ extraOutputs: extra ? [extra] : undefined,
16
+ };
17
+ }
18
+ export function getWalletAddress(wallet) {
19
+ return wallet.address;
20
+ }
21
+ export function formatSats(sats) {
22
+ const btc = Number(sats) / 100_000_000;
23
+ if (btc >= 0.001) {
24
+ return `${sats} sats (${btc.toFixed(8)} BTC)`;
25
+ }
26
+ return `${sats} sats`;
27
+ }
28
+ export async function checkBalance(wallet, network, minBalance = 10000n) {
29
+ const provider = getProvider(network);
30
+ const balance = await provider.getBalance(wallet.p2trAddress);
31
+ return {
32
+ sufficient: balance >= minBalance,
33
+ balance,
34
+ };
35
+ }
36
+ export const DEFAULT_POLL_INTERVAL = 10_000;
37
+ export const DEFAULT_MAX_WAIT_TIME = 600_000;
38
+ export async function waitForTransactionConfirmation(txHash, network, options) {
39
+ const pollInterval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;
40
+ const maxWaitTime = options?.maxWaitTime ?? DEFAULT_MAX_WAIT_TIME;
41
+ const message = options?.message ?? 'Waiting for transaction confirmation';
42
+ const provider = getProvider(network);
43
+ const startTime = Date.now();
44
+ const spinner = ora({
45
+ text: `${message} (0s elapsed)`,
46
+ spinner: 'dots',
47
+ }).start();
48
+ const updateSpinnerText = () => {
49
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
50
+ spinner.text = `${message} (${elapsed}s elapsed)`;
51
+ };
52
+ const textUpdateInterval = setInterval(updateSpinnerText, 1000);
53
+ try {
54
+ while (Date.now() - startTime < maxWaitTime) {
55
+ try {
56
+ const tx = await provider.getTransaction(txHash);
57
+ if (tx.blockNumber !== undefined && tx.blockNumber !== null) {
58
+ const blockNum = typeof tx.blockNumber === 'bigint'
59
+ ? tx.blockNumber
60
+ : BigInt(tx.blockNumber);
61
+ spinner.succeed(`Transaction confirmed in block ${blockNum}`);
62
+ return {
63
+ confirmed: true,
64
+ blockNumber: blockNum,
65
+ };
66
+ }
67
+ if (tx.revert) {
68
+ spinner.fail(`Transaction reverted: ${tx.revert}`);
69
+ return {
70
+ confirmed: false,
71
+ revert: tx.revert,
72
+ };
73
+ }
74
+ }
75
+ catch {
76
+ }
77
+ await sleep(pollInterval);
78
+ }
79
+ spinner.fail(`Timeout waiting for transaction confirmation`);
80
+ return {
81
+ confirmed: false,
82
+ error: `Transaction not confirmed within ${maxWaitTime / 1000} seconds`,
83
+ };
84
+ }
85
+ finally {
86
+ clearInterval(textUpdateInterval);
87
+ }
88
+ }
89
+ function sleep(ms) {
90
+ return new Promise((resolve) => setTimeout(resolve, ms));
91
+ }
@@ -1,7 +1,8 @@
1
- import { Network } from '@btc-vision/bitcoin';
2
- import { MLDSASecurityLevel } from '@btc-vision/bip32';
3
- import { EcKeyPair, Wallet } from '@btc-vision/transaction';
1
+ import { Network, Signer } from '@btc-vision/bitcoin';
2
+ import { MLDSASecurityLevel, QuantumBIP32Interface } from '@btc-vision/bip32';
3
+ import { Address } from '@btc-vision/transaction';
4
4
  import { CLICredentials, CLIMldsaLevel, NetworkName } from '../types/index.js';
5
+ import { ECPairInterface } from 'ecpair';
5
6
  export declare function getNetwork(networkName: NetworkName): Network;
6
7
  export declare function getMLDSASecurityLevel(level: CLIMldsaLevel): MLDSASecurityLevel;
7
8
  export declare class CLIWallet {
@@ -9,14 +10,13 @@ export declare class CLIWallet {
9
10
  private readonly network;
10
11
  private readonly mldsaLevel;
11
12
  private constructor();
13
+ get address(): Address;
12
14
  get p2trAddress(): string;
13
- get underlyingWallet(): Wallet;
14
- get keypair(): EcKeyPair;
15
- get mldsaKeypair(): EcKeyPair;
15
+ get keypair(): Signer | ECPairInterface | null;
16
+ get mldsaKeypair(): QuantumBIP32Interface;
16
17
  get mldsaPublicKey(): Buffer;
17
18
  get mldsaPublicKeyHash(): string;
18
19
  get securityLevel(): CLIMldsaLevel;
19
- get walletNetwork(): Network;
20
20
  static fromCredentials(credentials: CLICredentials): CLIWallet;
21
21
  static load(): CLIWallet;
22
22
  static verifyMLDSA(data: Buffer, signature: Buffer, publicKey: Buffer, level: CLIMldsaLevel): boolean;
@@ -32,12 +32,12 @@ export class CLIWallet {
32
32
  this.network = network;
33
33
  this.mldsaLevel = mldsaLevel;
34
34
  }
35
+ get address() {
36
+ return this.wallet.address;
37
+ }
35
38
  get p2trAddress() {
36
39
  return this.wallet.p2tr;
37
40
  }
38
- get underlyingWallet() {
39
- return this.wallet;
40
- }
41
41
  get keypair() {
42
42
  return this.wallet.keypair;
43
43
  }
@@ -54,9 +54,6 @@ export class CLIWallet {
54
54
  get securityLevel() {
55
55
  return this.mldsaLevel;
56
56
  }
57
- get walletNetwork() {
58
- return this.network;
59
- }
60
57
  static fromCredentials(credentials) {
61
58
  const network = getNetwork(credentials.network);
62
59
  const securityLevel = getMLDSASecurityLevel(credentials.mldsaLevel);
@@ -8,6 +8,7 @@ export interface CLIConfig {
8
8
  ipfsGateways: string[];
9
9
  ipfsPinningEndpoint: string;
10
10
  ipfsPinningApiKey: string;
11
+ ipfsPinningSecret: string;
11
12
  ipfsPinningAuthHeader: string;
12
13
  registryAddresses: Record<NetworkName, string>;
13
14
  defaultMldsaLevel: CLIMldsaLevel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "CLI for the OPNet plugin ecosystem - scaffolding, compilation, signing, and registry interaction",
6
6
  "author": "OP_NET",
@@ -52,6 +52,7 @@
52
52
  "@inquirer/prompts": "^7.5.3",
53
53
  "bytenode": "^1.5.7",
54
54
  "commander": "^14.0.0",
55
+ "ecpair": "^2.1.0",
55
56
  "esbuild": "^0.25.5",
56
57
  "opnet": "^1.7.18",
57
58
  "ora": "^8.2.0"
@@ -6,9 +6,19 @@
6
6
 
7
7
  import { confirm } from '@inquirer/prompts';
8
8
  import { BaseCommand } from './BaseCommand.js';
9
- import { getPendingScopeTransfer, getPendingTransfer } from '../lib/registry.js';
9
+ import {
10
+ getPendingScopeTransfer,
11
+ getPendingTransfer,
12
+ getRegistryContract,
13
+ } from '../lib/registry.js';
10
14
  import { canSign, loadCredentials } from '../lib/credentials.js';
11
15
  import { CLIWallet } from '../lib/wallet.js';
16
+ import {
17
+ buildTransactionParams,
18
+ checkBalance,
19
+ formatSats,
20
+ getWalletAddress,
21
+ } from '../lib/transaction.js';
12
22
  import { NetworkName } from '../types/index.js';
13
23
 
14
24
  interface AcceptOptions {
@@ -41,7 +51,7 @@ export class AcceptCommand extends BaseCommand {
41
51
  this.logger.warn('Run `opnet login` to configure your wallet.');
42
52
  process.exit(1);
43
53
  }
44
- CLIWallet.fromCredentials(credentials);
54
+ const wallet = CLIWallet.fromCredentials(credentials);
45
55
  this.logger.success('Wallet loaded');
46
56
 
47
57
  const network = (options?.network || 'mainnet') as NetworkName;
@@ -84,12 +94,46 @@ export class AcceptCommand extends BaseCommand {
84
94
  }
85
95
  }
86
96
 
97
+ // Check wallet balance
98
+ this.logger.info('Checking wallet balance...');
99
+ const { sufficient, balance } = await checkBalance(wallet, network);
100
+ if (!sufficient) {
101
+ this.logger.fail('Insufficient balance');
102
+ this.logger.error(`Wallet balance: ${formatSats(balance)}`);
103
+ this.logger.error('Please fund your wallet before accepting transfer.');
104
+ process.exit(1);
105
+ }
106
+ this.logger.success(`Wallet balance: ${formatSats(balance)}`);
107
+
87
108
  // Execute acceptance
88
109
  this.logger.info('Accepting transfer...');
89
- this.logger.warn('Acceptance transaction required.');
90
- this.logger.log('Transaction would call: acceptScopeTransfer(');
91
- this.logger.log(` scopeName: "${scopeName}"`);
92
- this.logger.log(')');
110
+
111
+ const sender = getWalletAddress(wallet);
112
+ const contract = getRegistryContract(network, sender);
113
+ const txParams = buildTransactionParams(wallet, network);
114
+
115
+ const acceptResult = await contract.acceptScopeTransfer(scopeName);
116
+
117
+ if (acceptResult.revert) {
118
+ this.logger.fail('Acceptance would fail');
119
+ this.logger.error(`Reason: ${acceptResult.revert}`);
120
+ process.exit(1);
121
+ }
122
+
123
+ if (acceptResult.estimatedGas) {
124
+ this.logger.info(`Estimated gas: ${acceptResult.estimatedGas} sats`);
125
+ }
126
+
127
+ const receipt = await acceptResult.sendTransaction(txParams);
128
+
129
+ this.logger.log('');
130
+ this.logger.success('Scope transfer accepted successfully!');
131
+ this.logger.log('');
132
+ this.logger.log(`Scope: ${name}`);
133
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
134
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
135
+ this.logger.log('');
136
+ return;
93
137
  } else {
94
138
  const pending = await getPendingTransfer(name, network);
95
139
 
@@ -123,20 +167,49 @@ export class AcceptCommand extends BaseCommand {
123
167
  }
124
168
  }
125
169
 
170
+ // Check wallet balance
171
+ this.logger.info('Checking wallet balance...');
172
+ const { sufficient: sufficientPkg, balance: balancePkg } = await checkBalance(
173
+ wallet,
174
+ network,
175
+ );
176
+ if (!sufficientPkg) {
177
+ this.logger.fail('Insufficient balance');
178
+ this.logger.error(`Wallet balance: ${formatSats(balancePkg)}`);
179
+ this.logger.error('Please fund your wallet before accepting transfer.');
180
+ process.exit(1);
181
+ }
182
+ this.logger.success(`Wallet balance: ${formatSats(balancePkg)}`);
183
+
126
184
  // Execute acceptance
127
185
  this.logger.info('Accepting transfer...');
128
- this.logger.warn('Acceptance transaction required.');
129
- this.logger.log('Transaction would call: acceptTransfer(');
130
- this.logger.log(` packageName: "${name}"`);
131
- this.logger.log(')');
132
- }
133
186
 
134
- this.logger.info('Acceptance (transaction pending)');
187
+ const sender = getWalletAddress(wallet);
188
+ const contract = getRegistryContract(network, sender);
189
+ const txParams = buildTransactionParams(wallet, network);
190
+
191
+ const acceptResult = await contract.acceptTransfer(name);
135
192
 
136
- this.logger.log('');
137
- this.logger.success('Transfer acceptance submitted!');
138
- this.logger.warn('Note: Registry transaction support is coming soon.');
139
- this.logger.log('');
193
+ if (acceptResult.revert) {
194
+ this.logger.fail('Acceptance would fail');
195
+ this.logger.error(`Reason: ${acceptResult.revert}`);
196
+ process.exit(1);
197
+ }
198
+
199
+ if (acceptResult.estimatedGas) {
200
+ this.logger.info(`Estimated gas: ${acceptResult.estimatedGas} sats`);
201
+ }
202
+
203
+ const receipt = await acceptResult.sendTransaction(txParams);
204
+
205
+ this.logger.log('');
206
+ this.logger.success('Package transfer accepted successfully!');
207
+ this.logger.log('');
208
+ this.logger.log(`Package: ${name}`);
209
+ this.logger.log(`Transaction ID: ${receipt.transactionId}`);
210
+ this.logger.log(`Fees paid: ${formatSats(receipt.estimatedFees)}`);
211
+ this.logger.log('');
212
+ }
140
213
  } catch (error) {
141
214
  this.logger.fail('Acceptance failed');
142
215
  if (this.isUserCancelled(error)) {
@@ -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