@bsv/sdk 1.2.18 → 1.2.19

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 (26) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/transaction/MerklePath.js +12 -1
  3. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  4. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js +20 -2
  5. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
  6. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  7. package/dist/esm/src/transaction/MerklePath.js +12 -1
  8. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  9. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js +20 -2
  10. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
  11. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  12. package/dist/types/src/transaction/ChainTracker.d.ts +6 -0
  13. package/dist/types/src/transaction/ChainTracker.d.ts.map +1 -1
  14. package/dist/types/src/transaction/MerklePath.d.ts +1 -0
  15. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  16. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts +2 -1
  17. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts.map +1 -1
  18. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  19. package/dist/umd/bundle.js +1 -1
  20. package/docs/transaction.md +6 -1
  21. package/package.json +1 -1
  22. package/src/transaction/ChainTracker.ts +6 -0
  23. package/src/transaction/MerklePath.ts +13 -1
  24. package/src/transaction/__tests/MerklePath.test.ts +23 -2
  25. package/src/transaction/chaintrackers/WhatsOnChain.ts +20 -2
  26. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -1
@@ -160,12 +160,16 @@ const chainTracker = {
160
160
  isValidRootForHeight: async (root, height) => {
161
161
  // Implementation to check if the Merkle root is valid for the specified block height.
162
162
  }
163
+ currentHeight: async () => {
164
+ // Implementation to get the current block height.
165
+ }
163
166
  };
164
167
  ```
165
168
 
166
169
  ```ts
167
170
  export default interface ChainTracker {
168
171
  isValidRootForHeight: (root: string, height: number) => Promise<boolean>;
172
+ currentHeight: () => Promise<number>;
169
173
  }
170
174
  ```
171
175
 
@@ -1101,7 +1105,7 @@ isParty(party: string)
1101
1105
 
1102
1106
  Returns
1103
1107
 
1104
- `true` if `party` has already beed added to this `BeefParty`.
1108
+ `true` if `party` has already been added to this `BeefParty`.
1105
1109
 
1106
1110
  #### Method mergeBeefFromParty
1107
1111
 
@@ -2076,6 +2080,7 @@ export default class WhatsOnChain implements ChainTracker {
2076
2080
  readonly apiKey: string;
2077
2081
  constructor(network: "main" | "test" | "stn" = "main", config: WhatsOnChainConfig = {})
2078
2082
  async isValidRootForHeight(root: string, height: number): Promise<boolean>
2083
+ async currentHeight(): Promise<number>
2079
2084
  }
2080
2085
  ```
2081
2086
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.2.18",
3
+ "version": "1.2.19",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -10,13 +10,19 @@
10
10
  * @function isValidRootForHeight - A method to verify the validity of a Merkle root
11
11
  * for a given block height.
12
12
  *
13
+ * @function currentHeight - A method to get the current block height.
14
+ *
13
15
  * @example
14
16
  * const chainTracker = {
15
17
  * isValidRootForHeight: async (root, height) => {
16
18
  * // Implementation to check if the Merkle root is valid for the specified block height.
17
19
  * }
20
+ * currentHeight: async () => {
21
+ * // Implementation to get the current block height.
22
+ * }
18
23
  * };
19
24
  */
20
25
  export default interface ChainTracker {
21
26
  isValidRootForHeight: (root: string, height: number) => Promise<boolean>
27
+ currentHeight: () => Promise<number>
22
28
  }
@@ -192,6 +192,11 @@ export default class MerklePath {
192
192
  return toHex(this.toBinary())
193
193
  }
194
194
 
195
+ //
196
+ private indexOf (txid: string): number {
197
+ return this.path[0].find(l => l.hash === txid).offset
198
+ }
199
+
195
200
  /**
196
201
  * Computes the Merkle root from the provided transaction ID.
197
202
  *
@@ -204,7 +209,7 @@ export default class MerklePath {
204
209
  txid = this.path[0].find(leaf => Boolean(leaf?.hash)).hash
205
210
  }
206
211
  // Find the index of the txid at the lowest level of the Merkle tree
207
- const index = this.path[0].find(l => l.hash === txid).offset
212
+ const index = this.indexOf(txid)
208
213
  if (typeof index !== 'number') {
209
214
  throw new Error(`This proof does not contain the txid: ${txid}`)
210
215
  }
@@ -282,6 +287,13 @@ export default class MerklePath {
282
287
  */
283
288
  async verify (txid: string, chainTracker: ChainTracker): Promise<boolean> {
284
289
  const root = this.computeRoot(txid)
290
+ if (this.indexOf(txid) === 0) {
291
+ // Coinbase transaction outputs can only be spent once they're 100 blocks deep.
292
+ const height = await chainTracker.currentHeight()
293
+ if (this.blockHeight + 100 < height) {
294
+ return false
295
+ }
296
+ }
285
297
  // Use the chain tracker to determine whether this is a valid merkle root at the given block height
286
298
  return await chainTracker.isValidRootForHeight(root, this.blockHeight)
287
299
  }
@@ -1,4 +1,5 @@
1
- import MerklePath from '../../../dist/cjs/src/transaction/MerklePath'
1
+ import ChainTracker from '../ChainTracker'
2
+ import MerklePath from '../../../dist/cjs/src/transaction/MerklePath.js'
2
3
  import invalidBumps from './bump.invalid.vectors'
3
4
  import validBumps from './bump.valid.vectors'
4
5
 
@@ -111,6 +112,15 @@ const BRC74TXID1 = '304e737fdfcb017a1a322e78b067ecebb5e07b44f0a36ed1f01264d2014f
111
112
  const BRC74TXID2 = 'd888711d588021e588984e8278a2decf927298173a06737066e43f3e75534e00'
112
113
  const BRC74TXID3 = '98c9c5dd79a18f40837061d5e0395ffb52e700a2689e641d19f053fc9619445e'
113
114
 
115
+ class FakeChainTracker implements ChainTracker {
116
+ async isValidRootForHeight (root: string, height: number): Promise<boolean> {
117
+ return root === 'd5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02' && height === 10000
118
+ }
119
+ async currentHeight (): Promise<number> {
120
+ return 10100
121
+ }
122
+ }
123
+
114
124
  describe('MerklePath', () => {
115
125
  it('Parses from hex', () => {
116
126
  const path = MerklePath.fromHex(BRC74Hex)
@@ -129,7 +139,8 @@ describe('MerklePath', () => {
129
139
  it('Verifies using a ChainTracker', async () => {
130
140
  const path = new MerklePath(BRC74JSON.blockHeight, BRC74JSON.path)
131
141
  const tracker = {
132
- isValidRootForHeight: jest.fn((root, height) => root === BRC74Root && height === BRC74JSON.blockHeight)
142
+ isValidRootForHeight: jest.fn(async (root, height) => root === BRC74Root && height === BRC74JSON.blockHeight),
143
+ currentHeight: jest.fn(async () => 2029209)
133
144
  }
134
145
  const result = await path.verify(BRC74TXID1, tracker)
135
146
  expect(result).toBe(true)
@@ -192,4 +203,14 @@ describe('MerklePath', () => {
192
203
  it('Creates a valid MerklePath from a txid', () => {
193
204
  expect(() => MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 1)).not.toThrowError()
194
205
  })
206
+ it('Valid for Coinbase if currentHeight is more than 100 blocks prior', async () => {
207
+ const mp = MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 10000)
208
+ const isValid = await mp.verify('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', new FakeChainTracker())
209
+ expect(isValid).toBe(true)
210
+ })
211
+ it('Invalid for Coinbase if currentheight is less than 100 blocks prior ', async () => {
212
+ const mp = MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 10099)
213
+ const isValid = await mp.verify('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', new FakeChainTracker())
214
+ expect(isValid).toBe(false)
215
+ })
195
216
  })
@@ -40,7 +40,7 @@ export default class WhatsOnChain implements ChainTracker {
40
40
  async isValidRootForHeight (root: string, height: number): Promise<boolean> {
41
41
  const requestOptions = {
42
42
  method: 'GET',
43
- headers: this.getHeaders()
43
+ headers: this.getHttpHeaders()
44
44
  }
45
45
 
46
46
  const response = await this.httpClient.request<WhatsOnChainBlockHeader>(`${this.URL}/block/${height}/header`, requestOptions)
@@ -54,7 +54,25 @@ export default class WhatsOnChain implements ChainTracker {
54
54
  }
55
55
  }
56
56
 
57
- private getHeaders () {
57
+ async currentHeight (): Promise<number> {
58
+ try {
59
+ const requestOptions = {
60
+ method: 'GET',
61
+ headers: this.getHttpHeaders()
62
+ }
63
+
64
+ const response = await this.httpClient.request<{ height: number }>(`${this.URL}/block/headers`, requestOptions)
65
+ if (response.ok) {
66
+ return response.data[0].height
67
+ } else {
68
+ throw new Error(`Failed to get current height because of an error: ${JSON.stringify(response.data)} `)
69
+ }
70
+ } catch (error) {
71
+ throw new Error(`Failed to get current height because of an error: ${error?.message}`)
72
+ }
73
+ }
74
+
75
+ private getHttpHeaders () {
58
76
  const headers: Record<string, string> = {
59
77
  Accept: 'application/json'
60
78
  }
@@ -29,7 +29,7 @@ describe('WhatsOnChain ChainTracker', () => {
29
29
  it('should verify merkleroot successfully using Node.js https', async () => {
30
30
  // Mocking Node.js https module
31
31
  mockedHttps(successResponse)
32
- delete global.window
32
+ if (global.window) global.window = {} as any
33
33
 
34
34
  const chainTracker = new WhatsOnChain(network)
35
35
  const response = await chainTracker.isValidRootForHeight(merkleroot, height)
@@ -87,6 +87,37 @@ describe('WhatsOnChain ChainTracker', () => {
87
87
  await expect(chainTracker.isValidRootForHeight(merkleroot, height)).rejects.toThrow(/Failed to verify merkleroot for height \d+ because of an error: .*/)
88
88
  })
89
89
 
90
+ it('should return the current height', async () => {
91
+ const mockFetch = mockedFetch({
92
+ status: 200,
93
+ data: [
94
+ {
95
+ hash: '00000000000000000af9958a79fd4dbac1b04a553cc00c80e108718561ccb5a5',
96
+ confirmations: 1,
97
+ size: 29605652,
98
+ height: 875904,
99
+ version: 704643072,
100
+ versionHex: '2a000000',
101
+ merkleroot: '8af5a2d4325ec30e30103b1f365c303d4b49d11e42d26b0d7e9b6866724392e9',
102
+ time: 1734667612,
103
+ mediantime: 1734663717,
104
+ nonce: 157007350,
105
+ bits: '180f2b74',
106
+ difficulty: 72479484799.59058,
107
+ chainwork: '00000000000000000000000000000000000000000160c2f41c8793b90f4500dd',
108
+ previousblockhash: '00000000000000000128f312a7c62ef5f9a91a3f845a4464d10cfbaaecd233a0',
109
+ nextblockhash: '',
110
+ nTx: 0,
111
+ num_tx: 167567
112
+ }
113
+ ]
114
+ })
115
+
116
+ const chainTracker = new WhatsOnChain(network, { httpClient: new FetchHttpClient(mockFetch) })
117
+
118
+ await expect(await chainTracker.currentHeight()).toBe(875904)
119
+ })
120
+
90
121
  function mockedFetch (response) {
91
122
  return jest.fn().mockResolvedValue({
92
123
  ok: response.status === 200,