@bsv/wallet-toolbox 1.1.13 → 1.1.14

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 (132) hide show
  1. package/docs/README.md +8 -2
  2. package/docs/client.md +453 -346
  3. package/docs/open-rpc/index.html +46 -0
  4. package/docs/services.md +236 -211
  5. package/docs/setup.md +20 -4
  6. package/docs/storage.md +30 -5
  7. package/docs/wallet.md +453 -346
  8. package/out/src/Setup.d.ts.map +1 -1
  9. package/out/src/Setup.js.map +1 -1
  10. package/out/src/SetupClient.d.ts +9 -0
  11. package/out/src/SetupClient.d.ts.map +1 -1
  12. package/out/src/SetupClient.js +19 -3
  13. package/out/src/SetupClient.js.map +1 -1
  14. package/out/src/Wallet.d.ts +6 -0
  15. package/out/src/Wallet.d.ts.map +1 -1
  16. package/out/src/Wallet.js +57 -0
  17. package/out/src/Wallet.js.map +1 -1
  18. package/out/src/sdk/WalletServices.interfaces.d.ts +18 -13
  19. package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  20. package/out/src/services/Services.d.ts +4 -11
  21. package/out/src/services/Services.d.ts.map +1 -1
  22. package/out/src/services/Services.js +19 -37
  23. package/out/src/services/Services.js.map +1 -1
  24. package/out/src/services/__tests/ARC.test.d.ts +2 -0
  25. package/out/src/services/__tests/ARC.test.d.ts.map +1 -0
  26. package/out/src/services/__tests/ARC.test.js +98 -0
  27. package/out/src/services/__tests/ARC.test.js.map +1 -0
  28. package/out/src/services/__tests/arcServices.test.d.ts +2 -0
  29. package/out/src/services/__tests/arcServices.test.d.ts.map +1 -0
  30. package/out/src/services/__tests/arcServices.test.js +7 -0
  31. package/out/src/services/__tests/arcServices.test.js.map +1 -0
  32. package/out/src/services/__tests/postBeef.test.js +45 -7
  33. package/out/src/services/__tests/postBeef.test.js.map +1 -1
  34. package/out/src/services/createDefaultWalletServicesOptions.d.ts +1 -0
  35. package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  36. package/out/src/services/createDefaultWalletServicesOptions.js +16 -4
  37. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  38. package/out/src/services/providers/ARC.d.ts +91 -0
  39. package/out/src/services/providers/ARC.d.ts.map +1 -0
  40. package/out/src/services/providers/ARC.js +192 -0
  41. package/out/src/services/providers/ARC.js.map +1 -0
  42. package/out/src/services/providers/SdkWhatsOnChain.d.ts +21 -0
  43. package/out/src/services/providers/SdkWhatsOnChain.d.ts.map +1 -0
  44. package/out/src/services/providers/SdkWhatsOnChain.js +67 -0
  45. package/out/src/services/providers/SdkWhatsOnChain.js.map +1 -0
  46. package/out/src/services/providers/WhatsOnChain.d.ts +35 -0
  47. package/out/src/services/providers/WhatsOnChain.d.ts.map +1 -0
  48. package/out/src/services/providers/WhatsOnChain.js +266 -0
  49. package/out/src/services/providers/WhatsOnChain.js.map +1 -0
  50. package/out/src/services/providers/__tests/WhatsOnChain.test.d.ts +2 -0
  51. package/out/src/services/providers/__tests/WhatsOnChain.test.d.ts.map +1 -0
  52. package/out/src/services/providers/__tests/WhatsOnChain.test.js +176 -0
  53. package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -0
  54. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  55. package/out/src/storage/methods/createAction.js +9 -2
  56. package/out/src/storage/methods/createAction.js.map +1 -1
  57. package/out/src/storage/methods/generateChange.d.ts +12 -1
  58. package/out/src/storage/methods/generateChange.d.ts.map +1 -1
  59. package/out/src/storage/methods/generateChange.js +24 -1
  60. package/out/src/storage/methods/generateChange.js.map +1 -1
  61. package/out/src/storage/methods/processAction.d.ts.map +1 -1
  62. package/out/src/storage/methods/processAction.js +1 -1
  63. package/out/src/storage/methods/processAction.js.map +1 -1
  64. package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js +0 -1
  65. package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js.map +1 -1
  66. package/out/src/storage/sync/StorageMySQLDojoReader.js +1 -1
  67. package/out/src/storage/sync/StorageMySQLDojoReader.js.map +1 -1
  68. package/out/src/utility/utilityHelpers.js +1 -1
  69. package/out/src/utility/utilityHelpers.js.map +1 -1
  70. package/out/test/Wallet/StorageClient/storageClient.man.test.js +22 -3
  71. package/out/test/Wallet/StorageClient/storageClient.man.test.js.map +1 -1
  72. package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js +40 -0
  73. package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js.map +1 -1
  74. package/out/test/services/Services.test.js +0 -31
  75. package/out/test/services/Services.test.js.map +1 -1
  76. package/out/test/utils/TestUtilsWalletStorage.d.ts +26 -18
  77. package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
  78. package/out/test/utils/TestUtilsWalletStorage.js +135 -27
  79. package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
  80. package/out/test/wallet/list/listActions2.test.js +36 -2
  81. package/out/test/wallet/list/listActions2.test.js.map +1 -1
  82. package/out/tsconfig.all.tsbuildinfo +1 -1
  83. package/package.json +5 -7
  84. package/src/Setup.ts +0 -1
  85. package/src/SetupClient.ts +26 -6
  86. package/src/Wallet.ts +66 -2
  87. package/src/sdk/WalletServices.interfaces.ts +19 -14
  88. package/src/services/Services.ts +23 -62
  89. package/src/services/__tests/ARC.test.ts +110 -0
  90. package/src/services/__tests/arcServices.test.ts +8 -0
  91. package/src/services/__tests/postBeef.test.ts +47 -9
  92. package/src/services/createDefaultWalletServicesOptions.ts +19 -6
  93. package/src/services/providers/ARC.ts +289 -0
  94. package/src/services/providers/SdkWhatsOnChain.ts +96 -0
  95. package/src/services/providers/WhatsOnChain.ts +369 -0
  96. package/src/services/providers/__tests/WhatsOnChain.test.ts +227 -0
  97. package/src/storage/methods/createAction.ts +26 -4
  98. package/src/storage/methods/generateChange.ts +42 -2
  99. package/src/storage/methods/processAction.ts +2 -1
  100. package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +0 -1
  101. package/src/storage/sync/StorageMySQLDojoReader.ts +1 -1
  102. package/src/utility/utilityHelpers.ts +1 -1
  103. package/test/Wallet/StorageClient/storageClient.man.test.ts +30 -4
  104. package/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.ts +54 -0
  105. package/test/services/Services.test.ts +0 -30
  106. package/test/utils/TestUtilsWalletStorage.ts +175 -45
  107. package/test/wallet/list/listActions2.test.ts +4 -7
  108. package/out/src/services/__tests/postBeefToArcTaal.test.d.ts +0 -2
  109. package/out/src/services/__tests/postBeefToArcTaal.test.d.ts.map +0 -1
  110. package/out/src/services/__tests/postBeefToArcTaal.test.js +0 -479
  111. package/out/src/services/__tests/postBeefToArcTaal.test.js.map +0 -1
  112. package/out/src/services/__tests/postTxs.test.d.ts +0 -2
  113. package/out/src/services/__tests/postTxs.test.d.ts.map +0 -1
  114. package/out/src/services/__tests/postTxs.test.js +0 -28
  115. package/out/src/services/__tests/postTxs.test.js.map +0 -1
  116. package/out/src/services/providers/arcServices.d.ts +0 -62
  117. package/out/src/services/providers/arcServices.d.ts.map +0 -1
  118. package/out/src/services/providers/arcServices.js +0 -375
  119. package/out/src/services/providers/arcServices.js.map +0 -1
  120. package/out/src/services/providers/whatsonchain.d.ts +0 -17
  121. package/out/src/services/providers/whatsonchain.d.ts.map +0 -1
  122. package/out/src/services/providers/whatsonchain.js +0 -130
  123. package/out/src/services/providers/whatsonchain.js.map +0 -1
  124. package/out/test/examples/README.man.test.d.ts +0 -2
  125. package/out/test/examples/README.man.test.d.ts.map +0 -1
  126. package/out/test/examples/README.man.test.js +0 -47
  127. package/out/test/examples/README.man.test.js.map +0 -1
  128. package/src/services/__tests/postBeefToArcTaal.test.ts +0 -487
  129. package/src/services/__tests/postTxs.test.ts +0 -28
  130. package/src/services/providers/arcServices.ts +0 -578
  131. package/src/services/providers/whatsonchain.ts +0 -170
  132. package/test/examples/README.man.test.ts +0 -53
@@ -0,0 +1,369 @@
1
+ import { HexString, WhatsOnChainConfig } from '@bsv/sdk'
2
+ import {
3
+ asArray,
4
+ asString,
5
+ sdk,
6
+ validateScriptHash,
7
+ wait
8
+ } from '../../index.client'
9
+ import { convertProofToMerklePath } from '../../utility/tscProofToMerklePath'
10
+ import SdkWhatsOnChain from './SdkWhatsOnChain'
11
+
12
+ /**
13
+ *
14
+ */
15
+ export class WhatsOnChain extends SdkWhatsOnChain {
16
+ constructor(chain: sdk.Chain = 'main', config: WhatsOnChainConfig = {}) {
17
+ super(chain, config)
18
+ }
19
+
20
+ /**
21
+ * 2025-02-16 throwing internal server error 500.
22
+ * @param txid
23
+ * @returns
24
+ */
25
+ async getTxPropagation(txid: string): Promise<number> {
26
+ const requestOptions = {
27
+ method: 'GET',
28
+ headers: this.getHttpHeaders()
29
+ }
30
+
31
+ const response = await this.httpClient.request<string>(
32
+ `${this.URL}/tx/hash/${txid}/propagation`,
33
+ requestOptions
34
+ )
35
+
36
+ if (
37
+ !response.data ||
38
+ !response.ok ||
39
+ response.status !== 200 ||
40
+ response.statusText !== 'OK'
41
+ )
42
+ throw new sdk.WERR_INVALID_PARAMETER(
43
+ 'txid',
44
+ `valid transaction. '${txid}' response ${response.statusText}`
45
+ )
46
+
47
+ return 0
48
+ }
49
+
50
+ /**
51
+ * May return undefined for unmined transactions that are in the mempool.
52
+ * @param txid
53
+ * @returns raw transaction as hex string or undefined if txid not found in mined block.
54
+ */
55
+ async getRawTx(txid: string): Promise<string | undefined> {
56
+ const headers = this.getHttpHeaders()
57
+ headers['Cache-Control'] = 'no-cache'
58
+
59
+ const requestOptions = {
60
+ method: 'GET',
61
+ headers
62
+ }
63
+
64
+ for (let retry = 0; retry < 2; retry++) {
65
+ const response = await this.httpClient.request<string>(
66
+ `${this.URL}/tx/${txid}/hex`,
67
+ requestOptions
68
+ )
69
+ if (response.statusText === 'Too Many Requests' && retry < 2) {
70
+ await wait(2000)
71
+ continue
72
+ }
73
+
74
+ if (response.status === 404 && response.statusText === 'Not Found')
75
+ return undefined
76
+
77
+ if (
78
+ !response.data ||
79
+ !response.ok ||
80
+ response.status !== 200 ||
81
+ response.statusText !== 'OK'
82
+ )
83
+ throw new sdk.WERR_INVALID_PARAMETER(
84
+ 'txid',
85
+ `valid transaction. '${txid}' response ${response.statusText}`
86
+ )
87
+
88
+ return response.data
89
+ }
90
+ throw new sdk.WERR_INTERNAL()
91
+ }
92
+
93
+ async getRawTxResult(txid: string): Promise<sdk.GetRawTxResult> {
94
+ const r: sdk.GetRawTxResult = { name: 'WoC', txid: asString(txid) }
95
+
96
+ try {
97
+ const rawTxHex = await this.getRawTx(txid)
98
+ if (rawTxHex) r.rawTx = asArray(rawTxHex)
99
+ } catch (err: unknown) {
100
+ r.error = sdk.WalletError.fromUnknown(err)
101
+ }
102
+
103
+ return r
104
+ }
105
+
106
+ /**
107
+ * @param rawTx raw transaction to broadcast as hex string
108
+ * @returns txid returned by transaction processor of transaction broadcast
109
+ */
110
+ async postRawTx(rawTx: HexString): Promise<string> {
111
+ const headers = this.getHttpHeaders()
112
+ headers['Content-Type'] = 'application/json'
113
+ headers['Accept'] = 'text/plain'
114
+
115
+ const requestOptions = {
116
+ method: 'POST',
117
+ headers,
118
+ data: { txhex: rawTx }
119
+ }
120
+
121
+ for (let retry = 0; retry < 2; retry++) {
122
+ try {
123
+ const response = await this.httpClient.request<string>(
124
+ `${this.URL}/tx/raw`,
125
+ requestOptions
126
+ )
127
+ if (response.statusText === 'Too Many Requests' && retry < 2) {
128
+ await wait(2000)
129
+ continue
130
+ }
131
+ if (response.ok) {
132
+ const txid = response.data
133
+ return txid
134
+ } else {
135
+ if (typeof response.data === 'string')
136
+ throw new sdk.WERR_INVALID_PARAMETER(
137
+ 'rawTx',
138
+ `valid. ${response.data}`
139
+ )
140
+ else
141
+ throw new sdk.WERR_INVALID_PARAMETER(
142
+ 'rawTx',
143
+ `valid. ${response.status} ${response.statusText}`
144
+ )
145
+ }
146
+ } catch (eu: unknown) {
147
+ if (eu instanceof sdk.WERR_INVALID_PARAMETER) throw eu
148
+ const e = sdk.WalletError.fromUnknown(eu)
149
+ throw new sdk.WERR_INVALID_PARAMETER(
150
+ 'rawTx',
151
+ `valid rawTx. error ${e.code} ${e.message} ${rawTx}`
152
+ )
153
+ }
154
+ }
155
+ throw new sdk.WERR_INTERNAL()
156
+ }
157
+
158
+ /**
159
+ * @param txid
160
+ * @returns
161
+ */
162
+ async getMerklePath(
163
+ txid: string,
164
+ services: sdk.WalletServices
165
+ ): Promise<sdk.GetMerklePathResult> {
166
+ const r: sdk.GetMerklePathResult = { name: 'WoCTsc' }
167
+
168
+ const headers = this.getHttpHeaders()
169
+ const requestOptions = {
170
+ method: 'GET',
171
+ headers
172
+ }
173
+
174
+ for (let retry = 0; retry < 2; retry++) {
175
+ try {
176
+ const response = await this.httpClient.request<
177
+ WhatsOnChainTscProof | WhatsOnChainTscProof[]
178
+ >(`${this.URL}/tx/${txid}/proof/tsc`, requestOptions)
179
+ if (response.statusText === 'Too Many Requests' && retry < 2) {
180
+ await wait(2000)
181
+ continue
182
+ }
183
+
184
+ if (response.status === 404 && response.statusText === 'Not Found')
185
+ return r
186
+
187
+ if (
188
+ !response.ok ||
189
+ response.status !== 200 ||
190
+ response.statusText !== 'OK'
191
+ )
192
+ throw new sdk.WERR_INVALID_PARAMETER(
193
+ 'txid',
194
+ `valid transaction. '${txid}' response ${response.statusText}`
195
+ )
196
+
197
+ if (!response.data) {
198
+ // Unmined, proof not yet available.
199
+ return r
200
+ }
201
+
202
+ if (!Array.isArray(response.data)) response.data = [response.data]
203
+
204
+ if (response.data.length != 1) return r
205
+
206
+ const p = response.data[0]
207
+ const header = await services.hashToHeader(p.target)
208
+ if (header) {
209
+ const proof = {
210
+ index: p.index,
211
+ nodes: p.nodes,
212
+ height: header.height
213
+ }
214
+ r.merklePath = convertProofToMerklePath(txid, proof)
215
+ r.header = header
216
+ } else {
217
+ throw new sdk.WERR_INVALID_PARAMETER(
218
+ 'blockhash',
219
+ 'a valid on-chain block hash'
220
+ )
221
+ }
222
+ } catch (err: unknown) {
223
+ r.error = sdk.WalletError.fromUnknown(err)
224
+ }
225
+ return r
226
+ }
227
+ throw new sdk.WERR_INTERNAL()
228
+ }
229
+
230
+ async updateBsvExchangeRate(
231
+ rate?: sdk.BsvExchangeRate,
232
+ updateMsecs?: number
233
+ ): Promise<sdk.BsvExchangeRate> {
234
+ if (rate) {
235
+ // Check if the rate we know is stale enough to update.
236
+ updateMsecs ||= 1000 * 60 * 15
237
+ if (new Date(Date.now() - updateMsecs) < rate.timestamp) return rate
238
+ }
239
+
240
+ const requestOptions = {
241
+ method: 'GET',
242
+ headers: this.getHttpHeaders()
243
+ }
244
+
245
+ for (let retry = 0; retry < 2; retry++) {
246
+ const response = await this.httpClient.request<{
247
+ rate: number
248
+ time: number
249
+ currency: string
250
+ }>(`${this.URL}/exchangerate`, requestOptions)
251
+ if (response.statusText === 'Too Many Requests' && retry < 2) {
252
+ await wait(2000)
253
+ continue
254
+ }
255
+
256
+ if (
257
+ !response.data ||
258
+ !response.ok ||
259
+ response.status !== 200 ||
260
+ response.statusText !== 'OK'
261
+ )
262
+ throw new sdk.WERR_INVALID_OPERATION(
263
+ `WoC exchangerate response ${response.statusText}`
264
+ )
265
+
266
+ const wocrate = response.data
267
+ if (wocrate.currency !== 'USD') wocrate.rate = NaN
268
+
269
+ const newRate: sdk.BsvExchangeRate = {
270
+ timestamp: new Date(),
271
+ base: 'USD',
272
+ rate: wocrate.rate
273
+ }
274
+
275
+ return newRate
276
+ }
277
+ throw new sdk.WERR_INTERNAL()
278
+ }
279
+
280
+ async getUtxoStatus(
281
+ output: string,
282
+ outputFormat?: sdk.GetUtxoStatusOutputFormat
283
+ ): Promise<sdk.GetUtxoStatusResult> {
284
+ const r: sdk.GetUtxoStatusResult = {
285
+ name: 'WoC',
286
+ status: 'error',
287
+ error: new sdk.WERR_INTERNAL(),
288
+ details: []
289
+ }
290
+
291
+ for (let retry = 0; ; retry++) {
292
+ let url: string = ''
293
+
294
+ try {
295
+ const scriptHash = validateScriptHash(output, outputFormat)
296
+
297
+ const requestOptions = {
298
+ method: 'GET',
299
+ headers: this.getHttpHeaders()
300
+ }
301
+
302
+ const response = await this.httpClient.request<
303
+ WhatsOnChainUtxoStatus[]
304
+ >(`${this.URL}/script/${scriptHash}/unspent`, requestOptions)
305
+ if (response.statusText === 'Too Many Requests' && retry < 2) {
306
+ await wait(2000)
307
+ continue
308
+ }
309
+
310
+ if (
311
+ !response.data ||
312
+ !response.ok ||
313
+ response.status !== 200 ||
314
+ response.statusText !== 'OK'
315
+ )
316
+ throw new sdk.WERR_INVALID_OPERATION(
317
+ `WoC exchangerate response ${response.statusText}`
318
+ )
319
+
320
+ if (Array.isArray(response.data)) {
321
+ const data = response.data
322
+ if (data.length === 0) {
323
+ r.status = 'success'
324
+ r.error = undefined
325
+ r.isUtxo = false
326
+ } else {
327
+ r.status = 'success'
328
+ r.error = undefined
329
+ r.isUtxo = true
330
+ for (const s of data) {
331
+ r.details.push({
332
+ txid: s.tx_hash,
333
+ satoshis: s.value,
334
+ height: s.height,
335
+ index: s.tx_pos
336
+ })
337
+ }
338
+ }
339
+ } else {
340
+ throw new sdk.WERR_INTERNAL('data is not an array')
341
+ }
342
+
343
+ return r
344
+ } catch (eu: unknown) {
345
+ const e = sdk.WalletError.fromUnknown(eu)
346
+ if (e.code !== 'ECONNRESET' || retry > 2) {
347
+ r.error = new sdk.WERR_INTERNAL(
348
+ `service failure: ${url}, error: ${JSON.stringify(sdk.WalletError.fromUnknown(eu))}`
349
+ )
350
+ return r
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ interface WhatsOnChainTscProof {
358
+ index: number
359
+ nodes: string[]
360
+ target: string
361
+ txOrId: string
362
+ }
363
+
364
+ interface WhatsOnChainUtxoStatus {
365
+ value: number
366
+ height: number
367
+ tx_pos: number
368
+ tx_hash: string
369
+ }
@@ -0,0 +1,227 @@
1
+ import { WhatsOnChainBroadcaster, WhatsOnChainConfig } from '@bsv/sdk'
2
+ import { _tu } from '../../../../test/utils/TestUtilsWalletStorage'
3
+ import { WhatsOnChain } from '../WhatsOnChain'
4
+ import { Services } from '../../Services'
5
+ import { sdk, wait } from '../../../index.client'
6
+ import { Setup, StorageKnex } from '../../../index.all'
7
+ describe('whatsonchain tests', () => {
8
+ jest.setTimeout(99999999)
9
+
10
+ const envTest = _tu.getEnv('test')
11
+ const wocTest = new WhatsOnChain(envTest.chain, {
12
+ apiKey: envTest.taalApiKey
13
+ })
14
+ const envMain = _tu.getEnv('main')
15
+ const wocMain = new WhatsOnChain(envMain.chain, {
16
+ apiKey: envMain.taalApiKey
17
+ })
18
+
19
+ test('0 getRawTx testnet', async () => {
20
+ const rawTx = await wocTest.getRawTx(
21
+ '7e5b797b86abd31a654bf296900d6cb14d04ef0811568ff4675494af2d92166b'
22
+ )
23
+ expect(
24
+ rawTx ===
25
+ '010000000158EED5DBBB7E2F7D70C79A11B9B61AABEECFA5A7CEC679BEDD00F42C48A4BD45010000006B483045022100AE8BB45498A40E2AC797775C405C108168804CD84E8C09A9D42D280D18EDDB6D022024863BFAAC5FF3C24CA65E2F3677EDA092BC3CC5D2EFABA73264B8FF55CF416B412102094AAF520E14E1C4D68496822800BCC7D3B3B26CA368E004A2CB70B398D82FACFFFFFFFF0203000000000000007421020A624B72B34BC192851C5D8890926BBB70B31BC10FDD4E3BC6534E41B1C81B93AC03010203030405064630440220013B4984F4054C2FBCD2F448AB896CCA5C4E234BF765B0C7FB27EDE572A7F7DA02201A5C8D0D023F94C209046B9A2B96B2882C5E43B72D8115561DF8C07442010EEA6D7592090000000000001976A9146511FCE2F7EF785A2102142FBF381AD1291C918688AC00000000'
26
+ )
27
+
28
+ expect(await wocTest.getRawTx('1'.repeat(64))).toBeUndefined()
29
+ })
30
+
31
+ test('1 getRawTx mainnet', async () => {
32
+ const rawTx = await wocMain.getRawTx(
33
+ 'd9978ffc6676523208f7b33bebf1b176388bbeace2c7ef67ce35c2eababa1805'
34
+ )
35
+ expect(
36
+ rawTx ===
37
+ '0100000001026A66A5F724EB490A55E0E08553286F08AD57E92C4BF34B5C44EA6BC0A49828020000006B483045022100C3D9A5ACA30C1F2E1A54532162E7AFE5AA69150E4C06D760414A16D1EA1BABD602205E0D9191838B0911A1E7328554A2B22EFAA80CF52B15FBA37C3046A0996C7AAD412103FA3CF488CA98D9F2DB91843F36BAF6BE39F6C947976C02394602D09FBC5F4CF4FFFFFFFF0210270000000000001976A91444C04354E88975C4BEF30CFE89D300CC7659F7E588AC96BC0000000000001976A9149A53E5CF5F1876924D98A8B35CA0BC693618682488AC00000000'
38
+ )
39
+
40
+ expect(await wocMain.getRawTx('1'.repeat(64))).toBeUndefined()
41
+ })
42
+
43
+ test('2 getMerklePath testnet', async () => {
44
+ const services = new Services(envTest.chain)
45
+ {
46
+ const r = await wocTest.getMerklePath(
47
+ '7e5b797b86abd31a654bf296900d6cb14d04ef0811568ff4675494af2d92166b',
48
+ services
49
+ )
50
+ const s = JSON.stringify(r)
51
+ expect(s).toBe(
52
+ '{"name":"WoCTsc","merklePath":{"blockHeight":1661398,"path":[[{"offset":6,"hash":"7e5b797b86abd31a654bf296900d6cb14d04ef0811568ff4675494af2d92166b","txid":true},{"offset":7,"hash":"97dd9d9080394d52338588732d9f84e1debca93f171f674ac3beac1e75495568"}],[{"offset":2,"hash":"81beedcd219d9e03255bde2ee479db34b9fed04d30373ba8bc264a64af2515b9"}],[{"offset":0,"hash":"9965f9aaeea33f6878335e6f7e6bdb544c3a8550c84e2f0daca54e9cd912111c"}]]},"header":{"version":536870912,"previousHash":"000000000688340a14b77e49bb0fca5ac7b624f7f79a5517583d1aae61c4e658","merkleRoot":"edbc07082ca0a31d5ec89d1f503a9cd41112c0d8f3221a96acfb8a9d16f8e82b","time":1739624725,"bits":486604799,"nonce":1437884974,"height":1661398,"hash":"00000000d8a73bf9a37272a71886ea92a25376bed1c1916f2b5cfbec4d6f6a25"}}'
53
+ )
54
+ }
55
+
56
+ {
57
+ const r = await wocTest.getMerklePath('1'.repeat(64), services)
58
+ const s = JSON.stringify(r)
59
+ expect(s).toBe('{"name":"WoCTsc"}')
60
+ }
61
+ })
62
+
63
+ test('3 getMerklePath mainnet', async () => {
64
+ const services = new Services(envMain.chain)
65
+ {
66
+ const r = await wocMain.getMerklePath(
67
+ 'd9978ffc6676523208f7b33bebf1b176388bbeace2c7ef67ce35c2eababa1805',
68
+ services
69
+ )
70
+ const s = JSON.stringify(r)
71
+ expect(s).toBe(
72
+ '{"name":"WoCTsc","merklePath":{"blockHeight":883637,"path":[[{"offset":46,"hash":"d9978ffc6676523208f7b33bebf1b176388bbeace2c7ef67ce35c2eababa1805","txid":true},{"offset":47,"hash":"066f6fa6fa988f2e3a9d6fe35fa0d3666c652dac35cabaeebff3738a4e67f68f"}],[{"offset":22,"hash":"232089a6f77c566151bc4701fda394b5cc5bf17073140d46a73c4c3ed0a7b911"}],[{"offset":10,"hash":"c639b3a6ce127f67dbd01c7331a6fca62a4b429830387bd68ac6ac05e162116d"}],[{"offset":4,"hash":"730cec44be97881530947d782bb328d25f1122fdae206296937fffb03e936d48"}],[{"offset":3,"hash":"28b681f8ab8db0fa4d5d20cb1532b95184a155346b0b8447bde580b2406d51e6"}],[{"offset":0,"hash":"c49a18028e230dd1439b26794c08c339506f24a450f067c4facd4e0d5a346490"}],[{"offset":1,"hash":"0ba57d1b1fad6874de3640c01088e3dedad3507e5b3a3102b9a8a8055f3df88b"}],[{"offset":1,"hash":"c830edebe5565c19ba584ec73d49129344d17539f322509b7c314ae641c2fcdb"}],[{"offset":1,"hash":"ff62d5ed2a94eb93a2b7d084b8f15b12083573896b6a58cf871507e3352c75f5"}]]},"header":{"version":1040187392,"previousHash":"00000000000000000d9f6889dd6743500adee204ea25d8a57225ecd48b111769","merkleRoot":"59c1efd79fae0d9c29dd8da63f8eeec0aadde048f4491c6bfa324fcfd537156d","time":1739329877,"bits":403818359,"nonce":596827153,"height":883637,"hash":"0000000000000000060ac8d63b78d41f58c9aba0b09f81db7d51fa4905a47263"}}'
73
+ )
74
+ }
75
+
76
+ {
77
+ const r = await wocMain.getMerklePath('1'.repeat(64), services)
78
+ const s = JSON.stringify(r)
79
+ expect(s).toBe('{"name":"WoCTsc"}')
80
+ }
81
+ })
82
+
83
+ test('4 updateBsvExchangeRate', async () => {
84
+ {
85
+ const r = await wocMain.updateBsvExchangeRate()
86
+ expect(r.base).toBe('USD')
87
+ expect(r.rate).toBeGreaterThan(0)
88
+ expect(r.timestamp).toBeTruthy()
89
+ }
90
+ })
91
+
92
+ test('5 getTxPropagation testnet', async () => {
93
+ return
94
+ // throwing internal server error 500 when tested.
95
+ const count = await wocTest.getTxPropagation(
96
+ '7e5b797b86abd31a654bf296900d6cb14d04ef0811568ff4675494af2d92166b'
97
+ )
98
+ expect(count > 0)
99
+
100
+ expect((await wocTest.getTxPropagation('1'.repeat(64))) === 0)
101
+ })
102
+
103
+ test('6 getTxPropagation mainnet', async () => {})
104
+
105
+ test.skip('7 postRawTx testnet', async () => {
106
+ if (Setup.noEnv('test')) return
107
+ const woc = wocTest
108
+ const c = await _tu.createNoSendTxPair('test')
109
+
110
+ const rawTxDo = c.beef.findTxid(c.txidDo)!.tx!.toHex()
111
+ const rawTxUndo = c.beef.findTxid(c.txidUndo)!.tx!.toHex()
112
+
113
+ const txidDo = await woc.postRawTx(rawTxDo)
114
+ expect(txidDo).toBe(c.txidDo)
115
+
116
+ await wait(1000)
117
+
118
+ const txidUndo = await woc.postRawTx(rawTxUndo)
119
+ expect(txidUndo).toBe(c.txidUndo)
120
+ })
121
+
122
+ test.skip('7a nosend cleanup testnet', async () => {
123
+ const c = await _tu.createWalletSetupEnv('test')
124
+
125
+ const actions = await c.wallet.listActions({ labels: [], limit: 1000 })
126
+ const nosends = actions.actions.filter(a => a.status === 'nosend')
127
+
128
+ const refs = ['yUfgNVaFcBNyP2Xv']
129
+ for (const ref of refs) {
130
+ try {
131
+ await c.wallet.abortAction({ reference: ref })
132
+ } catch (eu: unknown) {
133
+ const e = sdk.WalletError.fromUnknown(eu)
134
+ }
135
+ }
136
+ })
137
+
138
+ test.skip('8 postRawTx mainnet', async () => {
139
+ if (Setup.noEnv('main')) return
140
+ const woc = wocMain
141
+ const c = await _tu.createNoSendTxPair('main')
142
+
143
+ const rawTxDo = c.beef.findTxid(c.txidDo)!.tx!.toHex()
144
+ const rawTxUndo = c.beef.findTxid(c.txidUndo)!.tx!.toHex()
145
+
146
+ const txidDo = await woc.postRawTx(rawTxDo)
147
+ expect(txidDo).toBe(c.txidDo)
148
+
149
+ /*
150
+ try {
151
+ // This method is broken as of 2025-02-16
152
+ const count = await woc.getTxPropagation(txidDo)
153
+ } catch {}
154
+ // getRawTx returns undefined for unmined transactions, sometimes.
155
+ let rawTx = await woc.getRawTx(txidDo)
156
+ let i = 0
157
+ while (!rawTx) {
158
+ console.log(`${i++} waiting for WhatsOnChain to acknowledge new transaction exists.`)
159
+ await wait(5000)
160
+ rawTx = await woc.getRawTx(txidDo)
161
+ }
162
+ expect(rawTx).toBe(rawTxDo)
163
+ */
164
+
165
+ // allow for propagation...
166
+ await wait(1000)
167
+
168
+ const txidUndo = await woc.postRawTx(rawTxUndo)
169
+ expect(txidUndo).toBe(c.txidUndo)
170
+
171
+ await wait(1000)
172
+
173
+ // Confirm double spend detection.
174
+ // 'The rawTx parameter must be valid. unexpected response code 500: 258: txn-mempool-conflict'
175
+ // 'The rawTx parameter must be valid. unexpected response code 500: Missing inputs'
176
+ try {
177
+ await woc.postRawTx(c.doubleSpendTx.toHex())
178
+ expect(false)
179
+ } catch (eu: unknown) {
180
+ const e = sdk.WalletError.fromUnknown(eu)
181
+ expect(
182
+ e.message ===
183
+ 'The rawTx parameter must be valid. unexpected response code 500: 258: txn-mempool-conflict' ||
184
+ 'The rawTx parameter must be valid. unexpected response code 500: Missing inputs'
185
+ )
186
+ }
187
+ })
188
+
189
+ test.skip('8a nosend cleanup mainnet', async () => {
190
+ const c = await _tu.createWalletSetupEnv('main')
191
+
192
+ const actions = await c.wallet.listActions({ labels: [], limit: 1000 })
193
+ const nosends = actions.actions.filter(a => a.status === 'nosend')
194
+ // No way to get from actions to reference string values for use with abortAction...
195
+
196
+ if (c['activeStorage']) {
197
+ const s = c['activeStorage'] as StorageKnex
198
+ const userId = c['userId'] as number
199
+ const txs = await s.findTransactions({
200
+ partial: { userId, status: 'nosend' }
201
+ })
202
+ const refs = txs.map(tx => tx.reference)
203
+ for (const ref of refs) {
204
+ try {
205
+ await c.wallet.abortAction({ reference: ref })
206
+ } catch (eu: unknown) {
207
+ const e = sdk.WalletError.fromUnknown(eu)
208
+ }
209
+ }
210
+ }
211
+
212
+ await c.wallet.destroy()
213
+ })
214
+
215
+ test.skip('8b run monitor mainnet', async () => {
216
+ if (Setup.noEnv('main')) return
217
+ if (!Setup.getEnv('main').filePath) return
218
+
219
+ // Only run if `Setup` style .env is present with a sqlite filePath...
220
+
221
+ const c = await _tu.createWalletSetupEnv('main')
222
+
223
+ await c.monitor.runOnce()
224
+
225
+ await c.wallet.destroy()
226
+ })
227
+ })
@@ -27,6 +27,7 @@ import {
27
27
  TableTransaction,
28
28
  validateStorageFeeModel,
29
29
  verifyId,
30
+ verifyInteger,
30
31
  verifyNumber,
31
32
  verifyOne,
32
33
  verifyOneOrNone,
@@ -35,7 +36,8 @@ import {
35
36
  import {
36
37
  generateChangeSdk,
37
38
  GenerateChangeSdkChangeInput,
38
- GenerateChangeSdkParams
39
+ GenerateChangeSdkParams,
40
+ maxPossibleSatoshis
39
41
  } from './generateChange'
40
42
 
41
43
  export async function createAction(
@@ -109,8 +111,19 @@ export async function createAction(
109
111
  transactionId: newTx.transactionId!
110
112
  }
111
113
 
112
- const { allocatedChange, changeOutputs, derivationPrefix } =
113
- await fundNewTransactionSdk(storage, userId, vargs, ctx)
114
+ const {
115
+ allocatedChange,
116
+ changeOutputs,
117
+ derivationPrefix,
118
+ maxPossibleSatoshisAdjustment
119
+ } = await fundNewTransactionSdk(storage, userId, vargs, ctx)
120
+
121
+ if (maxPossibleSatoshisAdjustment) {
122
+ const a = maxPossibleSatoshisAdjustment
123
+ if (ctx.xoutputs[a.fixedOutputIndex].satoshis !== maxPossibleSatoshis)
124
+ throw new sdk.WERR_INTERNAL()
125
+ ctx.xoutputs[a.fixedOutputIndex].satoshis = a.satoshis
126
+ }
114
127
 
115
128
  // The satoshis of the transaction is the satoshis we get back in change minus the satoshis we spend.
116
129
  const satoshis =
@@ -443,7 +456,7 @@ async function createNewOutputs(
443
456
  }
444
457
 
445
458
  const ro: sdk.StorageCreateTransactionSdkOutput = {
446
- vout: verifyTruthy(o.vout),
459
+ vout: verifyInteger(o.vout),
447
460
  satoshis: verifyTruthy(o.satoshis),
448
461
  lockingScript: !o.lockingScript ? '' : asString(o.lockingScript),
449
462
  providedBy: verifyTruthy(o.providedBy) as sdk.StorageProvidedBy,
@@ -767,6 +780,10 @@ async function fundNewTransactionSdk(
767
780
  allocatedChange: TableOutput[]
768
781
  changeOutputs: TableOutput[]
769
782
  derivationPrefix: string
783
+ maxPossibleSatoshisAdjustment?: {
784
+ fixedOutputIndex: number
785
+ satoshis: number
786
+ }
770
787
  }> {
771
788
  const params: GenerateChangeSdkParams = {
772
789
  fixedInputs: ctx.xinputs.map(xi => ({
@@ -891,7 +908,12 @@ async function fundNewTransactionSdk(
891
908
  allocatedChange: TableOutput[]
892
909
  changeOutputs: TableOutput[]
893
910
  derivationPrefix: string
911
+ maxPossibleSatoshisAdjustment?: {
912
+ fixedOutputIndex: number
913
+ satoshis: number
914
+ }
894
915
  } = {
916
+ maxPossibleSatoshisAdjustment: gcr.maxPossibleSatoshisAdjustment,
895
917
  allocatedChange: gcr.allocatedChangeInputs.map(i => outputs[i.outputId]),
896
918
  changeOutputs: gcr.changeOutputs.map(
897
919
  (o, i) =>