@bsv/sdk 2.0.11 → 2.0.13

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 (106) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +827 -0
  3. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
  4. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +654 -0
  5. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
  6. package/dist/cjs/src/overlay-tools/HostReputationTracker.js +21 -13
  7. package/dist/cjs/src/overlay-tools/HostReputationTracker.js.map +1 -1
  8. package/dist/cjs/src/primitives/PrivateKey.js +3 -3
  9. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  10. package/dist/cjs/src/script/Spend.js +17 -9
  11. package/dist/cjs/src/script/Spend.js.map +1 -1
  12. package/dist/cjs/src/storage/StorageDownloader.js +6 -6
  13. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  14. package/dist/cjs/src/storage/StorageUtils.js +1 -1
  15. package/dist/cjs/src/storage/StorageUtils.js.map +1 -1
  16. package/dist/cjs/src/transaction/MerklePath.js +168 -27
  17. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  18. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  19. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +825 -0
  20. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
  21. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +619 -0
  22. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
  23. package/dist/esm/src/overlay-tools/HostReputationTracker.js +21 -13
  24. package/dist/esm/src/overlay-tools/HostReputationTracker.js.map +1 -1
  25. package/dist/esm/src/primitives/PrivateKey.js +3 -3
  26. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  27. package/dist/esm/src/script/Spend.js +17 -9
  28. package/dist/esm/src/script/Spend.js.map +1 -1
  29. package/dist/esm/src/storage/StorageDownloader.js +6 -6
  30. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  31. package/dist/esm/src/storage/StorageUtils.js +1 -1
  32. package/dist/esm/src/storage/StorageUtils.js.map +1 -1
  33. package/dist/esm/src/transaction/MerklePath.js +168 -27
  34. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  35. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  36. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +21 -0
  37. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +1 -0
  38. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +2 -0
  39. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +1 -0
  40. package/dist/types/src/overlay-tools/HostReputationTracker.d.ts.map +1 -1
  41. package/dist/types/src/script/Spend.d.ts.map +1 -1
  42. package/dist/types/src/transaction/MerklePath.d.ts +27 -0
  43. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  44. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  45. package/dist/umd/bundle.js +3 -3
  46. package/dist/umd/bundle.js.map +1 -1
  47. package/docs/reference/storage.md +1 -1
  48. package/docs/reference/transaction.md +40 -0
  49. package/package.json +1 -1
  50. package/src/auth/clients/__tests__/AuthFetch.additional.test.ts +1131 -0
  51. package/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.ts +770 -0
  52. package/src/auth/utils/__tests/validateCertificates.test.ts +12 -9
  53. package/src/compat/__tests/Mnemonic.additional.test.ts +64 -0
  54. package/src/identity/__tests/IdentityClient.additional.test.ts +767 -0
  55. package/src/kvstore/__tests/LocalKVStore.additional.test.ts +611 -0
  56. package/src/kvstore/__tests/LocalKVStore.test.ts +4 -6
  57. package/src/kvstore/__tests/kvStoreInterpreter.test.ts +327 -0
  58. package/src/overlay-tools/HostReputationTracker.ts +17 -14
  59. package/src/overlay-tools/__tests/HostReputationTracker.additional.test.ts +561 -0
  60. package/src/overlay-tools/__tests/LookupResolver.additional.test.ts +612 -0
  61. package/src/overlay-tools/__tests/withDoubleSpendRetry.test.ts +278 -0
  62. package/src/primitives/PrivateKey.ts +3 -3
  63. package/src/primitives/__tests/BigNumber.additional.test.ts +79 -0
  64. package/src/primitives/__tests/Curve.additional.test.ts +208 -0
  65. package/src/primitives/__tests/ECDSA.additional.test.ts +122 -0
  66. package/src/primitives/__tests/Hash.additional.test.ts +59 -0
  67. package/src/primitives/__tests/JacobianPoint.test.ts +308 -0
  68. package/src/primitives/__tests/Point.additional.test.ts +503 -0
  69. package/src/primitives/__tests/PublicKey.additional.test.ts +383 -0
  70. package/src/primitives/__tests/Random.additional.test.ts +262 -0
  71. package/src/primitives/__tests/Signature.test.ts +333 -0
  72. package/src/primitives/__tests/TransactionSignature.additional.test.ts +241 -0
  73. package/src/registry/__tests/RegistryClient.additional.test.ts +750 -0
  74. package/src/remittance/__tests/BasicBRC29.additional.test.ts +657 -0
  75. package/src/remittance/__tests/RemittanceManager.additional.test.ts +1272 -0
  76. package/src/script/Spend.ts +19 -11
  77. package/src/script/__tests/LockingUnlockingScript.test.ts +79 -0
  78. package/src/script/__tests/Script.additional.test.ts +100 -0
  79. package/src/script/__tests/ScriptEvaluationError.test.ts +98 -0
  80. package/src/script/__tests/Spend.additional.test.ts +837 -0
  81. package/src/script/templates/__tests/RPuzzle.test.ts +134 -0
  82. package/src/storage/StorageDownloader.ts +6 -6
  83. package/src/storage/StorageUtils.ts +1 -1
  84. package/src/transaction/MerklePath.ts +196 -36
  85. package/src/transaction/__tests/BeefParty.additional.test.ts +22 -0
  86. package/src/transaction/__tests/Broadcaster.test.ts +159 -0
  87. package/src/transaction/__tests/MerklePath.bench.test.ts +105 -0
  88. package/src/transaction/__tests/MerklePath.test.ts +232 -21
  89. package/src/transaction/__tests/Transaction.additional.test.ts +225 -0
  90. package/src/transaction/broadcasters/__tests/ARC.additional.test.ts +585 -0
  91. package/src/transaction/broadcasters/__tests/Teranode.test.ts +349 -0
  92. package/src/transaction/chaintrackers/__tests/BlockHeadersService.test.ts +253 -0
  93. package/src/transaction/chaintrackers/__tests/DefaultChainTracker.test.ts +44 -0
  94. package/src/transaction/chaintrackers/__tests/WhatsOnChain.additional.test.ts +193 -0
  95. package/src/transaction/fee-models/__tests/SatoshisPerKilobyte.test.ts +262 -0
  96. package/src/transaction/http/__tests/BinaryFetchClient.test.ts +212 -0
  97. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +192 -0
  98. package/src/transaction/http/__tests/DefaultHttpClient.test.ts +71 -0
  99. package/src/wallet/__tests/ProtoWallet.additional.test.ts +134 -0
  100. package/src/wallet/__tests/WERR.test.ts +212 -0
  101. package/src/wallet/__tests/WalletClient.additional.test.ts +699 -0
  102. package/src/wallet/__tests/WalletClient.substrate.test.ts +759 -0
  103. package/src/wallet/__tests/WalletError.test.ts +290 -0
  104. package/src/wallet/__tests/validationHelpers.test.ts +1218 -0
  105. package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +496 -0
  106. package/src/wallet/substrates/__tests/HTTPWalletWire.test.ts +273 -0
@@ -266,7 +266,11 @@ export default class Spend {
266
266
  if (this.stack.length === 0) {
267
267
  this.scriptEvaluationError('Attempted to pop from an empty stack.')
268
268
  }
269
- const item = this.stack.pop() as number[]
269
+ const item = this.stack.pop()
270
+ if (item === undefined) {
271
+ this.scriptEvaluationError('Attempted to pop from an empty stack.')
272
+ return [] // unreachable; scriptEvaluationError always throws
273
+ }
270
274
  this.stackMem -= item.length
271
275
  return item
272
276
  }
@@ -290,7 +294,11 @@ export default class Spend {
290
294
  if (this.altStack.length === 0) {
291
295
  this.scriptEvaluationError('Attempted to pop from an empty alt stack.')
292
296
  }
293
- const item = this.altStack.pop() as number[]
297
+ const item = this.altStack.pop()
298
+ if (item === undefined) {
299
+ this.scriptEvaluationError('Attempted to pop from an empty alt stack.')
300
+ return [] // unreachable; scriptEvaluationError always throws
301
+ }
294
302
  this.altStackMem -= item.length
295
303
  return item
296
304
  }
@@ -308,7 +316,7 @@ export default class Spend {
308
316
  this.scriptEvaluationError('The signature must have a low S value.')
309
317
  return false
310
318
  }
311
- } catch (e) {
319
+ } catch {
312
320
  this.scriptEvaluationError('The signature format is invalid.')
313
321
  return false
314
322
  }
@@ -340,7 +348,7 @@ export default class Spend {
340
348
  }
341
349
  try {
342
350
  PublicKey.fromDER(buf as number[]) // This can throw for stricter DER rules
343
- } catch (e) {
351
+ } catch {
344
352
  this.scriptEvaluationError('The public key is in an unknown format.')
345
353
  return false
346
354
  }
@@ -395,7 +403,7 @@ export default class Spend {
395
403
  const operation = currentScript.chunks[this.programCounter]
396
404
 
397
405
  const currentOpcode = operation.op
398
- if (typeof currentOpcode === 'undefined') {
406
+ if (currentOpcode === undefined) {
399
407
  this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`) // Error thrown
400
408
  }
401
409
  if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
@@ -547,7 +555,7 @@ export default class Spend {
547
555
  break
548
556
  case OP.OP_ELSE:
549
557
  if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
550
- this.ifStack[this.ifStack.length - 1] = !this.ifStack[this.ifStack.length - 1]
558
+ this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1])
551
559
  break
552
560
  case OP.OP_ENDIF:
553
561
  if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
@@ -682,7 +690,7 @@ export default class Spend {
682
690
  // stack is [... rest, x1, x2]
683
691
  // We want [... rest, x2_copy, x1, x2]
684
692
  this.ensureStackMem(buf1.length)
685
- this.stack.splice(this.stack.length - 2, 0, buf1.slice()) // Insert copy of x2 before x1
693
+ this.stack.splice(-2, 0, buf1.slice()) // Insert copy of x2 before x1
686
694
  this.stackMem += buf1.length // Account for the new copy
687
695
  break
688
696
  case OP.OP_SIZE:
@@ -769,7 +777,7 @@ export default class Spend {
769
777
  case OP.OP_NEGATE: bn = bn.neg(); break
770
778
  case OP.OP_ABS: if (bn.isNeg()) bn = bn.neg(); break
771
779
  case OP.OP_NOT: bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0); break
772
- case OP.OP_0NOTEQUAL: bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0); break
780
+ case OP.OP_0NOTEQUAL: bn = new BigNumber(bn.cmpn(0) === 0 ? 0 : 1); break
773
781
  }
774
782
  this.pushStack(bn.toScriptNum())
775
783
  break
@@ -812,7 +820,7 @@ export default class Spend {
812
820
  case OP.OP_BOOLOR: resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0) ? 1 : 0); break
813
821
  case OP.OP_NUMEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
814
822
  case OP.OP_NUMEQUALVERIFY: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
815
- case OP.OP_NUMNOTEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0); break
823
+ case OP.OP_NUMNOTEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 0 : 1); break
816
824
  case OP.OP_LESSTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0); break
817
825
  case OP.OP_GREATERTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0); break
818
826
  case OP.OP_LESSTHANOREQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0); break
@@ -875,7 +883,7 @@ export default class Spend {
875
883
 
876
884
  pubkey = PublicKey.fromDER(bufPubkey)
877
885
  fSuccess = this.verifySignature(sig, pubkey, subscript)
878
- } catch (e) {
886
+ } catch {
879
887
  fSuccess = false
880
888
  }
881
889
  }
@@ -949,7 +957,7 @@ export default class Spend {
949
957
  sig = TransactionSignature.fromChecksigFormat(bufSig)
950
958
  pubkey = PublicKey.fromDER(bufPubkey)
951
959
  fOk = this.verifySignature(sig, pubkey, subscript)
952
- } catch (e) {
960
+ } catch {
953
961
  fOk = false
954
962
  }
955
963
  }
@@ -0,0 +1,79 @@
1
+ import LockingScript from '../../script/LockingScript'
2
+ import UnlockingScript from '../../script/UnlockingScript'
3
+ import Script from '../../script/Script'
4
+
5
+ describe('LockingScript', () => {
6
+ it('isLockingScript() returns true', () => {
7
+ const script = new LockingScript()
8
+ expect(script.isLockingScript()).toBe(true)
9
+ })
10
+
11
+ it('isUnlockingScript() returns false', () => {
12
+ const script = new LockingScript()
13
+ expect(script.isUnlockingScript()).toBe(false)
14
+ })
15
+
16
+ it('extends Script', () => {
17
+ const script = new LockingScript()
18
+ expect(script).toBeInstanceOf(Script)
19
+ })
20
+
21
+ it('can be constructed from hex via inherited fromHex', () => {
22
+ const hex = '76a914' + '00'.repeat(20) + '88ac'
23
+ const script = Script.fromHex(hex)
24
+ const locking = new LockingScript()
25
+ locking.chunks = script.chunks
26
+ expect(locking.toHex()).toBe(hex)
27
+ expect(locking.isLockingScript()).toBe(true)
28
+ })
29
+
30
+ it('fromHex returns a Script instance that can be used to populate a LockingScript', () => {
31
+ const hex = '51' // OP_1
32
+ const base = Script.fromHex(hex)
33
+ expect(base.toHex()).toBe(hex)
34
+ })
35
+ })
36
+
37
+ describe('UnlockingScript', () => {
38
+ it('isLockingScript() returns false', () => {
39
+ const script = new UnlockingScript()
40
+ expect(script.isLockingScript()).toBe(false)
41
+ })
42
+
43
+ it('isUnlockingScript() returns true', () => {
44
+ const script = new UnlockingScript()
45
+ expect(script.isUnlockingScript()).toBe(true)
46
+ })
47
+
48
+ it('extends Script', () => {
49
+ const script = new UnlockingScript()
50
+ expect(script).toBeInstanceOf(Script)
51
+ })
52
+
53
+ it('can be constructed from hex via inherited fromHex', () => {
54
+ const hex = '4830450221'
55
+ const script = Script.fromHex(hex)
56
+ const unlocking = new UnlockingScript()
57
+ unlocking.chunks = script.chunks
58
+ expect(unlocking.isUnlockingScript()).toBe(true)
59
+ })
60
+ })
61
+
62
+ describe('LockingScript vs UnlockingScript', () => {
63
+ it('LockingScript and UnlockingScript return opposite values for isLockingScript', () => {
64
+ const locking = new LockingScript()
65
+ const unlocking = new UnlockingScript()
66
+ expect(locking.isLockingScript()).not.toBe(unlocking.isLockingScript())
67
+ })
68
+
69
+ it('LockingScript and UnlockingScript return opposite values for isUnlockingScript', () => {
70
+ const locking = new LockingScript()
71
+ const unlocking = new UnlockingScript()
72
+ expect(locking.isUnlockingScript()).not.toBe(unlocking.isUnlockingScript())
73
+ })
74
+
75
+ it('both are instances of Script', () => {
76
+ expect(new LockingScript()).toBeInstanceOf(Script)
77
+ expect(new UnlockingScript()).toBeInstanceOf(Script)
78
+ })
79
+ })
@@ -0,0 +1,100 @@
1
+ import Script from '../Script'
2
+ import OP from '../OP'
3
+ import BigNumber from '../../primitives/BigNumber'
4
+
5
+ describe('Script – additional coverage', () => {
6
+ describe('fromHex', () => {
7
+ it('throws for odd-length hex string', () => {
8
+ expect(() => Script.fromHex('abc')).toThrow()
9
+ })
10
+
11
+ it('throws for non-hex characters', () => {
12
+ expect(() => Script.fromHex('gggg')).toThrow()
13
+ })
14
+ })
15
+
16
+ describe('isLockingScript / isUnlockingScript on base Script', () => {
17
+ it('throws NotImplemented for isLockingScript', () => {
18
+ const script = new Script()
19
+ expect(() => script.isLockingScript()).toThrow('Not implemented')
20
+ })
21
+
22
+ it('throws NotImplemented for isUnlockingScript', () => {
23
+ const script = new Script()
24
+ expect(() => script.isUnlockingScript()).toThrow('Not implemented')
25
+ })
26
+ })
27
+
28
+ describe('writeScript', () => {
29
+ it('appends all chunks from another script', () => {
30
+ const s1 = Script.fromASM('OP_1 OP_2')
31
+ const s2 = Script.fromASM('OP_3')
32
+ s1.writeScript(s2)
33
+ expect(s1.toASM()).toBe('OP_1 OP_2 OP_3')
34
+ })
35
+ })
36
+
37
+ describe('setChunkOpCode', () => {
38
+ it('replaces the opcode at the given index', () => {
39
+ const script = Script.fromASM('OP_1 OP_2 OP_3')
40
+ script.setChunkOpCode(1, OP.OP_NOP)
41
+ expect(script.chunks[1].op).toBe(OP.OP_NOP)
42
+ })
43
+ })
44
+
45
+ describe('writeBn', () => {
46
+ it('pushes OP_0 for zero', () => {
47
+ const script = new Script().writeBn(new BigNumber(0))
48
+ expect(script.chunks[0].op).toBe(OP.OP_0)
49
+ })
50
+
51
+ it('pushes OP_1NEGATE for -1', () => {
52
+ const script = new Script().writeBn(new BigNumber(-1))
53
+ expect(script.chunks[0].op).toBe(OP.OP_1NEGATE)
54
+ })
55
+
56
+ it('pushes OP_1..OP_16 for 1..16', () => {
57
+ for (let n = 1; n <= 16; n++) {
58
+ const script = new Script().writeBn(new BigNumber(n))
59
+ expect(script.chunks[0].op).toBe(OP.OP_1 + (n - 1))
60
+ }
61
+ })
62
+
63
+ it('pushes encoded binary for numbers > 16', () => {
64
+ const script = new Script().writeBn(new BigNumber(1000))
65
+ expect(script.chunks[0].data).toBeDefined()
66
+ })
67
+ })
68
+
69
+ describe('writeNumber', () => {
70
+ it('writes a number to the script', () => {
71
+ const script = new Script().writeNumber(5)
72
+ expect(script.chunks[0].op).toBe(OP.OP_5)
73
+ })
74
+ })
75
+
76
+ describe('writeBin', () => {
77
+ it('uses OP_PUSHDATA1 for data 76..255 bytes', () => {
78
+ const data = new Array(76).fill(0x01)
79
+ const script = new Script().writeBin(data)
80
+ expect(script.chunks[0].op).toBe(OP.OP_PUSHDATA1)
81
+ })
82
+
83
+ it('uses OP_PUSHDATA2 for data 256..65535 bytes', () => {
84
+ const data = new Array(256).fill(0x02)
85
+ const script = new Script().writeBin(data)
86
+ expect(script.chunks[0].op).toBe(OP.OP_PUSHDATA2)
87
+ })
88
+ })
89
+
90
+ describe('findAndDelete – PUSHDATA1 target', () => {
91
+ it('deletes chunks encoded with OP_PUSHDATA1 (76-255 bytes)', () => {
92
+ const data = new Array(76).fill(0xab)
93
+ const target = new Script().writeBin(data)
94
+ const source = new Script().writeBin(data).writeBin(data).writeOpCode(OP.OP_1)
95
+ source.findAndDelete(target)
96
+ expect(source.chunks).toHaveLength(1)
97
+ expect(source.chunks[0].op).toBe(OP.OP_1)
98
+ })
99
+ })
100
+ })
@@ -0,0 +1,98 @@
1
+ import ScriptEvaluationError from '../ScriptEvaluationError'
2
+
3
+ const baseParams = {
4
+ message: 'test error',
5
+ txid: 'a'.repeat(64),
6
+ outputIndex: 0,
7
+ context: 'LockingScript' as const,
8
+ programCounter: 3,
9
+ stackState: [] as number[][],
10
+ altStackState: [] as number[][],
11
+ ifStackState: [] as boolean[],
12
+ stackMem: 0,
13
+ altStackMem: 0
14
+ }
15
+
16
+ describe('ScriptEvaluationError', () => {
17
+ it('constructs with empty stacks', () => {
18
+ const err = new ScriptEvaluationError(baseParams)
19
+ expect(err).toBeInstanceOf(Error)
20
+ expect(err.message).toContain('test error')
21
+ expect(err.txid).toBe('a'.repeat(64))
22
+ expect(err.outputIndex).toBe(0)
23
+ expect(err.context).toBe('LockingScript')
24
+ expect(err.programCounter).toBe(3)
25
+ })
26
+
27
+ it('renders valid stack items as hex in the message (toHex branch)', () => {
28
+ const err = new ScriptEvaluationError({
29
+ ...baseParams,
30
+ stackState: [[0xde, 0xad], [0xbe, 0xef]],
31
+ altStackState: [[0x01]]
32
+ })
33
+ expect(err.message).toContain('dead')
34
+ expect(err.message).toContain('beef')
35
+ expect(err.message).toContain('01')
36
+ })
37
+
38
+ it('renders null stack item as null/undef (null/undef branch)', () => {
39
+ // The message is built before the deep-copy, so null/undef branch is executed
40
+ // even though the constructor later throws when trying to slice null.
41
+ expect(() => {
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ return new ScriptEvaluationError({ ...baseParams, stackState: [null as any] })
44
+ }).toThrow()
45
+ })
46
+
47
+ it('renders undefined stack item as null/undef (undefined branch)', () => {
48
+ expect(() => {
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ return new ScriptEvaluationError({ ...baseParams, stackState: [undefined as any] })
51
+ }).toThrow()
52
+ })
53
+
54
+ it('renders non-array stack item as INVALID_STACK_ITEM', () => {
55
+ expect(() => {
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ return new ScriptEvaluationError({ ...baseParams, stackState: [{ notAnArray: true } as any] })
58
+ }).toThrow()
59
+ })
60
+
61
+ it('renders null alt-stack item as null/undef (covers altStack map branches)', () => {
62
+ expect(() => {
63
+ return new ScriptEvaluationError({
64
+ ...baseParams,
65
+ stackState: [[0x01]],
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ altStackState: [null as any]
68
+ })
69
+ }).toThrow()
70
+ })
71
+
72
+ it('includes ifStack entries in the message', () => {
73
+ const err = new ScriptEvaluationError({
74
+ ...baseParams,
75
+ ifStackState: [true, false]
76
+ })
77
+ expect(err.message).toContain('true, false')
78
+ })
79
+
80
+ it('includes UnlockingScript context in the message', () => {
81
+ const err = new ScriptEvaluationError({
82
+ ...baseParams,
83
+ context: 'UnlockingScript'
84
+ })
85
+ expect(err.message).toContain('UnlockingScript')
86
+ })
87
+
88
+ it('deep-copies stackState and altStackState to prevent mutation', () => {
89
+ const originalStack = [[1, 2, 3]]
90
+ const err = new ScriptEvaluationError({
91
+ ...baseParams,
92
+ stackState: originalStack
93
+ })
94
+ // Mutate original — error should keep its own copy
95
+ originalStack[0].push(4)
96
+ expect(err.stackState[0]).toHaveLength(3)
97
+ })
98
+ })