@bsv/sdk 1.0.39 → 1.1.1

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 (38) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/compat/Utxo.js +50 -0
  3. package/dist/cjs/src/compat/Utxo.js.map +1 -0
  4. package/dist/cjs/src/compat/index.js +3 -1
  5. package/dist/cjs/src/compat/index.js.map +1 -1
  6. package/dist/cjs/src/script/templates/P2PKH.js +0 -1
  7. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  8. package/dist/cjs/src/transaction/Transaction.js +77 -11
  9. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/compat/Utxo.js +44 -0
  12. package/dist/esm/src/compat/Utxo.js.map +1 -0
  13. package/dist/esm/src/compat/index.js +1 -0
  14. package/dist/esm/src/compat/index.js.map +1 -1
  15. package/dist/esm/src/script/templates/P2PKH.js +0 -1
  16. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  17. package/dist/esm/src/transaction/Transaction.js +77 -11
  18. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/compat/Utxo.d.ts +42 -0
  21. package/dist/types/src/compat/Utxo.d.ts.map +1 -0
  22. package/dist/types/src/compat/index.d.ts +1 -0
  23. package/dist/types/src/compat/index.d.ts.map +1 -1
  24. package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
  25. package/dist/types/src/transaction/Transaction.d.ts +14 -0
  26. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  27. package/dist/types/src/transaction/TransactionInput.d.ts +0 -4
  28. package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
  29. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  30. package/docs/compat.md +52 -10
  31. package/docs/transaction.md +36 -1
  32. package/package.json +1 -1
  33. package/src/compat/Utxo.ts +55 -0
  34. package/src/compat/index.ts +1 -0
  35. package/src/script/templates/P2PKH.ts +0 -1
  36. package/src/transaction/Transaction.ts +103 -36
  37. package/src/transaction/TransactionInput.ts +0 -4
  38. package/src/transaction/__tests/Transaction.test.ts +38 -31
@@ -0,0 +1,55 @@
1
+ import Transaction from '../transaction/Transaction.js'
2
+ import TransactionInput from '../transaction/TransactionInput.js'
3
+ import LockingScript from '../script/LockingScript.js'
4
+ import UnlockingScript from '../script/UnlockingScript.js'
5
+
6
+ type jsonUtxo = {
7
+ txid: string
8
+ vout: number
9
+ satoshis: number
10
+ script: string
11
+ }
12
+ /**
13
+ * @method fromUtxo
14
+ *
15
+ * @description
16
+ * This function creates a transaction input from a utxo json object
17
+ * The idea being old code that uses utxos rather than sourceTranactions can convert using this.
18
+ *
19
+ * @deprecated
20
+ * This approach is made available for compatibility only. It is deprecated in favor of using sourceTransactions
21
+ * directly. It's recommended that wallets general keep transactions which store unspent outputs in their entirety,
22
+ * along with corresonding Merkle paths. The reason you would keep the whole transaction is such that you can prove
23
+ * the txid, and therefore its inclusion within a specific block.
24
+ *
25
+ * @example
26
+ * const i = fromUtxo({
27
+ * txid: '434555433eaca96dff6e71a4d02febd0dd3832e5ca4e5734623ca914522e17d5',
28
+ * vout: 0,
29
+ * script: '51',
30
+ * satoshis: 1234
31
+ * }, new P2PKH().unlock(p))
32
+ *
33
+ * tx.addInput(i)
34
+ *
35
+ * @param utxo: jsonUtxo
36
+ * @param unlockingScriptTemplate: { sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>, estimateLength: (tx: Transaction, inputIndex: number) => Promise<number> }
37
+ * @returns
38
+ */
39
+ export default function fromUtxo(utxo: jsonUtxo, unlockingScriptTemplate: {
40
+ sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
41
+ estimateLength: (tx: Transaction, inputIndex: number) => Promise<number>
42
+ }): TransactionInput {
43
+ const sourceTransaction = new Transaction(0, [], [], 0)
44
+ sourceTransaction.outputs = Array(utxo.vout + 1).fill(null)
45
+ sourceTransaction.outputs[utxo.vout] = {
46
+ satoshis: utxo.satoshis,
47
+ lockingScript: LockingScript.fromHex(utxo.script)
48
+ }
49
+ return {
50
+ sourceTransaction,
51
+ sourceOutputIndex: utxo.vout,
52
+ unlockingScriptTemplate,
53
+ sequence: 0xFFFFFFFF
54
+ }
55
+ }
@@ -2,3 +2,4 @@ export * as BSM from './BSM.js'
2
2
  export { default as HD } from './HD.js'
3
3
  export { default as Mnemonic } from './Mnemonic.js'
4
4
  export { default as ECIES } from './ECIES.js'
5
+ export { default as fromUtxo } from './Utxo.js'
@@ -91,7 +91,6 @@ export default class P2PKH implements ScriptTemplate {
91
91
  )
92
92
  }
93
93
  sourceSatoshis ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
94
- sourceSatoshis ||= input.sourceSatoshis
95
94
  if (!sourceSatoshis) {
96
95
  throw new Error(
97
96
  'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
@@ -62,7 +62,7 @@ export default class Transaction {
62
62
  * @param beef A binary representation of a transaction in BEEF format.
63
63
  * @returns An anchored transaction, linked to its associated inputs populated with merkle paths.
64
64
  */
65
- static fromBEEF (beef: number[]): Transaction {
65
+ static fromBEEF(beef: number[]): Transaction {
66
66
  const reader = new Reader(beef)
67
67
  // Read the version
68
68
  const version = reader.readUInt32LE()
@@ -122,6 +122,59 @@ export default class Transaction {
122
122
  return transactions[lastTXID].tx
123
123
  }
124
124
 
125
+
126
+ /**
127
+ * Creates a new transaction, linked to its inputs and their associated merkle paths, from a EF (BRC-30) structure.
128
+ * @param ef A binary representation of a transaction in EF format.
129
+ * @returns An extended transaction, linked to its associated inputs by locking script and satoshis amounts only.
130
+ */
131
+ static fromEF(ef: number[]): Transaction {
132
+ const br = new Reader(ef)
133
+ const version = br.readUInt32LE()
134
+ if (toHex(br.read(6)) !== '0000000000ef') throw new Error('Invalid EF marker')
135
+ const inputsLength = br.readVarIntNum()
136
+ const inputs: TransactionInput[] = []
137
+ for (let i = 0; i < inputsLength; i++) {
138
+ const sourceTXID = toHex(br.readReverse(32))
139
+ const sourceOutputIndex = br.readUInt32LE()
140
+ const scriptLength = br.readVarIntNum()
141
+ const scriptBin = br.read(scriptLength)
142
+ const unlockingScript = UnlockingScript.fromBinary(scriptBin)
143
+ const sequence = br.readUInt32LE()
144
+ const satoshis = br.readUInt64LEBn().toNumber()
145
+ const lockingScriptLength = br.readVarIntNum()
146
+ const lockingScriptBin = br.read(lockingScriptLength)
147
+ const lockingScript = LockingScript.fromBinary(lockingScriptBin)
148
+ const sourceTransaction = new Transaction(null, [], [], null)
149
+ sourceTransaction.outputs = Array(sourceOutputIndex + 1).fill(null)
150
+ sourceTransaction.outputs[sourceOutputIndex] = {
151
+ satoshis,
152
+ lockingScript
153
+ }
154
+ inputs.push({
155
+ sourceTransaction,
156
+ sourceTXID,
157
+ sourceOutputIndex,
158
+ unlockingScript,
159
+ sequence
160
+ })
161
+ }
162
+ const outputsLength = br.readVarIntNum()
163
+ const outputs: TransactionOutput[] = []
164
+ for (let i = 0; i < outputsLength; i++) {
165
+ const satoshis = br.readUInt64LEBn().toNumber()
166
+ const scriptLength = br.readVarIntNum()
167
+ const scriptBin = br.read(scriptLength)
168
+ const lockingScript = LockingScript.fromBinary(scriptBin)
169
+ outputs.push({
170
+ satoshis,
171
+ lockingScript
172
+ })
173
+ }
174
+ const lockTime = br.readUInt32LE()
175
+ return new Transaction(version, inputs, outputs, lockTime)
176
+ }
177
+
125
178
  /**
126
179
  * Since the validation of blockchain data is atomically transaction data validation,
127
180
  * any application seeking to validate data in output scripts must store the entire transaction as well.
@@ -137,7 +190,7 @@ export default class Transaction {
137
190
  * outputs: { vout: number, offset: number, length: number }[]
138
191
  * }
139
192
  */
140
- static parseScriptOffsets (bin: number[]): {
193
+ static parseScriptOffsets(bin: number[]): {
141
194
  inputs: Array<{ vin: number, offset: number, length: number }>
142
195
  outputs: Array<{ vout: number, offset: number, length: number }>
143
196
  } {
@@ -163,7 +216,7 @@ export default class Transaction {
163
216
  return { inputs, outputs }
164
217
  }
165
218
 
166
- private static fromReader (br: Reader): Transaction {
219
+ private static fromReader(br: Reader): Transaction {
167
220
  const version = br.readUInt32LE()
168
221
  const inputsLength = br.readVarIntNum()
169
222
  const inputs: TransactionInput[] = []
@@ -204,7 +257,7 @@ export default class Transaction {
204
257
  * @param {number[]} bin - The binary array representation of the transaction.
205
258
  * @returns {Transaction} - A new Transaction instance.
206
259
  */
207
- static fromBinary (bin: number[]): Transaction {
260
+ static fromBinary(bin: number[]): Transaction {
208
261
  const br = new Reader(bin)
209
262
  return Transaction.fromReader(br)
210
263
  }
@@ -216,10 +269,21 @@ export default class Transaction {
216
269
  * @param {string} hex - The hexadecimal string representation of the transaction.
217
270
  * @returns {Transaction} - A new Transaction instance.
218
271
  */
219
- static fromHex (hex: string): Transaction {
272
+ static fromHex(hex: string): Transaction {
220
273
  return Transaction.fromBinary(toArray(hex, 'hex'))
221
274
  }
222
275
 
276
+ /**
277
+ * Creates a Transaction instance from a hexadecimal string encoded EF.
278
+ *
279
+ * @static
280
+ * @param {string} hex - The hexadecimal string representation of the transaction EF.
281
+ * @returns {Transaction} - A new Transaction instance.
282
+ */
283
+ static fromHexEF(hex: string): Transaction {
284
+ return Transaction.fromEF(toArray(hex, 'hex'))
285
+ }
286
+
223
287
  /**
224
288
  * Creates a Transaction instance from a hexadecimal string encoded BEEF.
225
289
  *
@@ -227,11 +291,11 @@ export default class Transaction {
227
291
  * @param {string} hex - The hexadecimal string representation of the transaction BEEF.
228
292
  * @returns {Transaction} - A new Transaction instance.
229
293
  */
230
- static fromHexBEEF (hex: string): Transaction {
294
+ static fromHexBEEF(hex: string): Transaction {
231
295
  return Transaction.fromBEEF(toArray(hex, 'hex'))
232
296
  }
233
297
 
234
- constructor (
298
+ constructor(
235
299
  version: number = 1,
236
300
  inputs: TransactionInput[] = [],
237
301
  outputs: TransactionOutput[] = [],
@@ -253,7 +317,7 @@ export default class Transaction {
253
317
  * @param {TransactionInput} input - The TransactionInput object to add to the transaction.
254
318
  * @throws {Error} - If the input does not have a sourceTXID or sourceTransaction defined.
255
319
  */
256
- addInput (input: TransactionInput): void {
320
+ addInput(input: TransactionInput): void {
257
321
  if (
258
322
  typeof input.sourceTXID === 'undefined' &&
259
323
  typeof input.sourceTransaction === 'undefined'
@@ -273,7 +337,7 @@ export default class Transaction {
273
337
  *
274
338
  * @param {TransactionOutput} output - The TransactionOutput object to add to the transaction.
275
339
  */
276
- addOutput (output: TransactionOutput): void {
340
+ addOutput(output: TransactionOutput): void {
277
341
  this.cachedHash = undefined
278
342
  this.outputs.push(output)
279
343
  }
@@ -283,7 +347,7 @@ export default class Transaction {
283
347
  *
284
348
  * @param {Record<string, any>} metadata - The metadata object to merge into the existing metadata.
285
349
  */
286
- updateMetadata (metadata: Record<string, any>): void {
350
+ updateMetadata(metadata: Record<string, any>): void {
287
351
  this.metadata = {
288
352
  ...this.metadata,
289
353
  ...metadata
@@ -301,7 +365,7 @@ export default class Transaction {
301
365
  *
302
366
  * TODO: Benford's law change distribution.
303
367
  */
304
- async fee (modelOrFee?: FeeModel | number, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
368
+ async fee(modelOrFee?: FeeModel | number, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
305
369
  this.cachedHash = undefined
306
370
  if (typeof modelOrFee === 'undefined') {
307
371
  modelOrFee = new SatoshisPerKilobyte(10)
@@ -316,12 +380,10 @@ export default class Transaction {
316
380
  // change = inputs - fee - non-change outputs
317
381
  let change = 0
318
382
  for (const input of this.inputs) {
319
- if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
320
- throw new Error('Source transactions or sourceSatoshis are required for all inputs during fee computation')
383
+ if (typeof input.sourceTransaction !== 'object') {
384
+ throw new Error('Source transactions are required for all inputs during fee computation')
321
385
  }
322
- change += input.sourceTransaction
323
- ? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
324
- : input.sourceSatoshis
386
+ change += input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
325
387
  }
326
388
  change -= fee
327
389
  let changeCount = 0
@@ -364,15 +426,13 @@ export default class Transaction {
364
426
  *
365
427
  * @returns The current transaction fee
366
428
  */
367
- getFee (): number {
429
+ getFee(): number {
368
430
  let totalIn = 0
369
431
  for (const input of this.inputs) {
370
- if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
432
+ if (typeof input.sourceTransaction !== 'object') {
371
433
  throw new Error('Source transactions or sourceSatoshis are required for all inputs to calculate fee')
372
434
  }
373
- totalIn += input.sourceTransaction
374
- ? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
375
- : input.sourceSatoshis || 0
435
+ totalIn += input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
376
436
  }
377
437
  let totalOut = 0
378
438
  for (const output of this.outputs) {
@@ -384,7 +444,7 @@ export default class Transaction {
384
444
  /**
385
445
  * Signs a transaction, hydrating all its unlocking scripts based on the provided script templates where they are available.
386
446
  */
387
- async sign (): Promise<void> {
447
+ async sign(): Promise<void> {
388
448
  this.cachedHash = undefined
389
449
  for (const out of this.outputs) {
390
450
  if (typeof out.satoshis === 'undefined') {
@@ -415,7 +475,7 @@ export default class Transaction {
415
475
  * @param broadcaster The Broadcaster instance wwhere the transaction will be sent
416
476
  * @returns A BroadcastResponse or BroadcastFailure from the Broadcaster
417
477
  */
418
- async broadcast (broadcaster: Broadcaster = defaultBroadcaster()): Promise<BroadcastResponse | BroadcastFailure> {
478
+ async broadcast(broadcaster: Broadcaster = defaultBroadcaster()): Promise<BroadcastResponse | BroadcastFailure> {
419
479
  return await broadcaster.broadcast(this)
420
480
  }
421
481
 
@@ -424,7 +484,7 @@ export default class Transaction {
424
484
  *
425
485
  * @returns {number[]} - The binary array representation of the transaction.
426
486
  */
427
- toBinary (): number[] {
487
+ toBinary(): number[] {
428
488
  const writer = new Writer()
429
489
  writer.writeUInt32LE(this.version)
430
490
  writer.writeVarIntNum(this.inputs.length)
@@ -456,7 +516,7 @@ export default class Transaction {
456
516
  *
457
517
  * @returns {number[]} - The BRC-30 EF representation of the transaction.
458
518
  */
459
- toEF (): number[] {
519
+ toEF(): number[] {
460
520
  const writer = new Writer()
461
521
  writer.writeUInt32LE(this.version)
462
522
  writer.write([0, 0, 0, 0, 0, 0xef])
@@ -465,7 +525,11 @@ export default class Transaction {
465
525
  if (typeof i.sourceTransaction === 'undefined') {
466
526
  throw new Error('All inputs must have source transactions when serializing to EF format')
467
527
  }
468
- writer.write(i.sourceTransaction.hash() as number[])
528
+ if (typeof i.sourceTXID === 'undefined') {
529
+ writer.write(i.sourceTransaction.hash() as number[])
530
+ } else {
531
+ writer.write(toArray(i.sourceTXID, 'hex').reverse() as number[])
532
+ }
469
533
  writer.writeUInt32LE(i.sourceOutputIndex)
470
534
  const scriptBin = i.unlockingScript.toBinary()
471
535
  writer.writeVarIntNum(scriptBin.length)
@@ -492,7 +556,7 @@ export default class Transaction {
492
556
  *
493
557
  * @returns {string} - The hexadecimal string representation of the transaction EF.
494
558
  */
495
- toHexEF (): string {
559
+ toHexEF(): string {
496
560
  return toHex(this.toEF())
497
561
  }
498
562
 
@@ -501,7 +565,7 @@ export default class Transaction {
501
565
  *
502
566
  * @returns {string} - The hexadecimal string representation of the transaction.
503
567
  */
504
- toHex (): string {
568
+ toHex(): string {
505
569
  return toHex(this.toBinary())
506
570
  }
507
571
 
@@ -510,7 +574,7 @@ export default class Transaction {
510
574
  *
511
575
  * @returns {string} - The hexadecimal string representation of the transaction BEEF.
512
576
  */
513
- toHexBEEF (): string {
577
+ toHexBEEF(): string {
514
578
  return toHex(this.toBEEF())
515
579
  }
516
580
 
@@ -520,7 +584,7 @@ export default class Transaction {
520
584
  * @param {'hex' | undefined} enc - The encoding to use for the hash. If 'hex', returns a hexadecimal string; otherwise returns a binary array.
521
585
  * @returns {string | number[]} - The hash of the transaction in the specified format.
522
586
  */
523
- hash (enc?: 'hex'): number[] | string {
587
+ hash(enc?: 'hex'): number[] | string {
524
588
  let hash
525
589
  if (this.cachedHash) {
526
590
  hash = this.cachedHash
@@ -540,21 +604,21 @@ export default class Transaction {
540
604
  *
541
605
  * @returns {number[]} - The ID of the transaction in the binary array format.
542
606
  */
543
- id (): number[]
607
+ id(): number[]
544
608
  /**
545
609
  * Calculates the transaction's ID in hexadecimal format.
546
610
  *
547
611
  * @param {'hex'} enc - The encoding to use for the ID. If 'hex', returns a hexadecimal string.
548
612
  * @returns {string} - The ID of the transaction in the hex format.
549
613
  */
550
- id (enc: 'hex'): string
614
+ id(enc: 'hex'): string
551
615
  /**
552
616
  * Calculates the transaction's ID.
553
617
  *
554
618
  * @param {'hex' | undefined} enc - The encoding to use for the ID. If 'hex', returns a hexadecimal string; otherwise returns a binary array.
555
619
  * @returns {string | number[]} - The ID of the transaction in the specified format.
556
620
  */
557
- id (enc?: 'hex'): number[] | string {
621
+ id(enc?: 'hex'): number[] | string {
558
622
  const id = [...this.hash() as number[]]
559
623
  id.reverse()
560
624
  if (enc === 'hex') {
@@ -570,7 +634,7 @@ export default class Transaction {
570
634
  *
571
635
  * @returns Whether the transaction is valid according to the rules of SPV.
572
636
  */
573
- async verify (chainTracker: ChainTracker | 'scripts only' = defaultChainTracker()): Promise<boolean> {
637
+ async verify(chainTracker: ChainTracker | 'scripts only' = defaultChainTracker()): Promise<boolean> {
574
638
  // If the transaction has a valid merkle path, verification is complete.
575
639
  if (typeof this.merklePath === 'object' && chainTracker !== 'scripts only') {
576
640
  const proofValid = await this.merklePath.verify(
@@ -638,7 +702,7 @@ export default class Transaction {
638
702
  *
639
703
  * @returns The serialized BEEF structure
640
704
  */
641
- toBEEF (): number[] {
705
+ toBEEF(): number[] {
642
706
  const writer = new Writer()
643
707
  writer.writeUInt32LE(4022206465)
644
708
  const BUMPs: MerklePath[] = []
@@ -676,7 +740,10 @@ export default class Transaction {
676
740
  BUMPs.push(tx.merklePath)
677
741
  }
678
742
  }
679
- txs.unshift(obj)
743
+ const duplicate = txs.some(x => x.tx.id('hex') === tx.id('hex'))
744
+ if (!duplicate) {
745
+ txs.unshift(obj)
746
+ }
680
747
  if (!hasProof) {
681
748
  for (let i = 0; i < tx.inputs.length; i++) {
682
749
  const input = tx.inputs[i]
@@ -16,9 +16,6 @@ import Transaction from './Transaction.js'
16
16
  * @property {number} sourceOutputIndex - The index of the output in the source transaction
17
17
  * that this input is spending. It is zero-based, indicating the position of the
18
18
  * output in the array of outputs of the source transaction.
19
- * @property {number} [sourceSatoshis] - The amount of satoshis of the source transaction
20
- * output that this input is spending, used for fee calculation and signing when
21
- * source transaction is not present
22
19
  * @property {UnlockingScript} [unlockingScript] - Optional. The script that 'unlocks' the
23
20
  * source output for spending. This script typically contains signatures and
24
21
  * public keys that evidence the ownership of the output.
@@ -57,7 +54,6 @@ export default interface TransactionInput {
57
54
  sourceTransaction?: Transaction
58
55
  sourceTXID?: string
59
56
  sourceOutputIndex: number
60
- sourceSatoshis?: number
61
57
  unlockingScript?: UnlockingScript
62
58
  unlockingScriptTemplate?: {
63
59
  sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
@@ -9,6 +9,7 @@ import { hash256, hash160 } from '../../../dist/cjs/src/primitives/Hash'
9
9
  import PrivateKey from '../../../dist/cjs/src/primitives/PrivateKey'
10
10
  import Curve from '../../../dist/cjs/src/primitives/Curve'
11
11
  import P2PKH from '../../../dist/cjs/src/script/templates/P2PKH'
12
+ import fromUtxo from '../../../dist/cjs/src/compat/Utxo'
12
13
 
13
14
  import sighashVectors from '../../primitives/__tests/sighash.vectors'
14
15
  import invalidTransactions from './tx.invalid.vectors'
@@ -85,23 +86,23 @@ describe('Transaction', () => {
85
86
  })
86
87
 
87
88
  describe('#parseScriptOffsets', () => {
88
- it('should match sliced scripts to parsed scripts', async () => {
89
- const tx = Transaction.fromBinary(tx2buf)
90
- expect(tx.id("hex")).toBe(tx2idhex)
91
- const r = Transaction.parseScriptOffsets(tx2buf)
92
- expect(r.inputs.length).toBe(2)
93
- expect(r.outputs.length).toBe(2)
94
- for (let vin = 0; vin < 2; vin++) {
95
- const i = r.inputs[vin]
96
- const script = tx2buf.slice(i.offset, i.length + i.offset)
97
- expect(script).toEqual(tx.inputs[vin].unlockingScript?.toBinary())
98
- }
99
- for (let vout = 0; vout < 2; vout++) {
100
- const o = r.outputs[vout]
101
- const script = tx2buf.slice(o.offset, o.length + o.offset)
102
- expect(script).toEqual(tx.outputs[vout].lockingScript?.toBinary())
103
- }
104
- })
89
+ it('should match sliced scripts to parsed scripts', async () => {
90
+ const tx = Transaction.fromBinary(tx2buf)
91
+ expect(tx.id("hex")).toBe(tx2idhex)
92
+ const r = Transaction.parseScriptOffsets(tx2buf)
93
+ expect(r.inputs.length).toBe(2)
94
+ expect(r.outputs.length).toBe(2)
95
+ for (let vin = 0; vin < 2; vin++) {
96
+ const i = r.inputs[vin]
97
+ const script = tx2buf.slice(i.offset, i.length + i.offset)
98
+ expect(script).toEqual(tx.inputs[vin].unlockingScript?.toBinary())
99
+ }
100
+ for (let vout = 0; vout < 2; vout++) {
101
+ const o = r.outputs[vout]
102
+ const script = tx2buf.slice(o.offset, o.length + o.offset)
103
+ expect(script).toEqual(tx.outputs[vout].lockingScript?.toBinary())
104
+ }
105
+ })
105
106
  })
106
107
 
107
108
  describe('#toHex', () => {
@@ -396,14 +397,13 @@ describe('Transaction', () => {
396
397
  const priv = PrivateKey.fromRandom()
397
398
  const tx = new Transaction()
398
399
  utxos.forEach(utxo => {
399
- const script = new P2PKH().lock(priv.toPublicKey().toHash())
400
- tx.addInput({
401
- sourceTXID: utxo.tx_hash,
402
- sourceOutputIndex: utxo.tx_pos,
403
- sourceSatoshis: utxo.value,
404
- unlockingScriptTemplate: new P2PKH()
405
- .unlock(priv, 'all', false, utxo.value, script)
406
- })
400
+ const u = {
401
+ txid: utxo.tx_hash,
402
+ vout: utxo.tx_pos,
403
+ script: new P2PKH().lock(priv.toPublicKey().toHash()).toHex(),
404
+ satoshis: utxo.value
405
+ }
406
+ tx.addInput(fromUtxo(u, new P2PKH().unlock(priv)))
407
407
  })
408
408
  tx.addOutput({
409
409
  lockingScript: new P2PKH().lock(priv.toAddress()),
@@ -417,7 +417,7 @@ describe('Transaction', () => {
417
417
 
418
418
  describe('Broadcast', () => {
419
419
  it('Broadcasts with the default Broadcaster instance', async () => {
420
- const mockedFetch = jest.fn().mockResolvedValue({
420
+ const mockedFetch = jest.fn().mockResolvedValue({
421
421
  ok: true,
422
422
  status: 200,
423
423
  statusText: 'OK',
@@ -430,12 +430,12 @@ describe('Transaction', () => {
430
430
  },
431
431
  json: async () => ({
432
432
  txid: 'mocked_txid',
433
- txStatus: 'success',
434
- extraInfo: 'received'
433
+ txStatus: 'success',
434
+ extraInfo: 'received'
435
435
  })
436
436
  });
437
437
 
438
- (global as any).window = {fetch: mockedFetch} as any
438
+ (global as any).window = { fetch: mockedFetch } as any
439
439
 
440
440
  const tx = new Transaction()
441
441
  const rv = await tx.broadcast()
@@ -468,6 +468,13 @@ describe('Transaction', () => {
468
468
  const beef = toHex(tx.toBEEF())
469
469
  expect(beef).toEqual(BRC62Hex)
470
470
  })
471
+ it('Does not double-encode transactions', () => {
472
+ // Source: https://github.com/bitcoin-sv/ts-sdk/issues/77
473
+ const correct = '0100beef01fe76eb0c000e02c402deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111c500f4076b7f48031fc467f87d5e99d9c3c0b59e4dca5e3049f58b735c59b413a8b6016300bad9c2d948e8a2ca647fdb50f2fd36641c4adf937b41134405a3e7f734b8beb201300053604a579558b5f7030e618d5c726a19229e0ff677f6edf109f41c5cfdafc93e0119005f8465c2a8d1558afbfa80c2395f3f8866a2fa5015e54fab778b0149da58376c010d00cd452b4e74f57d199cdb81b8a0e4a62dcdaf89504d6c63a5a65d5b866912b8c0010700d2ae7e2ce76da560509172066f1a1cf81faf81d73f9c0f6fd5af0904973dcfb10102006e5e077bcaa35c0240d61c1f3bba8d789223711ec035ef88b0911fc569d2b95a010000c961038959b9d404297a180c066816562dd2a34986c0960121a87ba91a51262f010100a50e381b4e8812479ea561e5bab7dcaa80078652b1b39ee5410966c515a3442b010100383ce8891ca7bf1ddefa5e0d8a1ba9ab01cb4e18046e9d7d0d438b5aaecc38b2010100c694be322b4e74acca8a5ef7703afedb708281321fd674f1221eebc743b0e01c0101000f3cc61f2b3d762cfecdd977ba768a5cbb0a4b402ad4f0d1bd3a98a582794c35010100094ad56eeea3b47edb2b298775f2efabe48172612cb3419962632251d8cdb78e010100de84bf9dd8873f37decbda1b5188e24ead978b147a63c809691702d19c47e8cb030200000001b67f1b6a6c3e70742a39b82ba45d51c983f598963ebf237101cc372da1144b83020000006b483045022100d14c3eb0c1438747c124f099bc664bf945cd27cbd96915027057e508bbc9e03302203c73f79d4e00f8018783e1008ce0fbb8e8c58bff6bd8042ab7e3966a66c8788c41210356762156ee9347c3e2ceca17d13713028d5446f453e9cbcb0ea170b4ca7fab52ffffffff037c660000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac75330100000000001976a914eb645f9ea7e4e232e54b9651474c100026698c3088acf2458005000000001976a914802737e30c85b6fe86e26fb28e03140058aca65e88ac0000000001000100000001deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111000000006a473044022076da9f61380c208f43652587c219b4452a7b803a0407c2c7c0f3bc27612c4e88022021a9eb02da5529873a5986933f9c35965aa78537b9e2aef9382de33cfb1ab4bb41210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff023c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac3c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac000000000001000000022e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b000000006b483045022100d9a2d1efea4896b36b2eb5af42cf52009982c7c31b446213fe37f26835d9d72202203e4dee0ceb068a4936e79b0bf69f72203906a00a4256cb1a7b30a40764616e8441210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff2e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b010000006b483045022100b57a09145c57b7b5efb4b546f1b0bfb7adbc5e64d35d9d6989345d4c60c483940220280998a210a49a6efaacda6fb73670001bb7269d069be80eb14ea2227a73e82241210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff0174660000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac0000000000'
474
+ const incorrect = '0100beef01fe77eb0c000e02fdd8140017899f90c0513b78b832ea9b25596cdd5132c570addd0f4cf40d23030243b36afdd914022e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b01fd6d0a0004307f45c624b6f19e6f85ce0e9b56931aae58affef11a225bb564aa5f785bc101fd370500952937347592ef6df9d578da0d04c889193e7a3c807638549b49c697098596de01fd9a020035c7831b6f5088ca905b169ff33814284f6a27aa7362db5b7f0de517c3853fa901fd4c01004123b25530fe6a11b16cb3725e0b1ffff109e3ed8993116ff025a97b2cbcd46601a700adefed13a8d2a1a091b9b1807c1434611a5403845b6fa78bbb5d660af4e81602015200d0967c10ee6fd2b74e8989dc7e36eeb3ae13fe6cae0dcbb39ca79782ec729bbf0128003ff7aeff8cdb8ffecc82465ec67d64a29bbf81f725cf1da2911e9741e1667e1c011500d0e20a1598a95a7df7b54ac2e82325b0f5ae7924ce8f4b55520b42547c4dc1b1010b003642e6fd39340cf8626cff5aefb8dffb7483d0936ca241b4cef7cd819813b2820104000589e88fae5f2d465f2e7a5edaef98ebcd7d0b39f848fba1e86f0f3a93d4898b010300a161114db7317b74e1f535b8dc0bccd3c7c64c2812d6efbd7dbcdd5d5fc55012010000726acffff2f3cb821c418c81c6329360ea7a88c37d2bf8eba01bd4eb9ca32210010100bc8e959a21ffe2c6fcc64f292d8023557c1b4d52ff4ff829840ca5091d00c31b020100000001deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111000000006a473044022076da9f61380c208f43652587c219b4452a7b803a0407c2c7c0f3bc27612c4e88022021a9eb02da5529873a5986933f9c35965aa78537b9e2aef9382de33cfb1ab4bb41210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff023c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac3c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac00000000010001000000012e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b000000006b483045022100fbfd5d1969c455d0a8d8c5a78809b9fe4a98531d2ebaafc84b0d3411b577573f02204f8e945b76495ad46e57db3d09e8284d7859e8b6f7595808c56fef29356e313c41210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff013a330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac0000000000'
475
+ const tx1 = Transaction.fromHexBEEF(correct)
476
+ expect(tx1.toHexBEEF()).toEqual(correct)
477
+ })
471
478
  })
472
479
 
473
480
  describe('EF', () => {
@@ -489,7 +496,7 @@ describe('Transaction', () => {
489
496
  })
490
497
 
491
498
  it('Verifies the transaction from the BEEF spec with a default chain tracker', async () => {
492
- const mockFetch = jest.fn().mockResolvedValue({
499
+ const mockFetch = jest.fn().mockResolvedValue({
493
500
  ok: true,
494
501
  status: 200,
495
502
  statusText: 'OK',
@@ -504,7 +511,7 @@ describe('Transaction', () => {
504
511
  merkleroot: MerkleRootFromBEEF,
505
512
  })
506
513
  });
507
- (global as any).window = {fetch: mockFetch}
514
+ (global as any).window = { fetch: mockFetch }
508
515
 
509
516
 
510
517
  const tx = Transaction.fromHexBEEF(BRC62Hex)