@bsv/sdk 1.8.9 → 1.8.11

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 (90) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/utils.js +19 -1
  3. package/dist/cjs/src/primitives/utils.js.map +1 -1
  4. package/dist/cjs/src/script/templates/P2PKH.js +2 -7
  5. package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
  6. package/dist/cjs/src/script/templates/PushDrop.js +3 -8
  7. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  8. package/dist/cjs/src/transaction/Beef.js +3 -7
  9. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  10. package/dist/cjs/src/transaction/Transaction.js +3 -3
  11. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  12. package/dist/cjs/src/wallet/WERR_INSUFFICIENT_FUNDS.js +26 -0
  13. package/dist/cjs/src/wallet/WERR_INSUFFICIENT_FUNDS.js.map +1 -0
  14. package/dist/cjs/src/wallet/WERR_INVALID_PARAMETER.js +20 -0
  15. package/dist/cjs/src/wallet/WERR_INVALID_PARAMETER.js.map +1 -0
  16. package/dist/cjs/src/wallet/WalletClient.js +23 -0
  17. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  18. package/dist/cjs/src/wallet/WalletError.js +55 -0
  19. package/dist/cjs/src/wallet/WalletError.js.map +1 -1
  20. package/dist/cjs/src/wallet/index.js +18 -1
  21. package/dist/cjs/src/wallet/index.js.map +1 -1
  22. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +28 -11
  23. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  24. package/dist/cjs/src/wallet/validationHelpers.js +920 -0
  25. package/dist/cjs/src/wallet/validationHelpers.js.map +1 -0
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/esm/src/primitives/utils.js +17 -0
  28. package/dist/esm/src/primitives/utils.js.map +1 -1
  29. package/dist/esm/src/script/templates/P2PKH.js +3 -8
  30. package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
  31. package/dist/esm/src/script/templates/PushDrop.js +3 -8
  32. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  33. package/dist/esm/src/transaction/Beef.js +4 -8
  34. package/dist/esm/src/transaction/Beef.js.map +1 -1
  35. package/dist/esm/src/transaction/Transaction.js +3 -3
  36. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  37. package/dist/esm/src/wallet/WERR_INSUFFICIENT_FUNDS.js +25 -0
  38. package/dist/esm/src/wallet/WERR_INSUFFICIENT_FUNDS.js.map +1 -0
  39. package/dist/esm/src/wallet/WERR_INVALID_PARAMETER.js +18 -0
  40. package/dist/esm/src/wallet/WERR_INVALID_PARAMETER.js.map +1 -0
  41. package/dist/esm/src/wallet/WalletClient.js +23 -0
  42. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  43. package/dist/esm/src/wallet/WalletError.js +55 -0
  44. package/dist/esm/src/wallet/WalletError.js.map +1 -1
  45. package/dist/esm/src/wallet/index.js +3 -0
  46. package/dist/esm/src/wallet/index.js.map +1 -1
  47. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +25 -11
  48. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  49. package/dist/esm/src/wallet/validationHelpers.js +859 -0
  50. package/dist/esm/src/wallet/validationHelpers.js.map +1 -0
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/types/src/primitives/utils.d.ts +13 -0
  53. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  54. package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
  55. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  56. package/dist/types/src/transaction/Beef.d.ts +1 -0
  57. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  58. package/dist/types/src/wallet/WERR_INSUFFICIENT_FUNDS.d.ts +19 -0
  59. package/dist/types/src/wallet/WERR_INSUFFICIENT_FUNDS.d.ts.map +1 -0
  60. package/dist/types/src/wallet/WERR_INVALID_PARAMETER.d.ts +13 -0
  61. package/dist/types/src/wallet/WERR_INVALID_PARAMETER.d.ts.map +1 -0
  62. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  63. package/dist/types/src/wallet/WalletError.d.ts +14 -1
  64. package/dist/types/src/wallet/WalletError.d.ts.map +1 -1
  65. package/dist/types/src/wallet/index.d.ts +3 -0
  66. package/dist/types/src/wallet/index.d.ts.map +1 -1
  67. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
  68. package/dist/types/src/wallet/validationHelpers.d.ts +512 -0
  69. package/dist/types/src/wallet/validationHelpers.d.ts.map +1 -0
  70. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  71. package/dist/umd/bundle.js +4 -4
  72. package/dist/umd/bundle.js.map +1 -1
  73. package/docs/reference/wallet.md +1808 -400
  74. package/package.json +1 -1
  75. package/src/primitives/__tests/utils.test.ts +27 -1
  76. package/src/primitives/utils.ts +17 -0
  77. package/src/script/__tests/Spend.test.ts +74 -1
  78. package/src/script/__tests/SpendComplex.test.ts +5 -9
  79. package/src/script/templates/P2PKH.ts +3 -8
  80. package/src/script/templates/PushDrop.ts +3 -7
  81. package/src/transaction/Beef.ts +5 -8
  82. package/src/transaction/Transaction.ts +3 -3
  83. package/src/wallet/WERR_INSUFFICIENT_FUNDS.ts +25 -0
  84. package/src/wallet/WERR_INVALID_PARAMETER.ts +20 -0
  85. package/src/wallet/WalletClient.ts +30 -0
  86. package/src/wallet/WalletError.ts +52 -0
  87. package/src/wallet/__tests/WalletClient.test.ts +31 -0
  88. package/src/wallet/index.ts +3 -0
  89. package/src/wallet/substrates/HTTPWalletJSON.ts +19 -9
  90. package/src/wallet/validationHelpers.ts +1211 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.8.9",
3
+ "version": "1.8.11",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -7,7 +7,8 @@ import {
7
7
  fromBase58,
8
8
  toBase58,
9
9
  fromBase58Check,
10
- toBase58Check
10
+ toBase58Check,
11
+ verifyNotNull
11
12
  } from '../../primitives/utils'
12
13
 
13
14
  describe('utils', () => {
@@ -207,3 +208,28 @@ describe('utils', () => {
207
208
  expect(toArray(input)).toEqual(expected)
208
209
  })
209
210
  })
211
+
212
+ describe('verifyNotNull', () => {
213
+ it('should return the value if it is not null or undefined', () => {
214
+ expect(verifyNotNull(42)).toBe(42)
215
+ expect(verifyNotNull('hello')).toBe('hello')
216
+ expect(verifyNotNull({})).toEqual({})
217
+ expect(verifyNotNull([])).toEqual([])
218
+ })
219
+
220
+ it('should throw an error with default message if value is null', () => {
221
+ expect(() => verifyNotNull(null)).toThrow('Expected a valid value, but got undefined or null.')
222
+ })
223
+
224
+ it('should throw an error with default message if value is undefined', () => {
225
+ expect(() => verifyNotNull(undefined)).toThrow('Expected a valid value, but got undefined or null.')
226
+ })
227
+
228
+ it('should throw an error with custom message if value is null', () => {
229
+ expect(() => verifyNotNull(null, 'Custom error')).toThrow('Custom error')
230
+ })
231
+
232
+ it('should throw an error with custom message if value is undefined', () => {
233
+ expect(() => verifyNotNull(undefined, 'Another custom error')).toThrow('Another custom error')
234
+ })
235
+ })
@@ -819,3 +819,20 @@ export const minimallyEncode = (buf: number[]): number[] => {
819
819
 
820
820
  const OverflowInt64 = new BigNumber(2).pow(new BigNumber(63))
821
821
  const OverflowUint64 = new BigNumber(2).pow(new BigNumber(64))
822
+
823
+ /**
824
+ * Verifies that a value is not null or undefined, throwing an error if it is.
825
+ *
826
+ * @template T - The type of the value being verified
827
+ * @param {T | undefined | null} value - The value to verify
828
+ * @param {string} errorMessage - The error message to throw if the value is null or undefined
829
+ * @returns {T} - The verified value
830
+ * @throws {Error} - If the value is null or undefined
831
+ *
832
+ * @example
833
+ * const myValue = verifyNotNull(someValue, 'someValue must be defined')
834
+ */
835
+ export function verifyNotNull<T> (value: T | undefined | null, errorMessage: string = 'Expected a valid value, but got undefined or null.'): T {
836
+ if (value == null) throw new Error(errorMessage)
837
+ return value
838
+ }
@@ -7,8 +7,30 @@ import RPuzzle from '../../script/templates/RPuzzle'
7
7
  import Transaction from '../../transaction/Transaction'
8
8
  import LockingScript from '../../script/LockingScript'
9
9
  import UnlockingScript from '../../script/UnlockingScript'
10
-
10
+ import MerklePath from '../../transaction/MerklePath'
11
+ import ChainTracker from '../../transaction/ChainTracker'
11
12
  import spendValid from './spend.valid.vectors'
13
+ import Script from '../../script/Script'
14
+
15
+ export class MockChain implements ChainTracker {
16
+ mock: { blockheaders: string[] }
17
+
18
+ constructor(mock: { blockheaders: string[] }) {
19
+ this.mock = mock
20
+ }
21
+
22
+ addBlock(merkleRoot: string) {
23
+ this.mock.blockheaders.push(merkleRoot)
24
+ }
25
+
26
+ async isValidRootForHeight(root: string, height: number): Promise<boolean> {
27
+ return this.mock.blockheaders[height] === root
28
+ }
29
+
30
+ async currentHeight(): Promise<number> {
31
+ return this.mock.blockheaders.length
32
+ }
33
+ }
12
34
 
13
35
  describe('Spend', () => {
14
36
  it('Successfully validates a P2PKH spend', async () => {
@@ -368,4 +390,55 @@ describe('Spend', () => {
368
390
  expect(spend.validate()).toBe(true)
369
391
  })
370
392
  }
393
+
394
+ it('Successfully validates a spend where sequence is set to undefined', async () => {
395
+ const sourceTransaction = new Transaction(
396
+ 1,
397
+ [{
398
+ sourceTXID: '0000000000000000000000000000000000000000000000000000000000000000',
399
+ sourceOutputIndex: 0,
400
+ unlockingScript: Script.fromASM('OP_TRUE'),
401
+ sequence: 0xffffffff
402
+ }],
403
+ [
404
+ {
405
+ lockingScript: Script.fromASM('OP_NOP'),
406
+ satoshis: 2
407
+ }
408
+ ],
409
+ 0
410
+ )
411
+ const txid = sourceTransaction.id('hex')
412
+ sourceTransaction.merklePath = MerklePath.fromCoinbaseTxidAndHeight(txid, 0)
413
+ const chain = new MockChain({ blockheaders: [] })
414
+ chain.addBlock(txid)
415
+
416
+ const spendTx = new Transaction(
417
+ 1,
418
+ [
419
+ {
420
+ unlockingScript: Script.fromASM('OP_TRUE'),
421
+ sourceTransaction,
422
+ sourceOutputIndex: 0
423
+ }
424
+ ],
425
+ [{
426
+ lockingScript: Script.fromASM('OP_NOP'),
427
+ satoshis: 1
428
+ }],
429
+ 0
430
+ )
431
+
432
+ const valid = await spendTx.verify(chain)
433
+
434
+ expect(valid).toBe(true)
435
+
436
+ const b = spendTx.toBinary()
437
+ const t = Transaction.fromBinary(b)
438
+ expect(t.inputs[0].sequence).toBe(0xffffffff)
439
+
440
+ const b2 = spendTx.toEF()
441
+ const t2 = Transaction.fromEF(b2)
442
+ expect(t2.inputs[0].sequence).toBe(0xffffffff)
443
+ })
371
444
  })
@@ -1,6 +1,7 @@
1
1
  import Script from '../../script/Script'
2
2
  import Spend from '../../script/Spend'
3
3
  import Transaction from '../../transaction/Transaction'
4
+ import { verifyNotNull } from '../../primitives/utils'
4
5
 
5
6
  describe('SpendComplex', () => {
6
7
  it('complex unlock script validation', () => {
@@ -19,11 +20,6 @@ describe('SpendComplex', () => {
19
20
  })
20
21
  })
21
22
 
22
- function verifyTruthy<T> (v: T | undefined): T {
23
- if (v == null) throw new Error('must have value')
24
- return v
25
- }
26
-
27
23
  export function validateUnlockScript (
28
24
  spendingRawTx: string,
29
25
  vin: number,
@@ -34,16 +30,16 @@ export function validateUnlockScript (
34
30
  const ls = Script.fromHex(lockingScript)
35
31
 
36
32
  const spend = new Spend({
37
- sourceTXID: verifyTruthy(spendingTx.inputs[vin].sourceTXID),
38
- sourceOutputIndex: verifyTruthy(spendingTx.inputs[vin].sourceOutputIndex),
33
+ sourceTXID: verifyNotNull(spendingTx.inputs[vin].sourceTXID, 'sourceTXID must have value'),
34
+ sourceOutputIndex: verifyNotNull(spendingTx.inputs[vin].sourceOutputIndex, 'sourceOutputIndex must have value'),
39
35
  sourceSatoshis: amount,
40
36
  lockingScript: ls,
41
37
  transactionVersion: spendingTx.version,
42
38
  otherInputs: spendingTx.inputs.filter((v, i) => i !== vin),
43
39
  inputIndex: vin,
44
- unlockingScript: verifyTruthy(spendingTx.inputs[vin].unlockingScript),
40
+ unlockingScript: verifyNotNull(spendingTx.inputs[vin].unlockingScript, 'unlockingScript must have value'),
45
41
  outputs: spendingTx.outputs,
46
- inputSequence: verifyTruthy(spendingTx.inputs[vin].sequence),
42
+ inputSequence: verifyNotNull(spendingTx.inputs[vin].sequence, 'sequence must have value'),
47
43
  lockTime: spendingTx.lockTime
48
44
  })
49
45
 
@@ -1,6 +1,6 @@
1
1
  import OP from '../OP.js'
2
2
  import ScriptTemplate from '../ScriptTemplate.js'
3
- import { fromBase58Check } from '../../primitives/utils.js'
3
+ import { fromBase58Check, verifyNotNull } from '../../primitives/utils.js'
4
4
  import LockingScript from '../LockingScript.js'
5
5
  import UnlockingScript from '../UnlockingScript.js'
6
6
  import Transaction from '../../transaction/Transaction.js'
@@ -9,11 +9,6 @@ import TransactionSignature from '../../primitives/TransactionSignature.js'
9
9
  import { sha256 } from '../../primitives/Hash.js'
10
10
  import Script from '../Script.js'
11
11
 
12
- function verifyTruthy<T>(v: T | undefined): T {
13
- if (v == null) throw new Error('must have value')
14
- return v
15
- }
16
-
17
12
  /**
18
13
  * P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.
19
14
  *
@@ -125,13 +120,13 @@ export default class P2PKH implements ScriptTemplate {
125
120
 
126
121
  const preimage = TransactionSignature.format({
127
122
  sourceTXID,
128
- sourceOutputIndex: verifyTruthy(input.sourceOutputIndex),
123
+ sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
129
124
  sourceSatoshis,
130
125
  transactionVersion: tx.version,
131
126
  otherInputs,
132
127
  inputIndex,
133
128
  outputs: tx.outputs,
134
- inputSequence: verifyTruthy(input.sequence),
129
+ inputSequence: verifyNotNull(input.sequence, 'input.sequence must have value'),
135
130
  subscript: lockingScript,
136
131
  lockTime: tx.lockTime,
137
132
  scope: signatureScope
@@ -9,11 +9,7 @@ import {
9
9
  import { WalletInterface } from '../../wallet/Wallet.interfaces.js'
10
10
  import { Transaction } from '../../transaction/index.js'
11
11
  import { WalletProtocol } from '../../wallet/Wallet.interfaces.js'
12
-
13
- function verifyTruthy<T>(v: T | undefined): T {
14
- if (v == null) throw new Error('must have value')
15
- return v
16
- }
12
+ import { verifyNotNull } from '../../primitives/utils.js'
17
13
 
18
14
  /**
19
15
  * For a given piece of data to push onto the stack in script, creates the correct minimally-encoded script chunk,
@@ -71,7 +67,7 @@ export default class PushDrop implements ScriptTemplate {
71
67
  fields: number[][]
72
68
  } {
73
69
  const lockingPublicKey = PublicKey.fromString(
74
- Utils.toHex(verifyTruthy(script.chunks[0].data)) // Ensure not undefined
70
+ Utils.toHex(verifyNotNull(script.chunks[0].data, 'script.chunks[0].data must have value'))
75
71
  )
76
72
 
77
73
  const fields: number[][] = []
@@ -249,7 +245,7 @@ export default class PushDrop implements ScriptTemplate {
249
245
 
250
246
  const preimage = TransactionSignature.format({
251
247
  sourceTXID,
252
- sourceOutputIndex: verifyTruthy(input.sourceOutputIndex),
248
+ sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
253
249
  sourceSatoshis,
254
250
  transactionVersion: tx.version,
255
251
  otherInputs,
@@ -2,14 +2,9 @@ import MerklePath from './MerklePath.js'
2
2
  import Transaction from './Transaction.js'
3
3
  import ChainTracker from './ChainTracker.js'
4
4
  import BeefTx from './BeefTx.js'
5
- import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
5
+ import { Reader, Writer, toHex, toArray, verifyNotNull } from '../primitives/utils.js'
6
6
  import { hash256 } from '../primitives/Hash.js'
7
7
 
8
- function verifyTruthy<T> (v: T | undefined): T {
9
- if (v == null) throw new Error('Expected a valid value, but got undefined.')
10
- return v
11
- }
12
-
13
8
  export const BEEF_V1 = 4022206465 // 0100BEEF in LE order
14
9
  export const BEEF_V2 = 4022206466 // 0200BEEF in LE order
15
10
  export const ATOMIC_BEEF = 0x01010101 // 01010101
@@ -156,7 +151,7 @@ export class Beef {
156
151
 
157
152
  for (const i of beefTx.tx.inputs) {
158
153
  if (i.sourceTransaction == null) {
159
- const itx = this.findTxid(verifyTruthy(i.sourceTXID)) // Ensure sourceTXID is valid
154
+ const itx = this.findTxid(verifyNotNull(i.sourceTXID, 'sourceTXID must be valid'))
160
155
  if (itx != null) {
161
156
  i.sourceTransaction = itx.tx
162
157
  }
@@ -185,7 +180,7 @@ export class Beef {
185
180
  } else {
186
181
  for (const i of tx.inputs) {
187
182
  if (i.sourceTransaction == null) {
188
- const itx = beef.findTxid(verifyTruthy(i.sourceTXID)) // Ensure sourceTXID is valid
183
+ const itx = beef.findTxid(verifyNotNull(i.sourceTXID, 'sourceTXID must be valid'))
189
184
  if (itx != null) {
190
185
  i.sourceTransaction = itx.tx
191
186
  }
@@ -869,3 +864,5 @@ export class Beef {
869
864
  }
870
865
  }
871
866
  }
867
+
868
+ export default Beef
@@ -618,7 +618,7 @@ export default class Transaction {
618
618
  const scriptBin = i.unlockingScript.toBinary()
619
619
  writer.writeVarIntNum(scriptBin.length)
620
620
  writer.write(scriptBin)
621
- writer.writeUInt32LE(i.sequence ?? 0)
621
+ writer.writeUInt32LE(i.sequence ?? 0xffffffff) // default to max sequence
622
622
  }
623
623
  writer.writeVarIntNum(this.outputs.length)
624
624
  for (const o of this.outputs) {
@@ -659,7 +659,7 @@ export default class Transaction {
659
659
  const scriptBin = i.unlockingScript.toBinary()
660
660
  writer.writeVarIntNum(scriptBin.length)
661
661
  writer.write(scriptBin)
662
- writer.writeUInt32LE(i.sequence ?? 0)
662
+ writer.writeUInt32LE(i.sequence ?? 0xffffffff) // default to max sequence
663
663
  writer.writeUInt64LE(
664
664
  i.sourceTransaction.outputs[i.sourceOutputIndex].satoshis ?? 0
665
665
  )
@@ -866,7 +866,7 @@ export default class Transaction {
866
866
  transactionVersion: tx.version,
867
867
  otherInputs,
868
868
  unlockingScript: input.unlockingScript,
869
- inputSequence: input.sequence ?? 0,
869
+ inputSequence: input.sequence ?? 0xffffffff, // default to max sequence
870
870
  inputIndex: i,
871
871
  outputs: tx.outputs,
872
872
  lockTime: tx.lockTime,
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Insufficient funds in the available inputs to cover the cost of the required outputs
3
+ * and the transaction fee (${moreSatoshisNeeded} more satoshis are needed,
4
+ * for a total of ${totalSatoshisNeeded}), plus whatever would be required in order
5
+ * to pay the fee to unlock and spend the outputs used to provide the additional satoshis.
6
+ */
7
+ export class WERR_INSUFFICIENT_FUNDS extends Error {
8
+ code: number
9
+ isError: boolean = true
10
+
11
+ /**
12
+ * @param totalSatoshisNeeded Total satoshis required to fund transactions after net of required inputs and outputs.
13
+ * @param moreSatoshisNeeded Shortfall on total satoshis required to fund transactions after net of required inputs and outputs.
14
+ */
15
+ constructor (
16
+ public totalSatoshisNeeded: number,
17
+ public moreSatoshisNeeded: number
18
+ ) {
19
+ super(`Insufficient funds in the available inputs to cover the cost of the required outputs and the transaction fee (${moreSatoshisNeeded} more satoshis are needed, for a total of ${totalSatoshisNeeded}), plus whatever would be required in order to pay the fee to unlock and spend the outputs used to provide the additional satoshis.`)
20
+ this.code = 7
21
+ this.name = this.constructor.name
22
+ }
23
+ }
24
+
25
+ export default WERR_INSUFFICIENT_FUNDS
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The ${parameter} parameter is invalid.
3
+ *
4
+ * This is an example of an error object with a custom property `parameter` and templated `message`.
5
+ */
6
+ export class WERR_INVALID_PARAMETER extends Error {
7
+ code: number
8
+ isError: boolean = true
9
+
10
+ constructor (
11
+ public parameter: string,
12
+ mustBe?: string
13
+ ) {
14
+ super(`The ${parameter} parameter must be ${mustBe ?? 'valid.'}`)
15
+ this.code = 6
16
+ this.name = this.constructor.name
17
+ }
18
+ }
19
+
20
+ export default WERR_INVALID_PARAMETER
@@ -41,6 +41,23 @@ import WalletWireTransceiver from './substrates/WalletWireTransceiver.js'
41
41
  import HTTPWalletWire from './substrates/HTTPWalletWire.js'
42
42
  import HTTPWalletJSON from './substrates/HTTPWalletJSON.js'
43
43
  import ReactNativeWebView from './substrates/ReactNativeWebView.js'
44
+ import {
45
+ validateAbortActionArgs,
46
+ validateAcquireDirectCertificateArgs,
47
+ validateAcquireIssuanceCertificateArgs,
48
+ validateCreateActionArgs,
49
+ validateDiscoverByAttributesArgs,
50
+ validateDiscoverByIdentityKeyArgs,
51
+ validateInternalizeActionArgs,
52
+ validateListActionsArgs,
53
+ validateListCertificatesArgs,
54
+ validateListOutputsArgs,
55
+ validateProveCertificateArgs,
56
+ validateRelinquishCertificateArgs,
57
+ validateRelinquishOutputArgs,
58
+ validateSignActionArgs
59
+ } from './validationHelpers.js'
60
+ import { WERR_INVALID_PARAMETER } from './WERR_INVALID_PARAMETER.js'
44
61
 
45
62
  const MAX_XDM_RESPONSE_WAIT = 200
46
63
 
@@ -133,6 +150,7 @@ export default class WalletClient implements WalletInterface {
133
150
  }
134
151
 
135
152
  async createAction (args: CreateActionArgs): Promise<CreateActionResult> {
153
+ validateCreateActionArgs(args)
136
154
  await this.connectToSubstrate()
137
155
  return await (this.substrate as WalletInterface).createAction(
138
156
  args,
@@ -141,6 +159,7 @@ export default class WalletClient implements WalletInterface {
141
159
  }
142
160
 
143
161
  async signAction (args: SignActionArgs): Promise<SignActionResult> {
162
+ validateSignActionArgs(args)
144
163
  await this.connectToSubstrate()
145
164
  return await (this.substrate as WalletInterface).signAction(
146
165
  args,
@@ -151,6 +170,7 @@ export default class WalletClient implements WalletInterface {
151
170
  async abortAction (args: {
152
171
  reference: Base64String
153
172
  }): Promise<{ aborted: true }> {
173
+ validateAbortActionArgs(args)
154
174
  await this.connectToSubstrate()
155
175
  return await (this.substrate as WalletInterface).abortAction(
156
176
  args,
@@ -159,6 +179,7 @@ export default class WalletClient implements WalletInterface {
159
179
  }
160
180
 
161
181
  async listActions (args: ListActionsArgs): Promise<ListActionsResult> {
182
+ validateListActionsArgs(args)
162
183
  await this.connectToSubstrate()
163
184
  return await (this.substrate as WalletInterface).listActions(
164
185
  args,
@@ -169,6 +190,7 @@ export default class WalletClient implements WalletInterface {
169
190
  async internalizeAction (
170
191
  args: InternalizeActionArgs
171
192
  ): Promise<{ accepted: true }> {
193
+ validateInternalizeActionArgs(args)
172
194
  await this.connectToSubstrate()
173
195
  return await (this.substrate as WalletInterface).internalizeAction(
174
196
  args,
@@ -177,6 +199,7 @@ export default class WalletClient implements WalletInterface {
177
199
  }
178
200
 
179
201
  async listOutputs (args: ListOutputsArgs): Promise<ListOutputsResult> {
202
+ validateListOutputsArgs(args)
180
203
  await this.connectToSubstrate()
181
204
  return await (this.substrate as WalletInterface).listOutputs(
182
205
  args,
@@ -188,6 +211,7 @@ export default class WalletClient implements WalletInterface {
188
211
  basket: BasketStringUnder300Bytes
189
212
  output: OutpointString
190
213
  }): Promise<{ relinquished: true }> {
214
+ validateRelinquishOutputArgs(args)
191
215
  await this.connectToSubstrate()
192
216
  return await (this.substrate as WalletInterface).relinquishOutput(
193
217
  args,
@@ -352,6 +376,7 @@ export default class WalletClient implements WalletInterface {
352
376
  async acquireCertificate (
353
377
  args: AcquireCertificateArgs
354
378
  ): Promise<AcquireCertificateResult> {
379
+ if (args.acquisitionProtocol === 'direct') { validateAcquireDirectCertificateArgs(args) } else if (args.acquisitionProtocol === 'issuance') { validateAcquireIssuanceCertificateArgs(args) } else { throw new WERR_INVALID_PARAMETER('acquisitionProtocol', `valid. ${String(args.acquisitionProtocol)} is unrecognized.`) }
355
380
  await this.connectToSubstrate()
356
381
  return await (this.substrate as WalletInterface).acquireCertificate(
357
382
  args,
@@ -367,6 +392,7 @@ export default class WalletClient implements WalletInterface {
367
392
  privileged?: BooleanDefaultFalse
368
393
  privilegedReason?: DescriptionString5to50Bytes
369
394
  }): Promise<ListCertificatesResult> {
395
+ validateListCertificatesArgs(args)
370
396
  await this.connectToSubstrate()
371
397
  return await (this.substrate as WalletInterface).listCertificates(
372
398
  args,
@@ -377,6 +403,7 @@ export default class WalletClient implements WalletInterface {
377
403
  async proveCertificate (
378
404
  args: ProveCertificateArgs
379
405
  ): Promise<ProveCertificateResult> {
406
+ validateProveCertificateArgs(args)
380
407
  await this.connectToSubstrate()
381
408
  return await (this.substrate as WalletInterface).proveCertificate(
382
409
  args,
@@ -389,6 +416,7 @@ export default class WalletClient implements WalletInterface {
389
416
  serialNumber: Base64String
390
417
  certifier: PubKeyHex
391
418
  }): Promise<{ relinquished: true }> {
419
+ validateRelinquishCertificateArgs(args)
392
420
  await this.connectToSubstrate()
393
421
  return await (this.substrate as WalletInterface).relinquishCertificate(
394
422
  args,
@@ -401,6 +429,7 @@ export default class WalletClient implements WalletInterface {
401
429
  limit?: PositiveIntegerDefault10Max10000
402
430
  offset?: PositiveIntegerOrZero
403
431
  }): Promise<DiscoverCertificatesResult> {
432
+ validateDiscoverByIdentityKeyArgs(args)
404
433
  await this.connectToSubstrate()
405
434
  return await (this.substrate as WalletInterface).discoverByIdentityKey(
406
435
  args,
@@ -413,6 +442,7 @@ export default class WalletClient implements WalletInterface {
413
442
  limit?: PositiveIntegerDefault10Max10000
414
443
  offset?: PositiveIntegerOrZero
415
444
  }): Promise<DiscoverCertificatesResult> {
445
+ validateDiscoverByAttributesArgs(args)
416
446
  await this.connectToSubstrate()
417
447
  return await (this.substrate as WalletInterface).discoverByAttributes(
418
448
  args,
@@ -13,6 +13,56 @@ export class WalletError extends Error {
13
13
  Error.captureStackTrace(this, this.constructor)
14
14
  }
15
15
  }
16
+
17
+ /**
18
+ * Safely serializes a WalletError (including special cases), Error or unknown error to JSON.
19
+ *
20
+ * Safely means avoiding deep, large, circular issues.
21
+ *
22
+ * Example deserialization can be found in HTTPWalletJSON.ts of bsv ts-sdk.
23
+ *
24
+ * @param error
25
+ * @returns stringified JSON representation of the error such that it can be deserialized to a WalletError.
26
+ */
27
+ static unknownToJson (error: any): string {
28
+ let e: any | undefined
29
+ if (typeof error.constructor.name === 'string' && String(error.constructor.name).startsWith('WERR_')) {
30
+ e = {
31
+ name: error.constructor.name,
32
+ message: error.message,
33
+ isError: true
34
+ }
35
+ if (e.name === 'WERR_REVIEW_ACTIONS') {
36
+ e.reviewActionResults = error.reviewActionResults
37
+ e.sendWithResults = error.sendWithResults
38
+ e.txid = error.txid
39
+ e.tx = error.tx
40
+ e.noSendChange = error.noSendChange
41
+ e.code = 5
42
+ } else if (e.name === 'WERR_INVALID_PARAMETER') {
43
+ e.parameter = error.parameter
44
+ e.code = 6
45
+ } else if (e.name === 'WERR_INSUFFICIENT_FUNDS') {
46
+ e.totalSatoshisNeeded = error.totalSatoshisNeeded
47
+ e.moreSatoshisNeeded = error.moreSatoshisNeeded
48
+ e.code = 7
49
+ }
50
+ } else if (error instanceof Error) {
51
+ e = {
52
+ name: error.constructor.name,
53
+ message: error.message,
54
+ isError: true
55
+ }
56
+ } else {
57
+ e = {
58
+ name: 'WERR_UNKNOWN',
59
+ message: String(error),
60
+ isError: true
61
+ }
62
+ }
63
+ const json = JSON.stringify(e)
64
+ return json
65
+ }
16
66
  }
17
67
 
18
68
  // NOTE: Enum values must not exceed the UInt8 range (0–255)
@@ -22,6 +72,8 @@ export enum walletErrors {
22
72
  invalidHmac = 3,
23
73
  invalidSignature = 4,
24
74
  reviewActions = 5,
75
+ invalidParameter = 6,
76
+ insufficientFunds = 7,
25
77
  }
26
78
 
27
79
  export type WalletErrorCode = keyof typeof walletErrors
@@ -0,0 +1,31 @@
1
+ import { CreateActionArgs } from '../Wallet.interfaces'
2
+ import WalletClient from '../WalletClient'
3
+ describe('WalletClient', () => {
4
+ it('0 createAction', async () => {
5
+ const wallet = new WalletClient('auto', '0.WalletClient.test')
6
+
7
+ async function testArgs(args: CreateActionArgs, parameter: string) {
8
+ try {
9
+ const r = await wallet.createAction(args)
10
+ expect(true).toBe(false)
11
+ } catch (e: any) {
12
+ expect(e.name).toBe('WERR_INVALID_PARAMETER')
13
+ expect(e.parameter).toBe(parameter)
14
+ }
15
+ }
16
+
17
+ const args: CreateActionArgs = {
18
+ description: 't' // too short to be valid
19
+ }
20
+ testArgs(args, 'description')
21
+ args.description = '12345'
22
+ args.outputs = [{
23
+ lockingScript: '',
24
+ satoshis: 0,
25
+ outputDescription: ''
26
+ }]
27
+ testArgs(args, 'lockingScript')
28
+ args.outputs[0].lockingScript = '1234'
29
+ testArgs(args, 'outputDescription')
30
+ })
31
+ })
@@ -6,5 +6,8 @@ export { default as WalletClient } from './WalletClient.js'
6
6
  // Is this an error? should it be 'walletErrors', the enum not the class?
7
7
  export { default as WalletErrors } from './WalletError.js'
8
8
  export { default as WERR_REVIEW_ACTIONS } from './WERR_REVIEW_ACTIONS.js'
9
+ export { default as WERR_INVALID_PARAMETER } from './WERR_INVALID_PARAMETER.js'
10
+ export { default as WERR_INSUFFICIENT_FUNDS } from './WERR_INSUFFICIENT_FUNDS.js'
9
11
  export * from './WalletError.js'
12
+ export * as Validation from './validationHelpers.js'
10
13
  export * from './substrates/index.js'
@@ -36,7 +36,9 @@ import {
36
36
  VersionString7To30Bytes,
37
37
  } from '../Wallet.interfaces.js'
38
38
  import { WERR_REVIEW_ACTIONS } from '../WERR_REVIEW_ACTIONS.js'
39
+ import { WERR_INVALID_PARAMETER } from '../WERR_INVALID_PARAMETER.js'
39
40
  import { toOriginHeader } from './utils/toOriginHeader.js'
41
+ import WERR_INSUFFICIENT_FUNDS from '../WERR_INSUFFICIENT_FUNDS.js'
40
42
 
41
43
  export default class HTTPWalletJSON implements WalletInterface {
42
44
  baseUrl: string
@@ -84,17 +86,25 @@ export default class HTTPWalletJSON implements WalletInterface {
84
86
 
85
87
  // Check the HTTP status on the original response
86
88
  if (!res.ok) {
87
- if (res.status === 400 && data.isError && data.code === 5) {
88
- const err = new WERR_REVIEW_ACTIONS(data.reviewActionResults, data.sendWithResults, data.txid, data.tx, data.noSendChange)
89
- throw err
90
- } else {
91
- const err = {
92
- call,
93
- args,
94
- message: data.message ?? `HTTP Client error ${res.status}`
89
+ if (res.status === 400 && data.isError) {
90
+ let err: any
91
+ switch (data.code) {
92
+ case 5:
93
+ err = new WERR_REVIEW_ACTIONS(data.reviewActionResults, data.sendWithResults, data.txid, data.tx, data.noSendChange); break;
94
+ case 6:
95
+ err = new WERR_INVALID_PARAMETER(data.parameter); err.message = data.message; break;
96
+ case 7:
97
+ err = new WERR_INSUFFICIENT_FUNDS(data.totalSatoshisNeeded, data.moreSatoshisNeeded); break;
98
+ default: break;
95
99
  }
96
- throw new Error(JSON.stringify(err))
100
+ if (err) throw err
97
101
  }
102
+ const err = {
103
+ call,
104
+ args,
105
+ message: data.message ?? `HTTP Client error ${res.status}`
106
+ }
107
+ throw new Error(JSON.stringify(err))
98
108
  }
99
109
  return data
100
110
  }