@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/compat/Utxo.js +50 -0
- package/dist/cjs/src/compat/Utxo.js.map +1 -0
- package/dist/cjs/src/compat/index.js +3 -1
- package/dist/cjs/src/compat/index.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +0 -1
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +77 -11
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/compat/Utxo.js +44 -0
- package/dist/esm/src/compat/Utxo.js.map +1 -0
- package/dist/esm/src/compat/index.js +1 -0
- package/dist/esm/src/compat/index.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +0 -1
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +77 -11
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/compat/Utxo.d.ts +42 -0
- package/dist/types/src/compat/Utxo.d.ts.map +1 -0
- package/dist/types/src/compat/index.d.ts +1 -0
- package/dist/types/src/compat/index.d.ts.map +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +14 -0
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts +0 -4
- package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/compat.md +52 -10
- package/docs/transaction.md +36 -1
- package/package.json +1 -1
- package/src/compat/Utxo.ts +55 -0
- package/src/compat/index.ts +1 -0
- package/src/script/templates/P2PKH.ts +0 -1
- package/src/transaction/Transaction.ts +103 -36
- package/src/transaction/TransactionInput.ts +0 -4
- 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
|
+
}
|
package/src/compat/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'
|
|
320
|
-
throw new Error('Source transactions
|
|
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
|
|
429
|
+
getFee(): number {
|
|
368
430
|
let totalIn = 0
|
|
369
431
|
for (const input of this.inputs) {
|
|
370
|
-
if (typeof input.sourceTransaction !== 'object'
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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 =
|
|
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
|
-
|
|
434
|
-
|
|
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 =
|
|
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)
|