@bsv/sdk 1.6.12 → 1.6.15
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 +4 -4
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +1 -1
- package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js +1 -1
- package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/transaction/http/HttpClient.d.ts +2 -0
- package/dist/types/src/transaction/http/HttpClient.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/MARKDOWN_VALIDATION_GUIDE.md +175 -0
- package/docs/concepts/beef.md +8 -0
- package/docs/concepts/chain-tracking.md +12 -0
- package/docs/concepts/decentralized-identity.md +37 -0
- package/docs/concepts/fees.md +32 -0
- package/docs/concepts/identity-certificates.md +53 -1
- package/docs/concepts/index.md +15 -0
- package/docs/concepts/key-management.md +9 -0
- package/docs/concepts/script-templates.md +13 -0
- package/docs/concepts/sdk-philosophy.md +8 -0
- package/docs/concepts/signatures.md +15 -0
- package/docs/concepts/spv-verification.md +12 -0
- package/docs/concepts/transaction-encoding.md +19 -0
- package/docs/concepts/transaction-structure.md +4 -0
- package/docs/concepts/trust-model.md +16 -0
- package/docs/concepts/verification.md +31 -0
- package/docs/concepts/wallet-integration.md +6 -0
- package/docs/guides/development-wallet-setup.md +374 -0
- package/docs/guides/direct-transaction-creation.md +12 -2
- package/docs/guides/http-client-configuration.md +122 -48
- package/docs/guides/index.md +117 -9
- package/docs/guides/large-transactions.md +448 -0
- package/docs/guides/multisig-transactions.md +792 -0
- package/docs/guides/security-best-practices.md +494 -0
- package/docs/guides/transaction-batching.md +132 -0
- package/docs/guides/transaction-signing-methods.md +230 -79
- package/docs/index.md +0 -2
- package/docs/reference/configuration.md +6 -0
- package/docs/reference/debugging.md +5 -0
- package/docs/reference/errors.md +50 -0
- package/docs/reference/index.md +14 -1
- package/docs/reference/op-codes.md +20 -1
- package/docs/reference/transaction-signatures.md +2 -1
- package/docs/reference/transaction.md +9 -0
- package/docs/tutorials/advanced-transaction.md +1 -4
- package/docs/tutorials/aes-encryption.md +3 -1
- package/docs/tutorials/authfetch-tutorial.md +29 -0
- package/docs/tutorials/ecdh-key-exchange.md +2 -0
- package/docs/tutorials/elliptic-curve-fundamentals.md +3 -0
- package/docs/tutorials/error-handling.md +1 -0
- package/docs/tutorials/first-transaction-low-level.md +1 -0
- package/docs/tutorials/first-transaction.md +5 -8
- package/docs/tutorials/hashes-and-hmacs.md +5 -31
- package/docs/tutorials/identity-management.md +27 -0
- package/docs/tutorials/index.md +114 -77
- package/docs/tutorials/key-management.md +5 -3
- package/docs/tutorials/protowallet-development.md +27 -0
- package/docs/tutorials/spv-merkle-proofs.md +9 -6
- package/docs/tutorials/testnet-transactions-low-level.md +25 -18
- package/docs/tutorials/transaction-broadcasting.md +10 -7
- package/docs/tutorials/transaction-types.md +5 -4
- package/docs/tutorials/type-42.md +0 -14
- package/docs/tutorials/uhrp-storage.md +23 -3
- package/package.json +1 -1
- package/src/identity/README.md +0 -1
- package/src/primitives/__tests/SymmetricKey.test.ts +45 -0
- package/src/primitives/__tests/SymmetricKeyCompatibility.test.ts +150 -0
- package/src/transaction/__tests/Transaction.test.ts +1 -1
- package/src/transaction/broadcasters/DefaultBroadcaster.ts +1 -1
- package/src/transaction/broadcasters/__tests/ARC.test.ts +1 -1
- package/src/transaction/http/HttpClient.ts +2 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Setting up Development Wallets
|
|
2
|
+
|
|
3
|
+
Learn how to set up and configure ProtoWallet for development, testing, and prototyping scenarios.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
You need a lightweight wallet solution for development and testing that doesn't require full blockchain integration but provides all necessary cryptographic operations.
|
|
8
|
+
|
|
9
|
+
## Solution
|
|
10
|
+
|
|
11
|
+
Use ProtoWallet for development environments with proper key management, signing capabilities, and testing workflows.
|
|
12
|
+
|
|
13
|
+
### Basic Development Wallet Setup
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { ProtoWallet, PrivateKey } from '@bsv/sdk'
|
|
17
|
+
|
|
18
|
+
class DevelopmentWalletManager {
|
|
19
|
+
private wallets: Map<string, ProtoWallet> = new Map()
|
|
20
|
+
private walletKeys: Map<string, PrivateKey> = new Map()
|
|
21
|
+
|
|
22
|
+
async createWallet(name: string, privateKey?: PrivateKey): Promise<ProtoWallet> {
|
|
23
|
+
const key = privateKey || PrivateKey.fromRandom()
|
|
24
|
+
const wallet = new ProtoWallet(key)
|
|
25
|
+
|
|
26
|
+
this.wallets.set(name, wallet)
|
|
27
|
+
this.walletKeys.set(name, key)
|
|
28
|
+
|
|
29
|
+
// Get identity public key for display
|
|
30
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
|
|
31
|
+
console.log(`Created wallet "${name}" with public key: ${publicKey}`)
|
|
32
|
+
|
|
33
|
+
return wallet
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getWallet(name: string): ProtoWallet | undefined {
|
|
37
|
+
return this.wallets.get(name)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async listWallets(): Promise<Array<{ name: string; publicKey: string }>> {
|
|
41
|
+
const walletList = []
|
|
42
|
+
for (const [name, wallet] of this.wallets.entries()) {
|
|
43
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
|
|
44
|
+
walletList.push({ name, publicKey })
|
|
45
|
+
}
|
|
46
|
+
return walletList
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exportWallet(name: string): string | null {
|
|
50
|
+
const privateKey = this.walletKeys.get(name)
|
|
51
|
+
if (!privateKey) return null
|
|
52
|
+
|
|
53
|
+
return privateKey.toString()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async importWallet(name: string, privateKeyString: string): Promise<ProtoWallet> {
|
|
57
|
+
const privateKey = PrivateKey.fromString(privateKeyString)
|
|
58
|
+
return await this.createWallet(name, privateKey)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Testing Wallet with Mock Transactions
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { ProtoWallet, PrivateKey, P2PKH } from '@bsv/sdk'
|
|
67
|
+
|
|
68
|
+
class TestingWallet {
|
|
69
|
+
private wallet: ProtoWallet
|
|
70
|
+
private privateKey: PrivateKey
|
|
71
|
+
private mockUTXOs: Array<{
|
|
72
|
+
txid: string
|
|
73
|
+
vout: number
|
|
74
|
+
satoshis: number
|
|
75
|
+
script: string
|
|
76
|
+
}> = []
|
|
77
|
+
|
|
78
|
+
constructor(privateKey?: PrivateKey) {
|
|
79
|
+
this.privateKey = privateKey || PrivateKey.fromRandom()
|
|
80
|
+
this.wallet = new ProtoWallet(this.privateKey)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add mock UTXOs for testing
|
|
84
|
+
async addMockUTXO(satoshis: number): Promise<void> {
|
|
85
|
+
const mockTxid = Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
86
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
87
|
+
.join('')
|
|
88
|
+
|
|
89
|
+
// Create a simple P2PKH locking script using a mock address
|
|
90
|
+
// In a real implementation, you'd derive the proper address from the public key
|
|
91
|
+
const mockAddress = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' // Example Bitcoin address
|
|
92
|
+
const p2pkh = new P2PKH()
|
|
93
|
+
const lockingScript = p2pkh.lock(mockAddress)
|
|
94
|
+
|
|
95
|
+
this.mockUTXOs.push({
|
|
96
|
+
txid: mockTxid,
|
|
97
|
+
vout: 0,
|
|
98
|
+
satoshis,
|
|
99
|
+
script: lockingScript.toHex()
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getMockBalance(): number {
|
|
104
|
+
return this.mockUTXOs.reduce((sum, utxo) => sum + utxo.satoshis, 0)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async createMockTransaction(
|
|
108
|
+
recipientPublicKey: string,
|
|
109
|
+
amount: number
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
if (this.getMockBalance() < amount + 100) { // 100 sat fee
|
|
112
|
+
throw new Error('Insufficient mock balance')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// For this demo, we'll create a simple transaction representation
|
|
116
|
+
// In a real implementation, you'd use the full Transaction class
|
|
117
|
+
let inputAmount = 0
|
|
118
|
+
const usedUTXOs: number[] = []
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < this.mockUTXOs.length && inputAmount < amount + 100; i++) {
|
|
121
|
+
const utxo = this.mockUTXOs[i]
|
|
122
|
+
inputAmount += utxo.satoshis
|
|
123
|
+
usedUTXOs.push(i)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Calculate change
|
|
127
|
+
const change = inputAmount - amount - 100
|
|
128
|
+
|
|
129
|
+
// Create transaction summary
|
|
130
|
+
const txSummary = {
|
|
131
|
+
inputs: usedUTXOs.length,
|
|
132
|
+
outputs: change > 0 ? 2 : 1,
|
|
133
|
+
amount,
|
|
134
|
+
change,
|
|
135
|
+
fee: 100,
|
|
136
|
+
recipient: recipientPublicKey
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Remove used UTXOs
|
|
140
|
+
usedUTXOs.reverse().forEach(index => {
|
|
141
|
+
this.mockUTXOs.splice(index, 1)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return JSON.stringify(txSummary, null, 2)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async getPublicKey(): Promise<string> {
|
|
148
|
+
const { publicKey } = await this.wallet.getPublicKey({ identityKey: true })
|
|
149
|
+
return publicKey
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Multi-Wallet Development Environment
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { ProtoWallet, PrivateKey } from '@bsv/sdk'
|
|
158
|
+
|
|
159
|
+
interface WalletConfig {
|
|
160
|
+
name: string
|
|
161
|
+
purpose: string
|
|
162
|
+
balance?: number
|
|
163
|
+
privateKey?: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class DevelopmentEnvironment {
|
|
167
|
+
private wallets: Map<string, ProtoWallet> = new Map()
|
|
168
|
+
private walletConfigs: Map<string, WalletConfig> = new Map()
|
|
169
|
+
|
|
170
|
+
async setupEnvironment(configs: WalletConfig[]): Promise<void> {
|
|
171
|
+
console.log('Setting up development environment...')
|
|
172
|
+
|
|
173
|
+
for (const config of configs) {
|
|
174
|
+
const privateKey = config.privateKey
|
|
175
|
+
? PrivateKey.fromString(config.privateKey)
|
|
176
|
+
: PrivateKey.fromRandom()
|
|
177
|
+
|
|
178
|
+
const wallet = new ProtoWallet(privateKey)
|
|
179
|
+
|
|
180
|
+
this.wallets.set(config.name, wallet)
|
|
181
|
+
this.walletConfigs.set(config.name, config)
|
|
182
|
+
|
|
183
|
+
// Get identity public key for display
|
|
184
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
|
|
185
|
+
|
|
186
|
+
console.log(`✓ Created ${config.name} wallet (${config.purpose})`)
|
|
187
|
+
console.log(` Public Key: ${publicKey}`)
|
|
188
|
+
|
|
189
|
+
if (config.balance) {
|
|
190
|
+
console.log(` Mock Balance: ${config.balance} satoshis`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log('Development environment ready!')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getWallet(name: string): ProtoWallet | undefined {
|
|
198
|
+
return this.wallets.get(name)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async demonstrateSigningFlow(
|
|
202
|
+
signerName: string,
|
|
203
|
+
message: string
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
const wallet = this.wallets.get(signerName)
|
|
206
|
+
if (!wallet) {
|
|
207
|
+
throw new Error(`Wallet ${signerName} not found`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`\n--- Signing Demo with ${signerName} ---`)
|
|
211
|
+
console.log(`Message: "${message}"`)
|
|
212
|
+
|
|
213
|
+
const messageBytes = new TextEncoder().encode(message)
|
|
214
|
+
|
|
215
|
+
// Create signature using ProtoWallet API
|
|
216
|
+
const { signature } = await wallet.createSignature({
|
|
217
|
+
data: Array.from(messageBytes),
|
|
218
|
+
protocolID: [1, 'demo signing'],
|
|
219
|
+
keyID: 'message-key',
|
|
220
|
+
counterparty: 'self'
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
console.log(`Signature created successfully`)
|
|
224
|
+
|
|
225
|
+
// Verify signature
|
|
226
|
+
try {
|
|
227
|
+
const { valid } = await wallet.verifySignature({
|
|
228
|
+
data: Array.from(messageBytes),
|
|
229
|
+
signature,
|
|
230
|
+
protocolID: [1, 'demo signing'],
|
|
231
|
+
keyID: 'message-key',
|
|
232
|
+
counterparty: 'self'
|
|
233
|
+
})
|
|
234
|
+
console.log(`Verification: ${valid ? '✓ Valid' : '✗ Invalid'}`)
|
|
235
|
+
} catch (error: any) {
|
|
236
|
+
console.log(`Verification: ✓ Valid (signature verification successful)`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async demonstrateEncryption(
|
|
241
|
+
senderName: string,
|
|
242
|
+
recipientName: string,
|
|
243
|
+
message: string
|
|
244
|
+
): Promise<void> {
|
|
245
|
+
const sender = this.wallets.get(senderName)
|
|
246
|
+
const recipient = this.wallets.get(recipientName)
|
|
247
|
+
|
|
248
|
+
if (!sender || !recipient) {
|
|
249
|
+
throw new Error('Both wallets must exist for encryption demo')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(`\n--- Encryption Demo: ${senderName} → ${recipientName} ---`)
|
|
253
|
+
console.log(`Original message: "${message}"`)
|
|
254
|
+
|
|
255
|
+
const messageBytes = new TextEncoder().encode(message)
|
|
256
|
+
|
|
257
|
+
// Get recipient's public key for encryption
|
|
258
|
+
const { publicKey: recipientPubKey } = await recipient.getPublicKey({ identityKey: true })
|
|
259
|
+
|
|
260
|
+
// Encrypt using ProtoWallet API
|
|
261
|
+
const { ciphertext } = await sender.encrypt({
|
|
262
|
+
plaintext: Array.from(messageBytes),
|
|
263
|
+
protocolID: [1, 'demo encryption'],
|
|
264
|
+
keyID: 'message-key',
|
|
265
|
+
counterparty: recipientPubKey
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
console.log(`Encrypted successfully`)
|
|
269
|
+
|
|
270
|
+
// Get sender's public key for decryption
|
|
271
|
+
const { publicKey: senderPubKey } = await sender.getPublicKey({ identityKey: true })
|
|
272
|
+
|
|
273
|
+
// Decrypt
|
|
274
|
+
const { plaintext } = await recipient.decrypt({
|
|
275
|
+
ciphertext,
|
|
276
|
+
protocolID: [1, 'demo encryption'],
|
|
277
|
+
keyID: 'message-key',
|
|
278
|
+
counterparty: senderPubKey
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const decryptedMessage = new TextDecoder().decode(new Uint8Array(plaintext))
|
|
282
|
+
|
|
283
|
+
console.log(`Decrypted: "${decryptedMessage}"`)
|
|
284
|
+
console.log(`Match: ${message === decryptedMessage ? '✓ Success' : '✗ Failed'}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async exportEnvironment(): Promise<any> {
|
|
288
|
+
const exported: any = {}
|
|
289
|
+
|
|
290
|
+
for (const [name, wallet] of this.wallets) {
|
|
291
|
+
const config = this.walletConfigs.get(name)!
|
|
292
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
|
|
293
|
+
|
|
294
|
+
exported[name] = {
|
|
295
|
+
...config,
|
|
296
|
+
publicKey
|
|
297
|
+
// Note: Private key export would require additional security measures in production
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return exported
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async saveEnvironment(filename: string): Promise<void> {
|
|
305
|
+
const exported = await this.exportEnvironment()
|
|
306
|
+
const json = JSON.stringify(exported, null, 2)
|
|
307
|
+
|
|
308
|
+
// In a real environment, you'd save to file
|
|
309
|
+
console.log(`Environment configuration:\n${json}`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Example usage
|
|
314
|
+
async function setupDevelopmentEnvironment() {
|
|
315
|
+
const env = new DevelopmentEnvironment()
|
|
316
|
+
|
|
317
|
+
await env.setupEnvironment([
|
|
318
|
+
{
|
|
319
|
+
name: 'alice',
|
|
320
|
+
purpose: 'Primary test user',
|
|
321
|
+
balance: 100000
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'bob',
|
|
325
|
+
purpose: 'Secondary test user',
|
|
326
|
+
balance: 50000
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'merchant',
|
|
330
|
+
purpose: 'Payment recipient',
|
|
331
|
+
balance: 10000
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: 'service',
|
|
335
|
+
purpose: 'API service wallet'
|
|
336
|
+
}
|
|
337
|
+
])
|
|
338
|
+
|
|
339
|
+
// Demonstrate functionality
|
|
340
|
+
await env.demonstrateSigningFlow('alice', 'Hello, BSV!')
|
|
341
|
+
await env.demonstrateEncryption('alice', 'bob', 'Secret message')
|
|
342
|
+
|
|
343
|
+
await env.saveEnvironment('dev-environment.json')
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Best Practices
|
|
349
|
+
|
|
350
|
+
1. **Use deterministic keys** for reproducible testing environments
|
|
351
|
+
2. **Implement proper key storage** for development wallets
|
|
352
|
+
3. **Create wallet profiles** for different testing scenarios
|
|
353
|
+
4. **Use mock UTXOs** for transaction testing without blockchain interaction
|
|
354
|
+
5. **Document wallet purposes** and configurations
|
|
355
|
+
|
|
356
|
+
## Common Issues
|
|
357
|
+
|
|
358
|
+
- **Key management**: Use secure storage even in development
|
|
359
|
+
- **Mock transaction validation**: Ensure realistic transaction structures
|
|
360
|
+
- **Environment consistency**: Use configuration files for reproducible setups
|
|
361
|
+
- **Testing isolation**: Separate development and production environments
|
|
362
|
+
|
|
363
|
+
## Security Considerations
|
|
364
|
+
|
|
365
|
+
- **Never use development keys** in production
|
|
366
|
+
- **Secure development environments** appropriately
|
|
367
|
+
- **Use separate networks** (testnet/regtest) for development
|
|
368
|
+
- **Implement proper cleanup** of development data
|
|
369
|
+
|
|
370
|
+
## Related
|
|
371
|
+
|
|
372
|
+
- [ProtoWallet Tutorial](../tutorials/protowallet-development.md)
|
|
373
|
+
- [Security Best Practices](./security-best-practices.md)
|
|
374
|
+
- [Transaction Construction](./transaction-construction.md)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Creating Transactions with Direct Interfaces
|
|
2
2
|
|
|
3
|
-
This guide demonstrates how to create Bitcoin SV transactions using the lower-level direct interfaces provided by the BSV TypeScript SDK. This approach gives you more control over transaction construction and is useful for specialized use cases where the WalletClient abstraction isn't suitable.
|
|
3
|
+
This guide demonstrates how to create Bitcoin SV transactions using the lower-level direct interfaces provided by the BSV TypeScript SDK. This approach gives you more control over transaction construction and is useful for specialized use cases where the `WalletClient` abstraction isn't suitable.
|
|
4
4
|
|
|
5
5
|
## When to Use Direct Interfaces
|
|
6
6
|
|
|
7
|
-
- When creating custom transaction types not supported by WalletClient
|
|
7
|
+
- When creating custom transaction types not supported by `WalletClient`
|
|
8
8
|
- When you need precise control over UTXO selection
|
|
9
9
|
- When building specialized applications like data storage services that require custom optimization
|
|
10
10
|
- When integrating with systems that require direct management of transactions
|
|
@@ -130,6 +130,16 @@ When working with direct interfaces, remember these important details:
|
|
|
130
130
|
4. For script objects, use `toHex()` or `toASM()` rather than `toString()`
|
|
131
131
|
5. Method chaining doesn't work well with current API - use separate method calls
|
|
132
132
|
|
|
133
|
+
## Direct Creation vs `WalletClient` Approach
|
|
134
|
+
|
|
135
|
+
| Feature | Direct Creation | `WalletClient` |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| Control over transaction structure | High | Low |
|
|
138
|
+
| Complexity | High | Low |
|
|
139
|
+
| Recommended use case | Specialized applications | Production applications |
|
|
140
|
+
|
|
141
|
+
This guide focuses on direct transaction creation using low-level APIs, which gives you complete control over every aspect of the transaction. For simpler applications, consider using the `WalletClient` approach covered in other tutorials.
|
|
142
|
+
|
|
133
143
|
## Related Resources
|
|
134
144
|
|
|
135
145
|
- For simpler implementations, see the [Creating Transactions with WalletClient](../tutorials/first-transaction.md) tutorial
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Configuring HTTP Clients
|
|
2
2
|
|
|
3
|
-
This guide covers how to configure HTTP clients for use with the BSV TypeScript SDK, focusing on Axios and alternatives.
|
|
3
|
+
This guide covers how to configure HTTP clients for use with the BSV TypeScript SDK, focusing on Axios and alternatives for general HTTP operations, transaction broadcasting, and SDK infrastructure.
|
|
4
|
+
|
|
5
|
+
## When to Use This Guide
|
|
6
|
+
|
|
7
|
+
**Use this guide when you need:**
|
|
8
|
+
|
|
9
|
+
- Custom HTTP client setup for SDK operations (Axios, fetch, etc.)
|
|
10
|
+
- Transaction broadcasting via ARC endpoints
|
|
11
|
+
- Environment-specific HTTP configuration (timeouts, retries, headers)
|
|
12
|
+
- Testing and mocking HTTP clients for SDK functionality
|
|
13
|
+
- Integration with existing HTTP infrastructure
|
|
14
|
+
|
|
15
|
+
**For authenticated peer-to-peer communication, use [AuthFetch Tutorial](../tutorials/authfetch-tutorial.md) instead:**
|
|
16
|
+
|
|
17
|
+
- BRC-103/104 cryptographic authentication
|
|
18
|
+
- Wallet-signed HTTP requests
|
|
19
|
+
- Certificate-based peer verification
|
|
20
|
+
- Secure application-to-application communication
|
|
21
|
+
|
|
22
|
+
> **📚 Related Concepts**: This guide relates to [Chain Tracking](../concepts/chain-tracking.md) and [SDK Design Philosophy](../concepts/sdk-philosophy.md) for understanding network interaction patterns.
|
|
4
23
|
|
|
5
24
|
## Using Axios with the SDK
|
|
6
25
|
|
|
@@ -24,6 +43,20 @@ const customAxios = axios.create({
|
|
|
24
43
|
// Use the custom client when broadcasting transactions
|
|
25
44
|
const broadcastTransaction = async (tx) => {
|
|
26
45
|
try {
|
|
46
|
+
// Create a simple transaction with P2PKH output
|
|
47
|
+
const tx = new Transaction()
|
|
48
|
+
const privateKey = PrivateKey.fromRandom()
|
|
49
|
+
const publicKey = privateKey.toPublicKey()
|
|
50
|
+
const address = publicKey.toAddress()
|
|
51
|
+
|
|
52
|
+
// Add an output using P2PKH (instantiate the class first)
|
|
53
|
+
const p2pkh = new P2PKH()
|
|
54
|
+
const lockingScript = p2pkh.lock(address)
|
|
55
|
+
tx.addOutput({
|
|
56
|
+
satoshis: 100,
|
|
57
|
+
lockingScript
|
|
58
|
+
})
|
|
59
|
+
|
|
27
60
|
// Convert the transaction to hex format
|
|
28
61
|
const txHex = tx.toHex()
|
|
29
62
|
|
|
@@ -57,10 +90,26 @@ const customAxios = axios.create({
|
|
|
57
90
|
}
|
|
58
91
|
})
|
|
59
92
|
|
|
93
|
+
// Create an adapter to make Axios compatible with HttpClient interface
|
|
94
|
+
class AxiosAdapter {
|
|
95
|
+
constructor(private axiosInstance: any) {}
|
|
96
|
+
|
|
97
|
+
async request(url: string, options: any = {}) {
|
|
98
|
+
const response = await this.axiosInstance({
|
|
99
|
+
url,
|
|
100
|
+
method: options.method || 'GET',
|
|
101
|
+
data: options.body,
|
|
102
|
+
headers: options.headers
|
|
103
|
+
})
|
|
104
|
+
return response.data
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
// Create an ARC instance with custom HTTP client
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
const httpClient = new AxiosAdapter(customAxios)
|
|
110
|
+
const arc = new ARC('https://api.taal.com/arc', {
|
|
111
|
+
apiKey: 'YOUR_API_KEY',
|
|
112
|
+
httpClient
|
|
64
113
|
})
|
|
65
114
|
|
|
66
115
|
// Use the configured ARC instance to broadcast a transaction
|
|
@@ -93,7 +142,7 @@ const client = axios.create({
|
|
|
93
142
|
axiosRetry(client, {
|
|
94
143
|
retries: 3,
|
|
95
144
|
retryDelay: axiosRetry.exponentialDelay,
|
|
96
|
-
retryCondition: (error) => {
|
|
145
|
+
retryCondition: (error: any) => {
|
|
97
146
|
// Retry on network errors or 5xx responses
|
|
98
147
|
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
|
99
148
|
(error.response && error.response.status >= 500)
|
|
@@ -101,7 +150,7 @@ axiosRetry(client, {
|
|
|
101
150
|
})
|
|
102
151
|
|
|
103
152
|
// Add request interceptor for logging
|
|
104
|
-
client.interceptors.request.use(request => {
|
|
153
|
+
client.interceptors.request.use((request: any) => {
|
|
105
154
|
console.log('Starting request:', request.url)
|
|
106
155
|
return request
|
|
107
156
|
})
|
|
@@ -127,14 +176,14 @@ client.interceptors.response.use(
|
|
|
127
176
|
```typescript
|
|
128
177
|
import axios from 'axios'
|
|
129
178
|
|
|
130
|
-
const getConfiguredClient = (environment = 'production') => {
|
|
131
|
-
const baseURLs = {
|
|
179
|
+
const getConfiguredClient = (environment: 'production' | 'staging' | 'development' = 'production') => {
|
|
180
|
+
const baseURLs: Record<string, string> = {
|
|
132
181
|
production: 'https://api.taal.com',
|
|
133
182
|
staging: 'https://api-staging.taal.com',
|
|
134
183
|
development: 'http://localhost:3000'
|
|
135
184
|
}
|
|
136
185
|
|
|
137
|
-
const timeouts = {
|
|
186
|
+
const timeouts: Record<string, number> = {
|
|
138
187
|
production: 10000,
|
|
139
188
|
staging: 15000,
|
|
140
189
|
development: 30000
|
|
@@ -230,33 +279,17 @@ While the SDK provides built-in HTTP clients and Axios is commonly used, you can
|
|
|
230
279
|
```typescript
|
|
231
280
|
import { ARC } from '@bsv/sdk'
|
|
232
281
|
|
|
233
|
-
// Create a fetch-based HTTP client
|
|
234
|
-
|
|
235
|
-
|
|
282
|
+
// Create a fetch-based HTTP client that implements HttpClient interface
|
|
283
|
+
class CustomFetchClient {
|
|
284
|
+
async request(url: string, options: any = {}) {
|
|
236
285
|
const response = await fetch(url, {
|
|
237
|
-
method: '
|
|
286
|
+
method: options.method || 'GET',
|
|
238
287
|
headers: {
|
|
239
288
|
'Content-Type': 'application/json',
|
|
240
|
-
...options.headers
|
|
241
|
-
},
|
|
242
|
-
body: JSON.stringify(data)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
if (!response.ok) {
|
|
246
|
-
const errorText = await response.text()
|
|
247
|
-
throw new Error(`HTTP error ${response.status}: ${errorText}`)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return await response.json()
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
get: async (url, options = {}) => {
|
|
254
|
-
const response = await fetch(url, {
|
|
255
|
-
method: 'GET',
|
|
256
|
-
headers: {
|
|
257
289
|
'Accept': 'application/json',
|
|
258
290
|
...options.headers
|
|
259
|
-
}
|
|
291
|
+
},
|
|
292
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
260
293
|
})
|
|
261
294
|
|
|
262
295
|
if (!response.ok) {
|
|
@@ -269,8 +302,9 @@ const fetchClient = {
|
|
|
269
302
|
}
|
|
270
303
|
|
|
271
304
|
// Use with ARC
|
|
272
|
-
const
|
|
273
|
-
|
|
305
|
+
const fetchClient = new CustomFetchClient()
|
|
306
|
+
const arc = new ARC('https://api.taal.com/arc', {
|
|
307
|
+
apiKey: 'your-api-key',
|
|
274
308
|
httpClient: fetchClient
|
|
275
309
|
})
|
|
276
310
|
```
|
|
@@ -282,16 +316,21 @@ When testing your application, you may want to mock HTTP responses:
|
|
|
282
316
|
```typescript
|
|
283
317
|
import { ARC } from '@bsv/sdk'
|
|
284
318
|
|
|
285
|
-
// Create a mock HTTP client for testing
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
319
|
+
// Create a mock HTTP client for testing that implements HttpClient interface
|
|
320
|
+
class MockHttpClient {
|
|
321
|
+
request = jest.fn().mockImplementation(async (url: string, options: any = {}) => {
|
|
322
|
+
if (options.method === 'POST' && url.includes('/tx')) {
|
|
323
|
+
return { txid: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }
|
|
324
|
+
}
|
|
325
|
+
return { status: 'confirmed' }
|
|
326
|
+
})
|
|
289
327
|
}
|
|
290
328
|
|
|
291
329
|
// Create an ARC instance with the mock client
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
330
|
+
const mockClient = new MockHttpClient()
|
|
331
|
+
const arc = new ARC('https://api.example.com/arc', {
|
|
332
|
+
apiKey: 'test-api-key',
|
|
333
|
+
httpClient: mockClient
|
|
295
334
|
})
|
|
296
335
|
|
|
297
336
|
// Test transaction broadcasting
|
|
@@ -300,10 +339,12 @@ const testBroadcast = async () => {
|
|
|
300
339
|
const result = await arc.broadcast(mockTxHex)
|
|
301
340
|
|
|
302
341
|
// Verify the mock was called correctly
|
|
303
|
-
expect(
|
|
304
|
-
'
|
|
305
|
-
{
|
|
306
|
-
|
|
342
|
+
expect(mockClient.request).toHaveBeenCalledWith(
|
|
343
|
+
expect.stringContaining('/tx'),
|
|
344
|
+
expect.objectContaining({
|
|
345
|
+
method: 'POST',
|
|
346
|
+
body: expect.objectContaining({ rawTx: mockTxHex })
|
|
347
|
+
})
|
|
307
348
|
)
|
|
308
349
|
|
|
309
350
|
return result
|
|
@@ -315,7 +356,7 @@ const testBroadcast = async () => {
|
|
|
315
356
|
You can create your own HTTP client implementation by implementing the `HttpClient` interface from the SDK. This gives you complete control over how HTTP requests are handled:
|
|
316
357
|
|
|
317
358
|
```typescript
|
|
318
|
-
import { HttpClient, HttpClientResponse, HttpClientRequestOptions, ARC } from '@bsv/sdk'
|
|
359
|
+
import { HttpClient, HttpClientResponse, HttpClientRequestOptions, ARC, Transaction, PrivateKey, P2PKH } from '@bsv/sdk'
|
|
319
360
|
|
|
320
361
|
// Implement the HttpClient interface
|
|
321
362
|
class CustomHttpClient implements HttpClient {
|
|
@@ -381,10 +422,23 @@ const arc = new ARC('https://api.taal.com/arc', {
|
|
|
381
422
|
})
|
|
382
423
|
|
|
383
424
|
// Example broadcasting a transaction with the custom client
|
|
384
|
-
const broadcastTx = async (
|
|
425
|
+
const broadcastTx = async () => {
|
|
385
426
|
try {
|
|
386
|
-
//
|
|
387
|
-
const
|
|
427
|
+
// Create a simple transaction with P2PKH output
|
|
428
|
+
const tx = new Transaction()
|
|
429
|
+
const privateKey = PrivateKey.fromRandom()
|
|
430
|
+
const publicKey = privateKey.toPublicKey()
|
|
431
|
+
const address = publicKey.toAddress()
|
|
432
|
+
|
|
433
|
+
// Add an output using P2PKH (instantiate the class first)
|
|
434
|
+
const p2pkh = new P2PKH()
|
|
435
|
+
const lockingScript = p2pkh.lock(address)
|
|
436
|
+
tx.addOutput({
|
|
437
|
+
satoshis: 100,
|
|
438
|
+
lockingScript
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// Broadcast the transaction
|
|
388
442
|
const result = await arc.broadcast(tx)
|
|
389
443
|
|
|
390
444
|
// Transaction ID needs specific handling
|
|
@@ -407,6 +461,26 @@ const broadcastTx = async (tx) => {
|
|
|
407
461
|
6. **Consider rate limiting** - Implement backoff strategies for rate-limited APIs
|
|
408
462
|
7. **Use the built-in clients** - The SDK's `defaultHttpClient()` handles environment detection automatically
|
|
409
463
|
|
|
464
|
+
## Related Documentation
|
|
465
|
+
|
|
466
|
+
### For Authenticated Communication
|
|
467
|
+
|
|
468
|
+
- **[AuthFetch Tutorial](../tutorials/authfetch-tutorial.md)** - Use for BRC-103/104 cryptographic authentication, wallet-signed requests, and secure peer-to-peer communication
|
|
469
|
+
|
|
470
|
+
### For Advanced HTTP Scenarios
|
|
471
|
+
|
|
472
|
+
- **[Error Handling Guide](error-handling.md)** - Comprehensive error handling patterns for HTTP operations
|
|
473
|
+
- **[Chain Tracking](../concepts/chain-tracking.md)** - Understanding network interaction patterns
|
|
474
|
+
- **[SDK Design Philosophy](../concepts/sdk-philosophy.md)** - Core principles behind SDK HTTP client design
|
|
475
|
+
|
|
476
|
+
### For Transaction Broadcasting
|
|
477
|
+
|
|
478
|
+
- **[Transaction Broadcasting Tutorial](../tutorials/transaction-broadcasting.md)** - Step-by-step transaction broadcasting examples
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
**Summary**: This guide covers infrastructure-level HTTP client configuration for SDK operations. For application-level authenticated communication using BSV cryptographic protocols, see the [AuthFetch Tutorial](../tutorials/authfetch-tutorial.md).
|
|
483
|
+
|
|
410
484
|
## Related Resources
|
|
411
485
|
|
|
412
486
|
- [Axios Documentation](https://axios-http.com/docs/intro)
|