@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.
- package/README.md +55 -23
- package/build/commands/AcceptCommand.js +62 -15
- package/build/commands/CompileCommand.js +9 -10
- package/build/commands/ConfigCommand.js +2 -27
- package/build/commands/DeprecateCommand.js +32 -11
- package/build/commands/InitCommand.js +1 -7
- package/build/commands/PublishCommand.js +82 -27
- package/build/commands/ScopeRegisterCommand.d.ts +7 -0
- package/build/commands/ScopeRegisterCommand.js +115 -0
- package/build/commands/SignCommand.js +6 -9
- package/build/commands/TransferCommand.js +118 -30
- package/build/commands/UndeprecateCommand.js +31 -10
- package/build/index.js +2 -0
- package/build/lib/binary.d.ts +5 -2
- package/build/lib/binary.js +11 -6
- package/build/lib/config.d.ts +1 -0
- package/build/lib/config.js +3 -2
- package/build/lib/ipfs.js +85 -76
- package/build/lib/manifest.js +1 -1
- package/build/lib/registry.d.ts +1 -1
- package/build/lib/registry.js +3 -3
- package/build/lib/transaction.d.ts +27 -0
- package/build/lib/transaction.js +91 -0
- package/build/lib/wallet.d.ts +7 -7
- package/build/lib/wallet.js +3 -6
- package/build/types/index.d.ts +1 -0
- package/package.json +2 -1
- package/src/commands/AcceptCommand.ts +89 -16
- package/src/commands/CompileCommand.ts +13 -14
- package/src/commands/ConfigCommand.ts +2 -29
- package/src/commands/DeprecateCommand.ts +48 -11
- package/src/commands/InitCommand.ts +1 -7
- package/src/commands/PublishCommand.ts +138 -28
- package/src/commands/ScopeRegisterCommand.ts +164 -0
- package/src/commands/SignCommand.ts +9 -21
- package/src/commands/TransferCommand.ts +159 -31
- package/src/commands/UndeprecateCommand.ts +43 -10
- package/src/index.ts +2 -0
- package/src/lib/binary.ts +24 -22
- package/src/lib/config.ts +3 -2
- package/src/lib/ipfs.ts +113 -99
- package/src/lib/manifest.ts +1 -1
- package/src/lib/registry.ts +5 -2
- package/src/lib/transaction.ts +205 -0
- package/src/lib/wallet.ts +10 -19
- package/src/types/index.ts +3 -1
package/src/lib/ipfs.ts
CHANGED
|
@@ -103,117 +103,131 @@ async function httpRequest(url: string, options: RequestOptions): Promise<Buffer
|
|
|
103
103
|
* @returns The IPFS CID
|
|
104
104
|
*/
|
|
105
105
|
export async function pinToIPFS(data: Buffer, name?: string): Promise<PinResult> {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
try {
|
|
107
|
+
const config = loadConfig();
|
|
108
|
+
const endpoint = config.ipfsPinningEndpoint;
|
|
109
|
+
|
|
110
|
+
if (!endpoint) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'IPFS pinning endpoint not configured. Run `opnet config set ipfsPinningEndpoint <url>`',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Build multipart form data
|
|
117
|
+
const boundary = '----FormBoundary' + Math.random().toString(36).substring(2);
|
|
118
|
+
const fileName = name || 'plugin.opnet';
|
|
119
|
+
|
|
120
|
+
const formParts: Buffer[] = [];
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
// File part
|
|
123
|
+
formParts.push(
|
|
124
|
+
Buffer.from(
|
|
125
|
+
`--${boundary}\r\n` +
|
|
126
|
+
`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
|
|
127
|
+
`Content-Type: application/octet-stream\r\n\r\n`,
|
|
128
|
+
),
|
|
112
129
|
);
|
|
113
|
-
|
|
130
|
+
formParts.push(data);
|
|
131
|
+
formParts.push(Buffer.from('\r\n'));
|
|
114
132
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const fileName = name || 'plugin.opnet';
|
|
133
|
+
// End boundary
|
|
134
|
+
formParts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
118
135
|
|
|
119
|
-
|
|
136
|
+
const body = Buffer.concat(formParts);
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Build headers
|
|
138
|
-
const headers: Record<string, string> = {
|
|
139
|
-
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
140
|
-
'Content-Length': body.length.toString(),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Add authorization if configured
|
|
144
|
-
if (config.ipfsPinningAuthHeader) {
|
|
145
|
-
const [headerName, headerValue] = config.ipfsPinningAuthHeader
|
|
146
|
-
.split(':')
|
|
147
|
-
.map((s) => s.trim());
|
|
148
|
-
if (headerName && headerValue) {
|
|
149
|
-
headers[headerName] = headerValue;
|
|
138
|
+
// Build headers
|
|
139
|
+
const headers: Record<string, string> = {
|
|
140
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
141
|
+
'Content-Length': body.length.toString(),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Add authorization if configured
|
|
145
|
+
if (config.ipfsPinningAuthHeader) {
|
|
146
|
+
const [headerName, headerValue] = config.ipfsPinningAuthHeader
|
|
147
|
+
.split(':')
|
|
148
|
+
.map((s) => s.trim());
|
|
149
|
+
if (headerName && headerValue) {
|
|
150
|
+
headers[headerName] = headerValue;
|
|
151
|
+
}
|
|
152
|
+
} else if (config.ipfsPinningApiKey) {
|
|
153
|
+
headers['Authorization'] = `Bearer ${config.ipfsPinningApiKey}`;
|
|
150
154
|
}
|
|
151
|
-
} else if (config.ipfsPinningApiKey) {
|
|
152
|
-
headers['Authorization'] = `Bearer ${config.ipfsPinningApiKey}`;
|
|
153
|
-
}
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
// Detect pinning service type from URL and adjust request
|
|
157
|
+
const url = new URL(endpoint);
|
|
158
|
+
|
|
159
|
+
let requestUrl: string;
|
|
160
|
+
if (url.hostname.includes('ipfs.opnet.org')) {
|
|
161
|
+
// OPNet IPFS gateway - uses standard IPFS API
|
|
162
|
+
requestUrl = endpoint;
|
|
163
|
+
} else if (url.hostname.includes('pinata')) {
|
|
164
|
+
// Pinata-specific endpoint
|
|
165
|
+
requestUrl = 'https://api.pinata.cloud/pinning/pinFileToIPFS';
|
|
166
|
+
// Only set pinata_api_key header if using API key (not JWT)
|
|
167
|
+
// JWT tokens start with "eyJ", API keys don't
|
|
168
|
+
if (config.ipfsPinningApiKey && !config.ipfsPinningApiKey.startsWith('eyJ')) {
|
|
169
|
+
headers['pinata_api_key'] = config.ipfsPinningApiKey;
|
|
170
|
+
if (config.ipfsPinningSecret) {
|
|
171
|
+
headers['pinata_secret_api_key'] = config.ipfsPinningSecret;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (url.hostname.includes('web3.storage') || url.hostname.includes('w3s.link')) {
|
|
175
|
+
// web3.storage endpoint
|
|
176
|
+
requestUrl = endpoint.endsWith('/') ? endpoint + 'upload' : endpoint + '/upload';
|
|
177
|
+
} else if (url.hostname.includes('nft.storage')) {
|
|
178
|
+
// nft.storage endpoint
|
|
179
|
+
requestUrl = 'https://api.nft.storage/upload';
|
|
180
|
+
} else if (url.pathname.includes('/api/v0/')) {
|
|
181
|
+
// Standard IPFS API endpoint
|
|
182
|
+
requestUrl = endpoint;
|
|
183
|
+
} else {
|
|
184
|
+
// Generic IPFS pinning service (assumed to follow IPFS Pinning Services API)
|
|
185
|
+
requestUrl = endpoint.endsWith('/') ? endpoint + 'pins' : endpoint + '/pins';
|
|
167
186
|
}
|
|
168
|
-
} else if (url.hostname.includes('web3.storage') || url.hostname.includes('w3s.link')) {
|
|
169
|
-
// web3.storage endpoint
|
|
170
|
-
requestUrl = endpoint.endsWith('/') ? endpoint + 'upload' : endpoint + '/upload';
|
|
171
|
-
} else if (url.hostname.includes('nft.storage')) {
|
|
172
|
-
// nft.storage endpoint
|
|
173
|
-
requestUrl = 'https://api.nft.storage/upload';
|
|
174
|
-
} else if (url.pathname.includes('/api/v0/')) {
|
|
175
|
-
// Standard IPFS API endpoint
|
|
176
|
-
requestUrl = endpoint;
|
|
177
|
-
} else {
|
|
178
|
-
// Generic IPFS pinning service (assumed to follow IPFS Pinning Services API)
|
|
179
|
-
requestUrl = endpoint.endsWith('/') ? endpoint + 'pins' : endpoint + '/pins';
|
|
180
|
-
}
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const response = await httpRequest(requestUrl, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers,
|
|
191
|
+
body,
|
|
192
|
+
timeout: 120000, // 2 minutes for upload
|
|
193
|
+
});
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
195
|
+
// Parse response to extract CID
|
|
196
|
+
const result = JSON.parse(response.toString()) as Record<string, unknown>;
|
|
197
|
+
|
|
198
|
+
// Handle different response formats
|
|
199
|
+
let cid: string | undefined;
|
|
200
|
+
|
|
201
|
+
if (typeof result.IpfsHash === 'string') {
|
|
202
|
+
// Pinata format
|
|
203
|
+
cid = result.IpfsHash;
|
|
204
|
+
} else if (typeof result.cid === 'string') {
|
|
205
|
+
// web3.storage / nft.storage format
|
|
206
|
+
cid = result.cid;
|
|
207
|
+
} else if (typeof result.Hash === 'string') {
|
|
208
|
+
// IPFS API format
|
|
209
|
+
cid = result.Hash;
|
|
210
|
+
} else if (
|
|
211
|
+
result.value &&
|
|
212
|
+
typeof (result.value as Record<string, unknown>).cid === 'string'
|
|
213
|
+
) {
|
|
214
|
+
// NFT.storage wrapped format
|
|
215
|
+
cid = (result.value as Record<string, unknown>).cid as string;
|
|
216
|
+
}
|
|
208
217
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
if (!cid) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Failed to extract CID from pinning response: ${JSON.stringify(result)}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
212
223
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
return {
|
|
225
|
+
cid,
|
|
226
|
+
size: data.length,
|
|
227
|
+
};
|
|
228
|
+
} catch (e) {
|
|
229
|
+
throw new Error(`IPFS pinning failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
230
|
+
}
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
/**
|
package/src/lib/manifest.ts
CHANGED
package/src/lib/registry.ts
CHANGED
|
@@ -73,12 +73,14 @@ const registryCache = new Map<string, IPackageRegistry>();
|
|
|
73
73
|
* Get the registry contract instance
|
|
74
74
|
*
|
|
75
75
|
* @param network - Network name (defaults to configured default)
|
|
76
|
+
* @param sender - Optional sender address for write operations
|
|
76
77
|
* @returns Contract instance
|
|
77
78
|
*/
|
|
78
|
-
export function getRegistryContract(network?: NetworkName): IPackageRegistry {
|
|
79
|
+
export function getRegistryContract(network?: NetworkName, sender?: Address): IPackageRegistry {
|
|
79
80
|
const config = loadConfig();
|
|
80
81
|
const targetNetwork = network || config.defaultNetwork;
|
|
81
|
-
|
|
82
|
+
// Include sender in cache key to handle both read-only and write cases
|
|
83
|
+
const cacheKey = sender ? `${targetNetwork}:${sender.toHex()}` : targetNetwork;
|
|
82
84
|
|
|
83
85
|
const cached = registryCache.get(cacheKey);
|
|
84
86
|
if (cached) {
|
|
@@ -94,6 +96,7 @@ export function getRegistryContract(network?: NetworkName): IPackageRegistry {
|
|
|
94
96
|
PACKAGE_REGISTRY_ABI,
|
|
95
97
|
provider,
|
|
96
98
|
bitcoinNetwork,
|
|
99
|
+
sender,
|
|
97
100
|
);
|
|
98
101
|
|
|
99
102
|
registryCache.set(cacheKey, contract);
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction helper for OPNet CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for building and executing registry transactions.
|
|
5
|
+
*
|
|
6
|
+
* @module lib/transaction
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Address } from '@btc-vision/transaction';
|
|
10
|
+
import { TransactionParameters } from 'opnet';
|
|
11
|
+
import ora, { Ora } from 'ora';
|
|
12
|
+
|
|
13
|
+
import { CLIWallet, getNetwork } from './wallet.js';
|
|
14
|
+
import { NetworkName } from '../types/index.js';
|
|
15
|
+
import { getProvider } from './provider.js';
|
|
16
|
+
import { PsbtOutputExtended } from '@btc-vision/bitcoin';
|
|
17
|
+
|
|
18
|
+
/** Default maximum satoshis to spend per transaction */
|
|
19
|
+
export const DEFAULT_MAX_SAT_TO_SPEND = 100_000n;
|
|
20
|
+
|
|
21
|
+
/** Default fee rate in sat/vbyte */
|
|
22
|
+
export const DEFAULT_FEE_RATE = 6;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build TransactionParameters from a CLIWallet
|
|
26
|
+
*
|
|
27
|
+
* @param wallet - The CLI wallet instance
|
|
28
|
+
* @param network - Target network name
|
|
29
|
+
* @param maxSatToSpend - Maximum satoshis to spend (optional)
|
|
30
|
+
* @param feeRate - Fee rate in sat/vbyte (optional)
|
|
31
|
+
* @param extra
|
|
32
|
+
* @returns TransactionParameters for contract calls
|
|
33
|
+
*/
|
|
34
|
+
export function buildTransactionParams(
|
|
35
|
+
wallet: CLIWallet,
|
|
36
|
+
network: NetworkName,
|
|
37
|
+
maxSatToSpend: bigint = DEFAULT_MAX_SAT_TO_SPEND,
|
|
38
|
+
feeRate: number = DEFAULT_FEE_RATE,
|
|
39
|
+
extra?: PsbtOutputExtended,
|
|
40
|
+
): TransactionParameters {
|
|
41
|
+
const bitcoinNetwork = getNetwork(network);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
signer: wallet.keypair,
|
|
45
|
+
mldsaSigner: wallet.mldsaKeypair,
|
|
46
|
+
refundTo: wallet.p2trAddress,
|
|
47
|
+
maximumAllowedSatToSpend: maxSatToSpend,
|
|
48
|
+
network: bitcoinNetwork,
|
|
49
|
+
feeRate,
|
|
50
|
+
extraOutputs: extra ? [extra] : undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the sender Address from a wallet
|
|
56
|
+
*
|
|
57
|
+
* @param wallet - The CLI wallet
|
|
58
|
+
* @returns Address instance for the wallet
|
|
59
|
+
*/
|
|
60
|
+
export function getWalletAddress(wallet: CLIWallet): Address {
|
|
61
|
+
return wallet.address;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format satoshis for display
|
|
66
|
+
*
|
|
67
|
+
* @param sats - Amount in satoshis
|
|
68
|
+
* @returns Formatted string with BTC equivalent
|
|
69
|
+
*/
|
|
70
|
+
export function formatSats(sats: bigint): string {
|
|
71
|
+
const btc = Number(sats) / 100_000_000;
|
|
72
|
+
if (btc >= 0.001) {
|
|
73
|
+
return `${sats} sats (${btc.toFixed(8)} BTC)`;
|
|
74
|
+
}
|
|
75
|
+
return `${sats} sats`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if wallet has sufficient balance for transaction
|
|
80
|
+
*
|
|
81
|
+
* @param wallet - The CLI wallet
|
|
82
|
+
* @param network - Target network
|
|
83
|
+
* @param minBalance - Minimum required balance in satoshis
|
|
84
|
+
* @returns True if balance is sufficient
|
|
85
|
+
*/
|
|
86
|
+
export async function checkBalance(
|
|
87
|
+
wallet: CLIWallet,
|
|
88
|
+
network: NetworkName,
|
|
89
|
+
minBalance: bigint = 10_000n,
|
|
90
|
+
): Promise<{ sufficient: boolean; balance: bigint }> {
|
|
91
|
+
const provider = getProvider(network);
|
|
92
|
+
const balance = await provider.getBalance(wallet.p2trAddress);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
sufficient: balance >= minBalance,
|
|
96
|
+
balance,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Default polling interval in milliseconds */
|
|
101
|
+
export const DEFAULT_POLL_INTERVAL = 10_000;
|
|
102
|
+
|
|
103
|
+
/** Default maximum wait time in milliseconds (10 minutes) */
|
|
104
|
+
export const DEFAULT_MAX_WAIT_TIME = 600_000;
|
|
105
|
+
|
|
106
|
+
export interface TransactionConfirmationResult {
|
|
107
|
+
confirmed: boolean;
|
|
108
|
+
blockNumber?: bigint;
|
|
109
|
+
revert?: string;
|
|
110
|
+
error?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Wait for a transaction to be confirmed on-chain
|
|
115
|
+
*
|
|
116
|
+
* Polls the transaction status every pollInterval ms until:
|
|
117
|
+
* - The transaction is confirmed (has a blockNumber)
|
|
118
|
+
* - The transaction fails (has a revert)
|
|
119
|
+
* - The maximum wait time is exceeded
|
|
120
|
+
*
|
|
121
|
+
* @param txHash - The transaction hash to wait for
|
|
122
|
+
* @param network - Target network
|
|
123
|
+
* @param options - Optional configuration
|
|
124
|
+
* @returns Transaction confirmation result
|
|
125
|
+
*/
|
|
126
|
+
export async function waitForTransactionConfirmation(
|
|
127
|
+
txHash: string,
|
|
128
|
+
network: NetworkName,
|
|
129
|
+
options?: {
|
|
130
|
+
pollInterval?: number;
|
|
131
|
+
maxWaitTime?: number;
|
|
132
|
+
message?: string;
|
|
133
|
+
},
|
|
134
|
+
): Promise<TransactionConfirmationResult> {
|
|
135
|
+
const pollInterval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;
|
|
136
|
+
const maxWaitTime = options?.maxWaitTime ?? DEFAULT_MAX_WAIT_TIME;
|
|
137
|
+
const message = options?.message ?? 'Waiting for transaction confirmation';
|
|
138
|
+
|
|
139
|
+
const provider = getProvider(network);
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
|
|
142
|
+
const spinner: Ora = ora({
|
|
143
|
+
text: `${message} (0s elapsed)`,
|
|
144
|
+
spinner: 'dots',
|
|
145
|
+
}).start();
|
|
146
|
+
|
|
147
|
+
const updateSpinnerText = (): void => {
|
|
148
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
149
|
+
spinner.text = `${message} (${elapsed}s elapsed)`;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Update spinner text every second
|
|
153
|
+
const textUpdateInterval = setInterval(updateSpinnerText, 1000);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
157
|
+
try {
|
|
158
|
+
const tx = await provider.getTransaction(txHash);
|
|
159
|
+
|
|
160
|
+
// Check if transaction has a block number (confirmed)
|
|
161
|
+
if (tx.blockNumber !== undefined && tx.blockNumber !== null) {
|
|
162
|
+
const blockNum =
|
|
163
|
+
typeof tx.blockNumber === 'bigint'
|
|
164
|
+
? tx.blockNumber
|
|
165
|
+
: BigInt(tx.blockNumber);
|
|
166
|
+
spinner.succeed(`Transaction confirmed in block ${blockNum}`);
|
|
167
|
+
return {
|
|
168
|
+
confirmed: true,
|
|
169
|
+
blockNumber: blockNum,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if transaction reverted
|
|
174
|
+
if (tx.revert) {
|
|
175
|
+
spinner.fail(`Transaction reverted: ${tx.revert}`);
|
|
176
|
+
return {
|
|
177
|
+
confirmed: false,
|
|
178
|
+
revert: tx.revert,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// Transaction not found yet, continue polling
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Wait before next poll
|
|
186
|
+
await sleep(pollInterval);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Timeout reached
|
|
190
|
+
spinner.fail(`Timeout waiting for transaction confirmation`);
|
|
191
|
+
return {
|
|
192
|
+
confirmed: false,
|
|
193
|
+
error: `Transaction not confirmed within ${maxWaitTime / 1000} seconds`,
|
|
194
|
+
};
|
|
195
|
+
} finally {
|
|
196
|
+
clearInterval(textUpdateInterval);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Sleep for a given number of milliseconds
|
|
202
|
+
*/
|
|
203
|
+
function sleep(ms: number): Promise<void> {
|
|
204
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
205
|
+
}
|
package/src/lib/wallet.ts
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* @module lib/wallet
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { Network, networks } from '@btc-vision/bitcoin';
|
|
10
|
-
import { MLDSASecurityLevel, QuantumBIP32Factory } from '@btc-vision/bip32';
|
|
11
|
-
import { EcKeyPair, MessageSigner, Mnemonic, Wallet } from '@btc-vision/transaction';
|
|
9
|
+
import { Network, networks, Signer } from '@btc-vision/bitcoin';
|
|
10
|
+
import { MLDSASecurityLevel, QuantumBIP32Factory, QuantumBIP32Interface } from '@btc-vision/bip32';
|
|
11
|
+
import { Address, EcKeyPair, MessageSigner, Mnemonic, Wallet } from '@btc-vision/transaction';
|
|
12
12
|
import * as crypto from 'crypto';
|
|
13
13
|
import { CLICredentials, CLIMldsaLevel, NetworkName } from '../types/index.js';
|
|
14
14
|
import { canSign, loadCredentials } from './credentials.js';
|
|
15
|
+
import { ECPairInterface } from 'ecpair';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Convert CLI network name to bitcoin-js Network object
|
|
@@ -55,6 +56,10 @@ export class CLIWallet {
|
|
|
55
56
|
this.mldsaLevel = mldsaLevel;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
get address(): Address {
|
|
60
|
+
return this.wallet.address;
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
/**
|
|
59
64
|
* Get the P2TR (taproot) address
|
|
60
65
|
*/
|
|
@@ -62,24 +67,17 @@ export class CLIWallet {
|
|
|
62
67
|
return this.wallet.p2tr;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
/**
|
|
66
|
-
* Get the underlying Wallet instance
|
|
67
|
-
*/
|
|
68
|
-
get underlyingWallet(): Wallet {
|
|
69
|
-
return this.wallet;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
70
|
/**
|
|
73
71
|
* Get the Bitcoin keypair for classical signing
|
|
74
72
|
*/
|
|
75
|
-
get keypair():
|
|
73
|
+
get keypair(): Signer | ECPairInterface | null {
|
|
76
74
|
return this.wallet.keypair;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
/**
|
|
80
78
|
* Get the MLDSA keypair for quantum-resistant signing
|
|
81
79
|
*/
|
|
82
|
-
get mldsaKeypair():
|
|
80
|
+
get mldsaKeypair(): QuantumBIP32Interface {
|
|
83
81
|
return this.wallet.mldsaKeypair;
|
|
84
82
|
}
|
|
85
83
|
|
|
@@ -105,13 +103,6 @@ export class CLIWallet {
|
|
|
105
103
|
return this.mldsaLevel;
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
/**
|
|
109
|
-
* Get the network this wallet is configured for
|
|
110
|
-
*/
|
|
111
|
-
get walletNetwork(): Network {
|
|
112
|
-
return this.network;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
106
|
/**
|
|
116
107
|
* Create a wallet from credentials
|
|
117
108
|
*
|
package/src/types/index.ts
CHANGED
|
@@ -32,8 +32,10 @@ export interface CLIConfig {
|
|
|
32
32
|
ipfsGateways: string[];
|
|
33
33
|
/** IPFS pinning service endpoint */
|
|
34
34
|
ipfsPinningEndpoint: string;
|
|
35
|
-
/** IPFS pinning API key */
|
|
35
|
+
/** IPFS pinning API key (or JWT token) */
|
|
36
36
|
ipfsPinningApiKey: string;
|
|
37
|
+
/** IPFS pinning API secret (for services requiring key+secret) */
|
|
38
|
+
ipfsPinningSecret: string;
|
|
37
39
|
/** Authorization header name for pinning service */
|
|
38
40
|
ipfsPinningAuthHeader: string;
|
|
39
41
|
/** Registry contract addresses per network */
|