@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.
- package/docs/README.md +8 -2
- package/docs/client.md +453 -346
- package/docs/open-rpc/index.html +46 -0
- package/docs/services.md +236 -211
- package/docs/setup.md +20 -4
- package/docs/storage.md +30 -5
- package/docs/wallet.md +453 -346
- package/out/src/Setup.d.ts.map +1 -1
- package/out/src/Setup.js.map +1 -1
- package/out/src/SetupClient.d.ts +9 -0
- package/out/src/SetupClient.d.ts.map +1 -1
- package/out/src/SetupClient.js +19 -3
- package/out/src/SetupClient.js.map +1 -1
- package/out/src/Wallet.d.ts +6 -0
- package/out/src/Wallet.d.ts.map +1 -1
- package/out/src/Wallet.js +57 -0
- package/out/src/Wallet.js.map +1 -1
- package/out/src/sdk/WalletServices.interfaces.d.ts +18 -13
- package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
- package/out/src/services/Services.d.ts +4 -11
- package/out/src/services/Services.d.ts.map +1 -1
- package/out/src/services/Services.js +19 -37
- package/out/src/services/Services.js.map +1 -1
- package/out/src/services/__tests/ARC.test.d.ts +2 -0
- package/out/src/services/__tests/ARC.test.d.ts.map +1 -0
- package/out/src/services/__tests/ARC.test.js +98 -0
- package/out/src/services/__tests/ARC.test.js.map +1 -0
- package/out/src/services/__tests/arcServices.test.d.ts +2 -0
- package/out/src/services/__tests/arcServices.test.d.ts.map +1 -0
- package/out/src/services/__tests/arcServices.test.js +7 -0
- package/out/src/services/__tests/arcServices.test.js.map +1 -0
- package/out/src/services/__tests/postBeef.test.js +45 -7
- package/out/src/services/__tests/postBeef.test.js.map +1 -1
- package/out/src/services/createDefaultWalletServicesOptions.d.ts +1 -0
- package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
- package/out/src/services/createDefaultWalletServicesOptions.js +16 -4
- package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
- package/out/src/services/providers/ARC.d.ts +91 -0
- package/out/src/services/providers/ARC.d.ts.map +1 -0
- package/out/src/services/providers/ARC.js +192 -0
- package/out/src/services/providers/ARC.js.map +1 -0
- package/out/src/services/providers/SdkWhatsOnChain.d.ts +21 -0
- package/out/src/services/providers/SdkWhatsOnChain.d.ts.map +1 -0
- package/out/src/services/providers/SdkWhatsOnChain.js +67 -0
- package/out/src/services/providers/SdkWhatsOnChain.js.map +1 -0
- package/out/src/services/providers/WhatsOnChain.d.ts +35 -0
- package/out/src/services/providers/WhatsOnChain.d.ts.map +1 -0
- package/out/src/services/providers/WhatsOnChain.js +266 -0
- package/out/src/services/providers/WhatsOnChain.js.map +1 -0
- package/out/src/services/providers/__tests/WhatsOnChain.test.d.ts +2 -0
- package/out/src/services/providers/__tests/WhatsOnChain.test.d.ts.map +1 -0
- package/out/src/services/providers/__tests/WhatsOnChain.test.js +176 -0
- package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -0
- package/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/out/src/storage/methods/createAction.js +9 -2
- package/out/src/storage/methods/createAction.js.map +1 -1
- package/out/src/storage/methods/generateChange.d.ts +12 -1
- package/out/src/storage/methods/generateChange.d.ts.map +1 -1
- package/out/src/storage/methods/generateChange.js +24 -1
- package/out/src/storage/methods/generateChange.js.map +1 -1
- package/out/src/storage/methods/processAction.d.ts.map +1 -1
- package/out/src/storage/methods/processAction.js +1 -1
- package/out/src/storage/methods/processAction.js.map +1 -1
- package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js +0 -1
- package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js.map +1 -1
- package/out/src/storage/sync/StorageMySQLDojoReader.js +1 -1
- package/out/src/storage/sync/StorageMySQLDojoReader.js.map +1 -1
- package/out/src/utility/utilityHelpers.js +1 -1
- package/out/src/utility/utilityHelpers.js.map +1 -1
- package/out/test/Wallet/StorageClient/storageClient.man.test.js +22 -3
- package/out/test/Wallet/StorageClient/storageClient.man.test.js.map +1 -1
- package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js +40 -0
- package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js.map +1 -1
- package/out/test/services/Services.test.js +0 -31
- package/out/test/services/Services.test.js.map +1 -1
- package/out/test/utils/TestUtilsWalletStorage.d.ts +26 -18
- package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
- package/out/test/utils/TestUtilsWalletStorage.js +135 -27
- package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
- package/out/test/wallet/list/listActions2.test.js +36 -2
- package/out/test/wallet/list/listActions2.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +5 -7
- package/src/Setup.ts +0 -1
- package/src/SetupClient.ts +26 -6
- package/src/Wallet.ts +66 -2
- package/src/sdk/WalletServices.interfaces.ts +19 -14
- package/src/services/Services.ts +23 -62
- package/src/services/__tests/ARC.test.ts +110 -0
- package/src/services/__tests/arcServices.test.ts +8 -0
- package/src/services/__tests/postBeef.test.ts +47 -9
- package/src/services/createDefaultWalletServicesOptions.ts +19 -6
- package/src/services/providers/ARC.ts +289 -0
- package/src/services/providers/SdkWhatsOnChain.ts +96 -0
- package/src/services/providers/WhatsOnChain.ts +369 -0
- package/src/services/providers/__tests/WhatsOnChain.test.ts +227 -0
- package/src/storage/methods/createAction.ts +26 -4
- package/src/storage/methods/generateChange.ts +42 -2
- package/src/storage/methods/processAction.ts +2 -1
- package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +0 -1
- package/src/storage/sync/StorageMySQLDojoReader.ts +1 -1
- package/src/utility/utilityHelpers.ts +1 -1
- package/test/Wallet/StorageClient/storageClient.man.test.ts +30 -4
- package/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.ts +54 -0
- package/test/services/Services.test.ts +0 -30
- package/test/utils/TestUtilsWalletStorage.ts +175 -45
- package/test/wallet/list/listActions2.test.ts +4 -7
- package/out/src/services/__tests/postBeefToArcTaal.test.d.ts +0 -2
- package/out/src/services/__tests/postBeefToArcTaal.test.d.ts.map +0 -1
- package/out/src/services/__tests/postBeefToArcTaal.test.js +0 -479
- package/out/src/services/__tests/postBeefToArcTaal.test.js.map +0 -1
- package/out/src/services/__tests/postTxs.test.d.ts +0 -2
- package/out/src/services/__tests/postTxs.test.d.ts.map +0 -1
- package/out/src/services/__tests/postTxs.test.js +0 -28
- package/out/src/services/__tests/postTxs.test.js.map +0 -1
- package/out/src/services/providers/arcServices.d.ts +0 -62
- package/out/src/services/providers/arcServices.d.ts.map +0 -1
- package/out/src/services/providers/arcServices.js +0 -375
- package/out/src/services/providers/arcServices.js.map +0 -1
- package/out/src/services/providers/whatsonchain.d.ts +0 -17
- package/out/src/services/providers/whatsonchain.d.ts.map +0 -1
- package/out/src/services/providers/whatsonchain.js +0 -130
- package/out/src/services/providers/whatsonchain.js.map +0 -1
- package/out/test/examples/README.man.test.d.ts +0 -2
- package/out/test/examples/README.man.test.d.ts.map +0 -1
- package/out/test/examples/README.man.test.js +0 -47
- package/out/test/examples/README.man.test.js.map +0 -1
- package/src/services/__tests/postBeefToArcTaal.test.ts +0 -487
- package/src/services/__tests/postTxs.test.ts +0 -28
- package/src/services/providers/arcServices.ts +0 -578
- package/src/services/providers/whatsonchain.ts +0 -170
- 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 {
|
|
113
|
-
|
|
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:
|
|
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) =>
|