@bsv/wallet-toolbox 1.6.32 → 1.6.34

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 (126) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/docs/client.md +340 -93
  3. package/docs/monitor.md +34 -12
  4. package/docs/storage.md +49 -1
  5. package/docs/wallet.md +340 -93
  6. package/mobile/out/src/monitor/Monitor.d.ts +8 -0
  7. package/mobile/out/src/monitor/Monitor.d.ts.map +1 -1
  8. package/mobile/out/src/monitor/Monitor.js +8 -0
  9. package/mobile/out/src/monitor/Monitor.js.map +1 -1
  10. package/mobile/out/src/monitor/tasks/TaskReorg.d.ts +8 -12
  11. package/mobile/out/src/monitor/tasks/TaskReorg.d.ts.map +1 -1
  12. package/mobile/out/src/monitor/tasks/TaskReorg.js +20 -73
  13. package/mobile/out/src/monitor/tasks/TaskReorg.js.map +1 -1
  14. package/mobile/out/src/sdk/WERR_errors.d.ts +8 -0
  15. package/mobile/out/src/sdk/WERR_errors.d.ts.map +1 -1
  16. package/mobile/out/src/sdk/WERR_errors.js +33 -0
  17. package/mobile/out/src/sdk/WERR_errors.js.map +1 -1
  18. package/mobile/out/src/sdk/WalletError.d.ts +19 -0
  19. package/mobile/out/src/sdk/WalletError.d.ts.map +1 -1
  20. package/mobile/out/src/sdk/WalletError.js +42 -1
  21. package/mobile/out/src/sdk/WalletError.js.map +1 -1
  22. package/mobile/out/src/sdk/WalletErrorFromJson.d.ts +12 -0
  23. package/mobile/out/src/sdk/WalletErrorFromJson.d.ts.map +1 -0
  24. package/mobile/out/src/sdk/WalletErrorFromJson.js +69 -0
  25. package/mobile/out/src/sdk/WalletErrorFromJson.js.map +1 -0
  26. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts +49 -0
  27. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  28. package/mobile/out/src/sdk/index.d.ts +1 -0
  29. package/mobile/out/src/sdk/index.d.ts.map +1 -1
  30. package/mobile/out/src/sdk/index.js +1 -0
  31. package/mobile/out/src/sdk/index.js.map +1 -1
  32. package/mobile/out/src/services/chaintracker/ChaintracksChainTracker.js +1 -1
  33. package/mobile/out/src/services/chaintracker/ChaintracksChainTracker.js.map +1 -1
  34. package/mobile/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.d.ts.map +1 -1
  35. package/mobile/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js +16 -9
  36. package/mobile/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js.map +1 -1
  37. package/mobile/out/src/storage/StorageProvider.d.ts.map +1 -1
  38. package/mobile/out/src/storage/StorageProvider.js +4 -0
  39. package/mobile/out/src/storage/StorageProvider.js.map +1 -1
  40. package/mobile/out/src/storage/WalletStorageManager.d.ts +34 -2
  41. package/mobile/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  42. package/mobile/out/src/storage/WalletStorageManager.js +146 -0
  43. package/mobile/out/src/storage/WalletStorageManager.js.map +1 -1
  44. package/mobile/out/src/storage/remoting/StorageClient.d.ts.map +1 -1
  45. package/mobile/out/src/storage/remoting/StorageClient.js +3 -5
  46. package/mobile/out/src/storage/remoting/StorageClient.js.map +1 -1
  47. package/mobile/package-lock.json +2 -2
  48. package/mobile/package.json +1 -1
  49. package/out/src/monitor/Monitor.d.ts +8 -0
  50. package/out/src/monitor/Monitor.d.ts.map +1 -1
  51. package/out/src/monitor/Monitor.js +8 -0
  52. package/out/src/monitor/Monitor.js.map +1 -1
  53. package/out/src/monitor/tasks/TaskReorg.d.ts +8 -12
  54. package/out/src/monitor/tasks/TaskReorg.d.ts.map +1 -1
  55. package/out/src/monitor/tasks/TaskReorg.js +20 -73
  56. package/out/src/monitor/tasks/TaskReorg.js.map +1 -1
  57. package/out/src/sdk/WERR_errors.d.ts +8 -0
  58. package/out/src/sdk/WERR_errors.d.ts.map +1 -1
  59. package/out/src/sdk/WERR_errors.js +33 -0
  60. package/out/src/sdk/WERR_errors.js.map +1 -1
  61. package/out/src/sdk/WalletError.d.ts +19 -0
  62. package/out/src/sdk/WalletError.d.ts.map +1 -1
  63. package/out/src/sdk/WalletError.js +42 -1
  64. package/out/src/sdk/WalletError.js.map +1 -1
  65. package/out/src/sdk/WalletErrorFromJson.d.ts +12 -0
  66. package/out/src/sdk/WalletErrorFromJson.d.ts.map +1 -0
  67. package/out/src/sdk/WalletErrorFromJson.js +69 -0
  68. package/out/src/sdk/WalletErrorFromJson.js.map +1 -0
  69. package/out/src/sdk/WalletStorage.interfaces.d.ts +49 -0
  70. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  71. package/out/src/sdk/__test/WalletError.test.d.ts +2 -0
  72. package/out/src/sdk/__test/WalletError.test.d.ts.map +1 -0
  73. package/out/src/sdk/__test/WalletError.test.js +255 -0
  74. package/out/src/sdk/__test/WalletError.test.js.map +1 -0
  75. package/out/src/sdk/index.d.ts +1 -0
  76. package/out/src/sdk/index.d.ts.map +1 -1
  77. package/out/src/sdk/index.js +1 -0
  78. package/out/src/sdk/index.js.map +1 -1
  79. package/out/src/services/__tests/verifyBeef.test.js +7 -0
  80. package/out/src/services/__tests/verifyBeef.test.js.map +1 -1
  81. package/out/src/services/chaintracker/ChaintracksChainTracker.js +1 -1
  82. package/out/src/services/chaintracker/ChaintracksChainTracker.js.map +1 -1
  83. package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.d.ts.map +1 -1
  84. package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js +16 -9
  85. package/out/src/services/chaintracker/chaintracks/util/ChaintracksFetch.js.map +1 -1
  86. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  87. package/out/src/storage/StorageProvider.js +4 -0
  88. package/out/src/storage/StorageProvider.js.map +1 -1
  89. package/out/src/storage/WalletStorageManager.d.ts +34 -2
  90. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  91. package/out/src/storage/WalletStorageManager.js +146 -0
  92. package/out/src/storage/WalletStorageManager.js.map +1 -1
  93. package/out/src/storage/__test/getBeefForTransaction.test.js +10 -0
  94. package/out/src/storage/__test/getBeefForTransaction.test.js.map +1 -1
  95. package/out/src/storage/remoting/StorageClient.d.ts.map +1 -1
  96. package/out/src/storage/remoting/StorageClient.js +3 -5
  97. package/out/src/storage/remoting/StorageClient.js.map +1 -1
  98. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  99. package/out/src/storage/remoting/StorageServer.js +12 -10
  100. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  101. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  102. package/out/src/storage/schema/KnexMigrations.js +12 -0
  103. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  104. package/out/test/Wallet/specOps/specOps.man.test.js +8 -6
  105. package/out/test/Wallet/specOps/specOps.man.test.js.map +1 -1
  106. package/out/tsconfig.all.tsbuildinfo +1 -1
  107. package/package.json +1 -1
  108. package/src/monitor/Monitor.ts +8 -0
  109. package/src/monitor/tasks/TaskReorg.ts +20 -70
  110. package/src/sdk/WERR_errors.ts +34 -0
  111. package/src/sdk/WalletError.ts +42 -1
  112. package/src/sdk/WalletErrorFromJson.ts +80 -0
  113. package/src/sdk/WalletStorage.interfaces.ts +44 -0
  114. package/src/sdk/__test/WalletError.test.ts +318 -0
  115. package/src/sdk/index.ts +1 -0
  116. package/src/services/__tests/verifyBeef.test.ts +10 -1
  117. package/src/services/chaintracker/ChaintracksChainTracker.ts +1 -1
  118. package/src/services/chaintracker/chaintracks/util/ChaintracksFetch.ts +18 -11
  119. package/src/storage/StorageProvider.ts +4 -0
  120. package/src/storage/WalletStorageManager.ts +158 -0
  121. package/src/storage/__test/getBeefForTransaction.test.ts +11 -2
  122. package/src/storage/methods/internalizeAction.ts +1 -1
  123. package/src/storage/remoting/StorageClient.ts +4 -6
  124. package/src/storage/remoting/StorageServer.ts +13 -11
  125. package/src/storage/schema/KnexMigrations.ts +13 -0
  126. package/test/Wallet/specOps/specOps.man.test.ts +6 -4
@@ -0,0 +1,318 @@
1
+ import { WalletError } from '../WalletError'
2
+ import { WalletErrorFromJson } from '../WalletErrorFromJson'
3
+ import {
4
+ WERR_NOT_IMPLEMENTED,
5
+ WERR_INTERNAL,
6
+ WERR_INVALID_PARAMETER,
7
+ WERR_REVIEW_ACTIONS,
8
+ WERR_INSUFFICIENT_FUNDS,
9
+ WERR_BROADCAST_UNAVAILABLE,
10
+ WERR_NETWORK_CHAIN,
11
+ WERR_INVALID_OPERATION,
12
+ WERR_MISSING_PARAMETER,
13
+ WERR_BAD_REQUEST,
14
+ WERR_UNAUTHORIZED,
15
+ WERR_NOT_ACTIVE,
16
+ WERR_INVALID_PUBLIC_KEY
17
+ } from '../WERR_errors'
18
+
19
+ // Mock WalletStorage interface
20
+ const mockWalletStorage = {
21
+ createAction: jest.fn().mockImplementation((args: any) => {
22
+ throw new WERR_REVIEW_ACTIONS(
23
+ [{ txid: 'txid123', status: 'doubleSpend', competingTxs: ['txid456'], competingBeef: [0, 1, 2, 3] }],
24
+ [{ txid: 'txid123', status: 'failed' }],
25
+ 'txid123',
26
+ [5, 6, 7, 8],
27
+ ['00'.repeat(32) + '.0']
28
+ )
29
+ })
30
+ }
31
+
32
+ describe('WalletError tests', () => {
33
+ jest.setTimeout(99999999)
34
+
35
+ test('0 - WERR_REVIEW_ACTIONS from createAction failure', async () => {
36
+ try {
37
+ await mockWalletStorage.createAction({ someArgs: 'test' })
38
+ } catch (err) {
39
+ const werr = WalletError.fromUnknown(err)
40
+ expect(werr.name).toBe('WERR_REVIEW_ACTIONS')
41
+ expect(werr.message).toBe('Undelayed createAction or signAction results require review.')
42
+
43
+ const json = WalletError.unknownToJson(werr)
44
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
45
+ expect(werr2 instanceof WERR_REVIEW_ACTIONS).toBe(true)
46
+ const werr3 = werr2 as WERR_REVIEW_ACTIONS
47
+ expect(werr3.txid).toBe('txid123')
48
+ expect(werr3.reviewActionResults).toEqual([
49
+ { txid: 'txid123', status: 'doubleSpend', competingTxs: ['txid456'], competingBeef: [0, 1, 2, 3] }
50
+ ])
51
+ expect(werr3.sendWithResults).toEqual([{ txid: 'txid123', status: 'failed' }])
52
+ expect(werr3.noSendChange).toEqual(['00'.repeat(32) + '.0'])
53
+ }
54
+ })
55
+
56
+ test('1 - WERR_NOT_IMPLEMENTED basic test', async () => {
57
+ const werr = new WERR_NOT_IMPLEMENTED('Custom not implemented message')
58
+ expect(werr.name).toBe('WERR_NOT_IMPLEMENTED')
59
+ expect(werr.message).toBe('Custom not implemented message')
60
+
61
+ const json = WalletError.unknownToJson(werr)
62
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
63
+ expect(werr2.name).toBe('WERR_NOT_IMPLEMENTED')
64
+ expect(werr2.message).toBe('Custom not implemented message')
65
+ })
66
+
67
+ test('2 - WERR_INTERNAL with default message', async () => {
68
+ const werr = new WERR_INTERNAL()
69
+ expect(werr.name).toBe('WERR_INTERNAL')
70
+ expect(werr.message).toBe('An internal error has occurred.')
71
+
72
+ const json = WalletError.unknownToJson(werr)
73
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
74
+ expect(werr2.name).toBe('WERR_INTERNAL')
75
+ expect(werr2.message).toBe('An internal error has occurred.')
76
+ })
77
+
78
+ test('3 - WERR_INVALID_PARAMETER with custom parameter', async () => {
79
+ const werr = new WERR_INVALID_PARAMETER('amount', 'positive')
80
+ expect(werr.name).toBe('WERR_INVALID_PARAMETER')
81
+ expect(werr.message).toBe('The amount parameter must be positive')
82
+ expect(werr.parameter).toBe('amount')
83
+
84
+ const json = werr.toJson()
85
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
86
+ expect(werr2.name).toBe('WERR_INVALID_PARAMETER')
87
+ expect(werr2.message).toBe('The amount parameter must be positive')
88
+ expect((werr2 as WERR_INVALID_PARAMETER).parameter).toBe('amount')
89
+ })
90
+
91
+ test('4 - WERR_INSUFFICIENT_FUNDS with numeric values', async () => {
92
+ const werr = new WERR_INSUFFICIENT_FUNDS(1000, 500)
93
+ expect(werr.name).toBe('WERR_INSUFFICIENT_FUNDS')
94
+ expect(werr.message).toContain('500 more satoshis are needed')
95
+ expect(werr.message).toContain('for a total of 1000')
96
+ expect(werr.totalSatoshisNeeded).toBe(1000)
97
+ expect(werr.moreSatoshisNeeded).toBe(500)
98
+
99
+ const json = werr.toJson()
100
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
101
+ expect(werr2.name).toBe('WERR_INSUFFICIENT_FUNDS')
102
+ expect((werr2 as WERR_INSUFFICIENT_FUNDS).totalSatoshisNeeded).toBe(1000)
103
+ expect((werr2 as WERR_INSUFFICIENT_FUNDS).moreSatoshisNeeded).toBe(500)
104
+ })
105
+
106
+ test('5 - WERR_BROADCAST_UNAVAILABLE test', async () => {
107
+ const werr = new WERR_BROADCAST_UNAVAILABLE('Network issue')
108
+ expect(werr.name).toBe('WERR_BROADCAST_UNAVAILABLE')
109
+ expect(werr.message).toBe('Unable to broadcast transaction at this time.')
110
+
111
+ const json = WalletError.unknownToJson(werr)
112
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
113
+ expect(werr2.name).toBe('WERR_BROADCAST_UNAVAILABLE')
114
+ expect(werr2.message).toBe('Unable to broadcast transaction at this time.')
115
+ })
116
+
117
+ test('6 - WERR_NETWORK_CHAIN test', async () => {
118
+ const werr = new WERR_NETWORK_CHAIN('Chain mismatch')
119
+ expect(werr.name).toBe('WERR_NETWORK_CHAIN')
120
+ expect(werr.message).toBe('Chain mismatch')
121
+
122
+ const json = WalletError.unknownToJson(werr)
123
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
124
+ expect(werr2.name).toBe('WERR_NETWORK_CHAIN')
125
+ expect(werr2.message).toBe('Chain mismatch')
126
+ })
127
+
128
+ test('7 - WalletError.fromUnknown with plain Error', async () => {
129
+ const err = new Error('Test error')
130
+ const werr = WalletError.fromUnknown(err)
131
+ expect(werr.name).toBe('WERR_UNKNOWN')
132
+ expect(werr.message).toBe('Test error')
133
+ })
134
+
135
+ test('8 - WalletError.unknownToJson with unknown object', async () => {
136
+ const obj = { custom: 'data', code: 404 }
137
+ const json = WalletError.unknownToJson(obj)
138
+ const werr = WalletErrorFromJson(JSON.parse(json))
139
+ expect(werr.name).toBe('WERR_UNKNOWN')
140
+ expect(werr.message).toBe('[object Object]')
141
+ })
142
+
143
+ test('9 - WERR_INVALID_OPERATION basic test', async () => {
144
+ const werr = new WERR_INVALID_OPERATION('Custom invalid operation message')
145
+ expect(werr.name).toBe('WERR_INVALID_OPERATION')
146
+ expect(werr.message).toBe('Custom invalid operation message')
147
+
148
+ const json = WalletError.unknownToJson(werr)
149
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
150
+ expect(werr2.name).toBe('WERR_INVALID_OPERATION')
151
+ expect(werr2.message).toBe('Custom invalid operation message')
152
+ })
153
+
154
+ test('10 - WERR_MISSING_PARAMETER with parameter', async () => {
155
+ const werr = new WERR_MISSING_PARAMETER('requiredField')
156
+ expect(werr.name).toBe('WERR_MISSING_PARAMETER')
157
+ expect(werr.message).toBe('The required requiredField parameter is missing.')
158
+ expect(werr.parameter).toBe('requiredField')
159
+
160
+ const json = werr.toJson()
161
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
162
+ expect(werr2.name).toBe('WERR_MISSING_PARAMETER')
163
+ expect(werr2.message).toBe('The required requiredField parameter is missing.')
164
+ expect((werr2 as WERR_MISSING_PARAMETER).parameter).toBe('requiredField')
165
+ })
166
+
167
+ test('11 - WERR_BAD_REQUEST with custom message', async () => {
168
+ const werr = new WERR_BAD_REQUEST('Invalid request data')
169
+ expect(werr.name).toBe('WERR_BAD_REQUEST')
170
+ expect(werr.message).toBe('Invalid request data')
171
+
172
+ const json = WalletError.unknownToJson(werr)
173
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
174
+ expect(werr2.name).toBe('WERR_BAD_REQUEST')
175
+ expect(werr2.message).toBe('Invalid request data')
176
+ })
177
+
178
+ test('12 - WERR_UNAUTHORIZED with default message', async () => {
179
+ const werr = new WERR_UNAUTHORIZED()
180
+ expect(werr.name).toBe('WERR_UNAUTHORIZED')
181
+ expect(werr.message).toBe('Access is denied due to an authorization error.')
182
+
183
+ const json = WalletError.unknownToJson(werr)
184
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
185
+ expect(werr2.name).toBe('WERR_UNAUTHORIZED')
186
+ expect(werr2.message).toBe('Access is denied due to an authorization error.')
187
+ })
188
+
189
+ test('13 - WERR_NOT_ACTIVE with default message', async () => {
190
+ const werr = new WERR_NOT_ACTIVE()
191
+ expect(werr.name).toBe('WERR_NOT_ACTIVE')
192
+ expect(werr.message).toBe(
193
+ `WalletStorageManager is not accessing user's active storage or there are conflicting active stores configured.`
194
+ )
195
+
196
+ const json = WalletError.unknownToJson(werr)
197
+ const werr2 = WalletErrorFromJson(JSON.parse(json))
198
+ expect(werr2.name).toBe('WERR_NOT_ACTIVE')
199
+ expect(werr2.message).toBe(
200
+ `WalletStorageManager is not accessing user's active storage or there are conflicting active stores configured.`
201
+ )
202
+ })
203
+
204
+ test('14 - WERR_INVALID_PUBLIC_KEY with key and mainnet network', async () => {
205
+ const werr = new WERR_INVALID_PUBLIC_KEY('invalidkey123', 'mainnet')
206
+ expect(werr.name).toBe('WERR_INVALID_PUBLIC_KEY')
207
+ expect(werr.message).toBe('The provided public key "invalidkey123" is invalid or malformed.')
208
+ expect((werr as WERR_INVALID_PUBLIC_KEY).key).toBe('invalidkey123')
209
+
210
+ const json = WalletError.unknownToJson(werr)
211
+ const parsedJson = JSON.parse(json)
212
+ const werr2 = WalletErrorFromJson(parsedJson)
213
+ expect(werr2.name).toBe('WERR_INVALID_PUBLIC_KEY')
214
+ expect(werr2.message).toBe('The provided public key "invalidkey123" is invalid or malformed.')
215
+ expect((werr2 as WERR_INVALID_PUBLIC_KEY).key).toBe('invalidkey123')
216
+ })
217
+
218
+ test('15 - WERR_INVALID_PUBLIC_KEY with key and testnet network', async () => {
219
+ const werr = new WERR_INVALID_PUBLIC_KEY('invalidkey123', 'testnet')
220
+ expect(werr.name).toBe('WERR_INVALID_PUBLIC_KEY')
221
+ expect(werr.message).toBe('The provided public key is invalid or malformed.')
222
+ expect((werr as WERR_INVALID_PUBLIC_KEY).key).toBe('invalidkey123')
223
+
224
+ const json = WalletError.unknownToJson(werr)
225
+ const parsedJson = JSON.parse(json)
226
+ const werr2 = WalletErrorFromJson(parsedJson)
227
+ expect(werr2.name).toBe('WERR_INVALID_PUBLIC_KEY')
228
+ expect(werr2.message).toBe('The provided public key is invalid or malformed.')
229
+ expect((werr2 as WERR_INVALID_PUBLIC_KEY).key).toBe('invalidkey123')
230
+ })
231
+
232
+ test('16 - WalletError basic constructor with details and stack', async () => {
233
+ const customStack = 'custom stack trace'
234
+ const werr = new WalletError('WERR_TEST', 'Test message', customStack, { detail1: 'value1', detail2: 'value2' })
235
+ expect(werr.isError).toBe(true)
236
+ expect(werr.name).toBe('WERR_TEST')
237
+ expect(werr.message).toBe('Test message')
238
+ expect(werr.stack).toBe(customStack)
239
+ expect(werr.details).toEqual({ detail1: 'value1', detail2: 'value2' })
240
+ expect(werr.code).toBe('WERR_TEST')
241
+ expect(werr.description).toBe('Test message')
242
+
243
+ werr.code = 'WERR_NEW_CODE'
244
+ werr.description = 'New description'
245
+ expect(werr.name).toBe('WERR_NEW_CODE')
246
+ expect(werr.message).toBe('New description')
247
+ })
248
+
249
+ test('17 - WalletError.fromUnknown with string', async () => {
250
+ const werr = WalletError.fromUnknown('String error message')
251
+ expect(werr.name).toBe('WERR_UNKNOWN')
252
+ expect(werr.message).toBe('String error message')
253
+ })
254
+
255
+ test('18 - WalletError.fromUnknown with number', async () => {
256
+ const werr = WalletError.fromUnknown(404)
257
+ expect(werr.name).toBe('WERR_UNKNOWN')
258
+ expect(werr.message).toBe('404')
259
+ })
260
+
261
+ test('19 - WalletError.fromUnknown with custom object', async () => {
262
+ const obj = { code: 'ERR_404', message: 'Not found', status: 404 }
263
+ const werr = WalletError.fromUnknown(obj)
264
+ expect(werr.name).toBe('ERR_404')
265
+ expect(werr.message).toBe('Not found')
266
+ })
267
+
268
+ test('20 - WalletError.fromUnknown with nested walletError', async () => {
269
+ const innerErr = new WERR_INTERNAL('Inner error')
270
+ const outerObj = { message: 'Outer error', walletError: innerErr }
271
+ const werr = WalletError.fromUnknown(outerObj)
272
+ expect(werr.name).toBe('WERR_UNKNOWN')
273
+ expect(werr.message).toBe('Outer error')
274
+ expect((werr as any).walletError).toBeInstanceOf(WERR_INTERNAL)
275
+ expect((werr as any).walletError.message).toBe('Inner error')
276
+ })
277
+
278
+ test('21 - WalletError.fromUnknown with SQL details', async () => {
279
+ const err = { name: 'DBError', message: 'Query failed', sql: 'SELECT * FROM table', sqlMessage: 'Syntax error' }
280
+ const werr = WalletError.fromUnknown(err)
281
+ expect(werr.name).toBe('DBError')
282
+ expect(werr.message).toBe('Query failed')
283
+ expect(werr.details).toEqual({ sql: 'SELECT * FROM table', sqlMessage: 'Syntax error' })
284
+ })
285
+
286
+ test('22 - WalletError.unknownToJson with WalletError', async () => {
287
+ const werr = new WalletError('WERR_TEST', 'Test message')
288
+ const json = WalletError.unknownToJson(werr)
289
+ const parsed = JSON.parse(json)
290
+ expect(parsed.name).toBe('WERR_TEST')
291
+ expect(parsed.message).toBe('Test message')
292
+ })
293
+
294
+ test('23 - WalletError.unknownToJson with standard Error', async () => {
295
+ const err = new Error('Standard error')
296
+ const json = WalletError.unknownToJson(err)
297
+ const werr = WalletErrorFromJson(JSON.parse(json))
298
+ expect(werr.name).toBe('Error')
299
+ expect(werr.message).toBe('Standard error')
300
+ })
301
+
302
+ test('24 - WalletError.unknownToJson with string', async () => {
303
+ const json = WalletError.unknownToJson('String error')
304
+ const werr = WalletErrorFromJson(JSON.parse(json))
305
+ expect(werr.name).toBe('WERR_UNKNOWN')
306
+ expect(werr.message).toBe('String error')
307
+ })
308
+
309
+ test('25 - WalletError asStatus method', async () => {
310
+ const werr = new WalletError('WERR_TEST', 'Test description')
311
+ const status = werr.asStatus()
312
+ expect(status).toEqual({
313
+ status: 'error',
314
+ code: 'WERR_TEST',
315
+ description: 'Test description'
316
+ })
317
+ })
318
+ })
package/src/sdk/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './WalletError'
2
+ export * from './WalletErrorFromJson'
2
3
  export * from './WalletSigner.interfaces'
3
4
  export * from './WalletStorage.interfaces'
4
5
  export * from './WERR_errors'
@@ -1,4 +1,4 @@
1
- import { Beef } from '@bsv/sdk'
1
+ import { Beef, Utils } from '@bsv/sdk'
2
2
  import { Services } from '../Services'
3
3
  import { _tu, logger } from '../../../test/utils/TestUtilsWalletStorage'
4
4
  import { verifyTruthy } from '../../utility/utilityHelpers'
@@ -38,4 +38,13 @@ describe('verifyBeef tests', () => {
38
38
  expect(ok).toBe(true)
39
39
  }
40
40
  })
41
+
42
+ test.skip('2_ review beef verify root', async () => {
43
+ const bhex =
44
+ '0200beef01fe7c830d000a02a0021d4ca6c031db7f6334c08ddfda43cbde3800c7fa27892f8e80a5218ca8493918a10081788ac8d8267d409b6258a6a6f5d28317ee65b5b25892def4f6cbf44f92571d01510027c2382032711033d0a1e2724b9eefcf257e27bce28e37b7472877860570ee6e0129008e15879954392f322efdd32376077a3323db02501926a697f5db6b68862f67ce01150061dcb195186d564d754a056d9ad90d65ece5bfa5ddccebd24b64d25df3780b15010b00bcd8f2c9c62b4fbbefad9640f9f6dccf21246fa08a6e1cab2c052666dee4182001040018ad6a5739749e27c191a5ef7442d861e5b8d204d36c91e08bf8015811851dbe010300f47047d1c4582eb02349eabcdafc7f4573e93ed687718275475d6f528783d16201000039a5fa5dbbbcd4a1754c250a7879ae1ad2eeb189d87d3614c2a2d9519a7a47af0101001670fc6a8d40adbd3f8a84ae35f0a702695f19f19a8feddcfd1de6249cc164e901010092a689a4cda27aea3552a98a7441ffbaed8566ae31e0a1a67e67647e2f3b8fda05025a8b77e1c82cfcfda197fec3f805a6b7000737a583e45833df6721975fe8bad102448f38860c45d33c87041c0fda51befb1c90853d3141a0df3ac737ccb9b5e61b01000100000001f7ddf439a165bf63a7d6c144b4bd8882ff45dc35a3ca3e75517fa56482fed6bd000000006b4830450221008106bc7164333415bc485ae1d12acd72bbc536f1f03b25aa42d92971565b329902202d484d09935be7fa49bbd5806148dbfdb90cc86516537351acf20655c03fa656412102b53b5339d6241c4271a07e7b09035966defe37c1a3edd60b8a427d5a5b488cb5ffffffff021d00000000000000c421029664d9baa433b4ded47ce151d348fda7ed30df597b93bf5f321ec2fe742b0faaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a4734aba7171082ff009628f6d1abea57bc1ffcdb6c2b45a5e17219eaf6bc6b6e093b5243036565505084548f9715a440b6c03e73427d4730450221008e4964dc5e8f3cc6f41da7508cba05babb2ce211fa47fe91ae9c06903d95fde902206cb21d6c188f302fccedbbcd80459561dbabcabe3da16853371fede9f5d027d06d75c8030000000000001976a914f8a84c2bef6eed4eb3270c8605a8063202ed25cb88ac000000000001000000015a8b77e1c82cfcfda197fec3f805a6b7000737a583e45833df6721975fe8bad1010000006b483045022100fb62de36ac2930029b1397931c3f30bf6df5166f2e82bed6b2ef1d23491f8e450220730105461dc12236439ee568709ee72c345bb6748efe8656a0e96e4cc5eaecfb412102c6b33e96f3b635ebd71bcedd9bcb90b4c098b9b38730f58984e23615e0864833ffffffff042800000000000000c521029664d9baa433b4ded47ce151d348fda7ed30df597b93bf5f321ec2fe742b0faaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a47361c08d47822cb0806cd17af298948641db6bd36440da9a988af0f6600cba6dabfcfe5c7fe086b7a08e8feef3a9d21d8b0126c2f4a260b46304402204f418ece238fb0587f887c1e0ea6beb4ebcefa6749d1b523195bd65dc9971374022009d0b21c669a72a8a01808d394c55de730a3a4d287b3bb209697b2e79a9787ce6d7516050000000000001976a914803a2e1d2ca2373c21129a7075f1a42587f16c8188acec030000000000001976a91441cb6381a584c464df4b6dd75b91fb0ab6c4b7a688acd0040000000000001976a914e08fbd92ba37c1d84bba8439c55793ea60c0dd6b88ac00000000000100000001448f38860c45d33c87041c0fda51befb1c90853d3141a0df3ac737ccb9b5e61b020000006a4730440220411ab1f23f747899bf71185fbb4ab03defc6e215fb1ee3d24060b14256d2dc40022035669cd13b5c5fd399a402862b4e6bc001d0cbf56660bac37b1563eeaf49a700412103b20f91159733fd69817cc4d4f9ed0cf4340f63b482e0a0a7f233885c61d1b044ffffffff020a00000000000000c421029664d9baa433b4ded47ce151d348fda7ed30df597b93bf5f321ec2fe742b0faaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a47343c32fe905bb02e70c0a9779048c921b1e26a2684c498ab44759ac25bcdfafa95309c59d1c3ac12f056ad8d10dabe777d1d57dd934730450221009a64cdc81a0ada12d329463db24260a15ad56bdc3523613c0fae2fb64762d20e022021b942e859749fc23585fdb0395585d6ea52dcf0a310cc989a38ff0483c8717e6d75b7150000000000001976a91468cce1214ccbd14d9dfd813d8490daadaa96b39288ac00000000'
45
+ const beef = Beef.fromString(bhex)
46
+ logger(beef.toLogString())
47
+ logger(Utils.toHex(beef.txs[1].rawTx!))
48
+ logger(beef.bumps[0].computeRoot('e47df21819ed320a78392e62e963ddd77143c3c52ad5255a07ff55ba507df71d'))
49
+ })
41
50
  })
@@ -20,7 +20,7 @@ export class ChaintracksChainTracker implements ChainTracker {
20
20
  chain ||= 'main'
21
21
  this.chaintracks =
22
22
  chaintracks ??
23
- new ChaintracksServiceClient(chain, `https://npm-registry.babbage.systems:808${chain === 'main' ? '4' : '3'}`)
23
+ new ChaintracksServiceClient(chain, `https://${chain}net-chaintracks.babbage.systems`)
24
24
  this.cache = {}
25
25
  this.options = options || {}
26
26
  }
@@ -12,20 +12,27 @@ export class ChaintracksFetch implements ChaintracksFetchApi {
12
12
  constructor() {}
13
13
 
14
14
  async download(url: string): Promise<Uint8Array> {
15
- const response = await fetch(url, {
16
- method: 'GET',
17
- headers: {
18
- 'Content-Type': 'application/octet-stream'
19
- }
20
- })
15
+ for (let retry = 0; ; retry++) {
16
+ const response = await fetch(url, {
17
+ method: 'GET',
18
+ headers: {
19
+ 'Content-Type': 'application/octet-stream'
20
+ }
21
+ })
21
22
 
22
- if (!response.ok) {
23
- throw new Error(`Failed to download from ${url}: ${response.statusText}`)
24
- }
23
+ if (!response.ok) {
24
+ if (response.statusText === 'Too Many Requests' && retry < 3) {
25
+ // WhatsOnChain rate limits requests, so backoff and retry
26
+ await wait(1000 * (retry + 1))
27
+ continue
28
+ }
29
+ throw new Error(`Failed to download from ${url}: ${response.statusText}`)
30
+ }
25
31
 
26
- const data = await response.arrayBuffer()
32
+ const data = await response.arrayBuffer()
27
33
 
28
- return new Uint8Array(data)
34
+ return new Uint8Array(data)
35
+ }
29
36
  }
30
37
 
31
38
  async fetchJson<R>(url: string): Promise<R> {
@@ -230,7 +230,11 @@ export abstract class StorageProvider extends StorageReaderWriter implements Wal
230
230
  for (const txid of txids) {
231
231
  const d: GetReqsAndBeefDetail = {
232
232
  txid,
233
+ // status: 'readyToSend' | 'alreadySent' | 'error' | 'unknown'
233
234
  status: 'unknown'
235
+ // req?: TableProvenTxReq
236
+ // proven?: TableProvenTx
237
+ // error?: string
234
238
  }
235
239
  r.details.push(d)
236
240
  try {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AbortActionArgs,
3
3
  AbortActionResult,
4
+ Beef,
4
5
  InternalizeActionArgs,
5
6
  InternalizeActionResult,
6
7
  ListActionsResult,
@@ -16,6 +17,7 @@ import {
16
17
  TableCertificateX,
17
18
  TableOutput,
18
19
  TableOutputBasket,
20
+ TableProvenTx,
19
21
  TableProvenTxReq,
20
22
  TableSettings,
21
23
  TableUser
@@ -519,6 +521,162 @@ export class WalletStorageManager implements sdk.WalletStorage {
519
521
  })
520
522
  }
521
523
 
524
+ /**
525
+ * For each proven_txs record currently sourcing its transaction merkle proof from the given deactivated header,
526
+ * attempt to reprove the transaction against the current chain,
527
+ * updating the proven_txs record if a new valid proof is found.
528
+ *
529
+ * @param deactivatedHash An orphaned header than may have served as a proof source for proven_txs records.
530
+ * @returns
531
+ */
532
+ async reproveHeader(deactivatedHash: string): Promise<sdk.ReproveHeaderResult> {
533
+ const r: sdk.ReproveHeaderResult = { log: '', updated: [], unchanged: [], unavailable: [] }
534
+ const services = this.getServices()
535
+ const chaintracker = await services.getChainTracker()
536
+
537
+ // Lookup all the proven_txs records matching the deactivated headers
538
+ let ptxs: TableProvenTx[] = []
539
+ await this.runAsStorageProvider(async sp => {
540
+ ptxs = await sp.findProvenTxs({ partial: { blockHash: deactivatedHash } })
541
+ })
542
+
543
+ r.log += ` block ${deactivatedHash} orphaned with ${ptxs.length} impacted transactions\n`
544
+
545
+ for (const ptx of ptxs) {
546
+ // Loop over proven_txs records matching the deactivated header
547
+ const rp = await this.reproveProven(ptx, true)
548
+
549
+ r.log += rp.log
550
+ if (rp.unavailable) r.unavailable.push(ptx)
551
+ if (rp.unchanged) r.unchanged.push(ptx)
552
+ if (rp.updated) r.updated.push({ was: ptx, update: rp.updated.update, logUpdate: rp.updated.logUpdate })
553
+ }
554
+
555
+ if (r.updated.length > 0) {
556
+ await this.runAsStorageProvider(async sp => {
557
+ for (const u of r.updated) {
558
+ await sp.updateProvenTx(u.was.provenTxId, u.update)
559
+ r.log += ` txid ${u.was.txid} proof data updated\n` + u.logUpdate
560
+ }
561
+ })
562
+ }
563
+
564
+ return r
565
+ }
566
+
567
+ /**
568
+ * Extends the Beef `verify` function to handle BUMPs that have become invalid due to a chain reorg.
569
+ *
570
+ * Any merkle root that fails `isValidRootForHeight` triggers a reprove attempt for that block header.
571
+ * This results in proven_txs with invalid proofs being updated with new valid proofs where possible.
572
+ * Finally, a new beef is generated and verified against the chaintracker.
573
+ *
574
+ * @param beef
575
+ * @param allowTxidOnly
576
+ * @returns
577
+ */
578
+ async verifyAndRepairBeef(beef: Beef, allowTxidOnly?: boolean): Promise<boolean> {
579
+ throw new sdk.WERR_NOT_IMPLEMENTED()
580
+
581
+ const services = this.getServices()
582
+ const chaintracker = await services.getChainTracker()
583
+ const verified = await beef.verify(chaintracker)
584
+
585
+ const r = beef.verifyValid(allowTxidOnly)
586
+ if (!r.valid) return false
587
+
588
+ const invalidRoots: Record<number, string> = {}
589
+ for (const height of Object.keys(r.roots)) {
590
+ const isValid = await chaintracker.isValidRootForHeight(r.roots[height], Number(height))
591
+ if (!isValid) {
592
+ invalidRoots[height] = r.roots[height]
593
+ }
594
+ }
595
+
596
+ if (Object.keys(invalidRoots).length === 0) {
597
+ // There are no invalid merkle roots and the beef is structurally valid,
598
+ // the beef is fully verified.
599
+ return true
600
+ }
601
+
602
+ for (const heightStr of Object.keys(invalidRoots)) {
603
+ const hash = invalidRoots[Number(heightStr)]
604
+ const r = await this.reproveHeader(hash)
605
+ }
606
+
607
+ // All invalid BUMPs must be removed from the beef
608
+ // and all txid's that were proven by those BUMPs need
609
+ // new beefs merged into the beef.
610
+ // In most cases, this will be a replacement BUMP,
611
+ // but it may also require a deeper proof.
612
+ }
613
+
614
+ /**
615
+ * Attempt to reprove the transaction against the current chain,
616
+ * If a new valid proof is found and noUpdate is not true,
617
+ * update the proven_txs record with new block and merkle proof data.
618
+ * If noUpdate is true, the update to be applied is available in the returned result.
619
+ *
620
+ * @param ptx proven_txs record to reprove
621
+ * @param noUpdate
622
+ * @returns
623
+ */
624
+ async reproveProven(ptx: TableProvenTx, noUpdate?: boolean): Promise<sdk.ReproveProvenResult> {
625
+ const r: sdk.ReproveProvenResult = { log: '', updated: undefined, unchanged: false, unavailable: false }
626
+ const services = this.getServices()
627
+ const chaintracker = await services.getChainTracker()
628
+
629
+ const mpr = await services.getMerklePath(ptx.txid)
630
+ if (mpr.merklePath && mpr.header) {
631
+ const mp = mpr.merklePath
632
+ const h = mpr.header
633
+ const leaf = mp.path[0].find(leaf => leaf.txid === true && leaf.hash === ptx.txid)
634
+ if (leaf) {
635
+ const update: Partial<TableProvenTx> = {
636
+ height: mp.blockHeight,
637
+ index: leaf.offset,
638
+ merklePath: mp.toBinary(),
639
+ merkleRoot: h.merkleRoot,
640
+ blockHash: h.hash
641
+ }
642
+ if (update.blockHash === ptx.blockHash) {
643
+ r.log += ` txid ${ptx.txid} merkle path update still based on deactivated header ${ptx.blockHash}\n`
644
+ r.unchanged = true
645
+ } else {
646
+ // Verify the new proof's validity.
647
+ const merkleRoot = mp.computeRoot(ptx.txid)
648
+ const isValid = await chaintracker.isValidRootForHeight(merkleRoot, update.height!)
649
+ const logUpdate = ` height ${ptx.height} ${ptx.height === update.height ? 'unchanged' : `-> ${update.height}`}\n`
650
+ r.log += ` blockHash ${ptx.blockHash} -> ${update.blockHash}\n`
651
+ r.log += ` merkleRoot ${ptx.merkleRoot} -> ${update.merkleRoot}\n`
652
+ r.log += ` index ${ptx.index} -> ${update.index}\n`
653
+ if (isValid) {
654
+ r.updated = { update, logUpdate }
655
+ } else {
656
+ r.log +=
657
+ ` txid ${ptx.txid} chaintracker fails to confirm updated merkle path update invalid\n` + logUpdate
658
+ r.unavailable = true
659
+ }
660
+ }
661
+ } else {
662
+ r.log += ` txid ${ptx.txid} merkle path update doesn't include txid\n`
663
+ r.unavailable = true
664
+ }
665
+ } else {
666
+ r.log += ` txid ${ptx.txid} merkle path update unavailable\n`
667
+ r.unavailable = true
668
+ }
669
+
670
+ if (r.updated && !noUpdate) {
671
+ await this.runAsStorageProvider(async sp => {
672
+ await sp.updateProvenTx(ptx.provenTxId, r.updated!.update)
673
+ r.log += ` txid ${ptx.txid} proof data updated\n` + r.updated!.logUpdate
674
+ })
675
+ }
676
+
677
+ return r
678
+ }
679
+
522
680
  async syncFromReader(
523
681
  identityKey: string,
524
682
  reader: sdk.WalletStorageSyncReader,
@@ -1,5 +1,4 @@
1
- import { Beef, ListActionsResult, ListOutputsResult } from '@bsv/sdk'
2
- import { WalletError } from '../../sdk/WalletError'
1
+ import { Beef, ListActionsResult, ListOutputsResult, Utils } from '@bsv/sdk'
3
2
  import { StorageAdminStats, StorageProvider } from '../StorageProvider'
4
3
  import { Chain } from '../../sdk/types'
5
4
  import { Services } from '../../services/Services'
@@ -60,6 +59,16 @@ describe('getBeefForTransaction tests', () => {
60
59
  expect(beef.bumps.length > 0)
61
60
  }
62
61
  })
62
+
63
+ test.skip('1 obtain atomic beef hex for txid', async () => {
64
+ const ps = new ProtoStorage('main')
65
+ const txid = '4cefbe79926d6ef2cc727d8faccac186d9bb141f170411dd75bc6329f428f5a4'
66
+ const beef = await ps.getBeefForTxid(txid)
67
+ expect(beef.bumps.length > 0)
68
+ console.log(beef.toLogString())
69
+ const hex = Utils.toHex(beef.toBinaryAtomic(txid))
70
+ console.log(hex)
71
+ })
63
72
  })
64
73
 
65
74
  class ProtoStorage extends StorageProvider {
@@ -18,7 +18,7 @@ import { randomBytesBase64, verifyId, verifyOne, verifyOneOrNone } from '../../u
18
18
  import { TransactionStatus } from '../../sdk/types'
19
19
  import { EntityProvenTxReq } from '../schema/entities/EntityProvenTxReq'
20
20
  import { blockHash } from '../../services/chaintracker/chaintracks/util/blockHeaderUtilities'
21
- import { TableProvenTx } from '../index.client'
21
+ import { TableProvenTx } from '../schema/tables/TableProvenTx'
22
22
 
23
23
  /**
24
24
  * Internalize Action allows a wallet to take ownership of outputs in a pre-existing transaction.
@@ -44,6 +44,8 @@ import { TableOutputBasket } from '../schema/tables/TableOutputBasket'
44
44
  import { TableOutput } from '../schema/tables/TableOutput'
45
45
  import { TableProvenTxReq } from '../schema/tables/TableProvenTxReq'
46
46
  import { EntityTimeStamp } from '../../sdk/types'
47
+ import { WalletError } from '../../sdk/WalletError'
48
+ import { WalletErrorFromJson } from '../../sdk/WalletErrorFromJson'
47
49
 
48
50
  /**
49
51
  * `StorageClient` implements the `WalletStorageProvider` interface which allows it to
@@ -118,12 +120,8 @@ export class StorageClient implements WalletStorageProvider {
118
120
 
119
121
  const json = await response.json()
120
122
  if (json.error) {
121
- const { code, message, data } = json.error
122
- const err = new Error(`RPC Error: ${message}`)
123
- // You could attach more info here if you like:
124
- ;(err as any).code = code
125
- ;(err as any).data = data
126
- throw err
123
+ const werr = WalletErrorFromJson(json.error)
124
+ throw werr
127
125
  }
128
126
 
129
127
  return json.result