@bsv/sdk 1.9.3 → 1.9.4
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/dist/cjs/package.json +1 -1
- package/docs/fast-docs.png +0 -0
- package/docs/index.md +49 -44
- package/docs/swagger.png +0 -0
- package/package.json +1 -1
- package/docs/MARKDOWN_VALIDATION_GUIDE.md +0 -175
- package/docs/concepts/beef.md +0 -92
- package/docs/concepts/chain-tracking.md +0 -134
- package/docs/concepts/decentralized-identity.md +0 -221
- package/docs/concepts/fees.md +0 -249
- package/docs/concepts/identity-certificates.md +0 -307
- package/docs/concepts/index.md +0 -77
- package/docs/concepts/key-management.md +0 -185
- package/docs/concepts/script-templates.md +0 -176
- package/docs/concepts/sdk-philosophy.md +0 -80
- package/docs/concepts/signatures.md +0 -194
- package/docs/concepts/spv-verification.md +0 -118
- package/docs/concepts/transaction-encoding.md +0 -167
- package/docs/concepts/transaction-structure.md +0 -67
- package/docs/concepts/trust-model.md +0 -139
- package/docs/concepts/verification.md +0 -250
- package/docs/concepts/wallet-integration.md +0 -101
- package/docs/guides/development-wallet-setup.md +0 -374
- package/docs/guides/direct-transaction-creation.md +0 -147
- package/docs/guides/http-client-configuration.md +0 -488
- package/docs/guides/index.md +0 -138
- package/docs/guides/large-transactions.md +0 -448
- package/docs/guides/multisig-transactions.md +0 -792
- package/docs/guides/security-best-practices.md +0 -494
- package/docs/guides/transaction-batching.md +0 -132
- package/docs/guides/transaction-signing-methods.md +0 -419
- package/docs/reference/arc-config.md +0 -698
- package/docs/reference/brc-100.md +0 -33
- package/docs/reference/configuration.md +0 -835
- package/docs/reference/debugging.md +0 -705
- package/docs/reference/errors.md +0 -597
- package/docs/reference/index.md +0 -111
- package/docs/reference/network-config.md +0 -914
- package/docs/reference/op-codes.md +0 -325
- package/docs/reference/transaction-signatures.md +0 -95
- package/docs/tutorials/advanced-transaction.md +0 -572
- package/docs/tutorials/aes-encryption.md +0 -949
- package/docs/tutorials/authfetch-tutorial.md +0 -986
- package/docs/tutorials/ecdh-key-exchange.md +0 -549
- package/docs/tutorials/elliptic-curve-fundamentals.md +0 -606
- package/docs/tutorials/error-handling.md +0 -1216
- package/docs/tutorials/first-transaction-low-level.md +0 -205
- package/docs/tutorials/first-transaction.md +0 -275
- package/docs/tutorials/hashes-and-hmacs.md +0 -788
- package/docs/tutorials/identity-management.md +0 -729
- package/docs/tutorials/index.md +0 -219
- package/docs/tutorials/key-management.md +0 -538
- package/docs/tutorials/protowallet-development.md +0 -743
- package/docs/tutorials/script-construction.md +0 -690
- package/docs/tutorials/spv-merkle-proofs.md +0 -685
- package/docs/tutorials/testnet-transactions-low-level.md +0 -359
- package/docs/tutorials/transaction-broadcasting.md +0 -538
- package/docs/tutorials/transaction-types.md +0 -420
- package/docs/tutorials/type-42.md +0 -568
- package/docs/tutorials/uhrp-storage.md +0 -599
|
@@ -1,1216 +0,0 @@
|
|
|
1
|
-
# Error Handling and Edge Cases
|
|
2
|
-
|
|
3
|
-
This tutorial covers robust error handling patterns and edge case management when working with the BSV TypeScript SDK. You'll learn to build resilient applications that gracefully handle network failures, wallet issues, validation errors, and other common scenarios.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
- Basic familiarity with the BSV TypeScript SDK
|
|
8
|
-
- Understanding of WalletClient usage
|
|
9
|
-
- Knowledge of async/await and Promise handling
|
|
10
|
-
- Basic TypeScript/JavaScript error handling concepts
|
|
11
|
-
|
|
12
|
-
## Learning Goals
|
|
13
|
-
|
|
14
|
-
By the end of this tutorial, you will:
|
|
15
|
-
|
|
16
|
-
- Understand common error types in the BSV ecosystem
|
|
17
|
-
- Implement robust retry mechanisms and recovery strategies
|
|
18
|
-
- Handle wallet-specific errors and edge cases
|
|
19
|
-
- Build production-ready error handling patterns
|
|
20
|
-
- Debug and troubleshoot common SDK issues
|
|
21
|
-
- Create user-friendly error reporting systems
|
|
22
|
-
|
|
23
|
-
## Error Types and Categories
|
|
24
|
-
|
|
25
|
-
### WalletErrorObject Interface
|
|
26
|
-
|
|
27
|
-
The SDK uses a standardized error interface for wallet operations:
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { WalletClient, WalletErrorObject } from '@bsv/sdk'
|
|
31
|
-
|
|
32
|
-
function isWalletError(error: any): error is WalletErrorObject {
|
|
33
|
-
return error && typeof error === 'object' && error.isError === true
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function handleWalletOperation() {
|
|
37
|
-
try {
|
|
38
|
-
const wallet = new WalletClient('auto', 'localhost')
|
|
39
|
-
const result = await wallet.createAction({
|
|
40
|
-
description: 'Test transaction',
|
|
41
|
-
outputs: [{
|
|
42
|
-
satoshis: 100,
|
|
43
|
-
lockingScript: '006a0474657374', // "test"
|
|
44
|
-
outputDescription: 'Test output'
|
|
45
|
-
}]
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
console.log('Transaction successful:', result.txid)
|
|
49
|
-
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (isWalletError(error)) {
|
|
52
|
-
console.error('Wallet error occurred:', error.message)
|
|
53
|
-
// Handle wallet-specific error
|
|
54
|
-
} else {
|
|
55
|
-
console.error('General error:', error)
|
|
56
|
-
// Handle other types of errors
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Common Error Categories
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
enum ErrorCategory {
|
|
66
|
-
NETWORK = 'network',
|
|
67
|
-
WALLET = 'wallet',
|
|
68
|
-
VALIDATION = 'validation',
|
|
69
|
-
AUTHENTICATION = 'authentication',
|
|
70
|
-
INSUFFICIENT_FUNDS = 'insufficient_funds',
|
|
71
|
-
SCRIPT = 'script',
|
|
72
|
-
CRYPTOGRAPHIC = 'cryptographic'
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface CategorizedError {
|
|
76
|
-
category: ErrorCategory
|
|
77
|
-
message: string
|
|
78
|
-
originalError: Error
|
|
79
|
-
retryable: boolean
|
|
80
|
-
suggestedAction?: string
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function categorizeError(error: Error): CategorizedError {
|
|
84
|
-
const message = error.message.toLowerCase()
|
|
85
|
-
|
|
86
|
-
if (message.includes('insufficient funds')) {
|
|
87
|
-
return {
|
|
88
|
-
category: ErrorCategory.INSUFFICIENT_FUNDS,
|
|
89
|
-
message: error.message,
|
|
90
|
-
originalError: error,
|
|
91
|
-
retryable: true,
|
|
92
|
-
suggestedAction: 'Check wallet balance and available UTXOs'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (message.includes('rpc error') || message.includes('no header should have returned false')) {
|
|
97
|
-
return {
|
|
98
|
-
category: ErrorCategory.NETWORK,
|
|
99
|
-
message: error.message,
|
|
100
|
-
originalError: error,
|
|
101
|
-
retryable: true,
|
|
102
|
-
suggestedAction: 'Wait for wallet synchronization or restart wallet'
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (message.includes('not authenticated') || message.includes('authentication')) {
|
|
107
|
-
return {
|
|
108
|
-
category: ErrorCategory.AUTHENTICATION,
|
|
109
|
-
message: error.message,
|
|
110
|
-
originalError: error,
|
|
111
|
-
retryable: true,
|
|
112
|
-
suggestedAction: 'Re-authenticate with wallet'
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (message.includes('script') || message.includes('validation')) {
|
|
117
|
-
return {
|
|
118
|
-
category: ErrorCategory.VALIDATION,
|
|
119
|
-
message: error.message,
|
|
120
|
-
originalError: error,
|
|
121
|
-
retryable: false,
|
|
122
|
-
suggestedAction: 'Check transaction inputs and script validity'
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
category: ErrorCategory.NETWORK,
|
|
128
|
-
message: error.message,
|
|
129
|
-
originalError: error,
|
|
130
|
-
retryable: false
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Retry Mechanisms and Recovery Strategies
|
|
136
|
-
|
|
137
|
-
### Exponential Backoff Implementation
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
interface RetryOptions {
|
|
141
|
-
maxAttempts: number
|
|
142
|
-
baseDelay: number
|
|
143
|
-
maxDelay: number
|
|
144
|
-
backoffMultiplier: number
|
|
145
|
-
retryableErrors: string[]
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
class RetryManager {
|
|
149
|
-
private defaultOptions: RetryOptions = {
|
|
150
|
-
maxAttempts: 3,
|
|
151
|
-
baseDelay: 1000,
|
|
152
|
-
maxDelay: 30000,
|
|
153
|
-
backoffMultiplier: 2,
|
|
154
|
-
retryableErrors: [
|
|
155
|
-
'insufficient funds',
|
|
156
|
-
'rpc error',
|
|
157
|
-
'no header should have returned false',
|
|
158
|
-
'network error',
|
|
159
|
-
'timeout'
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async executeWithRetry<T>(
|
|
164
|
-
operation: () => Promise<T>,
|
|
165
|
-
options: Partial<RetryOptions> = {}
|
|
166
|
-
): Promise<T> {
|
|
167
|
-
const config = { ...this.defaultOptions, ...options }
|
|
168
|
-
let lastError: Error
|
|
169
|
-
|
|
170
|
-
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
|
|
171
|
-
try {
|
|
172
|
-
console.log(`Attempt ${attempt}/${config.maxAttempts}`)
|
|
173
|
-
return await operation()
|
|
174
|
-
|
|
175
|
-
} catch (error: any) {
|
|
176
|
-
lastError = error
|
|
177
|
-
const categorized = categorizeError(error)
|
|
178
|
-
|
|
179
|
-
console.log(`Attempt ${attempt} failed:`, categorized.message)
|
|
180
|
-
|
|
181
|
-
if (!categorized.retryable || attempt === config.maxAttempts) {
|
|
182
|
-
break
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const isRetryableError = config.retryableErrors.some(
|
|
186
|
-
retryableError => error.message.toLowerCase().includes(retryableError)
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
if (!isRetryableError) {
|
|
190
|
-
console.log('Error is not retryable, stopping attempts')
|
|
191
|
-
break
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Calculate delay with exponential backoff
|
|
195
|
-
const delay = Math.min(
|
|
196
|
-
config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1),
|
|
197
|
-
config.maxDelay
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
console.log(`Waiting ${delay}ms before retry...`)
|
|
201
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
throw new Error(`Operation failed after ${config.maxAttempts} attempts: ${lastError.message}`)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Example usage
|
|
210
|
-
const retryManager = new RetryManager()
|
|
211
|
-
|
|
212
|
-
async function robustTransactionCreation() {
|
|
213
|
-
const wallet = new WalletClient('auto', 'localhost')
|
|
214
|
-
|
|
215
|
-
const result = await retryManager.executeWithRetry(async () => {
|
|
216
|
-
return await wallet.createAction({
|
|
217
|
-
description: 'Robust transaction with retry logic',
|
|
218
|
-
outputs: [{
|
|
219
|
-
satoshis: 100,
|
|
220
|
-
lockingScript: '006a0e526f6275737420746573742074786e', // "Robust test txn"
|
|
221
|
-
outputDescription: 'Retry demo output'
|
|
222
|
-
}]
|
|
223
|
-
})
|
|
224
|
-
}, {
|
|
225
|
-
maxAttempts: 5,
|
|
226
|
-
baseDelay: 2000
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
console.log('Transaction successful:', result.txid)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Example usage
|
|
233
|
-
const walletManager = new WalletManager()
|
|
234
|
-
|
|
235
|
-
async function demonstrateWalletErrorHandling() {
|
|
236
|
-
try {
|
|
237
|
-
const result = await walletManager.safeCreateAction({
|
|
238
|
-
description: 'Wallet error handling demo',
|
|
239
|
-
outputs: [{
|
|
240
|
-
satoshis: 100,
|
|
241
|
-
lockingScript: '006a0f57616c6c657420657272206465616c', // "Wallet err deal"
|
|
242
|
-
outputDescription: 'Error handling demo'
|
|
243
|
-
}]
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
console.log('Transaction created successfully:', result.txid)
|
|
247
|
-
|
|
248
|
-
} catch (error) {
|
|
249
|
-
console.error('Final error after all recovery attempts:', error.message)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
## Network and Chain Tracker Error Handling
|
|
254
|
-
|
|
255
|
-
### HTTP Client Error Management
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import { ChainTracker } from '@bsv/sdk'
|
|
259
|
-
|
|
260
|
-
class RobustChainTracker {
|
|
261
|
-
private tracker: ChainTracker
|
|
262
|
-
private retryManager: RetryManager
|
|
263
|
-
|
|
264
|
-
constructor(baseURL: string = 'https://api.whatsonchain.com/v1/bsv/main') {
|
|
265
|
-
this.tracker = new ChainTracker(baseURL)
|
|
266
|
-
this.retryManager = new RetryManager()
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async safeIsValidRootForHeight(root: string, height: number): Promise<boolean> {
|
|
270
|
-
return this.retryManager.executeWithRetry(async () => {
|
|
271
|
-
try {
|
|
272
|
-
return await this.tracker.isValidRootForHeight(root, height)
|
|
273
|
-
|
|
274
|
-
} catch (error: any) {
|
|
275
|
-
if (error.message.includes('404')) {
|
|
276
|
-
throw new Error(`Block height ${height} not found on chain`)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (error.message.includes('429')) {
|
|
280
|
-
console.log('Rate limited, waiting longer...')
|
|
281
|
-
await new Promise(resolve => setTimeout(resolve, 5000))
|
|
282
|
-
throw error // Will be retried
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (error.message.includes('timeout') || error.message.includes('ECONNRESET')) {
|
|
286
|
-
console.log('Network timeout, retrying...')
|
|
287
|
-
throw error // Will be retried
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// For other HTTP errors, don't retry
|
|
291
|
-
throw new Error(`Chain tracker error: ${error.message}`)
|
|
292
|
-
}
|
|
293
|
-
}, {
|
|
294
|
-
maxAttempts: 5,
|
|
295
|
-
baseDelay: 2000,
|
|
296
|
-
retryableErrors: ['timeout', 'econnreset', '429', 'network error']
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Example with fallback chain trackers
|
|
302
|
-
class FallbackChainTracker {
|
|
303
|
-
private trackers: RobustChainTracker[]
|
|
304
|
-
private currentTrackerIndex: number = 0
|
|
305
|
-
|
|
306
|
-
constructor() {
|
|
307
|
-
this.trackers = [
|
|
308
|
-
new RobustChainTracker('https://api.whatsonchain.com/v1/bsv/main'),
|
|
309
|
-
new RobustChainTracker('https://api.taal.com/api/v1/bsv/main'),
|
|
310
|
-
// Add more fallback endpoints as needed
|
|
311
|
-
]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async isValidRootForHeight(root: string, height: number): Promise<boolean> {
|
|
315
|
-
for (let i = 0; i < this.trackers.length; i++) {
|
|
316
|
-
const trackerIndex = (this.currentTrackerIndex + i) % this.trackers.length
|
|
317
|
-
const tracker = this.trackers[trackerIndex]
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
console.log(`Trying chain tracker ${trackerIndex + 1}/${this.trackers.length}`)
|
|
321
|
-
const result = await tracker.safeIsValidRootForHeight(root, height)
|
|
322
|
-
|
|
323
|
-
// Success - update current tracker for next time
|
|
324
|
-
this.currentTrackerIndex = trackerIndex
|
|
325
|
-
return result
|
|
326
|
-
|
|
327
|
-
} catch (error: any) {
|
|
328
|
-
console.log(`Chain tracker ${trackerIndex + 1} failed:`, error.message)
|
|
329
|
-
|
|
330
|
-
if (i === this.trackers.length - 1) {
|
|
331
|
-
throw new Error(`All chain trackers failed. Last error: ${error.message}`)
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
throw new Error('No chain trackers available')
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### Practical Retry Implementation
|
|
342
|
-
|
|
343
|
-
Here's a practical example that handles common wallet errors with specific recovery strategies:
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
import { WalletClient } from '@bsv/sdk'
|
|
347
|
-
|
|
348
|
-
async function robustTransactionCreation() {
|
|
349
|
-
try {
|
|
350
|
-
const wallet = new WalletClient('auto', 'localhost')
|
|
351
|
-
|
|
352
|
-
const { authenticated } = await wallet.isAuthenticated()
|
|
353
|
-
if (!authenticated) {
|
|
354
|
-
await wallet.waitForAuthentication()
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Implement retry logic for common wallet issues
|
|
358
|
-
async function createActionWithRetry(args: any, maxRetries = 3) {
|
|
359
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
360
|
-
try {
|
|
361
|
-
console.log(`Transaction attempt ${attempt}/${maxRetries}`)
|
|
362
|
-
|
|
363
|
-
const result = await wallet.createAction(args)
|
|
364
|
-
console.log('Transaction successful!')
|
|
365
|
-
return result
|
|
366
|
-
|
|
367
|
-
} catch (error: any) {
|
|
368
|
-
console.log(`Attempt ${attempt} failed:`, error.message)
|
|
369
|
-
|
|
370
|
-
if (error.message?.includes('Insufficient funds')) {
|
|
371
|
-
console.log('Checking available UTXOs...')
|
|
372
|
-
|
|
373
|
-
const { outputs } = await wallet.listOutputs({
|
|
374
|
-
basket: 'tutorial',
|
|
375
|
-
limit: 20
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
const spendableValue = outputs
|
|
379
|
-
.filter(o => o.spendable)
|
|
380
|
-
.reduce((sum, o) => sum + o.satoshis, 0)
|
|
381
|
-
|
|
382
|
-
console.log(`Total spendable: ${spendableValue} satoshis`)
|
|
383
|
-
|
|
384
|
-
if (spendableValue < 500) {
|
|
385
|
-
throw new Error('Insufficient confirmed funds available')
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Try with a smaller amount
|
|
389
|
-
if (args.outputs && args.outputs[0]) {
|
|
390
|
-
args.outputs[0].satoshis = Math.min(100, spendableValue - 50)
|
|
391
|
-
console.log(`Retrying with reduced amount: ${args.outputs[0].satoshis} satoshis`)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
} else if (error.message?.includes('no header should have returned false')) {
|
|
395
|
-
console.log('Wallet synchronization issue detected')
|
|
396
|
-
console.log('Waiting for wallet to sync...')
|
|
397
|
-
await new Promise(resolve => setTimeout(resolve, 5000))
|
|
398
|
-
|
|
399
|
-
} else if (error.message?.includes('RPC Error')) {
|
|
400
|
-
console.log('RPC communication error, retrying...')
|
|
401
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
402
|
-
|
|
403
|
-
} else {
|
|
404
|
-
// For other errors, don't retry
|
|
405
|
-
throw error
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (attempt === maxRetries) {
|
|
409
|
-
throw new Error(`Transaction failed after ${maxRetries} attempts: ${error.message}`)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Use the robust transaction creation
|
|
416
|
-
const result = await createActionWithRetry({
|
|
417
|
-
description: 'Robust transaction with error handling',
|
|
418
|
-
outputs: [
|
|
419
|
-
{
|
|
420
|
-
satoshis: 100,
|
|
421
|
-
lockingScript: '006a0e526f6275737420746573742074786e', // "Robust test txn"
|
|
422
|
-
outputDescription: 'Error handling demo'
|
|
423
|
-
}
|
|
424
|
-
]
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
if (result && result.txid) {
|
|
428
|
-
console.log(`Robust transaction created: ${result.txid}`)
|
|
429
|
-
} else {
|
|
430
|
-
console.log('Transaction completed but no TXID returned')
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
} catch (error: unknown) {
|
|
434
|
-
console.error('Final error:', error)
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
robustTransactionCreation().catch(console.error)
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
This example demonstrates:
|
|
442
|
-
|
|
443
|
-
- **Specific error pattern matching** for different error types
|
|
444
|
-
- **Dynamic amount adjustment** for insufficient funds
|
|
445
|
-
- **Wallet synchronization handling** with appropriate delays
|
|
446
|
-
- **RPC error recovery** with retry logic
|
|
447
|
-
- **Graceful degradation** when all retries are exhausted
|
|
448
|
-
|
|
449
|
-
## SPV Verification Error Handling
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
import { Transaction, MerklePath } from '@bsv/sdk'
|
|
453
|
-
|
|
454
|
-
class SPVVerificationManager {
|
|
455
|
-
private chainTracker: FallbackChainTracker
|
|
456
|
-
|
|
457
|
-
constructor() {
|
|
458
|
-
this.chainTracker = new FallbackChainTracker()
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async verifyTransactionSPV(
|
|
462
|
-
transaction: Transaction,
|
|
463
|
-
merklePath?: MerklePath,
|
|
464
|
-
chainTracker?: ChainTracker
|
|
465
|
-
): Promise<{ verified: boolean; error?: string }> {
|
|
466
|
-
try {
|
|
467
|
-
// Use provided chain tracker or fallback
|
|
468
|
-
const tracker = chainTracker || this.chainTracker
|
|
469
|
-
|
|
470
|
-
const isValid = await transaction.verify(tracker, merklePath)
|
|
471
|
-
|
|
472
|
-
return { verified: isValid }
|
|
473
|
-
|
|
474
|
-
} catch (error: any) {
|
|
475
|
-
const errorMessage = error.message.toLowerCase()
|
|
476
|
-
|
|
477
|
-
if (errorMessage.includes('missing source transaction')) {
|
|
478
|
-
return {
|
|
479
|
-
verified: false,
|
|
480
|
-
error: 'BEEF structure incomplete - missing input transactions'
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (errorMessage.includes('merkle root')) {
|
|
485
|
-
return {
|
|
486
|
-
verified: false,
|
|
487
|
-
error: 'Merkle proof verification failed - invalid proof or root mismatch'
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (errorMessage.includes('script')) {
|
|
492
|
-
return {
|
|
493
|
-
verified: false,
|
|
494
|
-
error: 'Script validation failed - invalid unlocking script'
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (errorMessage.includes('chain tracker')) {
|
|
499
|
-
return {
|
|
500
|
-
verified: false,
|
|
501
|
-
error: 'Chain tracker unavailable - cannot verify block headers'
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return {
|
|
506
|
-
verified: false,
|
|
507
|
-
error: `SPV verification failed: ${error.message}`
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
async batchVerifyTransactions(
|
|
513
|
-
transactions: Transaction[],
|
|
514
|
-
merkleProofs?: MerklePath[]
|
|
515
|
-
): Promise<Array<{ txid: string; verified: boolean; error?: string }>> {
|
|
516
|
-
const results = []
|
|
517
|
-
|
|
518
|
-
for (let i = 0; i < transactions.length; i++) {
|
|
519
|
-
const tx = transactions[i]
|
|
520
|
-
const proof = merkleProofs?.[i]
|
|
521
|
-
const txid = Buffer.from(tx.id()).toString('hex')
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
console.log(`Verifying transaction ${i + 1}/${transactions.length}: ${txid}`)
|
|
525
|
-
|
|
526
|
-
const result = await this.verifyTransactionSPV(tx, proof)
|
|
527
|
-
results.push({ txid, ...result })
|
|
528
|
-
|
|
529
|
-
// Add small delay to avoid overwhelming the chain tracker
|
|
530
|
-
if (i < transactions.length - 1) {
|
|
531
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
} catch (error: any) {
|
|
535
|
-
results.push({
|
|
536
|
-
txid,
|
|
537
|
-
verified: false,
|
|
538
|
-
error: `Batch verification error: ${error.message}`
|
|
539
|
-
})
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return results
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
## Cryptographic Operation Error Handling
|
|
549
|
-
|
|
550
|
-
### Key Validation and Recovery
|
|
551
|
-
|
|
552
|
-
```typescript
|
|
553
|
-
import { PrivateKey, PublicKey, SymmetricKey } from '@bsv/sdk'
|
|
554
|
-
|
|
555
|
-
class CryptographicErrorHandler {
|
|
556
|
-
|
|
557
|
-
static validatePrivateKey(key: PrivateKey): { valid: boolean; error?: string } {
|
|
558
|
-
try {
|
|
559
|
-
// Test key operations
|
|
560
|
-
const publicKey = key.toPublicKey()
|
|
561
|
-
const wif = key.toWif()
|
|
562
|
-
|
|
563
|
-
// Verify the key can sign and verify
|
|
564
|
-
const testMessage = 'validation test'
|
|
565
|
-
const signature = key.sign(Buffer.from(testMessage, 'utf8'))
|
|
566
|
-
const isValid = publicKey.verify(Buffer.from(testMessage, 'utf8'), signature)
|
|
567
|
-
|
|
568
|
-
if (!isValid) {
|
|
569
|
-
return { valid: false, error: 'Key failed signature verification test' }
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return { valid: true }
|
|
573
|
-
|
|
574
|
-
} catch (error: any) {
|
|
575
|
-
return { valid: false, error: `Key validation failed: ${error.message}` }
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
static validatePublicKey(key: PublicKey): { valid: boolean; error?: string } {
|
|
580
|
-
try {
|
|
581
|
-
// Test key operations
|
|
582
|
-
const point = key.point
|
|
583
|
-
const der = key.toDER()
|
|
584
|
-
|
|
585
|
-
// Verify point is on curve
|
|
586
|
-
if (!point.isOnCurve()) {
|
|
587
|
-
return { valid: false, error: 'Public key point is not on the secp256k1 curve' }
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
return { valid: true }
|
|
591
|
-
|
|
592
|
-
} catch (error: any) {
|
|
593
|
-
return { valid: false, error: `Public key validation failed: ${error.message}` }
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
static safeECDHKeyExchange(
|
|
598
|
-
privateKey: PrivateKey,
|
|
599
|
-
publicKey: PublicKey
|
|
600
|
-
): { success: boolean; sharedSecret?: number[]; error?: string } {
|
|
601
|
-
try {
|
|
602
|
-
// Validate inputs first
|
|
603
|
-
const privateValidation = this.validatePrivateKey(privateKey)
|
|
604
|
-
if (!privateValidation.valid) {
|
|
605
|
-
return { success: false, error: `Invalid private key: ${privateValidation.error}` }
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const publicValidation = this.validatePublicKey(publicKey)
|
|
609
|
-
if (!publicValidation.valid) {
|
|
610
|
-
return { success: false, error: `Invalid public key: ${publicValidation.error}` }
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Perform ECDH
|
|
614
|
-
const sharedSecret = privateKey.deriveSharedSecret(publicKey)
|
|
615
|
-
|
|
616
|
-
// Validate the result
|
|
617
|
-
if (!sharedSecret || sharedSecret.length !== 32) {
|
|
618
|
-
return { success: false, error: 'ECDH produced invalid shared secret' }
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return { success: true, sharedSecret }
|
|
622
|
-
|
|
623
|
-
} catch (error: any) {
|
|
624
|
-
return { success: false, error: `ECDH key exchange failed: ${error.message}` }
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
static safeSymmetricEncryption(
|
|
629
|
-
data: string,
|
|
630
|
-
key?: SymmetricKey
|
|
631
|
-
): { success: boolean; encrypted?: number[]; key?: SymmetricKey; error?: string } {
|
|
632
|
-
try {
|
|
633
|
-
const encryptionKey = key || SymmetricKey.fromRandom()
|
|
634
|
-
|
|
635
|
-
if (!data || data.length === 0) {
|
|
636
|
-
return { success: false, error: 'Data cannot be empty' }
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const encrypted = encryptionKey.encrypt(data) as number[]
|
|
640
|
-
|
|
641
|
-
if (!encrypted || encrypted.length === 0) {
|
|
642
|
-
return { success: false, error: 'Encryption produced empty result' }
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Verify we can decrypt
|
|
646
|
-
const decrypted = encryptionKey.decrypt(encrypted, 'utf8') as string
|
|
647
|
-
if (decrypted !== data) {
|
|
648
|
-
return { success: false, error: 'Encryption verification failed - decrypt mismatch' }
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
return { success: true, encrypted, key: encryptionKey }
|
|
652
|
-
|
|
653
|
-
} catch (error: any) {
|
|
654
|
-
return { success: false, error: `Symmetric encryption failed: ${error.message}` }
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Example usage with comprehensive error handling
|
|
660
|
-
async function demonstrateCryptographicErrorHandling() {
|
|
661
|
-
console.log('=== Cryptographic Error Handling Demo ===')
|
|
662
|
-
|
|
663
|
-
try {
|
|
664
|
-
// Test private key validation
|
|
665
|
-
const privateKey = PrivateKey.fromRandom()
|
|
666
|
-
const validation = CryptographicErrorHandler.validatePrivateKey(privateKey)
|
|
667
|
-
|
|
668
|
-
if (!validation.valid) {
|
|
669
|
-
console.error('Private key validation failed:', validation.error)
|
|
670
|
-
return
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
console.log('Private key validation: PASSED')
|
|
674
|
-
|
|
675
|
-
// Test ECDH with error handling
|
|
676
|
-
const otherPrivateKey = PrivateKey.fromRandom()
|
|
677
|
-
const otherPublicKey = otherPrivateKey.toPublicKey()
|
|
678
|
-
|
|
679
|
-
const ecdhResult = CryptographicErrorHandler.safeECDHKeyExchange(privateKey, otherPublicKey)
|
|
680
|
-
|
|
681
|
-
if (!ecdhResult.success) {
|
|
682
|
-
console.error('ECDH failed:', ecdhResult.error)
|
|
683
|
-
return
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
console.log('ECDH key exchange: PASSED')
|
|
687
|
-
|
|
688
|
-
// Test symmetric encryption with error handling
|
|
689
|
-
const testData = 'Sensitive data for encryption testing'
|
|
690
|
-
const encryptionResult = CryptographicErrorHandler.safeSymmetricEncryption(testData)
|
|
691
|
-
|
|
692
|
-
if (!encryptionResult.success) {
|
|
693
|
-
console.error('Symmetric encryption failed:', encryptionResult.error)
|
|
694
|
-
return
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
console.log('Symmetric encryption: PASSED')
|
|
698
|
-
console.log('All cryptographic operations completed successfully')
|
|
699
|
-
|
|
700
|
-
} catch (error) {
|
|
701
|
-
console.error('Unexpected error in cryptographic demo:', error)
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
## Production Error Monitoring
|
|
707
|
-
|
|
708
|
-
### Comprehensive Error Logging
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
interface ErrorLogEntry {
|
|
712
|
-
timestamp: Date
|
|
713
|
-
category: ErrorCategory
|
|
714
|
-
operation: string
|
|
715
|
-
message: string
|
|
716
|
-
stack?: string
|
|
717
|
-
context?: any
|
|
718
|
-
retryCount?: number
|
|
719
|
-
resolved?: boolean
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
class ErrorLogger {
|
|
723
|
-
private logs: ErrorLogEntry[] = []
|
|
724
|
-
private maxLogs: number = 1000
|
|
725
|
-
|
|
726
|
-
log(
|
|
727
|
-
category: ErrorCategory,
|
|
728
|
-
operation: string,
|
|
729
|
-
error: Error,
|
|
730
|
-
context?: any,
|
|
731
|
-
retryCount?: number
|
|
732
|
-
): void {
|
|
733
|
-
const entry: ErrorLogEntry = {
|
|
734
|
-
timestamp: new Date(),
|
|
735
|
-
category,
|
|
736
|
-
operation,
|
|
737
|
-
message: error.message,
|
|
738
|
-
stack: error.stack,
|
|
739
|
-
context,
|
|
740
|
-
retryCount,
|
|
741
|
-
resolved: false
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
this.logs.push(entry)
|
|
745
|
-
|
|
746
|
-
// Keep only recent logs
|
|
747
|
-
if (this.logs.length > this.maxLogs) {
|
|
748
|
-
this.logs = this.logs.slice(-this.maxLogs)
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// Console output for development
|
|
752
|
-
console.error(`[${category.toUpperCase()}] ${operation}: ${error.message}`)
|
|
753
|
-
|
|
754
|
-
if (context) {
|
|
755
|
-
console.error('Context:', context)
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
markResolved(operation: string, timestamp: Date): void {
|
|
760
|
-
const entry = this.logs.find(
|
|
761
|
-
log => log.operation === operation &&
|
|
762
|
-
Math.abs(log.timestamp.getTime() - timestamp.getTime()) < 1000
|
|
763
|
-
)
|
|
764
|
-
|
|
765
|
-
if (entry) {
|
|
766
|
-
entry.resolved = true
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
getErrorSummary(): { [key in ErrorCategory]: number } {
|
|
771
|
-
const summary = {} as { [key in ErrorCategory]: number }
|
|
772
|
-
|
|
773
|
-
for (const category of Object.values(ErrorCategory)) {
|
|
774
|
-
summary[category] = this.logs.filter(
|
|
775
|
-
log => log.category === category && !log.resolved
|
|
776
|
-
).length
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
return summary
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
getRecentErrors(minutes: number = 60): ErrorLogEntry[] {
|
|
783
|
-
const cutoff = new Date(Date.now() - minutes * 60 * 1000)
|
|
784
|
-
return this.logs.filter(log => log.timestamp > cutoff)
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
exportLogs(): string {
|
|
788
|
-
return JSON.stringify(this.logs, null, 2)
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Global error logger instance
|
|
793
|
-
const errorLogger = new ErrorLogger()
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
### User-Friendly Error Messages
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
class UserErrorHandler {
|
|
800
|
-
private errorLogger: ErrorLogger
|
|
801
|
-
|
|
802
|
-
constructor() {
|
|
803
|
-
this.errorLogger = errorLogger
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
getUserFriendlyMessage(error: Error, operation: string): string {
|
|
807
|
-
const categorized = categorizeError(error)
|
|
808
|
-
this.errorLogger.log(categorized.category, operation, error)
|
|
809
|
-
|
|
810
|
-
switch (categorized.category) {
|
|
811
|
-
case ErrorCategory.INSUFFICIENT_FUNDS:
|
|
812
|
-
return 'Insufficient funds available. Please check your wallet balance and try again with a smaller amount.'
|
|
813
|
-
|
|
814
|
-
case ErrorCategory.AUTHENTICATION:
|
|
815
|
-
return 'Wallet authentication required. Please unlock your wallet and try again.'
|
|
816
|
-
|
|
817
|
-
case ErrorCategory.NETWORK:
|
|
818
|
-
if (error.message.includes('no header should have returned false')) {
|
|
819
|
-
return 'Wallet is synchronizing with the network. Please wait a moment and try again.'
|
|
820
|
-
}
|
|
821
|
-
return 'Network connection issue. Please check your internet connection and try again.'
|
|
822
|
-
|
|
823
|
-
case ErrorCategory.VALIDATION:
|
|
824
|
-
return 'Transaction validation failed. Please check your transaction details and try again.'
|
|
825
|
-
|
|
826
|
-
case ErrorCategory.SCRIPT:
|
|
827
|
-
return 'Transaction script error. Please verify your transaction parameters.'
|
|
828
|
-
|
|
829
|
-
case ErrorCategory.CRYPTOGRAPHIC:
|
|
830
|
-
return 'Cryptographic operation failed. Please try again or contact support if the issue persists.'
|
|
831
|
-
|
|
832
|
-
default:
|
|
833
|
-
return 'An unexpected error occurred. Please try again or contact support if the issue persists.'
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
async handleOperationWithUserFeedback<T>(
|
|
838
|
-
operation: () => Promise<T>,
|
|
839
|
-
operationName: string,
|
|
840
|
-
onProgress?: (message: string) => void,
|
|
841
|
-
onError?: (userMessage: string, technicalError: Error) => void
|
|
842
|
-
): Promise<T> {
|
|
843
|
-
const retryManager = new RetryManager()
|
|
844
|
-
|
|
845
|
-
try {
|
|
846
|
-
return await retryManager.executeWithRetry(async () => {
|
|
847
|
-
if (onProgress) {
|
|
848
|
-
onProgress(`Executing ${operationName}...`)
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
return await operation()
|
|
852
|
-
|
|
853
|
-
}, {
|
|
854
|
-
maxAttempts: 3,
|
|
855
|
-
baseDelay: 2000
|
|
856
|
-
})
|
|
857
|
-
|
|
858
|
-
} catch (error: any) {
|
|
859
|
-
const userMessage = this.getUserFriendlyMessage(error, operationName)
|
|
860
|
-
|
|
861
|
-
if (onError) {
|
|
862
|
-
onError(userMessage, error)
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
throw new Error(userMessage)
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Example usage in a user interface
|
|
871
|
-
async function demonstrateUserErrorHandling() {
|
|
872
|
-
const userErrorHandler = new UserErrorHandler()
|
|
873
|
-
const wallet = new WalletClient('auto', 'localhost')
|
|
874
|
-
|
|
875
|
-
try {
|
|
876
|
-
await userErrorHandler.handleOperationWithUserFeedback(
|
|
877
|
-
async () => {
|
|
878
|
-
return await wallet.createAction({
|
|
879
|
-
description: 'User-friendly error handling demo',
|
|
880
|
-
outputs: [{
|
|
881
|
-
satoshis: 100,
|
|
882
|
-
lockingScript: '006a0f5573657220667269656e646c79', // "User friendly"
|
|
883
|
-
outputDescription: 'User error demo'
|
|
884
|
-
}]
|
|
885
|
-
})
|
|
886
|
-
},
|
|
887
|
-
'Create Transaction',
|
|
888
|
-
(message) => console.log('Progress:', message),
|
|
889
|
-
(userMessage, technicalError) => {
|
|
890
|
-
console.log('User Message:', userMessage)
|
|
891
|
-
console.log('Technical Details:', technicalError.message)
|
|
892
|
-
}
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
console.log('Transaction completed successfully!')
|
|
896
|
-
|
|
897
|
-
} catch (error) {
|
|
898
|
-
console.log('Operation failed with user-friendly error:', error.message)
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
## Testing Error Scenarios
|
|
904
|
-
|
|
905
|
-
### Error Simulation for Testing
|
|
906
|
-
|
|
907
|
-
```typescript
|
|
908
|
-
class ErrorSimulator {
|
|
909
|
-
private shouldSimulateError: boolean = false
|
|
910
|
-
private errorType: ErrorCategory = ErrorCategory.NETWORK
|
|
911
|
-
private errorMessage: string = 'Simulated error'
|
|
912
|
-
|
|
913
|
-
enableErrorSimulation(type: ErrorCategory, message: string): void {
|
|
914
|
-
this.shouldSimulateError = true
|
|
915
|
-
this.errorType = type
|
|
916
|
-
this.errorMessage = message
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
disableErrorSimulation(): void {
|
|
920
|
-
this.shouldSimulateError = false
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
checkAndThrowSimulatedError(operation: string): void {
|
|
924
|
-
if (this.shouldSimulateError) {
|
|
925
|
-
console.log(`Simulating ${this.errorType} error for operation: ${operation}`)
|
|
926
|
-
throw new Error(this.errorMessage)
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
async simulateNetworkDelay(minMs: number = 100, maxMs: number = 1000): Promise<void> {
|
|
931
|
-
const delay = Math.random() * (maxMs - minMs) + minMs
|
|
932
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Test suite for error handling
|
|
937
|
-
class ErrorHandlingTestSuite {
|
|
938
|
-
private simulator: ErrorSimulator
|
|
939
|
-
private userErrorHandler: UserErrorHandler
|
|
940
|
-
|
|
941
|
-
constructor() {
|
|
942
|
-
this.simulator = new ErrorSimulator()
|
|
943
|
-
this.userErrorHandler = new UserErrorHandler()
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
async testInsufficientFundsHandling(): Promise<boolean> {
|
|
947
|
-
console.log('Testing insufficient funds error handling...')
|
|
948
|
-
|
|
949
|
-
this.simulator.enableErrorSimulation(
|
|
950
|
-
ErrorCategory.INSUFFICIENT_FUNDS,
|
|
951
|
-
'Insufficient funds: 101 more satoshis are needed'
|
|
952
|
-
)
|
|
953
|
-
|
|
954
|
-
try {
|
|
955
|
-
await this.userErrorHandler.handleOperationWithUserFeedback(
|
|
956
|
-
async () => {
|
|
957
|
-
this.simulator.checkAndThrowSimulatedError('createAction')
|
|
958
|
-
return { txid: 'simulated-success' }
|
|
959
|
-
},
|
|
960
|
-
'Test Transaction'
|
|
961
|
-
)
|
|
962
|
-
|
|
963
|
-
return false // Should have thrown
|
|
964
|
-
|
|
965
|
-
} catch (error: any) {
|
|
966
|
-
const isCorrectMessage = error.message.includes('Insufficient funds available')
|
|
967
|
-
console.log('Insufficient funds test:', isCorrectMessage ? 'PASSED' : 'FAILED')
|
|
968
|
-
return isCorrectMessage
|
|
969
|
-
|
|
970
|
-
} finally {
|
|
971
|
-
this.simulator.disableErrorSimulation()
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
async testNetworkErrorHandling(): Promise<boolean> {
|
|
976
|
-
console.log('Testing network error handling...')
|
|
977
|
-
|
|
978
|
-
this.simulator.enableErrorSimulation(
|
|
979
|
-
ErrorCategory.NETWORK,
|
|
980
|
-
'RPC Error: no header should have returned false'
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
try {
|
|
984
|
-
await this.userErrorHandler.handleOperationWithUserFeedback(
|
|
985
|
-
async () => {
|
|
986
|
-
this.simulator.checkAndThrowSimulatedError('createAction')
|
|
987
|
-
return { txid: 'simulated-success' }
|
|
988
|
-
},
|
|
989
|
-
'Test Transaction'
|
|
990
|
-
)
|
|
991
|
-
|
|
992
|
-
return false // Should have thrown
|
|
993
|
-
|
|
994
|
-
} catch (error: any) {
|
|
995
|
-
const isCorrectMessage = error.message.includes('synchronizing with the network')
|
|
996
|
-
console.log('Network error test:', isCorrectMessage ? 'PASSED' : 'FAILED')
|
|
997
|
-
return isCorrectMessage
|
|
998
|
-
|
|
999
|
-
} finally {
|
|
1000
|
-
this.simulator.disableErrorSimulation()
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
async runAllTests(): Promise<void> {
|
|
1005
|
-
console.log('=== Error Handling Test Suite ===')
|
|
1006
|
-
|
|
1007
|
-
const tests = [
|
|
1008
|
-
this.testInsufficientFundsHandling(),
|
|
1009
|
-
this.testNetworkErrorHandling()
|
|
1010
|
-
]
|
|
1011
|
-
|
|
1012
|
-
const results = await Promise.all(tests)
|
|
1013
|
-
const passedTests = results.filter(result => result).length
|
|
1014
|
-
|
|
1015
|
-
console.log(`\nTest Results: ${passedTests}/${results.length} tests passed`)
|
|
1016
|
-
|
|
1017
|
-
if (passedTests === results.length) {
|
|
1018
|
-
console.log('All error handling tests PASSED! ✅')
|
|
1019
|
-
} else {
|
|
1020
|
-
console.log('Some error handling tests FAILED! ❌')
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
## Best Practices Summary
|
|
1027
|
-
|
|
1028
|
-
### Production-Ready Error Handling Checklist
|
|
1029
|
-
|
|
1030
|
-
```typescript
|
|
1031
|
-
// Production error handling implementation example
|
|
1032
|
-
class ProductionWalletService {
|
|
1033
|
-
private wallet: WalletClient
|
|
1034
|
-
private retryManager: RetryManager
|
|
1035
|
-
private errorLogger: ErrorLogger
|
|
1036
|
-
private userErrorHandler: UserErrorHandler
|
|
1037
|
-
|
|
1038
|
-
constructor() {
|
|
1039
|
-
this.wallet = new WalletClient('auto', 'localhost')
|
|
1040
|
-
this.retryManager = new RetryManager()
|
|
1041
|
-
this.errorLogger = new ErrorLogger()
|
|
1042
|
-
this.userErrorHandler = new UserErrorHandler()
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
async createTransaction(
|
|
1046
|
-
description: string,
|
|
1047
|
-
outputs: any[],
|
|
1048
|
-
options?: any
|
|
1049
|
-
): Promise<{ success: boolean; txid?: string; userMessage?: string }> {
|
|
1050
|
-
const operation = 'createTransaction'
|
|
1051
|
-
|
|
1052
|
-
try {
|
|
1053
|
-
const result = await this.userErrorHandler.handleOperationWithUserFeedback(
|
|
1054
|
-
async () => {
|
|
1055
|
-
return await this.retryManager.executeWithRetry(async () => {
|
|
1056
|
-
// Ensure authentication
|
|
1057
|
-
const { authenticated } = await this.wallet.isAuthenticated()
|
|
1058
|
-
if (!authenticated) {
|
|
1059
|
-
await this.wallet.waitForAuthentication()
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Create transaction
|
|
1063
|
-
return await this.wallet.createAction({
|
|
1064
|
-
description,
|
|
1065
|
-
outputs,
|
|
1066
|
-
...options
|
|
1067
|
-
})
|
|
1068
|
-
})
|
|
1069
|
-
},
|
|
1070
|
-
operation
|
|
1071
|
-
)
|
|
1072
|
-
|
|
1073
|
-
return {
|
|
1074
|
-
success: true,
|
|
1075
|
-
txid: result.txid
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
} catch (error: any) {
|
|
1079
|
-
const userMessage = this.userErrorHandler.getUserFriendlyMessage(error, operation)
|
|
1080
|
-
|
|
1081
|
-
return {
|
|
1082
|
-
success: false,
|
|
1083
|
-
userMessage
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
getHealthStatus(): {
|
|
1089
|
-
status: 'healthy' | 'degraded' | 'unhealthy'
|
|
1090
|
-
errors: { [key in ErrorCategory]: number }
|
|
1091
|
-
uptime: number
|
|
1092
|
-
} {
|
|
1093
|
-
const errors = this.errorLogger.getErrorSummary()
|
|
1094
|
-
const totalErrors = Object.values(errors).reduce((sum, count) => sum + count, 0)
|
|
1095
|
-
|
|
1096
|
-
let status: 'healthy' | 'degraded' | 'unhealthy'
|
|
1097
|
-
|
|
1098
|
-
if (totalErrors === 0) {
|
|
1099
|
-
status = 'healthy'
|
|
1100
|
-
} else if (totalErrors < 5) {
|
|
1101
|
-
status = 'degraded'
|
|
1102
|
-
} else {
|
|
1103
|
-
status = 'unhealthy'
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
return {
|
|
1107
|
-
status,
|
|
1108
|
-
errors,
|
|
1109
|
-
uptime: process.uptime()
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
## Troubleshooting Common Issues
|
|
1116
|
-
|
|
1117
|
-
### Quick Diagnostic Guide
|
|
1118
|
-
|
|
1119
|
-
```typescript
|
|
1120
|
-
async function diagnoseCommonIssues(): Promise<void> {
|
|
1121
|
-
console.log('=== BSV SDK Diagnostic Tool ===')
|
|
1122
|
-
|
|
1123
|
-
const wallet = new WalletClient('auto', 'localhost')
|
|
1124
|
-
|
|
1125
|
-
// Test 1: Wallet Connection
|
|
1126
|
-
try {
|
|
1127
|
-
console.log('1. Testing wallet connection...')
|
|
1128
|
-
const { authenticated } = await wallet.isAuthenticated()
|
|
1129
|
-
console.log(` Wallet authenticated: ${authenticated}`)
|
|
1130
|
-
|
|
1131
|
-
if (!authenticated) {
|
|
1132
|
-
console.log(' ⚠️ Wallet not authenticated - this may cause transaction failures')
|
|
1133
|
-
} else {
|
|
1134
|
-
console.log(' ✅ Wallet connection OK')
|
|
1135
|
-
}
|
|
1136
|
-
} catch (error) {
|
|
1137
|
-
console.log(` ❌ Wallet connection failed: ${error.message}`)
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// Test 2: UTXO Availability
|
|
1141
|
-
try {
|
|
1142
|
-
console.log('2. Checking UTXO availability...')
|
|
1143
|
-
const { outputs, totalValue } = await wallet.listOutputs({ limit: 10 })
|
|
1144
|
-
const spendable = outputs.filter(o => o.spendable)
|
|
1145
|
-
|
|
1146
|
-
console.log(` Total outputs: ${outputs.length}`)
|
|
1147
|
-
console.log(` Spendable outputs: ${spendable.length}`)
|
|
1148
|
-
console.log(` Total value: ${totalValue} satoshis`)
|
|
1149
|
-
|
|
1150
|
-
if (spendable.length === 0) {
|
|
1151
|
-
console.log(' ⚠️ No spendable UTXOs available')
|
|
1152
|
-
} else if (totalValue < 1000) {
|
|
1153
|
-
console.log(' ⚠️ Low balance - may cause insufficient funds errors')
|
|
1154
|
-
} else {
|
|
1155
|
-
console.log(' ✅ UTXO availability OK')
|
|
1156
|
-
}
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
console.log(` ❌ UTXO check failed: ${error.message}`)
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
// Test 3: Simple Transaction
|
|
1162
|
-
try {
|
|
1163
|
-
console.log('3. Testing simple transaction creation...')
|
|
1164
|
-
const result = await wallet.createAction({
|
|
1165
|
-
description: 'Diagnostic test transaction',
|
|
1166
|
-
outputs: [{
|
|
1167
|
-
satoshis: 100,
|
|
1168
|
-
lockingScript: '006a0a4469616720746573742074786e', // "Diag test txn"
|
|
1169
|
-
outputDescription: 'Diagnostic test'
|
|
1170
|
-
}]
|
|
1171
|
-
})
|
|
1172
|
-
|
|
1173
|
-
console.log(` ✅ Transaction created successfully: ${result.txid}`)
|
|
1174
|
-
|
|
1175
|
-
} catch (error: any) {
|
|
1176
|
-
console.log(` ❌ Transaction creation failed: ${error.message}`)
|
|
1177
|
-
|
|
1178
|
-
if (error.message.includes('Insufficient funds')) {
|
|
1179
|
-
console.log(' 💡 Try reducing the transaction amount or funding your wallet')
|
|
1180
|
-
} else if (error.message.includes('no header should have returned false')) {
|
|
1181
|
-
console.log(' 💡 Try restarting your wallet or waiting for synchronization')
|
|
1182
|
-
} else if (error.message.includes('not authenticated')) {
|
|
1183
|
-
console.log(' 💡 Ensure your wallet is unlocked and authenticated')
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
console.log('\n=== Diagnostic Complete ===')
|
|
1188
|
-
}
|
|
1189
|
-
```
|
|
1190
|
-
|
|
1191
|
-
## Conclusion
|
|
1192
|
-
|
|
1193
|
-
You've now mastered comprehensive error handling for the BSV TypeScript SDK. You can:
|
|
1194
|
-
|
|
1195
|
-
- Categorize and handle different types of errors appropriately
|
|
1196
|
-
- Implement robust retry mechanisms with exponential backoff
|
|
1197
|
-
- Handle wallet-specific errors and authentication issues
|
|
1198
|
-
- Manage network failures and chain tracker problems
|
|
1199
|
-
- Validate cryptographic operations and handle edge cases
|
|
1200
|
-
- Build production-ready error monitoring and logging systems
|
|
1201
|
-
- Create user-friendly error messages and recovery strategies
|
|
1202
|
-
- Test error scenarios systematically
|
|
1203
|
-
|
|
1204
|
-
These patterns enable you to build resilient Bitcoin applications that gracefully handle failures and provide excellent user experiences even when things go wrong.
|
|
1205
|
-
|
|
1206
|
-
## Next Steps
|
|
1207
|
-
|
|
1208
|
-
- Review the [Large Transactions Guide](../guides/large-transactions.md) for efficiency patterns
|
|
1209
|
-
- Check out [Security Best Practices](../guides/security-best-practices.md) for comprehensive security
|
|
1210
|
-
- Explore specific error handling in other tutorials for your use case
|
|
1211
|
-
|
|
1212
|
-
## Additional Resources
|
|
1213
|
-
|
|
1214
|
-
- [WalletClient API Reference](../reference/wallet.md)
|
|
1215
|
-
- [BSV Blockchain Documentation](https://docs.bsvblockchain.org/)
|
|
1216
|
-
- [Error Handling Best Practices](https://docs.bsvblockchain.org/error-handling)
|