otx-btc-wallet-connectors 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +554 -0
  2. package/dist/base-IAFq7sd8.d.mts +53 -0
  3. package/dist/base-IAFq7sd8.d.ts +53 -0
  4. package/dist/binance/index.d.mts +81 -0
  5. package/dist/binance/index.d.ts +81 -0
  6. package/dist/binance/index.js +13 -0
  7. package/dist/binance/index.js.map +1 -0
  8. package/dist/binance/index.mjs +4 -0
  9. package/dist/binance/index.mjs.map +1 -0
  10. package/dist/bitget/index.d.mts +84 -0
  11. package/dist/bitget/index.d.ts +84 -0
  12. package/dist/bitget/index.js +13 -0
  13. package/dist/bitget/index.js.map +1 -0
  14. package/dist/bitget/index.mjs +4 -0
  15. package/dist/bitget/index.mjs.map +1 -0
  16. package/dist/chunk-5Z5Q2Y75.mjs +91 -0
  17. package/dist/chunk-5Z5Q2Y75.mjs.map +1 -0
  18. package/dist/chunk-7KK2LZLZ.mjs +208 -0
  19. package/dist/chunk-7KK2LZLZ.mjs.map +1 -0
  20. package/dist/chunk-AW2JZIHR.mjs +753 -0
  21. package/dist/chunk-AW2JZIHR.mjs.map +1 -0
  22. package/dist/chunk-EIJOSZXZ.js +331 -0
  23. package/dist/chunk-EIJOSZXZ.js.map +1 -0
  24. package/dist/chunk-EQHR7P7G.js +541 -0
  25. package/dist/chunk-EQHR7P7G.js.map +1 -0
  26. package/dist/chunk-EWRXLZO4.mjs +539 -0
  27. package/dist/chunk-EWRXLZO4.mjs.map +1 -0
  28. package/dist/chunk-FISNQZZ7.js +802 -0
  29. package/dist/chunk-FISNQZZ7.js.map +1 -0
  30. package/dist/chunk-HL4WDMGS.js +200 -0
  31. package/dist/chunk-HL4WDMGS.js.map +1 -0
  32. package/dist/chunk-IPYWR76I.js +314 -0
  33. package/dist/chunk-IPYWR76I.js.map +1 -0
  34. package/dist/chunk-JYYNWR5G.js +142 -0
  35. package/dist/chunk-JYYNWR5G.js.map +1 -0
  36. package/dist/chunk-LNKTYZJM.js +701 -0
  37. package/dist/chunk-LNKTYZJM.js.map +1 -0
  38. package/dist/chunk-LVZMONQL.mjs +699 -0
  39. package/dist/chunk-LVZMONQL.mjs.map +1 -0
  40. package/dist/chunk-MFXLQWOE.js +93 -0
  41. package/dist/chunk-MFXLQWOE.js.map +1 -0
  42. package/dist/chunk-NBIA4TTE.mjs +204 -0
  43. package/dist/chunk-NBIA4TTE.mjs.map +1 -0
  44. package/dist/chunk-O4DD2XJ2.js +206 -0
  45. package/dist/chunk-O4DD2XJ2.js.map +1 -0
  46. package/dist/chunk-P7HVBU2B.mjs +140 -0
  47. package/dist/chunk-P7HVBU2B.mjs.map +1 -0
  48. package/dist/chunk-Q7QVQYEB.js +210 -0
  49. package/dist/chunk-Q7QVQYEB.js.map +1 -0
  50. package/dist/chunk-RLZEG6KL.mjs +329 -0
  51. package/dist/chunk-RLZEG6KL.mjs.map +1 -0
  52. package/dist/chunk-SYLDBJ75.mjs +246 -0
  53. package/dist/chunk-SYLDBJ75.mjs.map +1 -0
  54. package/dist/chunk-TTEUU3CI.mjs +198 -0
  55. package/dist/chunk-TTEUU3CI.mjs.map +1 -0
  56. package/dist/chunk-V66BXDTR.mjs +292 -0
  57. package/dist/chunk-V66BXDTR.mjs.map +1 -0
  58. package/dist/chunk-X77ZT4OI.js +268 -0
  59. package/dist/chunk-X77ZT4OI.js.map +1 -0
  60. package/dist/imtoken/index.d.mts +116 -0
  61. package/dist/imtoken/index.d.ts +116 -0
  62. package/dist/imtoken/index.js +14 -0
  63. package/dist/imtoken/index.js.map +1 -0
  64. package/dist/imtoken/index.mjs +5 -0
  65. package/dist/imtoken/index.mjs.map +1 -0
  66. package/dist/index.d.mts +14 -0
  67. package/dist/index.d.ts +14 -0
  68. package/dist/index.js +170 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/index.mjs +13 -0
  71. package/dist/index.mjs.map +1 -0
  72. package/dist/ledger/index.d.mts +290 -0
  73. package/dist/ledger/index.d.ts +290 -0
  74. package/dist/ledger/index.js +14 -0
  75. package/dist/ledger/index.js.map +1 -0
  76. package/dist/ledger/index.mjs +5 -0
  77. package/dist/ledger/index.mjs.map +1 -0
  78. package/dist/okx/index.d.mts +88 -0
  79. package/dist/okx/index.d.ts +88 -0
  80. package/dist/okx/index.js +13 -0
  81. package/dist/okx/index.js.map +1 -0
  82. package/dist/okx/index.mjs +4 -0
  83. package/dist/okx/index.mjs.map +1 -0
  84. package/dist/phantom/index.d.mts +96 -0
  85. package/dist/phantom/index.d.ts +96 -0
  86. package/dist/phantom/index.js +14 -0
  87. package/dist/phantom/index.js.map +1 -0
  88. package/dist/phantom/index.mjs +5 -0
  89. package/dist/phantom/index.mjs.map +1 -0
  90. package/dist/psbt-builder-CFOs69Z5.d.mts +131 -0
  91. package/dist/psbt-builder-CFOs69Z5.d.ts +131 -0
  92. package/dist/trezor/index.d.mts +155 -0
  93. package/dist/trezor/index.d.ts +155 -0
  94. package/dist/trezor/index.js +14 -0
  95. package/dist/trezor/index.js.map +1 -0
  96. package/dist/trezor/index.mjs +5 -0
  97. package/dist/trezor/index.mjs.map +1 -0
  98. package/dist/unisat/index.d.mts +75 -0
  99. package/dist/unisat/index.d.ts +75 -0
  100. package/dist/unisat/index.js +13 -0
  101. package/dist/unisat/index.js.map +1 -0
  102. package/dist/unisat/index.mjs +4 -0
  103. package/dist/unisat/index.mjs.map +1 -0
  104. package/dist/utils/index.d.mts +398 -0
  105. package/dist/utils/index.d.ts +398 -0
  106. package/dist/utils/index.js +120 -0
  107. package/dist/utils/index.js.map +1 -0
  108. package/dist/utils/index.mjs +3 -0
  109. package/dist/utils/index.mjs.map +1 -0
  110. package/dist/xverse/index.d.mts +79 -0
  111. package/dist/xverse/index.d.ts +79 -0
  112. package/dist/xverse/index.js +13 -0
  113. package/dist/xverse/index.js.map +1 -0
  114. package/dist/xverse/index.mjs +4 -0
  115. package/dist/xverse/index.mjs.map +1 -0
  116. package/package.json +108 -0
  117. package/src/base.ts +132 -0
  118. package/src/binance/BinanceConnector.ts +307 -0
  119. package/src/binance/index.ts +1 -0
  120. package/src/bitget/BitgetConnector.ts +301 -0
  121. package/src/bitget/index.ts +1 -0
  122. package/src/imtoken/ImTokenConnector.ts +420 -0
  123. package/src/imtoken/index.ts +2 -0
  124. package/src/index.ts +78 -0
  125. package/src/ledger/LedgerConnector.ts +1019 -0
  126. package/src/ledger/index.ts +8 -0
  127. package/src/okx/OKXConnector.ts +230 -0
  128. package/src/okx/index.ts +1 -0
  129. package/src/phantom/PhantomConnector.ts +381 -0
  130. package/src/phantom/index.ts +2 -0
  131. package/src/trezor/TrezorConnector.ts +824 -0
  132. package/src/trezor/index.ts +6 -0
  133. package/src/unisat/UnisatConnector.ts +312 -0
  134. package/src/unisat/index.ts +1 -0
  135. package/src/utils/blockstream.ts +230 -0
  136. package/src/utils/btc-service.ts +364 -0
  137. package/src/utils/index.ts +56 -0
  138. package/src/utils/mempool.ts +232 -0
  139. package/src/utils/psbt-builder.ts +492 -0
  140. package/src/utils/types.ts +183 -0
  141. package/src/xverse/XverseConnector.ts +479 -0
  142. package/src/xverse/index.ts +1 -0
@@ -0,0 +1,492 @@
1
+ /**
2
+ * PSBT Builder Utility
3
+ * Reusable functions for building PSBTs across different wallet connectors
4
+ */
5
+
6
+ import type { BitcoinNetwork, AddressType } from 'otx-btc-wallet-core';
7
+ import * as bitcoin from 'bitcoinjs-lib';
8
+ import { BtcService } from './btc-service';
9
+
10
+ /**
11
+ * UTXO type
12
+ */
13
+ export interface Utxo {
14
+ txid: string;
15
+ vout: number;
16
+ value: number;
17
+ status?: {
18
+ confirmed: boolean;
19
+ block_height?: number;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Options for generating a send PSBT
25
+ */
26
+ export interface GeneratePsbtOptions {
27
+ /** Fee rate in sat/vB */
28
+ feeRate?: number;
29
+ /** Fee rate multiplier */
30
+ feeRateMultiplier?: number;
31
+ }
32
+
33
+ /**
34
+ * Result from generating a send PSBT
35
+ */
36
+ export interface GeneratePsbtResult {
37
+ /** The constructed PSBT */
38
+ psbt: bitcoin.Psbt;
39
+ /** PSBT as hex string */
40
+ psbtHex: string;
41
+ /** PSBT as base64 string */
42
+ psbtBase64: string;
43
+ /** Bitcoin network used */
44
+ btcNetwork: bitcoin.Network;
45
+ /** Selected UTXOs used in the transaction */
46
+ selectedUtxos: Utxo[];
47
+ /** Total input value in satoshis */
48
+ totalInputValue: number;
49
+ /** Estimated fee in satoshis */
50
+ estimatedFee: number;
51
+ /** Change amount in satoshis (0 if no change output) */
52
+ changeAmount: number;
53
+ /** Inputs to sign (for wallet APIs that require this) */
54
+ inputsToSign: Array<{ address: string; index: number }>;
55
+ }
56
+
57
+ /**
58
+ * Helper to convert hex string to Uint8Array
59
+ */
60
+ export function hexToBytes(hex: string): Uint8Array {
61
+ const cleanHex = hex.replace(/^0x/, '');
62
+ const bytes = new Uint8Array(cleanHex.length / 2);
63
+ for (let i = 0; i < bytes.length; i++) {
64
+ bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16);
65
+ }
66
+ return bytes;
67
+ }
68
+
69
+ /**
70
+ * Helper to convert Uint8Array to hex string
71
+ */
72
+ export function bytesToHex(bytes: Uint8Array): string {
73
+ return Array.from(bytes)
74
+ .map((b) => b.toString(16).padStart(2, '0'))
75
+ .join('');
76
+ }
77
+
78
+ /**
79
+ * Helper to convert public key to x-only (for Taproot)
80
+ */
81
+ export function toXOnly(pubKey: Uint8Array): Uint8Array {
82
+ return pubKey.subarray(1, 33);
83
+ }
84
+
85
+ /**
86
+ * Get address type from address string
87
+ */
88
+ export function getAddressType(address: string): AddressType {
89
+ if (address.startsWith('bc1q') || address.startsWith('tb1q')) {
90
+ return 'segwit';
91
+ }
92
+ if (address.startsWith('bc1p') || address.startsWith('tb1p')) {
93
+ return 'taproot';
94
+ }
95
+ if (address.startsWith('3') || address.startsWith('2')) {
96
+ return 'nested-segwit';
97
+ }
98
+ return 'legacy';
99
+ }
100
+
101
+ /**
102
+ * Get bitcoinjs-lib network object from BitcoinNetwork
103
+ */
104
+ export function getBitcoinJsNetwork(network: BitcoinNetwork): bitcoin.Network {
105
+ switch (network) {
106
+ case 'mainnet':
107
+ return bitcoin.networks.bitcoin;
108
+ case 'testnet':
109
+ case 'testnet4':
110
+ case 'signet':
111
+ return bitcoin.networks.testnet;
112
+ default:
113
+ return bitcoin.networks.bitcoin;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Estimate vBytes per input based on address type
119
+ */
120
+ export function getInputVBytes(addressType: AddressType): number {
121
+ switch (addressType) {
122
+ case 'taproot':
123
+ return 58; // P2TR input
124
+ case 'segwit':
125
+ return 68; // P2WPKH input
126
+ case 'nested-segwit':
127
+ return 91; // P2SH-P2WPKH input
128
+ default:
129
+ return 148; // P2PKH input
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Estimate vBytes per output based on address type
135
+ */
136
+ export function getOutputVBytes(addressType: AddressType): number {
137
+ switch (addressType) {
138
+ case 'taproot':
139
+ return 43; // P2TR output
140
+ case 'segwit':
141
+ return 31; // P2WPKH output
142
+ case 'nested-segwit':
143
+ return 32; // P2SH output
144
+ default:
145
+ return 34; // P2PKH output
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Get dust threshold for address type
151
+ */
152
+ export function getDustThreshold(addressType: AddressType): number {
153
+ return addressType === 'legacy' ? 546 : 294;
154
+ }
155
+
156
+ /**
157
+ * Select UTXOs for transaction using a simple greedy algorithm
158
+ */
159
+ export function selectUtxos(
160
+ utxos: Utxo[],
161
+ fromAddressType: AddressType,
162
+ toAddressType: AddressType,
163
+ amount: number,
164
+ feeRate: number
165
+ ): { selectedUtxos: Utxo[]; totalValue: number; estimatedFee: number } {
166
+ const inputVBytes = getInputVBytes(fromAddressType);
167
+ const outputVBytes = getOutputVBytes(toAddressType);
168
+ const baseVBytes = 10.5; // Base transaction overhead
169
+
170
+ // Sort UTXOs by value (descending) for better selection
171
+ const sortedUtxos = [...utxos].sort((a, b) => b.value - a.value);
172
+
173
+ const selectedUtxos: Utxo[] = [];
174
+ let totalValue = 0;
175
+
176
+ for (const utxo of sortedUtxos) {
177
+ selectedUtxos.push(utxo);
178
+ totalValue += utxo.value;
179
+
180
+ // Calculate fee with current selection (2 outputs: recipient + change)
181
+ const estimatedVBytes =
182
+ baseVBytes + selectedUtxos.length * inputVBytes + 2 * outputVBytes;
183
+ const estimatedFee = Math.ceil(estimatedVBytes * feeRate);
184
+
185
+ if (totalValue >= amount + estimatedFee) {
186
+ return { selectedUtxos, totalValue, estimatedFee };
187
+ }
188
+ }
189
+
190
+ // If we get here, we don't have enough funds
191
+ // Return what we have and let the caller handle the error
192
+ const estimatedVBytes =
193
+ baseVBytes + selectedUtxos.length * inputVBytes + 2 * outputVBytes;
194
+ const estimatedFee = Math.ceil(estimatedVBytes * feeRate);
195
+
196
+ return { selectedUtxos, totalValue, estimatedFee };
197
+ }
198
+
199
+ /**
200
+ * Generate a PSBT for sending Bitcoin
201
+ *
202
+ * @param toAddress - Recipient address
203
+ * @param amount - Amount to send in satoshis
204
+ * @param fromAddress - Sender address
205
+ * @param publicKey - Sender's public key (hex string)
206
+ * @param network - Bitcoin network
207
+ * @param options - Additional options (feeRate, feeRateMultiplier)
208
+ * @returns GeneratePsbtResult containing the PSBT and related info
209
+ */
210
+ export async function generatePsbtForSend(
211
+ toAddress: string,
212
+ amount: number,
213
+ fromAddress: string,
214
+ publicKey: string,
215
+ network: BitcoinNetwork,
216
+ options?: GeneratePsbtOptions
217
+ ): Promise<GeneratePsbtResult> {
218
+ const btcService = new BtcService(network);
219
+ const btcNetwork = getBitcoinJsNetwork(network);
220
+
221
+ // 1. Get UTXOs (only confirmed)
222
+ const allUtxos = await btcService.getUtxos(fromAddress);
223
+ const confirmedUtxos = allUtxos.filter(
224
+ (utxo) => utxo.status?.confirmed !== false
225
+ );
226
+
227
+ if (confirmedUtxos.length === 0) {
228
+ throw new Error('No confirmed UTXOs available for spending');
229
+ }
230
+
231
+ // 2. Get fee rate
232
+ let feeRate = options?.feeRate;
233
+ if (!feeRate) {
234
+ const feeRates = await btcService.getFeeRates();
235
+ feeRate = feeRates.hour;
236
+ }
237
+ if (options?.feeRateMultiplier) {
238
+ feeRate = Math.ceil(feeRate * options.feeRateMultiplier);
239
+ }
240
+
241
+ // 3. Get address types
242
+ const fromAddressType = getAddressType(fromAddress);
243
+ const toAddressType = getAddressType(toAddress);
244
+
245
+ // 4. Select UTXOs
246
+ const { selectedUtxos, totalValue, estimatedFee } = selectUtxos(
247
+ confirmedUtxos,
248
+ fromAddressType,
249
+ toAddressType,
250
+ amount,
251
+ feeRate
252
+ );
253
+
254
+ if (totalValue < amount + estimatedFee) {
255
+ throw new Error(
256
+ `Insufficient funds. Available: ${totalValue} sats, Required: ${amount + estimatedFee} sats (including fee)`
257
+ );
258
+ }
259
+
260
+ // 5. Calculate change
261
+ const dustThreshold = getDustThreshold(fromAddressType);
262
+ let changeAmount = totalValue - amount - estimatedFee;
263
+
264
+ // If change is below dust threshold, add it to fee
265
+ if (changeAmount > 0 && changeAmount <= dustThreshold) {
266
+ changeAmount = 0;
267
+ }
268
+
269
+ // 6. Build PSBT
270
+ const psbt = new bitcoin.Psbt({ network: btcNetwork });
271
+ const publicKeyBytes = hexToBytes(publicKey);
272
+ const inputsToSign: Array<{ address: string; index: number }> = [];
273
+
274
+ // Add inputs
275
+ for (let i = 0; i < selectedUtxos.length; i++) {
276
+ const utxo = selectedUtxos[i]!;
277
+ const script = bitcoin.address.toOutputScript(fromAddress, btcNetwork);
278
+
279
+ if (fromAddressType === 'taproot') {
280
+ // P2TR - Taproot
281
+ psbt.addInput({
282
+ hash: utxo.txid,
283
+ index: utxo.vout,
284
+ witnessUtxo: {
285
+ script,
286
+ value: BigInt(utxo.value),
287
+ },
288
+ tapInternalKey: toXOnly(publicKeyBytes),
289
+ });
290
+ } else if (fromAddressType === 'segwit') {
291
+ // P2WPKH - Native SegWit
292
+ psbt.addInput({
293
+ hash: utxo.txid,
294
+ index: utxo.vout,
295
+ witnessUtxo: {
296
+ script,
297
+ value: BigInt(utxo.value),
298
+ },
299
+ });
300
+ } else if (fromAddressType === 'nested-segwit') {
301
+ // P2SH-P2WPKH - Nested SegWit
302
+ const p2wpkh = bitcoin.payments.p2wpkh({
303
+ pubkey: publicKeyBytes,
304
+ network: btcNetwork,
305
+ });
306
+ if (!p2wpkh.output) {
307
+ throw new Error('Failed to generate P2WPKH redeem script');
308
+ }
309
+ psbt.addInput({
310
+ hash: utxo.txid,
311
+ index: utxo.vout,
312
+ witnessUtxo: {
313
+ script,
314
+ value: BigInt(utxo.value),
315
+ },
316
+ redeemScript: p2wpkh.output,
317
+ });
318
+ } else {
319
+ // Legacy P2PKH
320
+ psbt.addInput({
321
+ hash: utxo.txid,
322
+ index: utxo.vout,
323
+ witnessUtxo: {
324
+ script,
325
+ value: BigInt(utxo.value),
326
+ },
327
+ });
328
+ }
329
+
330
+ inputsToSign.push({ address: fromAddress, index: i });
331
+ }
332
+
333
+ // Add recipient output
334
+ psbt.addOutput({
335
+ address: toAddress,
336
+ value: BigInt(amount),
337
+ });
338
+
339
+ // Add change output if needed
340
+ if (changeAmount > 0) {
341
+ psbt.addOutput({
342
+ address: fromAddress,
343
+ value: BigInt(Math.floor(changeAmount)),
344
+ });
345
+ }
346
+
347
+ return {
348
+ psbt,
349
+ psbtHex: psbt.toHex(),
350
+ psbtBase64: psbt.toBase64(),
351
+ btcNetwork,
352
+ selectedUtxos,
353
+ totalInputValue: totalValue,
354
+ estimatedFee,
355
+ changeAmount,
356
+ inputsToSign,
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Finalize all inputs in a signed PSBT
362
+ */
363
+ export function finalizeAllInputs(
364
+ signedPsbt: bitcoin.Psbt,
365
+ inputCount: number
366
+ ): void {
367
+ for (let i = 0; i < inputCount; i++) {
368
+ try {
369
+ signedPsbt.finalizeInput(i);
370
+ } catch (finalizeError) {
371
+ console.warn(
372
+ `Failed to finalize input ${i}, attempting alternative finalization:`,
373
+ finalizeError
374
+ );
375
+ // For complex input types, try finalization with empty witness
376
+ try {
377
+ signedPsbt.finalizeInput(i, () => ({
378
+ finalScriptSig: undefined,
379
+ finalScriptWitness: undefined,
380
+ }));
381
+ } catch {
382
+ // If that also fails, just skip - the transaction might still work
383
+ console.warn(`Could not finalize input ${i}`);
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Extract and broadcast a signed PSBT
391
+ *
392
+ * @param signedPsbtHex - Signed PSBT hex string
393
+ * @param network - Bitcoin network
394
+ * @param inputCount - Number of inputs to finalize
395
+ * @returns Transaction ID
396
+ */
397
+ export async function finalizeAndBroadcast(
398
+ signedPsbtHex: string,
399
+ network: BitcoinNetwork,
400
+ inputCount: number
401
+ ): Promise<string> {
402
+ const btcNetwork = getBitcoinJsNetwork(network);
403
+ const btcService = new BtcService(network);
404
+
405
+ // Parse signed PSBT
406
+ const signedPsbt = bitcoin.Psbt.fromHex(signedPsbtHex, { network: btcNetwork });
407
+
408
+ // Finalize all inputs
409
+ finalizeAllInputs(signedPsbt, inputCount);
410
+
411
+ // Extract transaction
412
+ const tx = signedPsbt.extractTransaction();
413
+ const txHex = tx.toHex();
414
+
415
+ // Broadcast
416
+ return await btcService.broadcastTransaction(txHex);
417
+ }
418
+
419
+ /**
420
+ * Derive Bitcoin address from public key based on address type
421
+ *
422
+ * @param publicKeyHex - Public key in hex format (33 bytes compressed)
423
+ * @param addressType - Type of address to derive (legacy, nested-segwit, segwit, taproot)
424
+ * @param network - Bitcoin network
425
+ * @returns Derived Bitcoin address
426
+ */
427
+ export function deriveAddressFromPublicKey(
428
+ publicKeyHex: string,
429
+ addressType: AddressType,
430
+ network: BitcoinNetwork
431
+ ): string {
432
+ const btcNetwork = getBitcoinJsNetwork(network);
433
+ const publicKeyBytes = hexToBytes(publicKeyHex);
434
+
435
+ switch (addressType) {
436
+ case 'legacy': {
437
+ // P2PKH - Legacy address (starts with 1 or m/n)
438
+ const p2pkh = bitcoin.payments.p2pkh({
439
+ pubkey: publicKeyBytes,
440
+ network: btcNetwork,
441
+ });
442
+ if (!p2pkh.address) {
443
+ throw new Error('Failed to derive legacy address from public key');
444
+ }
445
+ return p2pkh.address;
446
+ }
447
+
448
+ case 'nested-segwit': {
449
+ // P2SH-P2WPKH - Nested SegWit (starts with 3 or 2)
450
+ const p2wpkh = bitcoin.payments.p2wpkh({
451
+ pubkey: publicKeyBytes,
452
+ network: btcNetwork,
453
+ });
454
+ const p2sh = bitcoin.payments.p2sh({
455
+ redeem: p2wpkh,
456
+ network: btcNetwork,
457
+ });
458
+ if (!p2sh.address) {
459
+ throw new Error('Failed to derive nested-segwit address from public key');
460
+ }
461
+ return p2sh.address;
462
+ }
463
+
464
+ case 'segwit': {
465
+ // P2WPKH - Native SegWit (starts with bc1q or tb1q)
466
+ const p2wpkh = bitcoin.payments.p2wpkh({
467
+ pubkey: publicKeyBytes,
468
+ network: btcNetwork,
469
+ });
470
+ if (!p2wpkh.address) {
471
+ throw new Error('Failed to derive segwit address from public key');
472
+ }
473
+ return p2wpkh.address;
474
+ }
475
+
476
+ case 'taproot': {
477
+ // P2TR - Taproot (starts with bc1p or tb1p)
478
+ const xOnlyPubKey = toXOnly(publicKeyBytes);
479
+ const p2tr = bitcoin.payments.p2tr({
480
+ internalPubkey: xOnlyPubKey,
481
+ network: btcNetwork,
482
+ });
483
+ if (!p2tr.address) {
484
+ throw new Error('Failed to derive taproot address from public key');
485
+ }
486
+ return p2tr.address;
487
+ }
488
+
489
+ default:
490
+ throw new Error(`Unsupported address type: ${addressType}`);
491
+ }
492
+ }
@@ -0,0 +1,183 @@
1
+ import type { BitcoinNetwork } from 'otx-btc-wallet-core';
2
+
3
+ /**
4
+ * UTXO (Unspent Transaction Output) interface
5
+ */
6
+ export interface Utxo {
7
+ txid: string;
8
+ vout: number;
9
+ value: number; // in satoshis
10
+ status: {
11
+ confirmed: boolean;
12
+ block_height?: number;
13
+ block_hash?: string;
14
+ block_time?: number;
15
+ };
16
+ }
17
+
18
+ /**
19
+ * UTXO with raw transaction hex (needed for Ledger signing)
20
+ */
21
+ export interface UtxoWithTx extends Utxo {
22
+ txHex: string;
23
+ }
24
+
25
+ /**
26
+ * Fee rates response
27
+ */
28
+ export interface FeeRates {
29
+ fastest: number;
30
+ halfHour: number;
31
+ hour: number;
32
+ economy: number;
33
+ minimum: number;
34
+ }
35
+
36
+ /**
37
+ * Transaction details
38
+ */
39
+ export interface Transaction {
40
+ txid: string;
41
+ version: number;
42
+ locktime: number;
43
+ size: number;
44
+ weight: number;
45
+ fee: number;
46
+ status: {
47
+ confirmed: boolean;
48
+ block_height?: number;
49
+ block_hash?: string;
50
+ block_time?: number;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Full transaction with inputs and outputs (for Trezor refTxs)
56
+ */
57
+ export interface FullTransaction extends Transaction {
58
+ vin: Array<{
59
+ txid: string;
60
+ vout: number;
61
+ sequence: number;
62
+ scriptsig: string;
63
+ scriptsig_asm?: string;
64
+ witness?: string[];
65
+ prevout?: {
66
+ scriptpubkey: string;
67
+ scriptpubkey_asm?: string;
68
+ scriptpubkey_type?: string;
69
+ scriptpubkey_address?: string;
70
+ value: number;
71
+ };
72
+ }>;
73
+ vout: Array<{
74
+ scriptpubkey: string;
75
+ scriptpubkey_asm?: string;
76
+ scriptpubkey_type?: string;
77
+ scriptpubkey_address?: string;
78
+ value: number;
79
+ }>;
80
+ }
81
+
82
+ /**
83
+ * Address balance
84
+ */
85
+ export interface AddressBalance {
86
+ address: string;
87
+ chain_stats: {
88
+ funded_txo_count: number;
89
+ funded_txo_sum: number;
90
+ spent_txo_count: number;
91
+ spent_txo_sum: number;
92
+ tx_count: number;
93
+ };
94
+ mempool_stats: {
95
+ funded_txo_count: number;
96
+ funded_txo_sum: number;
97
+ spent_txo_count: number;
98
+ spent_txo_sum: number;
99
+ tx_count: number;
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Bitcoin service interface that both Mempool and Blockstream services implement
105
+ */
106
+ export interface IBtcService {
107
+ readonly name: string;
108
+ readonly network: BitcoinNetwork;
109
+
110
+ setNetwork(network: BitcoinNetwork): void;
111
+
112
+ // UTXO methods
113
+ getUtxos(address: string): Promise<Utxo[]>;
114
+ getUtxosWithTxHex(address: string): Promise<UtxoWithTx[]>;
115
+
116
+ // Transaction methods
117
+ getTxHex(txid: string): Promise<string>;
118
+ getTransaction(txid: string): Promise<Transaction>;
119
+ getFullTransaction(txid: string): Promise<FullTransaction>;
120
+ broadcastTransaction(txHex: string): Promise<string>;
121
+
122
+ // Address methods
123
+ getBalance(address: string): Promise<number>;
124
+ getConfirmedBalance(address: string): Promise<number>;
125
+ getAddressInfo(address: string): Promise<AddressBalance>;
126
+
127
+ // Fee methods
128
+ getFeeRates(): Promise<FeeRates>;
129
+ }
130
+
131
+ /**
132
+ * Network-specific endpoint URLs
133
+ */
134
+ export interface NetworkEndpoints {
135
+ mainnet?: string;
136
+ testnet?: string;
137
+ testnet4?: string;
138
+ signet?: string;
139
+ }
140
+
141
+ /**
142
+ * Configuration for BtcService endpoints
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // Use custom mempool instance
147
+ * const config: BtcServiceConfig = {
148
+ * mempool: {
149
+ * mainnet: 'https://my-mempool.com/api',
150
+ * testnet: 'https://my-mempool.com/testnet/api',
151
+ * }
152
+ * };
153
+ *
154
+ * // Or use only one provider
155
+ * const config: BtcServiceConfig = {
156
+ * preferredProvider: 'mempool',
157
+ * mempool: {
158
+ * mainnet: 'https://custom-mempool.example.com/api',
159
+ * }
160
+ * };
161
+ * ```
162
+ */
163
+ export interface BtcServiceConfig {
164
+ /**
165
+ * Custom Mempool.space API endpoints
166
+ * Default: https://mempool.space/api (mainnet)
167
+ */
168
+ mempool?: NetworkEndpoints;
169
+
170
+ /**
171
+ * Custom Blockstream.info API endpoints
172
+ * Default: https://blockstream.info/api (mainnet)
173
+ */
174
+ blockstream?: NetworkEndpoints;
175
+
176
+ /**
177
+ * Preferred provider to use ('mempool' | 'blockstream' | 'race')
178
+ * - 'mempool': Only use Mempool API
179
+ * - 'blockstream': Only use Blockstream API
180
+ * - 'race': Use both and return fastest response (default)
181
+ */
182
+ preferredProvider?: 'mempool' | 'blockstream' | 'race';
183
+ }