@bsv/wallet-toolbox 1.2.28 → 1.2.31
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/client.md +159 -69
- package/docs/services.md +32 -2
- package/docs/storage.md +0 -15
- package/docs/wallet.md +159 -69
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +2 -2
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.encryption.test.js +8 -7
- package/out/src/__tests/WalletPermissionsManager.encryption.test.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts +2 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.js +5 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
- package/out/src/sdk/WalletServices.interfaces.d.ts +37 -0
- package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
- package/out/src/sdk/WalletStorage.interfaces.d.ts +0 -7
- package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
- package/out/src/services/Services.d.ts +2 -0
- package/out/src/services/Services.d.ts.map +1 -1
- package/out/src/services/Services.js +24 -0
- package/out/src/services/Services.js.map +1 -1
- package/out/src/services/providers/WhatsOnChain.d.ts +22 -0
- package/out/src/services/providers/WhatsOnChain.d.ts.map +1 -1
- package/out/src/services/providers/WhatsOnChain.js +60 -0
- package/out/src/services/providers/WhatsOnChain.js.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts +0 -7
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js +27 -34
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js.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 +5 -0
- package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js.map +1 -1
- package/out/test/Wallet/local/localWallet2.man.test.js +1 -2
- package/out/test/Wallet/local/localWallet2.man.test.js.map +1 -1
- package/out/test/services/Services.test.js +21 -0
- package/out/test/services/Services.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/WalletPermissionsManager.ts +5 -5
- package/src/__tests/WalletPermissionsManager.encryption.test.ts +8 -7
- package/src/__tests/WalletPermissionsManager.fixtures.ts +6 -0
- package/src/sdk/WalletServices.interfaces.ts +41 -0
- package/src/sdk/WalletStorage.interfaces.ts +0 -4
- package/src/services/Services.ts +29 -0
- package/src/services/providers/WhatsOnChain.ts +74 -0
- package/src/storage/methods/attemptToPostReqsToNetwork.ts +28 -39
- package/src/storage/methods/processAction.ts +1 -1
- package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +6 -0
- package/test/Wallet/local/localWallet2.man.test.ts +0 -1
- package/test/services/Services.test.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsv/wallet-toolbox",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.31",
|
|
4
4
|
"description": "BRC100 conforming wallet, wallet storage and wallet signer components",
|
|
5
5
|
"main": "./out/src/index.js",
|
|
6
6
|
"types": "./out/src/index.d.ts",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/bitcoin-sv/wallet-toolbox#readme",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@bsv/auth-express-middleware": "^1.
|
|
34
|
-
"@bsv/payment-express-middleware": "^1.0.
|
|
35
|
-
"@bsv/sdk": "^1.4.
|
|
33
|
+
"@bsv/auth-express-middleware": "^1.1.2",
|
|
34
|
+
"@bsv/payment-express-middleware": "^1.0.4",
|
|
35
|
+
"@bsv/sdk": "^1.4.15",
|
|
36
36
|
"express": "^4.21.2",
|
|
37
37
|
"knex": "^3.1.0",
|
|
38
38
|
"mysql2": "^3.12.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WalletInterface, Utils, PushDrop, LockingScript, Transaction, WalletProtocol } from '@bsv/sdk'
|
|
1
|
+
import { WalletInterface, Utils, PushDrop, LockingScript, Transaction, WalletProtocol, Base64String } from '@bsv/sdk'
|
|
2
2
|
import { validateCreateActionArgs } from './sdk'
|
|
3
3
|
|
|
4
4
|
////// TODO: ADD SUPPORT FOR ADMIN COUNTERPARTIES BASED ON WALLET STORAGE
|
|
@@ -939,7 +939,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
939
939
|
* @param plaintext The metadata to encrypt if configured to do so
|
|
940
940
|
* @returns The encrypted metadata, or the original value if encryption was disabled.
|
|
941
941
|
*/
|
|
942
|
-
private async maybeEncryptMetadata(plaintext: string): Promise<
|
|
942
|
+
private async maybeEncryptMetadata(plaintext: string): Promise<Base64String> {
|
|
943
943
|
if (!this.config.encryptWalletMetadata) {
|
|
944
944
|
return plaintext
|
|
945
945
|
}
|
|
@@ -951,7 +951,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
951
951
|
},
|
|
952
952
|
this.adminOriginator
|
|
953
953
|
)
|
|
954
|
-
return Utils.
|
|
954
|
+
return Utils.toBase64(ciphertext)
|
|
955
955
|
}
|
|
956
956
|
|
|
957
957
|
/**
|
|
@@ -959,11 +959,11 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
959
959
|
* @param ciphertext The metadata to attempt decryption for.
|
|
960
960
|
* @returns The decrypted metadata. If decryption fails, returns the original value instead.
|
|
961
961
|
*/
|
|
962
|
-
private async maybeDecryptMetadata(ciphertext:
|
|
962
|
+
private async maybeDecryptMetadata(ciphertext: Base64String): Promise<string> {
|
|
963
963
|
try {
|
|
964
964
|
const { plaintext } = await this.underlying.decrypt(
|
|
965
965
|
{
|
|
966
|
-
ciphertext: Utils.toArray(ciphertext, '
|
|
966
|
+
ciphertext: Utils.toArray(ciphertext, 'base64'),
|
|
967
967
|
protocolID: WalletPermissionsManager.METADATA_ENCRYPTION_PROTOCOL,
|
|
968
968
|
keyID: '1'
|
|
969
969
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mockUnderlyingWallet, MockedBSV_SDK } from './WalletPermissionsManager.fixtures'
|
|
2
2
|
import { WalletPermissionsManager } from '../WalletPermissionsManager'
|
|
3
3
|
import { jest } from '@jest/globals'
|
|
4
|
+
import { Utils } from '@bsv/sdk'
|
|
4
5
|
|
|
5
6
|
jest.mock('@bsv/sdk', () => MockedBSV_SDK)
|
|
6
7
|
|
|
@@ -62,7 +63,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
|
|
|
62
63
|
plaintext: [72, 105] // 'Hi'
|
|
63
64
|
})
|
|
64
65
|
|
|
65
|
-
const ciphertext = 'random-string-representing-ciphertext'
|
|
66
|
+
const ciphertext = Utils.toBase64(Utils.toArray('random-string-representing-ciphertext'))
|
|
66
67
|
const result = await manager['maybeDecryptMetadata'](ciphertext)
|
|
67
68
|
|
|
68
69
|
// We expect underlying.decrypt() to have been called
|
|
@@ -153,19 +154,19 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
|
|
|
153
154
|
totalActions: 1,
|
|
154
155
|
actions: [
|
|
155
156
|
{
|
|
156
|
-
description: 'fake-encrypted-string-for-description',
|
|
157
|
+
description: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-description')),
|
|
157
158
|
inputs: [
|
|
158
159
|
{
|
|
159
160
|
outpoint: 'txid1.0',
|
|
160
|
-
inputDescription: 'fake-encrypted-string-for-inputDesc'
|
|
161
|
+
inputDescription: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-inputDesc'))
|
|
161
162
|
}
|
|
162
163
|
],
|
|
163
164
|
outputs: [
|
|
164
165
|
{
|
|
165
166
|
lockingScript: 'OP_RETURN 1234',
|
|
166
167
|
satoshis: 500,
|
|
167
|
-
outputDescription: 'fake-encrypted-string-for-outputDesc',
|
|
168
|
-
customInstructions: 'fake-encrypted-string-for-customInstr'
|
|
168
|
+
outputDescription: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-outputDesc')),
|
|
169
|
+
customInstructions: Utils.toBase64(Utils.toArray('fake-encrypted-string-for-customInstr'))
|
|
169
170
|
}
|
|
170
171
|
]
|
|
171
172
|
}
|
|
@@ -268,7 +269,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
|
|
|
268
269
|
// To simulate, we make decryption pass through.
|
|
269
270
|
underlying.decrypt.mockImplementation(x => x)
|
|
270
271
|
const listResult = await (manager as any).listActions({}, 'nonadmin.com')
|
|
271
|
-
expect(underlying.decrypt).toHaveBeenCalledTimes(
|
|
272
|
+
expect(underlying.decrypt).toHaveBeenCalledTimes(3)
|
|
272
273
|
|
|
273
274
|
// Confirm the returned data is the same as originally provided (plaintext)
|
|
274
275
|
const [first] = listResult.actions
|
|
@@ -297,7 +298,7 @@ describe('WalletPermissionsManager - Metadata Encryption & Decryption', () => {
|
|
|
297
298
|
satoshis: 999,
|
|
298
299
|
lockingScript: 'OP_RETURN something',
|
|
299
300
|
basket: 'some-basket',
|
|
300
|
-
customInstructions: 'fake-encrypted-instructions-string'
|
|
301
|
+
customInstructions: Utils.toBase64(Utils.toArray('fake-encrypted-instructions-string'))
|
|
301
302
|
}
|
|
302
303
|
]
|
|
303
304
|
})
|
|
@@ -153,6 +153,12 @@ export const MockUtils = {
|
|
|
153
153
|
toUTF8: (arr: number[]) => {
|
|
154
154
|
// Converts an array of numbers to a UTF-8 string.
|
|
155
155
|
return String.fromCharCode(...arr)
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
toBase64: (arr: number[]) => {
|
|
159
|
+
// Converts an array of numbers to a Base64 string.
|
|
160
|
+
const binaryStr = String.fromCharCode(...arr)
|
|
161
|
+
return btoa(binaryStr)
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -97,6 +97,18 @@ export interface WalletServices {
|
|
|
97
97
|
*/
|
|
98
98
|
hashOutputScript(script: string): string
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* For an array of one or more txids, returns for each wether it is a 'known', 'mined', or 'unknown' transaction.
|
|
102
|
+
*
|
|
103
|
+
* Primarily useful for determining if a recently broadcast transaction is known to the processing network.
|
|
104
|
+
*
|
|
105
|
+
* Also returns the current depth from chain tip if 'mined'.
|
|
106
|
+
*
|
|
107
|
+
* @param txids
|
|
108
|
+
* @param useNext
|
|
109
|
+
*/
|
|
110
|
+
getStatusForTxids(txids: string[], useNext?: boolean): Promise<sdk.GetStatusForTxidsResult>
|
|
111
|
+
|
|
100
112
|
/**
|
|
101
113
|
* Attempts to determine the UTXO status of a transaction output.
|
|
102
114
|
*
|
|
@@ -166,6 +178,33 @@ export interface WalletServicesOptions {
|
|
|
166
178
|
arcConfig: ArcConfig
|
|
167
179
|
}
|
|
168
180
|
|
|
181
|
+
export interface GetStatusForTxidsResult {
|
|
182
|
+
/**
|
|
183
|
+
* The name of the service returning these results.
|
|
184
|
+
*/
|
|
185
|
+
name: string
|
|
186
|
+
status: 'success' | 'error'
|
|
187
|
+
/**
|
|
188
|
+
* The first exception error that occurred during processing, if any.
|
|
189
|
+
*/
|
|
190
|
+
error?: sdk.WalletError
|
|
191
|
+
results: sdk.StatusForTxidResult[]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface StatusForTxidResult {
|
|
195
|
+
txid: string
|
|
196
|
+
/**
|
|
197
|
+
* roughly depth of block containing txid from chain tip.
|
|
198
|
+
*/
|
|
199
|
+
depth: number | undefined
|
|
200
|
+
/**
|
|
201
|
+
* 'mined' if depth > 0
|
|
202
|
+
* 'known' if depth === 0
|
|
203
|
+
* 'unknown' if depth === undefined, txid may be old an purged or never processed.
|
|
204
|
+
*/
|
|
205
|
+
status: 'mined' | 'known' | 'unknown'
|
|
206
|
+
}
|
|
207
|
+
|
|
169
208
|
/**
|
|
170
209
|
* Properties on result returned from `WalletServices` function `getRawTx`.
|
|
171
210
|
*/
|
|
@@ -416,6 +455,8 @@ export type GetUtxoStatusService = (
|
|
|
416
455
|
outpoint?: string
|
|
417
456
|
) => Promise<GetUtxoStatusResult>
|
|
418
457
|
|
|
458
|
+
export type GetStatusForTxidsService = (txids: string[]) => Promise<sdk.GetStatusForTxidsResult>
|
|
459
|
+
|
|
419
460
|
export type GetScriptHashHistoryService = (hash: string) => Promise<GetScriptHashHistoryResult>
|
|
420
461
|
|
|
421
462
|
export type GetMerklePathService = (txid: string, services: WalletServices) => Promise<GetMerklePathResult>
|
|
@@ -264,10 +264,6 @@ export interface ReviewActionResult {
|
|
|
264
264
|
* Merged beef of competingTxs, valid when status is 'doubleSpend'.
|
|
265
265
|
*/
|
|
266
266
|
competingBeef?: number[]
|
|
267
|
-
/**
|
|
268
|
-
* Transaction input indices that have been spent, valid when status is 'doubleSpend'.
|
|
269
|
-
*/
|
|
270
|
-
spentInputs?: { vin: number; scriptHash: string }[]
|
|
271
267
|
}
|
|
272
268
|
|
|
273
269
|
export interface StorageProcessActionResults {
|
package/src/services/Services.ts
CHANGED
|
@@ -22,6 +22,7 @@ export class Services implements sdk.WalletServices {
|
|
|
22
22
|
getRawTxServices: ServiceCollection<sdk.GetRawTxService>
|
|
23
23
|
postBeefServices: ServiceCollection<sdk.PostBeefService>
|
|
24
24
|
getUtxoStatusServices: ServiceCollection<sdk.GetUtxoStatusService>
|
|
25
|
+
getStatusForTxidsServices: ServiceCollection<sdk.GetStatusForTxidsService>
|
|
25
26
|
getScriptHashHistoryServices: ServiceCollection<sdk.GetScriptHashHistoryService>
|
|
26
27
|
updateFiatExchangeRateServices: ServiceCollection<sdk.UpdateFiatExchangeRateService>
|
|
27
28
|
|
|
@@ -57,6 +58,10 @@ export class Services implements sdk.WalletServices {
|
|
|
57
58
|
this.getUtxoStatusServices = new ServiceCollection<sdk.GetUtxoStatusService>()
|
|
58
59
|
.add({ name: 'WhatsOnChain', service: this.whatsonchain.getUtxoStatus.bind(this.whatsonchain) })
|
|
59
60
|
|
|
61
|
+
//prettier-ignore
|
|
62
|
+
this.getStatusForTxidsServices = new ServiceCollection<sdk.GetStatusForTxidsService>()
|
|
63
|
+
.add({ name: 'WhatsOnChain', service: this.whatsonchain.getStatusForTxids.bind(this.whatsonchain) })
|
|
64
|
+
|
|
60
65
|
//prettier-ignore
|
|
61
66
|
this.getScriptHashHistoryServices = new ServiceCollection<sdk.GetScriptHashHistoryService>()
|
|
62
67
|
.add({ name: 'WhatsOnChain', service: this.whatsonchain.getScriptHashHistory.bind(this.whatsonchain) })
|
|
@@ -105,6 +110,30 @@ export class Services implements sdk.WalletServices {
|
|
|
105
110
|
return this.getUtxoStatusServices.count
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
async getStatusForTxids(txids: string[], useNext?: boolean): Promise<sdk.GetStatusForTxidsResult> {
|
|
114
|
+
const services = this.getStatusForTxidsServices
|
|
115
|
+
if (useNext) services.next()
|
|
116
|
+
|
|
117
|
+
let r0: sdk.GetStatusForTxidsResult = {
|
|
118
|
+
name: '<noservices>',
|
|
119
|
+
status: 'error',
|
|
120
|
+
error: new sdk.WERR_INTERNAL('No services available.'),
|
|
121
|
+
results: []
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (let tries = 0; tries < services.count; tries++) {
|
|
125
|
+
const service = services.service
|
|
126
|
+
const r = await service(txids)
|
|
127
|
+
if (r.status === 'success') {
|
|
128
|
+
r0 = r
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
services.next()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return r0
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
/**
|
|
109
138
|
* @param script Output script to be hashed for `getUtxoStatus` default `outputFormat`
|
|
110
139
|
* @returns script hash in 'hashLE' format, which is the default.
|
|
@@ -15,6 +15,68 @@ export class WhatsOnChain extends SdkWhatsOnChain {
|
|
|
15
15
|
this.services = services || new Services(chain)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* POST
|
|
20
|
+
* https://api.whatsonchain.com/v1/bsv/main/txs/status
|
|
21
|
+
* Content-Type: application/json
|
|
22
|
+
* data: "{\"txids\":[\"6815f8014db74eab8b7f75925c68929597f1d97efa970109d990824c25e5e62b\"]}"
|
|
23
|
+
*
|
|
24
|
+
* result for a mined txid:
|
|
25
|
+
* [{
|
|
26
|
+
* "txid":"294cd1ebd5689fdee03509f92c32184c0f52f037d4046af250229b97e0c8f1aa",
|
|
27
|
+
* "blockhash":"000000000000000004b5ce6670f2ff27354a1e87d0a01bf61f3307f4ccd358b5",
|
|
28
|
+
* "blockheight":612251,
|
|
29
|
+
* "blocktime":1575841517,
|
|
30
|
+
* "confirmations":278272
|
|
31
|
+
* }]
|
|
32
|
+
*
|
|
33
|
+
* result for a valid recent txid:
|
|
34
|
+
* [{"txid":"6815f8014db74eab8b7f75925c68929597f1d97efa970109d990824c25e5e62b"}]
|
|
35
|
+
*
|
|
36
|
+
* result for an unknown txid:
|
|
37
|
+
* [{"txid":"6815f8014db74eab8b7f75925c68929597f1d97efa970109d990824c25e5e62c","error":"unknown"}]
|
|
38
|
+
*/
|
|
39
|
+
async getStatusForTxids(txids: string[]): Promise<sdk.GetStatusForTxidsResult> {
|
|
40
|
+
const r: sdk.GetStatusForTxidsResult = {
|
|
41
|
+
name: 'WoC',
|
|
42
|
+
status: 'error',
|
|
43
|
+
error: undefined,
|
|
44
|
+
results: []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const requestOptions = {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: this.getHttpHeaders(),
|
|
50
|
+
data: { txids }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const url = `${this.URL}/txs/status`
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await this.httpClient.request<WhatsOnChainTxsStatusData[]>(url, requestOptions)
|
|
57
|
+
|
|
58
|
+
if (!response.data || !response.ok || response.status !== 200)
|
|
59
|
+
throw new sdk.WERR_INVALID_OPERATION(`Unable to get status for txids at this timei.`)
|
|
60
|
+
|
|
61
|
+
const data = response.data
|
|
62
|
+
for (const txid of txids) {
|
|
63
|
+
const d = data.find(d => d.txid === txid)
|
|
64
|
+
if (!d || d.error === 'unknown') r.results.push({ txid, status: 'unknown', depth: undefined })
|
|
65
|
+
else if (d.error !== undefined) {
|
|
66
|
+
console.log(`WhatsOnChain getStatusForTxids unexpected error ${d.error} ${txid}`)
|
|
67
|
+
r.results.push({ txid, status: 'unknown', depth: undefined })
|
|
68
|
+
} else if (d.confirmations === undefined) r.results.push({ txid, status: 'known', depth: 0 })
|
|
69
|
+
else r.results.push({ txid, status: 'mined', depth: d.confirmations })
|
|
70
|
+
}
|
|
71
|
+
r.status = 'success'
|
|
72
|
+
} catch (eu: unknown) {
|
|
73
|
+
const e = sdk.WalletError.fromUnknown(eu)
|
|
74
|
+
r.error = e
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return r
|
|
78
|
+
}
|
|
79
|
+
|
|
18
80
|
/**
|
|
19
81
|
* 2025-02-16 throwing internal server error 500.
|
|
20
82
|
* @param txid
|
|
@@ -617,3 +679,15 @@ interface WhatsOnChainScriptHashHistoryData {
|
|
|
617
679
|
error?: string
|
|
618
680
|
nextPageToken?: string
|
|
619
681
|
}
|
|
682
|
+
|
|
683
|
+
interface WhatsOnChainTxsStatusData {
|
|
684
|
+
txid: string
|
|
685
|
+
blockhash?: string
|
|
686
|
+
blockheight?: number
|
|
687
|
+
blocktime?: number
|
|
688
|
+
confirmations?: number
|
|
689
|
+
/**
|
|
690
|
+
* 'unknown' if txid isn't known
|
|
691
|
+
*/
|
|
692
|
+
error?: string
|
|
693
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Beef, Transaction } from '@bsv/sdk'
|
|
2
2
|
import { StorageProvider } from '../StorageProvider'
|
|
3
3
|
import { EntityProvenTxReq } from '../schema/entities'
|
|
4
|
-
import { sdk } from '../../index.client'
|
|
4
|
+
import { sdk, wait } from '../../index.client'
|
|
5
5
|
import { ReqHistoryNote } from '../../sdk'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -136,8 +136,7 @@ function aggregatePostBeefResultsByTxid(
|
|
|
136
136
|
doubleSpendCount: 0,
|
|
137
137
|
statusErrorCount: 0,
|
|
138
138
|
serviceErrorCount: 0,
|
|
139
|
-
competingTxs: []
|
|
140
|
-
spentInputs: []
|
|
139
|
+
competingTxs: []
|
|
141
140
|
}
|
|
142
141
|
r[txid] = ar
|
|
143
142
|
for (const pbr of pbrs) {
|
|
@@ -263,7 +262,6 @@ async function updateReqsFromAggregateResults(
|
|
|
263
262
|
const details = r.details.find(d => d.txid === txid)!
|
|
264
263
|
details.status = ar.status
|
|
265
264
|
details.competingTxs = ar.competingTxs
|
|
266
|
-
details.spentInputs = ar.spentInputs
|
|
267
265
|
}
|
|
268
266
|
}
|
|
269
267
|
|
|
@@ -286,43 +284,42 @@ async function confirmDoubleSpend(
|
|
|
286
284
|
): Promise<void> {
|
|
287
285
|
const req = ar.vreq.req
|
|
288
286
|
const note: ReqHistoryNote = { when: new Date().toISOString(), what: 'confirmDoubleSpend' }
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
for (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
ar.spentInputs.push({
|
|
301
|
-
vin,
|
|
302
|
-
scriptHash: hash,
|
|
303
|
-
sourceTXID: input.sourceTXID!,
|
|
304
|
-
sourceIndex: input.sourceOutputIndex
|
|
305
|
-
})
|
|
287
|
+
|
|
288
|
+
let known = false
|
|
289
|
+
|
|
290
|
+
for (let retry = 0; retry < 3; retry++) {
|
|
291
|
+
const gsr = await services.getStatusForTxids([req.txid])
|
|
292
|
+
note[`getStatus${retry}`] = `${gsr.status}${gsr.error ? `${gsr.error.code}` : ''},${gsr.results[0]?.status}`
|
|
293
|
+
if (gsr.status === 'success' && gsr.results[0].status !== 'unknown') {
|
|
294
|
+
known = true
|
|
295
|
+
break
|
|
296
|
+
} else {
|
|
297
|
+
await wait(1000)
|
|
306
298
|
}
|
|
307
299
|
}
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
else ar.status = 'serviceError'
|
|
300
|
+
|
|
301
|
+
if (known) {
|
|
302
|
+
// doubleSpend -> success
|
|
303
|
+
ar.status = 'success'
|
|
313
304
|
note.newStatus = ar.status
|
|
314
305
|
} else {
|
|
315
|
-
// Confirmed double spend.
|
|
306
|
+
// Confirmed double spend, get txids of possible competing transactions.
|
|
307
|
+
const tx = Transaction.fromBinary(req.rawTx)
|
|
316
308
|
const competingTxids = new Set(ar.competingTxs)
|
|
317
|
-
for (const
|
|
318
|
-
const
|
|
309
|
+
for (const input of tx.inputs) {
|
|
310
|
+
const sourceTx = beef.findTxid(input.sourceTXID!)?.tx
|
|
311
|
+
if (!sourceTx) throw new sdk.WERR_INTERNAL(`beef lacks tx for ${input.sourceTXID}`)
|
|
312
|
+
const lockingScript = sourceTx.outputs[input.sourceOutputIndex].lockingScript.toHex()
|
|
313
|
+
const hash = services.hashOutputScript(lockingScript)
|
|
314
|
+
const shhrs = await services.getScriptHashHistory(hash)
|
|
319
315
|
if (shhrs.status === 'success') {
|
|
320
316
|
for (const h of shhrs.history) {
|
|
321
|
-
|
|
317
|
+
// Neither the source of the input nor the current transaction are competition.
|
|
318
|
+
if (h.txid !== input.sourceTXID && h.txid !== ar.txid) competingTxids.add(h.txid)
|
|
322
319
|
}
|
|
323
320
|
}
|
|
324
321
|
}
|
|
325
|
-
ar.competingTxs = [...competingTxids].slice(
|
|
322
|
+
ar.competingTxs = [...competingTxids].slice(-1, 24) // keep at most 24, if they were sorted by time, keep newest
|
|
326
323
|
note.competingTxs = ar.competingTxs.join(',')
|
|
327
324
|
}
|
|
328
325
|
req.addHistoryNote(note)
|
|
@@ -343,10 +340,6 @@ interface AggregatePostBeefTxResult {
|
|
|
343
340
|
* Any competing double spend txids reported for this txid
|
|
344
341
|
*/
|
|
345
342
|
competingTxs: string[]
|
|
346
|
-
/**
|
|
347
|
-
* Input indices that have been spent, valid when status is 'doubleSpend'
|
|
348
|
-
*/
|
|
349
|
-
spentInputs: { vin: number; scriptHash: string; sourceTXID: string; sourceIndex: number }[]
|
|
350
343
|
}
|
|
351
344
|
|
|
352
345
|
/**
|
|
@@ -378,10 +371,6 @@ export interface PostReqsToNetworkDetails {
|
|
|
378
371
|
* Any competing double spend txids reported for this txid
|
|
379
372
|
*/
|
|
380
373
|
competingTxs?: string[]
|
|
381
|
-
/**
|
|
382
|
-
* Input indices that have been spent, valid when status is 'doubleSpend'
|
|
383
|
-
*/
|
|
384
|
-
spentInputs?: { vin: number; scriptHash: string }[]
|
|
385
374
|
}
|
|
386
375
|
|
|
387
376
|
export interface PostReqsToNetworkResult {
|
|
@@ -191,7 +191,7 @@ async function shareReqsWithWorld(
|
|
|
191
191
|
const txid = ar.txid
|
|
192
192
|
const d = prtn.details.find(d => d.txid === txid)
|
|
193
193
|
if (!d) throw new sdk.WERR_INTERNAL(`missing details for ${txid}`)
|
|
194
|
-
ar.ndr = { txid: d.txid, status: 'success', competingTxs: d.competingTxs
|
|
194
|
+
ar.ndr = { txid: d.txid, status: 'success', competingTxs: d.competingTxs }
|
|
195
195
|
switch (d.status) {
|
|
196
196
|
case 'success':
|
|
197
197
|
// processing network has accepted this transaction
|
|
@@ -103,6 +103,12 @@ describe('ProvenTx class method tests', () => {
|
|
|
103
103
|
getFiatExchangeRate: async () => 1,
|
|
104
104
|
postBeef: async () => [],
|
|
105
105
|
|
|
106
|
+
getStatusForTxids: async () => ({
|
|
107
|
+
name: 'mock-service',
|
|
108
|
+
status: 'success',
|
|
109
|
+
results: []
|
|
110
|
+
}),
|
|
111
|
+
|
|
106
112
|
getUtxoStatus: async () => ({
|
|
107
113
|
name: 'mock-service',
|
|
108
114
|
status: 'success',
|
|
@@ -233,4 +233,25 @@ describe('Wallet services tests', () => {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
})
|
|
236
|
+
|
|
237
|
+
test('7 getStatusForTxids', async () => {
|
|
238
|
+
for (const { chain, services } of ctxs) {
|
|
239
|
+
{
|
|
240
|
+
const txids = ['32c691a077b0ce46051aa7a45fa3b131c71ff85950264575a32171086b02ad98']
|
|
241
|
+
const r = await services.getStatusForTxids(txids)
|
|
242
|
+
expect(r.results.length).toBe(1)
|
|
243
|
+
expect(r.results[0].txid).toBe(txids[0])
|
|
244
|
+
expect(r.name).toBeTruthy()
|
|
245
|
+
expect(r.status).toBe('success')
|
|
246
|
+
expect(r.error).toBe(undefined)
|
|
247
|
+
if (chain === 'main') {
|
|
248
|
+
expect(r.results[0].status).toBe('mined')
|
|
249
|
+
expect(r.results[0].depth).toBeGreaterThan(146)
|
|
250
|
+
} else {
|
|
251
|
+
expect(r.results[0].status).toBe('unknown')
|
|
252
|
+
expect(r.results[0].depth).toBe(undefined)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
})
|
|
236
257
|
})
|