@bsv/sdk 1.0.32 → 1.0.34

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 (138) hide show
  1. package/README.md +1 -3
  2. package/dist/cjs/mod.js +1 -0
  3. package/dist/cjs/mod.js.map +1 -1
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/src/primitives/PublicKey.js +36 -0
  6. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  7. package/dist/cjs/src/primitives/Signature.js +6 -6
  8. package/dist/cjs/src/primitives/Signature.js.map +1 -1
  9. package/dist/cjs/src/script/Script.js.map +1 -1
  10. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  11. package/dist/cjs/src/transaction/Transaction.js +5 -3
  12. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  13. package/dist/cjs/src/transaction/broadcasters/ARC.js +39 -57
  14. package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
  15. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +12 -0
  16. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
  17. package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +66 -0
  18. package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
  19. package/dist/cjs/src/transaction/broadcasters/index.js +5 -1
  20. package/dist/cjs/src/transaction/broadcasters/index.js.map +1 -1
  21. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js +12 -0
  22. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
  23. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js +49 -0
  24. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
  25. package/dist/cjs/src/transaction/chaintrackers/index.js +11 -0
  26. package/dist/cjs/src/transaction/chaintrackers/index.js.map +1 -0
  27. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +37 -0
  28. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -0
  29. package/dist/cjs/src/transaction/http/FetchHttpClient.js +29 -0
  30. package/dist/cjs/src/transaction/http/FetchHttpClient.js.map +1 -0
  31. package/dist/cjs/src/transaction/http/HttpClient.js +3 -0
  32. package/dist/cjs/src/transaction/http/HttpClient.js.map +1 -0
  33. package/dist/cjs/src/transaction/http/NodejsHttpClient.js +41 -0
  34. package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -0
  35. package/dist/cjs/src/transaction/http/index.js +10 -0
  36. package/dist/cjs/src/transaction/http/index.js.map +1 -0
  37. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  38. package/dist/esm/mod.js +1 -0
  39. package/dist/esm/mod.js.map +1 -1
  40. package/dist/esm/src/primitives/PublicKey.js +36 -0
  41. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  42. package/dist/esm/src/primitives/Signature.js +6 -6
  43. package/dist/esm/src/primitives/Signature.js.map +1 -1
  44. package/dist/esm/src/script/Script.js.map +1 -1
  45. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  46. package/dist/esm/src/transaction/Transaction.js +5 -3
  47. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  48. package/dist/esm/src/transaction/broadcasters/ARC.js +37 -57
  49. package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
  50. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js +5 -0
  51. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
  52. package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +65 -0
  53. package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
  54. package/dist/esm/src/transaction/broadcasters/index.js +2 -0
  55. package/dist/esm/src/transaction/broadcasters/index.js.map +1 -1
  56. package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js +5 -0
  57. package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
  58. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js +50 -0
  59. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
  60. package/dist/esm/src/transaction/chaintrackers/index.js +3 -0
  61. package/dist/esm/src/transaction/chaintrackers/index.js.map +1 -0
  62. package/dist/esm/src/transaction/http/DefaultHttpClient.js +33 -0
  63. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -0
  64. package/dist/esm/src/transaction/http/FetchHttpClient.js +26 -0
  65. package/dist/esm/src/transaction/http/FetchHttpClient.js.map +1 -0
  66. package/dist/esm/src/transaction/http/HttpClient.js +2 -0
  67. package/dist/esm/src/transaction/http/HttpClient.js.map +1 -0
  68. package/dist/esm/src/transaction/http/NodejsHttpClient.js +38 -0
  69. package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -0
  70. package/dist/esm/src/transaction/http/index.js +4 -0
  71. package/dist/esm/src/transaction/http/index.js.map +1 -0
  72. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  73. package/dist/types/mod.d.ts +1 -0
  74. package/dist/types/mod.d.ts.map +1 -1
  75. package/dist/types/src/primitives/PublicKey.d.ts +18 -0
  76. package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
  77. package/dist/types/src/primitives/Signature.d.ts +3 -3
  78. package/dist/types/src/script/Script.d.ts.map +1 -1
  79. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  80. package/dist/types/src/transaction/Transaction.d.ts +7 -7
  81. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  82. package/dist/types/src/transaction/broadcasters/ARC.d.ts +23 -7
  83. package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
  84. package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts +3 -0
  85. package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts.map +1 -0
  86. package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts +26 -0
  87. package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts.map +1 -0
  88. package/dist/types/src/transaction/broadcasters/index.d.ts +3 -0
  89. package/dist/types/src/transaction/broadcasters/index.d.ts.map +1 -1
  90. package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts +3 -0
  91. package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts.map +1 -0
  92. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts +28 -0
  93. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts.map +1 -0
  94. package/dist/types/src/transaction/chaintrackers/index.d.ts +4 -0
  95. package/dist/types/src/transaction/chaintrackers/index.d.ts.map +1 -0
  96. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +8 -0
  97. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -0
  98. package/dist/types/src/transaction/http/FetchHttpClient.d.ts +31 -0
  99. package/dist/types/src/transaction/http/FetchHttpClient.d.ts.map +1 -0
  100. package/dist/types/src/transaction/http/HttpClient.d.ts +43 -0
  101. package/dist/types/src/transaction/http/HttpClient.d.ts.map +1 -0
  102. package/dist/types/src/transaction/http/NodejsHttpClient.d.ts +21 -0
  103. package/dist/types/src/transaction/http/NodejsHttpClient.d.ts.map +1 -0
  104. package/dist/types/src/transaction/http/index.d.ts +7 -0
  105. package/dist/types/src/transaction/http/index.d.ts.map +1 -0
  106. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  107. package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +2 -0
  108. package/docs/examples/EXAMPLE_COMPLEX_TX.md +2 -4
  109. package/docs/examples/EXAMPLE_SIMPLE_TX.md +83 -2
  110. package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +29 -22
  111. package/docs/examples/EXAMPLE_VERIFYING_ROOTS.md +18 -63
  112. package/docs/examples/GETTING_STARTED_NODE_CJS.md +2 -4
  113. package/docs/examples/GETTING_STARTED_REACT.md +2 -4
  114. package/docs/primitives.md +36 -3
  115. package/docs/transaction.md +48 -0
  116. package/mod.ts +2 -1
  117. package/package.json +21 -1
  118. package/src/primitives/PublicKey.ts +39 -0
  119. package/src/primitives/Signature.ts +6 -6
  120. package/src/script/Script.ts +19 -19
  121. package/src/transaction/MerklePath.ts +9 -9
  122. package/src/transaction/Transaction.ts +13 -13
  123. package/src/transaction/__tests/Transaction.test.ts +62 -0
  124. package/src/transaction/broadcasters/ARC.ts +71 -57
  125. package/src/transaction/broadcasters/DefaultBroadcaster.ts +6 -0
  126. package/src/transaction/broadcasters/WhatsOnChainBroadcaster.ts +70 -0
  127. package/src/transaction/broadcasters/__tests/ARC.test.ts +152 -41
  128. package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +165 -0
  129. package/src/transaction/broadcasters/index.ts +3 -0
  130. package/src/transaction/chaintrackers/DefaultChainTracker.ts +6 -0
  131. package/src/transaction/chaintrackers/WhatsOnChain.ts +70 -0
  132. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +135 -0
  133. package/src/transaction/chaintrackers/index.ts +3 -0
  134. package/src/transaction/http/DefaultHttpClient.ts +32 -0
  135. package/src/transaction/http/FetchHttpClient.ts +50 -0
  136. package/src/transaction/http/HttpClient.ts +45 -0
  137. package/src/transaction/http/NodejsHttpClient.ts +55 -0
  138. package/src/transaction/http/index.ts +6 -0
@@ -21,7 +21,7 @@ export default class Script {
21
21
  * @example
22
22
  * const script = Script.fromASM("OP_DUP OP_HASH160 abcd... OP_EQUALVERIFY OP_CHECKSIG")
23
23
  */
24
- static fromASM(asm: string): Script {
24
+ static fromASM (asm: string): Script {
25
25
  const chunks: ScriptChunk[] = []
26
26
  const tokens = asm.split(' ')
27
27
  let i = 0
@@ -100,7 +100,7 @@ export default class Script {
100
100
  * @example
101
101
  * const script = Script.fromHex("76a9...");
102
102
  */
103
- static fromHex(hex: string): Script {
103
+ static fromHex (hex: string): Script {
104
104
  if (hex.length === 0) return Script.fromBinary([])
105
105
  if (hex.length % 2 !== 0) throw new Error('There is an uneven number of characters in the string which suggests it is not hex encoded.')
106
106
  if (!/^[0-9a-fA-F]+$/.test(hex)) throw new Error('Some elements in this string are not hex encoded.')
@@ -115,7 +115,7 @@ export default class Script {
115
115
  * @example
116
116
  * const script = Script.fromBinary([0x76, 0xa9, ...])
117
117
  */
118
- static fromBinary(bin: number[]): Script {
118
+ static fromBinary (bin: number[]): Script {
119
119
  bin = [...bin]
120
120
  const chunks: ScriptChunk[] = []
121
121
 
@@ -179,7 +179,7 @@ export default class Script {
179
179
  * Constructs a new Script object.
180
180
  * @param chunks=[] - An array of script chunks to directly initialize the script.
181
181
  */
182
- constructor(chunks: ScriptChunk[] = []) {
182
+ constructor (chunks: ScriptChunk[] = []) {
183
183
  this.chunks = chunks
184
184
  }
185
185
 
@@ -188,7 +188,7 @@ export default class Script {
188
188
  * Serializes the script to an ASM formatted string.
189
189
  * @returns The script in ASM string format.
190
190
  */
191
- toASM(): string {
191
+ toASM (): string {
192
192
  let str = ''
193
193
  for (let i = 0; i < this.chunks.length; i++) {
194
194
  const chunk = this.chunks[i]
@@ -203,7 +203,7 @@ export default class Script {
203
203
  * Serializes the script to a hexadecimal string.
204
204
  * @returns The script in hexadecimal format.
205
205
  */
206
- toHex(): string {
206
+ toHex (): string {
207
207
  return encode(this.toBinary(), 'hex') as string
208
208
  }
209
209
 
@@ -212,7 +212,7 @@ export default class Script {
212
212
  * Serializes the script to a binary array.
213
213
  * @returns The script in binary array format.
214
214
  */
215
- toBinary(): number[] {
215
+ toBinary (): number[] {
216
216
  const writer = new Writer()
217
217
 
218
218
  for (let i = 0; i < this.chunks.length; i++) {
@@ -244,7 +244,7 @@ export default class Script {
244
244
  * @param script - The script to append.
245
245
  * @returns This script instance for chaining.
246
246
  */
247
- writeScript(script: Script): Script {
247
+ writeScript (script: Script): Script {
248
248
  this.chunks = this.chunks.concat(script.chunks)
249
249
  return this
250
250
  }
@@ -255,7 +255,7 @@ export default class Script {
255
255
  * @param op - The opcode to append.
256
256
  * @returns This script instance for chaining.
257
257
  */
258
- writeOpCode(op: number): Script {
258
+ writeOpCode (op: number): Script {
259
259
  this.chunks.push({ op })
260
260
  return this
261
261
  }
@@ -267,7 +267,7 @@ export default class Script {
267
267
  * @param op - The opcode to set.
268
268
  * @returns This script instance for chaining.
269
269
  */
270
- setChunkOpCode(i: number, op: number): Script {
270
+ setChunkOpCode (i: number, op: number): Script {
271
271
  this.chunks[i] = { op }
272
272
  return this
273
273
  }
@@ -278,7 +278,7 @@ export default class Script {
278
278
  * @param bn - The BigNumber to append.
279
279
  * @returns This script instance for chaining.
280
280
  */
281
- writeBn(bn: BigNumber): Script {
281
+ writeBn (bn: BigNumber): Script {
282
282
  if (bn.cmpn(0) === OP.OP_0) {
283
283
  this.chunks.push({
284
284
  op: OP.OP_0
@@ -306,7 +306,7 @@ export default class Script {
306
306
  * @returns This script instance for chaining.
307
307
  * @throws {Error} Throws an error if the data is too large to be pushed.
308
308
  */
309
- writeBin(bin: number[]): Script {
309
+ writeBin (bin: number[]): Script {
310
310
  let op
311
311
  if (bin.length > 0 && bin.length < OP.OP_PUSHDATA1) {
312
312
  op = bin.length
@@ -334,7 +334,7 @@ export default class Script {
334
334
  * @param num - The number to append.
335
335
  * @returns This script instance for chaining.
336
336
  */
337
- writeNumber(num: number): Script {
337
+ writeNumber (num: number): Script {
338
338
  this.writeBn(new BigNumber(num))
339
339
  return this
340
340
  }
@@ -344,7 +344,7 @@ export default class Script {
344
344
  * Removes all OP_CODESEPARATOR opcodes from the script.
345
345
  * @returns This script instance for chaining.
346
346
  */
347
- removeCodeseparators(): Script {
347
+ removeCodeseparators (): Script {
348
348
  const chunks = []
349
349
  for (let i = 0; i < this.chunks.length; i++) {
350
350
  if (this.chunks[i].op !== OP.OP_CODESEPARATOR) {
@@ -362,7 +362,7 @@ export default class Script {
362
362
  *
363
363
  * @returns This script instance for chaining.
364
364
  */
365
- findAndDelete(script: Script): Script {
365
+ findAndDelete (script: Script): Script {
366
366
  const buf = script.toHex()
367
367
  for (let i = 0; i < this.chunks.length; i++) {
368
368
  const script2 = new Script([this.chunks[i]])
@@ -379,7 +379,7 @@ export default class Script {
379
379
  * Checks if the script contains only push data operations.
380
380
  * @returns True if the script is push-only, otherwise false.
381
381
  */
382
- isPushOnly(): boolean {
382
+ isPushOnly (): boolean {
383
383
  for (let i = 0; i < this.chunks.length; i++) {
384
384
  const chunk = this.chunks[i]
385
385
  const opCodeNum = chunk.op
@@ -395,7 +395,7 @@ export default class Script {
395
395
  * Determines if the script is a locking script.
396
396
  * @returns True if the script is a locking script, otherwise false.
397
397
  */
398
- isLockingScript(): boolean {
398
+ isLockingScript (): boolean {
399
399
  throw new Error('Not implemented')
400
400
  }
401
401
 
@@ -404,7 +404,7 @@ export default class Script {
404
404
  * Determines if the script is an unlocking script.
405
405
  * @returns True if the script is an unlocking script, otherwise false.
406
406
  */
407
- isUnlockingScript(): boolean {
407
+ isUnlockingScript (): boolean {
408
408
  throw new Error('Not implemented')
409
409
  }
410
410
 
@@ -415,7 +415,7 @@ export default class Script {
415
415
  * @param chunk - The script chunk.
416
416
  * @returns The string representation of the chunk.
417
417
  */
418
- private _chunkToString(chunk: ScriptChunk): string {
418
+ private _chunkToString (chunk: ScriptChunk): string {
419
419
  const op = chunk.op
420
420
  let str = ''
421
421
  if (typeof chunk.data === 'undefined') {
@@ -39,11 +39,11 @@ export default class MerklePath {
39
39
  * @param {string} hex - The hexadecimal string representation of the Merkle Path.
40
40
  * @returns {MerklePath} - A new MerklePath instance.
41
41
  */
42
- static fromHex(hex: string): MerklePath {
42
+ static fromHex (hex: string): MerklePath {
43
43
  return MerklePath.fromBinary(toArray(hex, 'hex'))
44
44
  }
45
45
 
46
- static fromReader(reader: Reader): MerklePath {
46
+ static fromReader (reader: Reader): MerklePath {
47
47
  const blockHeight = reader.readVarIntNum()
48
48
  const treeHeight = reader.readUInt8()
49
49
  const path = Array(treeHeight).fill(0).map(() => ([]))
@@ -82,12 +82,12 @@ export default class MerklePath {
82
82
  * @param {number[]} bump - The binary array representation of the Merkle Path.
83
83
  * @returns {MerklePath} - A new MerklePath instance.
84
84
  */
85
- static fromBinary(bump: number[]): MerklePath {
85
+ static fromBinary (bump: number[]): MerklePath {
86
86
  const reader = new Reader(bump)
87
87
  return MerklePath.fromReader(reader)
88
88
  }
89
89
 
90
- constructor(blockHeight: number, path: Array<Array<{
90
+ constructor (blockHeight: number, path: Array<Array<{
91
91
  offset: number
92
92
  hash?: string
93
93
  txid?: boolean
@@ -135,7 +135,7 @@ export default class MerklePath {
135
135
  *
136
136
  * @returns {number[]} - The binary array representation of the Merkle Path.
137
137
  */
138
- toBinary(): number[] {
138
+ toBinary (): number[] {
139
139
  const writer = new Writer()
140
140
  writer.writeVarIntNum(this.blockHeight)
141
141
  const treeHeight = this.path.length
@@ -166,7 +166,7 @@ export default class MerklePath {
166
166
  *
167
167
  * @returns {string} - The hexadecimal string representation of the Merkle Path.
168
168
  */
169
- toHex(): string {
169
+ toHex (): string {
170
170
  return toHex(this.toBinary())
171
171
  }
172
172
 
@@ -177,7 +177,7 @@ export default class MerklePath {
177
177
  * @returns {string} - The computed Merkle root as a hexadecimal string.
178
178
  * @throws {Error} - If the transaction ID is not part of the Merkle Path.
179
179
  */
180
- computeRoot(txid?: string): string {
180
+ computeRoot (txid?: string): string {
181
181
  if (typeof txid !== 'string') {
182
182
  txid = this.path[0].find(leaf => Boolean(leaf?.hash)).hash
183
183
  }
@@ -216,7 +216,7 @@ export default class MerklePath {
216
216
  * @param {ChainTracker} chainTracker - The ChainTracker instance used to verify the Merkle root.
217
217
  * @returns {boolean} - True if the transaction ID is valid within the Merkle Path at the specified block height.
218
218
  */
219
- async verify(txid: string, chainTracker: ChainTracker): Promise<boolean> {
219
+ async verify (txid: string, chainTracker: ChainTracker): Promise<boolean> {
220
220
  const root = this.computeRoot(txid)
221
221
  // Use the chain tracker to determine whether this is a valid merkle root at the given block height
222
222
  return await chainTracker.isValidRootForHeight(root, this.blockHeight)
@@ -228,7 +228,7 @@ export default class MerklePath {
228
228
  * @param {MerklePath} other - Another MerklePath to combine with this path.
229
229
  * @throws {Error} - If the paths have different block heights or roots.
230
230
  */
231
- combine(other: MerklePath): void {
231
+ combine (other: MerklePath): void {
232
232
  if (this.blockHeight !== other.blockHeight) {
233
233
  throw new Error('You cannot combine paths which do not have the same block height.')
234
234
  }
@@ -10,6 +10,8 @@ import { Broadcaster, BroadcastResponse, BroadcastFailure } from './Broadcaster.
10
10
  import MerklePath from './MerklePath.js'
11
11
  import Spend from '../script/Spend.js'
12
12
  import ChainTracker from './ChainTracker.js'
13
+ import {defaultBroadcaster} from "./broadcasters/DefaultBroadcaster.js";
14
+ import {defaultChainTracker} from "./chaintrackers/DefaultChainTracker.js";
13
15
 
14
16
  /**
15
17
  * Represents a complete Bitcoin transaction. This class encapsulates all the details
@@ -125,25 +127,23 @@ export default class Transaction {
125
127
  * any application seeking to validate data in output scripts must store the entire transaction as well.
126
128
  * Since the transaction data includes the output script data, saving a second copy of potentially
127
129
  * large scripts can bloat application storage requirements.
128
- *
130
+ *
129
131
  * This function efficiently parses binary transaction data to determine the offsets and lengths of each script.
130
132
  * This supports the efficient retreival of script data from transaction data.
131
- *
133
+ *
132
134
  * @param bin binary transaction data
133
135
  * @returns {
134
136
  * inputs: { vin: number, offset: number, length: number }[]
135
137
  * outputs: { vout: number, offset: number, length: number }[]
136
138
  * }
137
139
  */
138
- static parseScriptOffsets(bin: number[])
139
- : {
140
- inputs: { vin: number, offset: number, length: number }[]
141
- outputs: { vout: number, offset: number, length: number }[]
142
- }
143
- {
140
+ static parseScriptOffsets (bin: number[]): {
141
+ inputs: Array<{ vin: number, offset: number, length: number }>
142
+ outputs: Array<{ vout: number, offset: number, length: number }>
143
+ } {
144
144
  const br = new Reader(bin)
145
- const inputs: { vin: number, offset: number, length: number }[] = []
146
- const outputs: { vout: number, offset: number, length: number }[] = []
145
+ const inputs: Array<{ vin: number, offset: number, length: number }> = []
146
+ const outputs: Array<{ vout: number, offset: number, length: number }> = []
147
147
 
148
148
  br.pos += 4 // version
149
149
  const inputsLength = br.readVarIntNum()
@@ -384,7 +384,7 @@ export default class Transaction {
384
384
  * @param broadcaster The Broadcaster instance wwhere the transaction will be sent
385
385
  * @returns A BroadcastResponse or BroadcastFailure from the Broadcaster
386
386
  */
387
- async broadcast (broadcaster: Broadcaster): Promise<BroadcastResponse | BroadcastFailure> {
387
+ async broadcast (broadcaster: Broadcaster = defaultBroadcaster()): Promise<BroadcastResponse | BroadcastFailure> {
388
388
  return await broadcaster.broadcast(this)
389
389
  }
390
390
 
@@ -535,11 +535,11 @@ export default class Transaction {
535
535
  /**
536
536
  * Verifies the legitimacy of the Bitcoin transaction according to the rules of SPV by ensuring all the input transactions link back to valid block headers, the chain of spends for all inputs are valid, and the sum of inputs is not less than the sum of outputs.
537
537
  *
538
- * @param chainTracker - An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified.
538
+ * @param chainTracker - An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified. If not provided then the default chain tracker will be used.
539
539
  *
540
540
  * @returns Whether the transaction is valid according to the rules of SPV.
541
541
  */
542
- async verify (chainTracker: ChainTracker | 'scripts only'): Promise<boolean> {
542
+ async verify (chainTracker: ChainTracker | 'scripts only' = defaultChainTracker()): Promise<boolean> {
543
543
  // If the transaction has a valid merkle path, verification is complete.
544
544
  if (typeof this.merklePath === 'object' && chainTracker !== 'scripts only') {
545
545
  const proofValid = await this.merklePath.verify(
@@ -16,6 +16,7 @@ import validTransactions from './tx.valid.vectors'
16
16
  import bigTX from './bigtx.vectors'
17
17
 
18
18
  const BRC62Hex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000'
19
+ const MerkleRootFromBEEF = 'bb6f640cc4ee56bf38eb5a1969ac0c16caa2d3d202b22bf3735d10eec0ca6e00'
19
20
 
20
21
  describe('Transaction', () => {
21
22
  const txIn = {
@@ -354,6 +355,40 @@ describe('Transaction', () => {
354
355
  })
355
356
 
356
357
  describe('Broadcast', () => {
358
+ it('Broadcasts with the default Broadcaster instance', async () => {
359
+ const mockedFetch = jest.fn().mockResolvedValue({
360
+ ok: true,
361
+ status: 200,
362
+ statusText: 'OK',
363
+ headers: {
364
+ get(key: string) {
365
+ if (key === 'Content-Type') {
366
+ return 'application/json'
367
+ }
368
+ }
369
+ },
370
+ json: async () => ({
371
+ txid: 'mocked_txid',
372
+ txStatus: 'success',
373
+ extraInfo: 'received'
374
+ })
375
+ });
376
+
377
+ (global as any).window = {fetch: mockedFetch} as any
378
+
379
+ const tx = new Transaction()
380
+ const rv = await tx.broadcast()
381
+
382
+ expect(mockedFetch).toHaveBeenCalled()
383
+ const url = (mockedFetch as jest.Mock).mock.calls[0][0] as string
384
+ expect(url).toEqual('https://arc.taal.com/v1/tx')
385
+ expect(rv).toEqual({
386
+ status: 'success',
387
+ txid: 'mocked_txid',
388
+ message: 'success received'
389
+ })
390
+ })
391
+
357
392
  it('Broadcasts with the provided Broadcaster instance', async () => {
358
393
  const mockBroadcast = jest.fn(() => 'MOCK_RV')
359
394
  const tx = new Transaction()
@@ -391,6 +426,33 @@ describe('Transaction', () => {
391
426
  const verified = await tx.verify(alwaysYesChainTracker)
392
427
  expect(verified).toBe(true)
393
428
  })
429
+
430
+ it('Verifies the transaction from the BEEF spec with a default chain tracker', async () => {
431
+ const mockFetch = jest.fn().mockResolvedValue({
432
+ ok: true,
433
+ status: 200,
434
+ statusText: 'OK',
435
+ headers: {
436
+ get(key: string) {
437
+ if (key === 'Content-Type') {
438
+ return 'application/json'
439
+ }
440
+ }
441
+ },
442
+ json: async () => ({
443
+ merkleroot: MerkleRootFromBEEF,
444
+ })
445
+ });
446
+ (global as any).window = {fetch: mockFetch}
447
+
448
+
449
+ const tx = Transaction.fromHexBEEF(BRC62Hex)
450
+
451
+ const verified = await tx.verify()
452
+
453
+ expect(mockFetch).toHaveBeenCalled()
454
+ expect(verified).toBe(true)
455
+ })
394
456
  })
395
457
 
396
458
  describe('vectors: a 1mb transaction', () => {
@@ -1,33 +1,69 @@
1
- import { BroadcastResponse, BroadcastFailure, Broadcaster } from '../Broadcaster.js'
1
+ import {BroadcastResponse, BroadcastFailure, Broadcaster} from '../Broadcaster.js'
2
2
  import Transaction from '../Transaction.js'
3
+ import {HttpClient, HttpClientRequestOptions} from "../http/HttpClient.js";
4
+ import {defaultHttpClient} from "../http/DefaultHttpClient.js";
5
+ import Random from "../../primitives/Random.js";
6
+ import {toHex} from "../../primitives/utils.js";
7
+
8
+ /** Configuration options for the ARC broadcaster. */
9
+ export interface ArcConfig {
10
+ /** Authentication token for the ARC API */
11
+ apiKey?: string
12
+ /** Deployment id used annotating api calls in XDeployment-ID header - this value will be randomly generated if not set */
13
+ deploymentId?: string
14
+ /** The HTTP client used to make requests to the ARC API. */
15
+ httpClient?: HttpClient
16
+ }
17
+
18
+
19
+ function defaultDeploymentId() {
20
+ return `ts-sdk-${toHex(Random(16))}`;
21
+ }
3
22
 
4
23
  /**
5
24
  * Represents an ARC transaction broadcaster.
6
25
  */
7
26
  export default class ARC implements Broadcaster {
8
- URL: string
9
- apiKey: string
27
+ readonly URL: string
28
+ readonly apiKey: string | undefined
29
+ readonly deploymentId: string
30
+ private readonly httpClient: HttpClient;
10
31
 
32
+ /**
33
+ * Constructs an instance of the ARC broadcaster.
34
+ *
35
+ * @param {string} URL - The URL endpoint for the ARC API.
36
+ * @param {ArcConfig} config - Configuration options for the ARC broadcaster.
37
+ */
38
+ constructor(URL: string, config?: ArcConfig)
11
39
  /**
12
40
  * Constructs an instance of the ARC broadcaster.
13
41
  *
14
42
  * @param {string} URL - The URL endpoint for the ARC API.
15
43
  * @param {string} apiKey - The API key used for authorization with the ARC API.
16
44
  */
17
- constructor (URL: string, apiKey: string) {
45
+ constructor(URL: string, apiKey?: string)
46
+
47
+ constructor(URL: string, config?: string | ArcConfig) {
18
48
  this.URL = URL
19
- this.apiKey = apiKey
49
+ if (typeof config === 'string') {
50
+ this.apiKey = config
51
+ this.httpClient = defaultHttpClient()
52
+ } else {
53
+ const {apiKey, deploymentId, httpClient} = config ?? {} as ArcConfig
54
+ this.httpClient = httpClient ?? defaultHttpClient()
55
+ this.deploymentId = deploymentId ?? defaultDeploymentId()
56
+ this.apiKey = apiKey
57
+ }
20
58
  }
21
59
 
22
60
  /**
23
61
  * Broadcasts a transaction via ARC.
24
- * This method will attempt to use `window.fetch` if available (in browser environments).
25
- * If running in a Node.js environment, it falls back to using the Node.js `https` module.
26
62
  *
27
63
  * @param {Transaction} tx - The transaction to be broadcasted.
28
64
  * @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
29
65
  */
30
- async broadcast (tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
66
+ async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
31
67
  let rawTx
32
68
  try {
33
69
  rawTx = tx.toHexEF()
@@ -37,45 +73,28 @@ export default class ARC implements Broadcaster {
37
73
  } else {
38
74
  throw error
39
75
  }
40
- }
41
- const requestOptions = {
76
+ }
77
+
78
+ const requestOptions: HttpClientRequestOptions = {
42
79
  method: 'POST',
43
- headers: {
44
- 'Content-Type': 'application/json',
45
- Authorization: `Bearer ${this.apiKey}`
46
- },
47
- body: JSON.stringify({ rawTx })
80
+ headers: this.requestHeaders(),
81
+ data: {rawTx}
48
82
  }
49
83
 
50
84
  try {
51
- let response
52
- let data: any = {}
53
-
54
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
55
- // Use fetch in a browser environment
56
- response = await window.fetch(`${this.URL}/v1/tx`, requestOptions)
57
- data = await response.json()
58
- } else if (typeof require !== 'undefined') {
59
- // Use Node.js https module
60
- // eslint-disable-next-line
61
- const https = require('https')
62
- response = await this.nodeFetch(https, requestOptions)
63
- data = JSON.parse(response)
64
- } else {
65
- throw new Error('No method available to perform HTTP request')
66
- }
67
-
68
- if (data.txid as boolean || response.ok as boolean || response.statusCode === 200) {
85
+ const response = await this.httpClient.request<ArcResponse>(`${this.URL}/v1/tx`, requestOptions)
86
+ if (response.ok) {
87
+ const {txid, extraInfo, txStatus} = response.data
69
88
  return {
70
89
  status: 'success',
71
- txid: data.txid,
72
- message: data?.txStatus + ' ' + data?.extraInfo
90
+ txid: txid,
91
+ message: `${txStatus} ${extraInfo}`
73
92
  }
74
93
  } else {
75
94
  return {
76
95
  status: 'error',
77
- code: data.status as boolean ? data.status : 'ERR_UNKNOWN',
78
- description: data.detail as boolean ? data.detail : 'Unknown error'
96
+ code: response.status.toString() ?? 'ERR_UNKNOWN',
97
+ description: response.data?.detail ?? 'Unknown error'
79
98
  }
80
99
  }
81
100
  } catch (error) {
@@ -89,27 +108,22 @@ export default class ARC implements Broadcaster {
89
108
  }
90
109
  }
91
110
 
92
- /** Helper function for Node.js HTTPS requests */
93
- private async nodeFetch (https, requestOptions): Promise<any> {
94
- return await new Promise((resolve, reject) => {
95
- const req = https.request(`${this.URL}/v1/tx`, requestOptions, res => {
96
- let data = ''
97
- res.on('data', (chunk: string) => {
98
- data += chunk
99
- })
100
- res.on('end', () => {
101
- resolve(data)
102
- })
103
- })
111
+ private requestHeaders() {
112
+ const headers: Record<string, string> = {
113
+ 'Content-Type': 'application/json',
114
+ 'XDeployment-ID': this.deploymentId,
115
+ }
104
116
 
105
- req.on('error', error => {
106
- reject(error)
107
- })
117
+ if (this.apiKey) {
118
+ headers['Authorization'] = `Bearer ${this.apiKey}`
119
+ }
108
120
 
109
- if (requestOptions.body as boolean) {
110
- req.write(requestOptions.body)
111
- }
112
- req.end()
113
- })
121
+ return headers
114
122
  }
115
123
  }
124
+
125
+ interface ArcResponse {
126
+ txid: string
127
+ extraInfo: string
128
+ txStatus: string
129
+ }
@@ -0,0 +1,6 @@
1
+ import {Broadcaster} from "../Broadcaster.js";
2
+ import ARC from "./ARC.js";
3
+
4
+ export function defaultBroadcaster(): Broadcaster {
5
+ return new ARC('https://arc.taal.com')
6
+ }
@@ -0,0 +1,70 @@
1
+ import {BroadcastResponse, BroadcastFailure, Broadcaster} from '../Broadcaster.js'
2
+ import Transaction from '../Transaction.js'
3
+ import {HttpClient} from "../http/HttpClient.js";
4
+ import {defaultHttpClient} from "../http/DefaultHttpClient.js";
5
+
6
+ /**
7
+ * Represents an WhatsOnChain transaction broadcaster.
8
+ */
9
+ export default class WhatsOnChainBroadcaster implements Broadcaster {
10
+ readonly network: string
11
+ private readonly URL: string
12
+ private readonly httpClient: HttpClient;
13
+
14
+ /**
15
+ * Constructs an instance of the WhatsOnChain broadcaster.
16
+ *
17
+ * @param {'main' | 'test' | 'stn'} network - The BSV network to use when calling the WhatsOnChain API.
18
+ * @param {HttpClient} httpClient - The HTTP client used to make requests to the API.
19
+ */
20
+ constructor(network: 'main' | 'test' | 'stn' = 'main', httpClient: HttpClient = defaultHttpClient()) {
21
+ this.network = network
22
+ this.URL = `https://api.whatsonchain.com/v1/bsv/${network}/tx/raw`
23
+ this.httpClient = httpClient
24
+ }
25
+
26
+ /**
27
+ * Broadcasts a transaction via WhatsOnChain.
28
+ *
29
+ * @param {Transaction} tx - The transaction to be broadcasted.
30
+ * @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
31
+ */
32
+ async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
33
+ let rawTx = tx.toHex()
34
+
35
+ const requestOptions = {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Accept': 'text/plain'
40
+ },
41
+ data: {txhex: rawTx}
42
+ }
43
+
44
+ try {
45
+ const response = await this.httpClient.request<string>(this.URL, requestOptions)
46
+ if (response.ok) {
47
+ const txid = response.data
48
+ return {
49
+ status: 'success',
50
+ txid: txid,
51
+ message: 'broadcast successful'
52
+ }
53
+ } else {
54
+ return {
55
+ status: 'error',
56
+ code: response.status.toString() ?? 'ERR_UNKNOWN',
57
+ description: response.data ?? 'Unknown error'
58
+ }
59
+ }
60
+ } catch (error) {
61
+ return {
62
+ status: 'error',
63
+ code: '500',
64
+ description: typeof error.message === 'string'
65
+ ? error.message
66
+ : 'Internal Server Error'
67
+ }
68
+ }
69
+ }
70
+ }