@bsv/sdk 2.1.2 → 2.1.4

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 (47) hide show
  1. package/dist/cjs/package.json +13 -13
  2. package/dist/cjs/src/auth/Peer.js +21 -18
  3. package/dist/cjs/src/auth/Peer.js.map +1 -1
  4. package/dist/cjs/src/auth/SessionManager.js.map +1 -1
  5. package/dist/cjs/src/auth/clients/AuthFetch.js +4 -1
  6. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
  7. package/dist/cjs/src/compat/Mnemonic.js +12 -0
  8. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  9. package/dist/cjs/src/overlay-tools/LookupResolver.js +99 -28
  10. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
  11. package/dist/cjs/src/transaction/MerklePath.js +1 -1
  12. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  13. package/dist/esm/src/auth/Peer.js +28 -18
  14. package/dist/esm/src/auth/Peer.js.map +1 -1
  15. package/dist/esm/src/auth/SessionManager.js.map +1 -1
  16. package/dist/esm/src/auth/clients/AuthFetch.js +4 -1
  17. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
  18. package/dist/esm/src/compat/Mnemonic.js +12 -0
  19. package/dist/esm/src/compat/Mnemonic.js.map +1 -1
  20. package/dist/esm/src/overlay-tools/LookupResolver.js +99 -28
  21. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
  22. package/dist/esm/src/transaction/MerklePath.js +1 -1
  23. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  24. package/dist/types/src/auth/Peer.d.ts +3 -3
  25. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  26. package/dist/types/src/auth/SessionManager.d.ts +21 -0
  27. package/dist/types/src/auth/SessionManager.d.ts.map +1 -1
  28. package/dist/types/src/auth/clients/AuthFetch.d.ts +2 -2
  29. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
  30. package/dist/types/src/compat/Mnemonic.d.ts +2 -0
  31. package/dist/types/src/compat/Mnemonic.d.ts.map +1 -1
  32. package/dist/types/src/overlay-tools/LookupResolver.d.ts +1 -0
  33. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
  34. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  35. package/dist/umd/bundle.js +3 -3
  36. package/package.json +13 -13
  37. package/src/auth/Peer.ts +30 -20
  38. package/src/auth/SessionManager.ts +22 -0
  39. package/src/auth/__tests/Peer.test.ts +47 -1
  40. package/src/auth/clients/AuthFetch.ts +6 -3
  41. package/src/compat/Mnemonic.ts +13 -0
  42. package/src/compat/__tests/Mnemonic.test.ts +49 -5
  43. package/src/overlay-tools/LookupResolver.ts +102 -25
  44. package/src/overlay-tools/__tests/LookupResolver.additional.test.ts +90 -0
  45. package/src/script/__tests/Spend.test.ts +45 -4
  46. package/src/transaction/MerklePath.ts +1 -1
  47. package/src/transaction/__tests/Transaction.test.ts +17 -0
@@ -472,6 +472,22 @@ describe('LookupResolver – additional coverage', () => {
472
472
  ).rejects.toThrow('Request timed out')
473
473
  })
474
474
 
475
+ it('rejects within the timeout even when fetch never settles', async () => {
476
+ // Simulate the CORS-blocked / hung-preflight case where the fetch promise
477
+ // does not honor the AbortController signal and never settles.
478
+ const neverFetch = jest.fn().mockImplementation(
479
+ () => new Promise(() => { /* never resolves */ })
480
+ )
481
+ const facilitator = new HTTPSOverlayLookupFacilitator(neverFetch, true)
482
+ const start = Date.now()
483
+ await expect(
484
+ facilitator.lookup('http://host', { service: 'ls_test', query: {} }, 50)
485
+ ).rejects.toThrow('Request timed out')
486
+ const elapsed = Date.now() - start
487
+ // Allow generous slack but must complete well before any global jest timeout.
488
+ expect(elapsed).toBeLessThan(2000)
489
+ })
490
+
475
491
  it('parses octet-stream responses', async () => {
476
492
  // Build a minimal octet-stream payload: 1 outpoint, then BEEF bytes
477
493
  const tx = new Transaction(
@@ -506,6 +522,36 @@ describe('LookupResolver – additional coverage', () => {
506
522
  expect(result.outputs[0].outputIndex).toBe(0)
507
523
  })
508
524
 
525
+ it('parses octet-stream responses when header carries parameters or differing case', async () => {
526
+ const tx = new Transaction(
527
+ 1,
528
+ [],
529
+ [{ lockingScript: LockingScript.fromHex('88'), satoshis: 1 }],
530
+ 0
531
+ )
532
+ const beef = tx.toBEEF()
533
+ const txid = Buffer.from(tx.id('hex'), 'hex')
534
+ const payload = Buffer.concat([
535
+ Buffer.from([0x01]), txid, Buffer.from([0x00]), Buffer.from([0x00]), Buffer.from(beef)
536
+ ])
537
+
538
+ for (const header of [
539
+ 'application/octet-stream; charset=utf-8',
540
+ 'Application/Octet-Stream',
541
+ ' application/octet-stream '
542
+ ]) {
543
+ const mockFetch = jest.fn().mockResolvedValue({
544
+ ok: true,
545
+ headers: { get: () => header },
546
+ arrayBuffer: async () => payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength)
547
+ })
548
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
549
+ const result = await facilitator.lookup('https://host', { service: 'ls_test', query: {} })
550
+ expect(result.type).toBe('output-list')
551
+ expect(result.outputs).toHaveLength(1)
552
+ }
553
+ })
554
+
509
555
  it('parses octet-stream responses with context bytes', async () => {
510
556
  const tx = new Transaction(
511
557
  1,
@@ -547,6 +593,50 @@ describe('LookupResolver – additional coverage', () => {
547
593
  ).rejects.toThrow('DNS failure')
548
594
  })
549
595
 
596
+ it('normalises string thrown values from fetch', async () => {
597
+ const mockFetch = jest.fn().mockRejectedValue('boom')
598
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
599
+ await expect(
600
+ facilitator.lookup('https://host', { service: 'ls_test', query: {} })
601
+ ).rejects.toThrow('boom')
602
+ })
603
+
604
+ it('normalises object-with-message thrown values from fetch', async () => {
605
+ const mockFetch = jest.fn().mockRejectedValue({ message: 'object boom' })
606
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
607
+ await expect(
608
+ facilitator.lookup('https://host', { service: 'ls_test', query: {} })
609
+ ).rejects.toThrow('object boom')
610
+ })
611
+
612
+ it('normalises plain-object thrown values via JSON', async () => {
613
+ const mockFetch = jest.fn().mockRejectedValue({ code: 42 })
614
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
615
+ await expect(
616
+ facilitator.lookup('https://host', { service: 'ls_test', query: {} })
617
+ ).rejects.toThrow('{"code":42}')
618
+ })
619
+
620
+ it('normalises number/boolean/null thrown values from fetch', async () => {
621
+ for (const value of [123, true, null]) {
622
+ const mockFetch = jest.fn().mockRejectedValue(value)
623
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
624
+ await expect(
625
+ facilitator.lookup('https://host', { service: 'ls_test', query: {} })
626
+ ).rejects.toThrow(String(value))
627
+ }
628
+ })
629
+
630
+ it('normalises circular thrown values without crashing', async () => {
631
+ const circular: { self?: unknown } = {}
632
+ circular.self = circular
633
+ const mockFetch = jest.fn().mockRejectedValue(circular)
634
+ const facilitator = new HTTPSOverlayLookupFacilitator(mockFetch, true)
635
+ await expect(
636
+ facilitator.lookup('https://host', { service: 'ls_test', query: {} })
637
+ ).rejects.toThrow('Unknown error')
638
+ })
639
+
550
640
  it('sends correct request body to /lookup endpoint', async () => {
551
641
  const mockFetch = jest.fn().mockResolvedValue({
552
642
  ok: true,
@@ -512,7 +512,7 @@ describe('Spend', () => {
512
512
  })
513
513
  })
514
514
 
515
- it('Successfully validates a spend where sequence is set to undefined', async () => {
515
+ it('Rejects spending an immature coinbase transaction', async () => {
516
516
  const sourceTransaction = new Transaction(
517
517
  1,
518
518
  [{
@@ -531,8 +531,49 @@ describe('Spend', () => {
531
531
  )
532
532
  const txid = sourceTransaction.id('hex')
533
533
  sourceTransaction.merklePath = MerklePath.fromCoinbaseTxidAndHeight(txid, 0)
534
- const chain = new MockChain({ blockheaders: [] })
535
- chain.addBlock(txid)
534
+ const chain = new MockChain({ blockheaders: [txid] })
535
+
536
+ const spendTx = new Transaction(
537
+ 1,
538
+ [
539
+ {
540
+ unlockingScript: Script.fromASM('OP_TRUE'),
541
+ sourceTransaction,
542
+ sourceOutputIndex: 0
543
+ }
544
+ ],
545
+ [{
546
+ lockingScript: Script.fromASM('OP_NOP'),
547
+ satoshis: 1
548
+ }],
549
+ 0
550
+ )
551
+
552
+ await expect(spendTx.verify(chain)).rejects.toThrow(
553
+ `Invalid merkle path for transaction ${txid}`
554
+ )
555
+ })
556
+
557
+ it('Successfully validates a mature coinbase spend where sequence is set to undefined', async () => {
558
+ const sourceTransaction = new Transaction(
559
+ 1,
560
+ [{
561
+ sourceTXID: '0000000000000000000000000000000000000000000000000000000000000000',
562
+ sourceOutputIndex: 0,
563
+ unlockingScript: Script.fromASM('OP_TRUE'),
564
+ sequence: 0xffffffff
565
+ }],
566
+ [
567
+ {
568
+ lockingScript: Script.fromASM('OP_NOP'),
569
+ satoshis: 2
570
+ }
571
+ ],
572
+ 0
573
+ )
574
+ const txid = sourceTransaction.id('hex')
575
+ sourceTransaction.merklePath = MerklePath.fromCoinbaseTxidAndHeight(txid, 0)
576
+ const chain = new MockChain({ blockheaders: [txid, ...new Array(100).fill('')] })
536
577
 
537
578
  const spendTx = new Transaction(
538
579
  1,
@@ -551,7 +592,7 @@ describe('Spend', () => {
551
592
  )
552
593
 
553
594
  const valid = await spendTx.verify(chain)
554
-
595
+
555
596
  expect(valid).toBe(true)
556
597
 
557
598
  const b = spendTx.toBinary()
@@ -375,7 +375,7 @@ export default class MerklePath {
375
375
  if (this.indexOf(txid) === 0) {
376
376
  // Coinbase transaction outputs can only be spent once they're 100 blocks deep.
377
377
  const height = await chainTracker.currentHeight()
378
- if (this.blockHeight + 100 < height) {
378
+ if (this.blockHeight + 100 > height) {
379
379
  return false
380
380
  }
381
381
  }
@@ -15,6 +15,23 @@ import MerklePath from '../../transaction/MerklePath'
15
15
  import { BEEF_V1 } from '../../transaction/Beef'
16
16
  import SatoshisPerKilobyte from '../../transaction/fee-models/SatoshisPerKilobyte'
17
17
 
18
+ // Default Transaction.fee() resolves to LivePolicy.getInstance(), which fetches the live ARC
19
+ // policy endpoint. Replace it with a deterministic 100 sat/kb model so tests never hit the
20
+ // network — the live endpoint is unreliable and not part of what these tests exercise.
21
+ jest.mock('../../transaction/fee-models/LivePolicy', () => {
22
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
23
+ const SPKB = require('../../transaction/fee-models/SatoshisPerKilobyte').default
24
+ class MockLivePolicy extends SPKB {
25
+ static instance: MockLivePolicy | null = null
26
+ constructor () { super(100) }
27
+ static getInstance (): MockLivePolicy {
28
+ if (!MockLivePolicy.instance) MockLivePolicy.instance = new MockLivePolicy()
29
+ return MockLivePolicy.instance
30
+ }
31
+ }
32
+ return { __esModule: true, default: MockLivePolicy }
33
+ })
34
+
18
35
  import sighashVectors from '../../primitives/__tests/sighash.vectors'
19
36
  import invalidTransactions from './tx.invalid.vectors'
20
37
  import validTransactions from './tx.valid.vectors'