@bsv/sdk 1.2.13 → 1.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +2 -0
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/transaction/MerklePath.js +18 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +26 -0
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +2 -0
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/transaction/MerklePath.js +18 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +26 -0
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts +12 -0
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +8 -0
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/transaction.md +35 -0
- package/package.json +1 -1
- package/src/script/templates/P2PKH.ts +1 -0
- package/src/transaction/MerklePath.ts +20 -1
- package/src/transaction/Transaction.ts +22 -0
- package/src/transaction/__tests/MerklePath.test.ts +7 -0
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +2 -2
- package/src/transaction/__tests/Transaction.test.ts +53 -0
package/docs/transaction.md
CHANGED
|
@@ -1219,6 +1219,7 @@ export default class MerklePath {
|
|
|
1219
1219
|
static fromHex(hex: string): MerklePath
|
|
1220
1220
|
static fromReader(reader: Reader): MerklePath
|
|
1221
1221
|
static fromBinary(bump: number[]): MerklePath
|
|
1222
|
+
static fromCoinbaseTxidAndHeight(txid: string, height: number): MerklePath
|
|
1222
1223
|
constructor(blockHeight: number, path: Array<Array<{
|
|
1223
1224
|
offset: number;
|
|
1224
1225
|
hash?: string;
|
|
@@ -1309,6 +1310,24 @@ Argument Details
|
|
|
1309
1310
|
+ **bump**
|
|
1310
1311
|
+ The binary array representation of the Merkle Path.
|
|
1311
1312
|
|
|
1313
|
+
#### Method fromCoinbaseTxidAndHeight
|
|
1314
|
+
|
|
1315
|
+
```ts
|
|
1316
|
+
static fromCoinbaseTxidAndHeight(txid: string, height: number): MerklePath
|
|
1317
|
+
```
|
|
1318
|
+
See also: [MerklePath](#class-merklepath)
|
|
1319
|
+
|
|
1320
|
+
Returns
|
|
1321
|
+
|
|
1322
|
+
- A new MerklePath instance which assumes the tx is in a block with no other transactions.
|
|
1323
|
+
|
|
1324
|
+
Argument Details
|
|
1325
|
+
|
|
1326
|
+
+ **txid**
|
|
1327
|
+
+ The coinbase txid.
|
|
1328
|
+
+ **height**
|
|
1329
|
+
+ The height of the block.
|
|
1330
|
+
|
|
1312
1331
|
#### Method fromHex
|
|
1313
1332
|
|
|
1314
1333
|
Creates a MerklePath instance from a hexadecimal string.
|
|
@@ -1505,6 +1524,7 @@ export default class Transaction {
|
|
|
1505
1524
|
constructor(version: number = 1, inputs: TransactionInput[] = [], outputs: TransactionOutput[] = [], lockTime: number = 0, metadata: Record<string, any> = {}, merklePath?: MerklePath)
|
|
1506
1525
|
addInput(input: TransactionInput): void
|
|
1507
1526
|
addOutput(output: TransactionOutput): void
|
|
1527
|
+
addP2PKHOutput(address: number[] | string, satoshis?: number): void
|
|
1508
1528
|
updateMetadata(metadata: Record<string, any>): void
|
|
1509
1529
|
async fee(modelOrFee: FeeModel | number = new SatoshisPerKilobyte(10), changeDistribution: "equal" | "random" = "equal"): Promise<void>
|
|
1510
1530
|
getFee(): number
|
|
@@ -1564,6 +1584,21 @@ Argument Details
|
|
|
1564
1584
|
+ **output**
|
|
1565
1585
|
+ The TransactionOutput object to add to the transaction.
|
|
1566
1586
|
|
|
1587
|
+
#### Method addP2PKHOutput
|
|
1588
|
+
|
|
1589
|
+
Adds a new P2PKH output to the transaction.
|
|
1590
|
+
|
|
1591
|
+
```ts
|
|
1592
|
+
addP2PKHOutput(address: number[] | string, satoshis?: number): void
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
Argument Details
|
|
1596
|
+
|
|
1597
|
+
+ **address**
|
|
1598
|
+
+ The P2PKH address of the output.
|
|
1599
|
+
+ **satoshis**
|
|
1600
|
+
+ The number of satoshis to send to the address - if not provided, the output is considered a change output.
|
|
1601
|
+
|
|
1567
1602
|
#### Method broadcast
|
|
1568
1603
|
|
|
1569
1604
|
Broadcasts a transaction.
|
package/package.json
CHANGED
|
@@ -94,6 +94,21 @@ export default class MerklePath {
|
|
|
94
94
|
return MerklePath.fromReader(reader)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @static fromCoinbaseTxid
|
|
100
|
+
*
|
|
101
|
+
* Creates a MerklePath instance for a coinbase transaction in an empty block.
|
|
102
|
+
* This edge case is difficult to retrieve from standard APIs.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} txid - The coinbase txid.
|
|
105
|
+
* @param {number} height - The height of the block.
|
|
106
|
+
* @returns {MerklePath} - A new MerklePath instance which assumes the tx is in a block with no other transactions.
|
|
107
|
+
*/
|
|
108
|
+
static fromCoinbaseTxidAndHeight (txid: string, height: number): MerklePath {
|
|
109
|
+
return new MerklePath(height, [[{ offset: 0, hash: txid, txid: true }]])
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
constructor (blockHeight: number, path: Array<Array<{
|
|
98
113
|
offset: number
|
|
99
114
|
hash?: string
|
|
@@ -127,8 +142,8 @@ export default class MerklePath {
|
|
|
127
142
|
})
|
|
128
143
|
})
|
|
129
144
|
|
|
130
|
-
let root: string
|
|
131
145
|
// every txid must calculate to the same root.
|
|
146
|
+
let root: string
|
|
132
147
|
this.path[0].map((leaf, idx) => {
|
|
133
148
|
if (idx === 0) root = this.computeRoot(leaf.hash)
|
|
134
149
|
if (root !== this.computeRoot(leaf.hash)) {
|
|
@@ -198,6 +213,10 @@ export default class MerklePath {
|
|
|
198
213
|
hash256(toArray(m, 'hex').reverse())
|
|
199
214
|
).reverse())
|
|
200
215
|
let workingHash = txid
|
|
216
|
+
|
|
217
|
+
// special case for blocks with only one transaction
|
|
218
|
+
if (this.path.length === 1 && this.path[0].length === 1) return workingHash
|
|
219
|
+
|
|
201
220
|
for (let height = 0; height < this.path.length; height++) {
|
|
202
221
|
const leaves = this.path[height]
|
|
203
222
|
const offset = index >> height ^ 1
|
|
@@ -13,6 +13,7 @@ import ChainTracker from './ChainTracker.js'
|
|
|
13
13
|
import { defaultBroadcaster } from './broadcasters/DefaultBroadcaster.js'
|
|
14
14
|
import { defaultChainTracker } from './chaintrackers/DefaultChainTracker.js'
|
|
15
15
|
import { ATOMIC_BEEF, BEEF_MAGIC } from './Beef.js'
|
|
16
|
+
import P2PKH from '../script/templates/P2PKH.js'
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Represents a complete Bitcoin transaction. This class encapsulates all the details
|
|
@@ -455,9 +456,30 @@ export default class Transaction {
|
|
|
455
456
|
*/
|
|
456
457
|
addOutput (output: TransactionOutput): void {
|
|
457
458
|
this.cachedHash = undefined
|
|
459
|
+
if (!output.change) {
|
|
460
|
+
if (typeof output.satoshis === 'undefined') throw new Error('either satoshis must be defined or change must be set to true')
|
|
461
|
+
if (output.satoshis <= 0) throw new Error('satoshis must be a positive integer or zero')
|
|
462
|
+
}
|
|
463
|
+
if (!output.lockingScript) throw new Error('lockingScript must be defined')
|
|
458
464
|
this.outputs.push(output)
|
|
459
465
|
}
|
|
460
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Adds a new P2PKH output to the transaction.
|
|
469
|
+
*
|
|
470
|
+
* @param {number[] | string} address - The P2PKH address of the output.
|
|
471
|
+
* @param {number} [satoshis] - The number of satoshis to send to the address - if not provided, the output is considered a change output.
|
|
472
|
+
*
|
|
473
|
+
*/
|
|
474
|
+
addP2PKHOutput (address: number[] | string, satoshis?: number): void {
|
|
475
|
+
const lockingScript = new P2PKH().lock(address)
|
|
476
|
+
if (typeof satoshis === 'undefined') { return this.addOutput({ lockingScript, change: true }) }
|
|
477
|
+
this.addOutput({
|
|
478
|
+
lockingScript,
|
|
479
|
+
satoshis
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
461
483
|
/**
|
|
462
484
|
* Updates the transaction's metadata.
|
|
463
485
|
*
|
|
@@ -185,4 +185,11 @@ describe('MerklePath', () => {
|
|
|
185
185
|
expect(() => MerklePath.fromHex(valid.bump)).not.toThrowError()
|
|
186
186
|
}
|
|
187
187
|
})
|
|
188
|
+
it('Validates a MerklePath for a block which only has 1 tx', () => {
|
|
189
|
+
const path = MerklePath.fromHex('fdd2040101000202ef57aa9f29c8141ae17935c88434457b2117890f23efba0d0e0cba7a7a37d5')
|
|
190
|
+
expect(path.computeRoot('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02')).toEqual('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02')
|
|
191
|
+
})
|
|
192
|
+
it('Creates a valid MerklePath from a txid', () => {
|
|
193
|
+
expect(() => MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 1)).not.toThrowError()
|
|
194
|
+
})
|
|
188
195
|
})
|
|
@@ -42,7 +42,7 @@ describe('Transaction Verification Benchmark', () => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Build the chain
|
|
45
|
-
for (let i =
|
|
45
|
+
for (let i = 1; i < depth + 1; i++) {
|
|
46
46
|
const newTx = new Transaction()
|
|
47
47
|
newTx.addInput({
|
|
48
48
|
sourceTransaction: tx,
|
|
@@ -52,7 +52,7 @@ describe('Transaction Verification Benchmark', () => {
|
|
|
52
52
|
})
|
|
53
53
|
newTx.addOutput({
|
|
54
54
|
lockingScript: p2pkh.lock(publicKeyHash),
|
|
55
|
-
satoshis: 100000 -
|
|
55
|
+
satoshis: 100000 - (i * 10)
|
|
56
56
|
})
|
|
57
57
|
await newTx.sign()
|
|
58
58
|
tx = newTx
|
|
@@ -661,6 +661,15 @@ describe('Transaction', () => {
|
|
|
661
661
|
const verified = await tx.verify("scripts only")
|
|
662
662
|
expect(verified).toBe(true)
|
|
663
663
|
})
|
|
664
|
+
|
|
665
|
+
it('Verifies tx scripts only when our input has no MerklePath.', async () => {
|
|
666
|
+
const sourceTransaction = Transaction.fromHex('01000000013834506e4af751dc3a0d73e9bdd067709ade5248860967fb3eb72854be6011dc020000006b4830450221008abf2ce1afbcc3c4895265b0966ac69e2275fb35a738f9873b5416cd3bb68cc102202c906f9771d2d6868f81a34d6a3af4cd7de070969286db7097c5e0f3a009b638412102f6a81c442dcd903c7808a75abde9217268bb70417ad538c18fb597b4c553c354ffffffff0301000000000000007c21039bf52152665699e24c1018bf5b48e575de316f844962a79db32b93b7e328bf71ac1062656e63686d61726b546f6b656e5f36463044022034ca3cc49163e4d5bc24f41c5ea4631efd4ae7c243f904c90e1db6e6a55e8f0e02206bb031baca2fc6d64da5ad722ce2e07137718e5e58092e5ecf54e52fcdd664ab6dc8000000000000001976a91470763a553d615a8144fa0889a0e1f93d1d8da8b088acaf430000000000001976a9145aa12f9be0121eee54e7331d8f995fe4dded72f588ac00000000')
|
|
667
|
+
const tx = Transaction.fromHex('01000000015c22e8d34a95a761a0685b8b5a99a0845e9f03d87d5716a7351d08939db5cd92020000006a47304402205101f2bdc40098cbde1a270071a5bfdd5a6fbefc8a38a8b4a1adaca0a6ff55e702200ec5c0a57cb65dfc6aa22e6856bb4c62b8987a7b9fa32cb601c94b579ba67d83412103c8be6b217fc55939851e28239fbbf731f997e026ad344aa9d4b739181fe96743ffffffff0301000000000000007c21039bf52152665699e24c1018bf5b48e575de316f844962a79db32b93b7e328bf71ac1062656e63686d61726b546f6b656e5f374630440220026808e9453f75ca1fc3c7bb0f7be1efaca5cea6b1eda96ab7db56c519b6793202207d1fa5e7d54254d2142256947f93a061781280a6ed2464052470633a7c888d1f6dc8000000000000001976a91428bb6f207ac8f4b7f6c1c5914e143a6165586b2488ace5420000000000001976a914197cbde77abbcb985ca60a85c2988b2ac32c541a88ac00000000')
|
|
668
|
+
sourceTransaction.merklePath = { assumeValid: true } // can be any object.
|
|
669
|
+
tx.inputs[0].sourceTransaction = sourceTransaction
|
|
670
|
+
const verified = await tx.verify('scripts only', undefined)
|
|
671
|
+
expect(verified).toBe(true)
|
|
672
|
+
})
|
|
664
673
|
})
|
|
665
674
|
|
|
666
675
|
describe('vectors: a 1mb transaction', () => {
|
|
@@ -964,4 +973,48 @@ describe('Transaction', () => {
|
|
|
964
973
|
}).toThrowError('Transaction with TXID 0000000000000000000000000000000000000000000000000000000000000000 not found in BEEF data.')
|
|
965
974
|
})
|
|
966
975
|
})
|
|
976
|
+
|
|
977
|
+
describe('addP2PKHOutput', () => {
|
|
978
|
+
it('should create an output on the current transaction using an address hash or string', async () => {
|
|
979
|
+
const privateKey = PrivateKey.fromRandom()
|
|
980
|
+
const lockingScript = new P2PKH().lock(privateKey.toAddress())
|
|
981
|
+
const tx = new Transaction()
|
|
982
|
+
tx.addInput({
|
|
983
|
+
sourceTXID: '00'.repeat(32),
|
|
984
|
+
sourceOutputIndex: 0,
|
|
985
|
+
unlockingScriptTemplate: new P2PKH().unlock(privateKey),
|
|
986
|
+
})
|
|
987
|
+
tx.addP2PKHOutput(privateKey.toAddress(), 10000)
|
|
988
|
+
expect(tx.outputs.length).toEqual(1)
|
|
989
|
+
expect(tx.outputs[0].satoshis).toEqual(10000)
|
|
990
|
+
expect(tx.outputs[0].lockingScript.toHex() === lockingScript.toHex()).toBeTruthy()
|
|
991
|
+
})
|
|
992
|
+
it('should error is the address is non base58', async () => {
|
|
993
|
+
const tx = new Transaction()
|
|
994
|
+
expect(() => tx.addP2PKHOutput('A small chicken', 10000)).toThrow("Invalid base58 character ")
|
|
995
|
+
})
|
|
996
|
+
it('should error if the address is incorrectly copied', async () => {
|
|
997
|
+
const tx = new Transaction()
|
|
998
|
+
expect(() => tx.addP2PKHOutput('14afWk1jLH9Uwi2mC9C5ehrsvcFxTLYDp', 10000)).toThrow("Invalid checksum")
|
|
999
|
+
})
|
|
1000
|
+
it('should error if the address is a hash of wrong length', async () => {
|
|
1001
|
+
const tx = new Transaction()
|
|
1002
|
+
const address = [1,2,3,4,5]
|
|
1003
|
+
expect(() => tx.addP2PKHOutput(address, 10000)).toThrow("P2PKH hash length must be 20 bytes")
|
|
1004
|
+
})
|
|
1005
|
+
it('should set the output to a change output if the satoshi value is not given', async () => {
|
|
1006
|
+
const tx = new Transaction()
|
|
1007
|
+
tx.addP2PKHOutput('18E63MgXH43KBb288ETM8u91J2cqdGNEmg')
|
|
1008
|
+
expect(tx.outputs[0].change).toBeTruthy()
|
|
1009
|
+
expect(tx.outputs[0].satoshis).toBeUndefined()
|
|
1010
|
+
})
|
|
1011
|
+
it('throw error when satoshi value is negative', async () => {
|
|
1012
|
+
const tx = new Transaction()
|
|
1013
|
+
expect(() => tx.addP2PKHOutput('18E63MgXH43KBb288ETM8u91J2cqdGNEmg', -1000)).toThrow('satoshis must be a positive integer or zero')
|
|
1014
|
+
})
|
|
1015
|
+
it('throw error when output does not set satoshi value', async () => {
|
|
1016
|
+
const tx = new Transaction()
|
|
1017
|
+
expect(() => tx.addOutput({ lockingScript: Script.fromASM('OP_TRUE') })).toThrow('either satoshis must be defined or change must be set to true')
|
|
1018
|
+
})
|
|
1019
|
+
})
|
|
967
1020
|
})
|