@bsv/wallet-toolbox 1.1.47 → 1.1.49

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 (31) hide show
  1. package/out/src/monitor/tasks/TaskNewHeader.js +1 -2
  2. package/out/src/monitor/tasks/TaskNewHeader.js.map +1 -1
  3. package/out/src/services/providers/__tests/WhatsOnChain.test.js +7 -4
  4. package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -1
  5. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  6. package/out/src/storage/StorageKnex.js +4 -2
  7. package/out/src/storage/StorageKnex.js.map +1 -1
  8. package/out/src/storage/methods/processAction.d.ts.map +1 -1
  9. package/out/src/storage/methods/processAction.js +10 -0
  10. package/out/src/storage/methods/processAction.js.map +1 -1
  11. package/out/test/Wallet/local/localWallet.man.test.d.ts +4 -1
  12. package/out/test/Wallet/local/localWallet.man.test.d.ts.map +1 -1
  13. package/out/test/Wallet/local/localWallet.man.test.js +218 -144
  14. package/out/test/Wallet/local/localWallet.man.test.js.map +1 -1
  15. package/out/test/Wallet/support/janitor.man.test.d.ts +2 -0
  16. package/out/test/Wallet/support/janitor.man.test.d.ts.map +1 -0
  17. package/out/test/Wallet/support/janitor.man.test.js +35 -0
  18. package/out/test/Wallet/support/janitor.man.test.js.map +1 -0
  19. package/out/test/utils/TestUtilsWalletStorage.d.ts +1 -0
  20. package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
  21. package/out/test/utils/TestUtilsWalletStorage.js +5 -1
  22. package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
  23. package/out/tsconfig.all.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/monitor/tasks/TaskNewHeader.ts +1 -1
  26. package/src/services/providers/__tests/WhatsOnChain.test.ts +12 -4
  27. package/src/storage/StorageKnex.ts +5 -2
  28. package/src/storage/methods/processAction.ts +14 -0
  29. package/test/Wallet/local/localWallet.man.test.ts +312 -159
  30. package/test/Wallet/support/janitor.man.test.ts +44 -0
  31. package/test/utils/TestUtilsWalletStorage.ts +7 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
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",
@@ -42,7 +42,7 @@ export class TaskNewHeader extends WalletMonitorTask {
42
42
  } else {
43
43
  isNew = false
44
44
  }
45
- if (isNew) TaskCheckForProofs.checkNow = true
45
+ if (isNew) this.monitor.processNewBlockHeader(this.header)
46
46
  return log
47
47
  }
48
48
  }
@@ -16,6 +16,9 @@ describe('whatsonchain tests', () => {
16
16
  apiKey: envMain.taalApiKey
17
17
  })
18
18
 
19
+ test('00', () => {})
20
+ if (_tu.noTestEnv('test')) return
21
+
19
22
  test('0 getRawTx testnet', async () => {
20
23
  const rawTx = await wocTest.getRawTx(
21
24
  '7e5b797b86abd31a654bf296900d6cb14d04ef0811568ff4675494af2d92166b'
@@ -49,14 +52,16 @@ describe('whatsonchain tests', () => {
49
52
  )
50
53
  const s = JSON.stringify(r)
51
54
  expect(s).toBe(
52
- "{\"name\":\"WoCTsc\",\"notes\":[{\"what\":\"getMerklePathSuccess\",\"name\":\"WoCTsc\",\"status\":200,\"statusText\":\"OK\"}],\"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\"}}"
55
+ '{"name":"WoCTsc","notes":[{"what":"getMerklePathSuccess","name":"WoCTsc","status":200,"statusText":"OK"}],"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
56
  )
54
57
  }
55
58
 
56
59
  {
57
60
  const r = await wocTest.getMerklePath('1'.repeat(64), services)
58
61
  const s = JSON.stringify(r)
59
- expect(s).toBe("{\"name\":\"WoCTsc\",\"notes\":[{\"what\":\"getMerklePathNoData\",\"name\":\"WoCTsc\",\"status\":200,\"statusText\":\"OK\"}]}")
62
+ expect(s).toBe(
63
+ '{"name":"WoCTsc","notes":[{"what":"getMerklePathNoData","name":"WoCTsc","status":200,"statusText":"OK"}]}'
64
+ )
60
65
  }
61
66
  })
62
67
 
@@ -69,13 +74,16 @@ describe('whatsonchain tests', () => {
69
74
  )
70
75
  const s = JSON.stringify(r)
71
76
  expect(s).toBe(
72
- "{\"name\":\"WoCTsc\",\"notes\":[{\"what\":\"getMerklePathSuccess\",\"name\":\"WoCTsc\",\"status\":200,\"statusText\":\"OK\"}],\"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\"}}" )
77
+ '{"name":"WoCTsc","notes":[{"what":"getMerklePathSuccess","name":"WoCTsc","status":200,"statusText":"OK"}],"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"}}'
78
+ )
73
79
  }
74
80
 
75
81
  {
76
82
  const r = await wocMain.getMerklePath('1'.repeat(64), services)
77
83
  const s = JSON.stringify(r)
78
- expect(s).toBe("{\"name\":\"WoCTsc\",\"notes\":[{\"what\":\"getMerklePathNoData\",\"name\":\"WoCTsc\",\"status\":200,\"statusText\":\"OK\"}]}")
84
+ expect(s).toBe(
85
+ '{"name":"WoCTsc","notes":[{"what":"getMerklePathNoData","name":"WoCTsc","status":200,"statusText":"OK"}]}'
86
+ )
79
87
  }
80
88
  })
81
89
 
@@ -112,6 +112,7 @@ export class StorageKnex
112
112
  trx?: sdk.TrxToken
113
113
  ): Promise<number[] | undefined> {
114
114
  if (!txid) return undefined
115
+ if (!this.isAvailable()) await this.makeAvailable()
115
116
 
116
117
  let rawTx: number[] | undefined = undefined
117
118
  if (Number.isInteger(offset) && Number.isInteger(length)) {
@@ -160,7 +161,8 @@ export class StorageKnex
160
161
  q = q.limit(args.paged.limit)
161
162
  q = q.offset(args.paged.offset || 0)
162
163
  }
163
- if (args.since) q = q.where('updated_at', '>=', args.since)
164
+ if (args.since)
165
+ q = q.where('updated_at', '>=', this.validateDateForWhere(args.since))
164
166
  return q
165
167
  }
166
168
  override async getProvenTxsForUser(
@@ -189,7 +191,8 @@ export class StorageKnex
189
191
  q = q.limit(args.paged.limit)
190
192
  q = q.offset(args.paged.offset || 0)
191
193
  }
192
- if (args.since) q = q.where('updated_at', '>=', args.since)
194
+ if (args.since)
195
+ q = q.where('updated_at', '>=', this.validateDateForWhere(args.since))
193
196
  return q
194
197
  }
195
198
  override async getProvenTxReqsForUser(
@@ -30,6 +30,7 @@ import {
30
30
  verifyOneOrNone,
31
31
  verifyTruthy
32
32
  } from '../../index.client'
33
+ import { ProvenTxReqNonTerminalStatus } from '../../sdk'
33
34
 
34
35
  export async function processAction(
35
36
  storage: StorageProvider,
@@ -169,6 +170,19 @@ async function shareReqsWithWorld(
169
170
  const d = prtn.details.find(d => d.txid === ar.txid)
170
171
  if (d) {
171
172
  ar.postBeef = d.pbrft
173
+ if (d.pbrft.status === 'error' && ar.getReq.req) {
174
+ // If the immediate (un-delayed) broadcast attempt APPEARS to fail,
175
+ // fall back to delayed sending and tracking to make sure transaction
176
+ // gets tracked if it is valid and floating around the network...
177
+ const req = await EntityProvenTxReq.fromStorageId(
178
+ storage,
179
+ ar.getReq.req.provenTxReqId
180
+ )
181
+ if (req.status === 'unprocessed') {
182
+ req.status = 'unsent'
183
+ await req.updateStorageDynamicProperties(storage)
184
+ }
185
+ }
172
186
  }
173
187
  }
174
188
 
@@ -1,172 +1,73 @@
1
1
  import {
2
2
  Beef,
3
3
  CreateActionArgs,
4
+ CreateActionOptions,
5
+ CreateActionResult,
4
6
  P2PKH,
5
7
  PrivateKey,
6
8
  PublicKey,
7
9
  Script,
8
10
  SignActionArgs
9
11
  } from '@bsv/sdk'
10
- import { EntityProvenTxReq, sdk, Setup } from '../../../src'
11
- import { _tu } from '../../utils/TestUtilsWalletStorage'
12
+ import {
13
+ asString,
14
+ EntityProvenTxReq,
15
+ EntitySyncState,
16
+ Monitor,
17
+ sdk,
18
+ Services,
19
+ Setup,
20
+ StorageKnex,
21
+ TableOutput,
22
+ TableUser,
23
+ verifyId,
24
+ verifyOne,
25
+ wait
26
+ } from '../../../src'
27
+ import { _tu, TestWalletNoSetup } from '../../utils/TestUtilsWalletStorage'
28
+ import { monitorEventLoopDelay } from 'perf_hooks'
12
29
 
13
30
  describe('localWallet tests', () => {
14
31
  jest.setTimeout(99999999)
15
32
 
16
- test('0 create 1 sat delayed output monitor runOnce', async () => {
17
- const chain: sdk.Chain = 'test'
18
- if (_tu.noTestEnv(chain)) return
19
- const env = _tu.getEnv(chain)
20
- if (!env.testIdentityKey)
21
- throw new sdk.WERR_INVALID_PARAMETER('env.testIdentityKey', 'valid')
22
- if (!env.testFilePath)
23
- throw new sdk.WERR_INVALID_PARAMETER('env.testFilePath', 'valid')
24
-
25
- const setup = await _tu.createTestWallet({
26
- chain,
27
- rootKeyHex: env.devKeys[env.testIdentityKey],
28
- filePath: env.testFilePath,
29
- setActiveClient: false,
30
- addLocalBackup: false
31
- })
33
+ test('00', () => {})
34
+ if (_tu.noTestEnv('test')) return
35
+ if (_tu.noTestEnv('main')) return
32
36
 
33
- console.log(`ACTIVE STORAGE: ${setup.storage.getActiveStoreName()}`)
37
+ test('0 create 1 sat delayed', async () => {
38
+ const setup = await createSetup('test')
34
39
 
35
- const createHowMany = 10
36
- for (let i = 0; i < createHowMany; i++) {
37
- const args: CreateActionArgs = {
38
- outputs: [
39
- {
40
- lockingScript: new P2PKH()
41
- .lock(PublicKey.fromString(setup.identityKey).toAddress())
42
- .toHex(),
43
- satoshis: 1,
44
- outputDescription: 'test output',
45
- customInstructions: JSON.stringify({
46
- type: 'P2PKH',
47
- key: 'identity'
48
- }),
49
- basket: 'test-output'
50
- }
51
- ],
52
- description: 'create test output'
53
- }
54
- const car = await setup.wallet.createAction(args)
55
- expect(car.txid)
56
-
57
- const req = await EntityProvenTxReq.fromStorageTxid(
58
- setup.activeStorage,
59
- car.txid!
60
- )
61
- expect(req !== undefined && req.history.notes !== undefined)
62
- if (req && req.history.notes) {
63
- expect(req.status === 'unsent')
64
- expect(req.history.notes.length === 1)
65
- const n = req.history.notes[0]
66
- expect(n.what === 'status' && n.status_now === 'unsent')
67
- }
40
+ const car = await createOneSatTestOutput(setup, {}, 1)
68
41
 
69
- let runOnce = false
70
- if (runOnce) {
71
- await setup.monitor.runOnce()
72
-
73
- const req = await EntityProvenTxReq.fromStorageTxid(
74
- setup.activeStorage,
75
- car.txid!
76
- )
77
- expect(req !== undefined && req.history.notes !== undefined)
78
- if (req && req.history.notes) {
79
- expect(req.status === 'unmined')
80
- expect(req.history.notes.length === 1)
81
- const n = req.history.notes[0]
82
- expect(n.what === 'status' && n.status_now === 'unsent')
83
- }
84
- }
85
- }
42
+ await trackReqByTxid(setup, car.txid!)
86
43
 
87
44
  await setup.wallet.destroy()
88
45
  })
89
46
 
90
- test('1 recover 1 sat outputs', async () => {
91
- const chain: sdk.Chain = 'test'
92
- if (_tu.noTestEnv(chain)) return
93
- const env = _tu.getEnv(chain)
94
- if (!env.testIdentityKey)
95
- throw new sdk.WERR_INVALID_PARAMETER('env.testIdentityKey', 'valid')
96
- if (!env.testFilePath)
97
- throw new sdk.WERR_INVALID_PARAMETER('env.testFilePath', 'valid')
98
-
99
- const setup = await _tu.createTestWallet({
100
- chain,
101
- rootKeyHex: env.devKeys[env.testIdentityKey],
102
- filePath: env.testFilePath,
103
- setActiveClient: false,
104
- addLocalBackup: false
105
- })
47
+ test('0a create 1 sat immediate', async () => {
48
+ const setup = await createSetup('test')
106
49
 
107
- console.log(`ACTIVE STORAGE: ${setup.storage.getActiveStoreName()}`)
50
+ const car = await createOneSatTestOutput(
51
+ setup,
52
+ { acceptDelayedBroadcast: false },
53
+ 1
54
+ )
108
55
 
109
- const outputs = await setup.wallet.listOutputs({
110
- basket: 'test-output',
111
- include: 'entire transactions',
112
- limit: 1000
113
- })
114
-
115
- if (outputs.outputs.length > 8) {
116
- const args: CreateActionArgs = {
117
- inputBEEF: outputs.BEEF!,
118
- inputs: [],
119
- description: 'recover test output'
120
- }
121
- const p2pkh = new P2PKH()
122
- for (const o of outputs.outputs) {
123
- args.inputs!.push({
124
- unlockingScriptLength: 108,
125
- outpoint: o.outpoint,
126
- inputDescription: 'recovered test output'
127
- })
128
- }
129
- const car = await setup.wallet.createAction(args)
130
- expect(car.signableTransaction)
131
-
132
- const st = car.signableTransaction!
133
- const beef = Beef.fromBinary(st.tx)
134
- const tx = beef.findAtomicTransaction(beef.txs.slice(-1)[0].txid)!
135
- const signArgs: SignActionArgs = {
136
- reference: st.reference,
137
- spends: {} // 0: { unlockingScript } },
138
- }
139
- for (let i = 0; i < outputs.outputs.length; i++) {
140
- const o = outputs.outputs[i]
141
- const unlock = p2pkh.unlock(setup.keyDeriver.rootKey, 'all', false)
142
- const unlockingScript = (await unlock.sign(tx, i)).toHex()
143
- signArgs.spends[i] = { unlockingScript }
144
- }
145
- const sar = await setup.wallet.signAction(signArgs)
146
- expect(sar.txid)
147
- }
56
+ await trackReqByTxid(setup, car.txid!)
148
57
 
149
58
  await setup.wallet.destroy()
150
59
  })
151
60
 
152
- test('2 monitor runOnce', async () => {
153
- const chain: sdk.Chain = 'test'
154
- if (_tu.noTestEnv(chain)) return
155
- const env = _tu.getEnv(chain)
156
- if (!env.testIdentityKey)
157
- throw new sdk.WERR_INVALID_PARAMETER('env.testIdentityKey', 'valid')
158
- if (!env.testFilePath)
159
- throw new sdk.WERR_INVALID_PARAMETER('env.testFilePath', 'valid')
160
-
161
- const setup = await _tu.createTestWallet({
162
- chain,
163
- rootKeyHex: env.devKeys[env.testIdentityKey],
164
- filePath: env.testFilePath,
165
- setActiveClient: false,
166
- addLocalBackup: false
167
- })
61
+ test('1 recover 1 sat outputs', async () => {
62
+ const setup = await createSetup('test')
168
63
 
169
- console.log(`ACTIVE STORAGE: ${setup.storage.getActiveStoreName()}`)
64
+ await recoverOneSatTestOutputs(setup)
65
+
66
+ await setup.wallet.destroy()
67
+ })
68
+
69
+ test('2 monitor runOnce', async () => {
70
+ const setup = await createSetup('test')
170
71
 
171
72
  await setup.monitor.runOnce()
172
73
 
@@ -174,23 +75,7 @@ describe('localWallet tests', () => {
174
75
  })
175
76
 
176
77
  test('3 return active to cloud client', async () => {
177
- const chain: sdk.Chain = 'test'
178
- if (_tu.noTestEnv(chain)) return
179
- const env = _tu.getEnv(chain)
180
- if (!env.testIdentityKey)
181
- throw new sdk.WERR_INVALID_PARAMETER('env.testIdentityKey', 'valid')
182
- if (!env.testFilePath)
183
- throw new sdk.WERR_INVALID_PARAMETER('env.testFilePath', 'valid')
184
-
185
- const setup = await _tu.createTestWallet({
186
- chain,
187
- rootKeyHex: env.devKeys[env.testIdentityKey],
188
- filePath: env.testFilePath,
189
- setActiveClient: false,
190
- addLocalBackup: false
191
- })
192
-
193
- console.log(`ACTIVE STORAGE: ${setup.storage.getActiveStoreName()}`)
78
+ const setup = await createSetup('test')
194
79
 
195
80
  const localBalance = await setup.wallet.balance()
196
81
 
@@ -205,4 +90,272 @@ describe('localWallet tests', () => {
205
90
 
206
91
  await setup.wallet.destroy()
207
92
  })
93
+
94
+ test('4 review change utxos', async () => {
95
+ const setup = await createSetup('test')
96
+
97
+ const storage = setup.activeStorage
98
+ const services = setup.services
99
+
100
+ const { invalidSpendableOutputs: notUtxos } = await confirmSpendableOutputs(
101
+ storage,
102
+ services
103
+ )
104
+ const outputsToUpdate = notUtxos.map(o => ({
105
+ id: o.outputId,
106
+ satoshis: o.satoshis
107
+ }))
108
+
109
+ const total: number = outputsToUpdate.reduce((t, o) => t + o.satoshis, 0)
110
+
111
+ debugger
112
+ // *** About set spendable = false for outputs ***/
113
+ for (const o of outputsToUpdate) {
114
+ await storage.updateOutput(o.id, { spendable: false })
115
+ }
116
+
117
+ await setup.wallet.destroy()
118
+ })
119
+
120
+ test('5 review synchunk', async () => {
121
+ const setup = await createSetup('test')
122
+
123
+ const identityKey = setup.identityKey
124
+ const reader = setup.activeStorage
125
+ const readerSettings = reader.getSettings()
126
+ const writer = setup.storage._backups![0].storage
127
+ const writerSettings = writer.getSettings()
128
+
129
+ const ss = await EntitySyncState.fromStorage(
130
+ writer,
131
+ identityKey,
132
+ readerSettings
133
+ )
134
+
135
+ const args = ss.makeRequestSyncChunkArgs(
136
+ identityKey,
137
+ writerSettings.storageIdentityKey
138
+ )
139
+
140
+ const chunk = await reader.getSyncChunk(args)
141
+
142
+ await setup.wallet.destroy()
143
+ })
144
+
145
+ test('6 backup', async () => {
146
+ const setup = await createSetup('test')
147
+
148
+ await setup.storage.updateBackups()
149
+
150
+ await setup.wallet.destroy()
151
+ })
208
152
  })
153
+
154
+ async function createSetup(chain: sdk.Chain): Promise<TestWalletNoSetup> {
155
+ const env = _tu.getEnv(chain)
156
+ if (!env.testIdentityKey)
157
+ throw new sdk.WERR_INVALID_PARAMETER('env.testIdentityKey', 'valid')
158
+ if (!env.testFilePath)
159
+ throw new sdk.WERR_INVALID_PARAMETER('env.testFilePath', 'valid')
160
+
161
+ const setup = await _tu.createTestWallet({
162
+ chain,
163
+ rootKeyHex: env.devKeys[env.testIdentityKey],
164
+ filePath: env.testFilePath,
165
+ setActiveClient: false,
166
+ addLocalBackup: false
167
+ })
168
+
169
+ console.log(`ACTIVE STORAGE: ${setup.storage.getActiveStoreName()}`)
170
+
171
+ return setup
172
+ }
173
+
174
+ async function createOneSatTestOutput(
175
+ setup: TestWalletNoSetup,
176
+ options: CreateActionOptions = {},
177
+ howMany: number = 1
178
+ ): Promise<CreateActionResult> {
179
+ let car: CreateActionResult = {}
180
+
181
+ for (let i = 0; i < howMany; i++) {
182
+ const args: CreateActionArgs = {
183
+ outputs: [
184
+ {
185
+ lockingScript: new P2PKH()
186
+ .lock(PublicKey.fromString(setup.identityKey).toAddress())
187
+ .toHex(),
188
+ satoshis: 1,
189
+ outputDescription: 'test output',
190
+ customInstructions: JSON.stringify({
191
+ type: 'P2PKH',
192
+ key: 'identity'
193
+ }),
194
+ basket: 'test-output'
195
+ }
196
+ ],
197
+ description: 'create test output',
198
+ options: {
199
+ ...options
200
+ }
201
+ }
202
+ car = await setup.wallet.createAction(args)
203
+ expect(car.txid)
204
+
205
+ const req = await EntityProvenTxReq.fromStorageTxid(
206
+ setup.activeStorage,
207
+ car.txid!
208
+ )
209
+ expect(req !== undefined && req.history.notes !== undefined)
210
+ if (req && req.history.notes) {
211
+ expect(req.status === 'unsent')
212
+ expect(req.history.notes.length === 1)
213
+ const n = req.history.notes[0]
214
+ expect(n.what === 'status' && n.status_now === 'unsent')
215
+ }
216
+ }
217
+ return car
218
+ }
219
+
220
+ async function recoverOneSatTestOutputs(
221
+ setup: TestWalletNoSetup
222
+ ): Promise<void> {
223
+ const outputs = await setup.wallet.listOutputs({
224
+ basket: 'test-output',
225
+ include: 'entire transactions',
226
+ limit: 1000
227
+ })
228
+
229
+ if (outputs.outputs.length > 8) {
230
+ const args: CreateActionArgs = {
231
+ inputBEEF: outputs.BEEF!,
232
+ inputs: [],
233
+ description: 'recover test output'
234
+ }
235
+ const p2pkh = new P2PKH()
236
+ for (const o of outputs.outputs) {
237
+ args.inputs!.push({
238
+ unlockingScriptLength: 108,
239
+ outpoint: o.outpoint,
240
+ inputDescription: 'recovered test output'
241
+ })
242
+ }
243
+ const car = await setup.wallet.createAction(args)
244
+ expect(car.signableTransaction)
245
+
246
+ const st = car.signableTransaction!
247
+ const beef = Beef.fromBinary(st.tx)
248
+ const tx = beef.findAtomicTransaction(beef.txs.slice(-1)[0].txid)!
249
+ const signArgs: SignActionArgs = {
250
+ reference: st.reference,
251
+ spends: {} // 0: { unlockingScript } },
252
+ }
253
+ for (let i = 0; i < outputs.outputs.length; i++) {
254
+ const o = outputs.outputs[i]
255
+ const unlock = p2pkh.unlock(setup.keyDeriver.rootKey, 'all', false)
256
+ const unlockingScript = (await unlock.sign(tx, i)).toHex()
257
+ signArgs.spends[i] = { unlockingScript }
258
+ }
259
+ const sar = await setup.wallet.signAction(signArgs)
260
+ expect(sar.txid)
261
+ }
262
+ }
263
+
264
+ async function trackReqByTxid(
265
+ setup: TestWalletNoSetup,
266
+ txid: string
267
+ ): Promise<void> {
268
+ const req = await EntityProvenTxReq.fromStorageTxid(setup.activeStorage, txid)
269
+
270
+ expect(req !== undefined && req.history.notes !== undefined)
271
+ if (!req || !req.history.notes) throw new sdk.WERR_INTERNAL()
272
+
273
+ let newBlocks = 0
274
+ let lastHeight: number | undefined
275
+ for (; req.status !== 'completed'; ) {
276
+ let height = setup.monitor.lastNewHeader?.height
277
+ if (req.status === 'unsent') {
278
+ // send it...
279
+ }
280
+ if (req.status === 'sending') {
281
+ // send it...
282
+ }
283
+ if (req.status === 'unmined') {
284
+ if (height && lastHeight) {
285
+ if (height === lastHeight) {
286
+ await wait(1000 * 60)
287
+ } else {
288
+ newBlocks++
289
+ expect(newBlocks < 5)
290
+ }
291
+ }
292
+ }
293
+
294
+ await setup.monitor.runOnce()
295
+ await req.refreshFromStorage(setup.activeStorage)
296
+ lastHeight = height
297
+ }
298
+ }
299
+
300
+ export async function confirmSpendableOutputs(
301
+ storage: StorageKnex,
302
+ services: Services,
303
+ identityKey?: string
304
+ ): Promise<{ invalidSpendableOutputs: TableOutput[] }> {
305
+ const invalidSpendableOutputs: TableOutput[] = []
306
+ const partial: Partial<TableUser> = {}
307
+ if (identityKey) partial.identityKey = identityKey
308
+ const users = await storage.findUsers({ partial })
309
+
310
+ for (const { userId } of users) {
311
+ const defaultBasket = verifyOne(
312
+ await storage.findOutputBaskets({ partial: { userId, name: 'default' } })
313
+ )
314
+ const where: Partial<TableOutput> = {
315
+ userId,
316
+ basketId: defaultBasket.basketId,
317
+ spendable: true
318
+ }
319
+
320
+ const outputs = await storage.findOutputs({ partial: where })
321
+
322
+ for (let i = outputs.length - 1; i >= 0; i--) {
323
+ const o = outputs[i]
324
+ const oid = verifyId(o.outputId)
325
+
326
+ if (o.spendable) {
327
+ let ok = false
328
+
329
+ if (o.lockingScript && o.lockingScript.length > 0) {
330
+ const r = await services.getUtxoStatus(
331
+ asString(o.lockingScript),
332
+ 'script'
333
+ )
334
+
335
+ if (r.status === 'success' && r.isUtxo && r.details?.length > 0) {
336
+ const tx = await storage.findTransactionById(o.transactionId)
337
+
338
+ if (
339
+ tx &&
340
+ tx.txid &&
341
+ r.details.some(
342
+ d =>
343
+ d.txid === tx.txid &&
344
+ d.satoshis === o.satoshis &&
345
+ d.index === o.vout
346
+ )
347
+ ) {
348
+ ok = true
349
+ }
350
+ }
351
+ }
352
+
353
+ if (!ok) {
354
+ invalidSpendableOutputs.push(o)
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ return { invalidSpendableOutputs }
361
+ }
@@ -0,0 +1,44 @@
1
+ import { Services, StorageKnex } from '../../../src'
2
+ import { _tu } from '../../utils/TestUtilsWalletStorage'
3
+ import { confirmSpendableOutputs } from '../local/localWallet.man.test'
4
+
5
+ describe('janitor tests', () => {
6
+ jest.setTimeout(99999999)
7
+
8
+ test('0 review utxos by identity key', async () => {
9
+ const env = _tu.getEnv('main')
10
+ if (!env.cloudMySQLConnection) return
11
+
12
+ const connection = JSON.parse(env.cloudMySQLConnection)
13
+ const storage = new StorageKnex({
14
+ ...StorageKnex.defaultOptions(),
15
+ knex: _tu.createMySQLFromConnection(connection),
16
+ chain: env.chain
17
+ })
18
+ await storage.makeAvailable()
19
+
20
+ const services = new Services(env.chain)
21
+
22
+ const identityKey =
23
+ '03894bda9b11626c0280ec28f0d0193e9bd34446679ed1b32e5621e94e0e807073'
24
+ const { invalidSpendableOutputs: notUtxos } = await confirmSpendableOutputs(
25
+ storage,
26
+ services,
27
+ identityKey
28
+ )
29
+ const outputsToUpdate = notUtxos.map(o => ({
30
+ id: o.outputId,
31
+ satoshis: o.satoshis
32
+ }))
33
+
34
+ const total: number = outputsToUpdate.reduce((t, o) => t + o.satoshis, 0)
35
+
36
+ debugger
37
+ // *** About set spendable = false for outputs ***/
38
+ for (const o of outputsToUpdate) {
39
+ await storage.updateOutput(o.id, { spendable: false })
40
+ }
41
+
42
+ await storage.destroy()
43
+ })
44
+ })