@bopen-io/wallet-toolbox 1.7.18

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 (390) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.env.template +22 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  4. package/.github/ISSUE_TEMPLATE/discussion.md +24 -0
  5. package/.github/pull_request_template.md +22 -0
  6. package/.github/workflows/push.yaml +145 -0
  7. package/.prettierrc +10 -0
  8. package/CHANGELOG.md +280 -0
  9. package/CONTRIBUTING.md +89 -0
  10. package/README.md +43 -0
  11. package/docs/README.md +85 -0
  12. package/docs/client.md +19627 -0
  13. package/docs/monitor.md +953 -0
  14. package/docs/open-rpc/index.html +46 -0
  15. package/docs/services.md +6377 -0
  16. package/docs/setup.md +1268 -0
  17. package/docs/storage.md +5367 -0
  18. package/docs/wallet.md +19626 -0
  19. package/jest.config.ts +25 -0
  20. package/license.md +28 -0
  21. package/out/tsconfig.all.tsbuildinfo +1 -0
  22. package/package.json +63 -0
  23. package/src/CWIStyleWalletManager.ts +1999 -0
  24. package/src/Setup.ts +579 -0
  25. package/src/SetupClient.ts +322 -0
  26. package/src/SetupWallet.ts +108 -0
  27. package/src/SimpleWalletManager.ts +526 -0
  28. package/src/Wallet.ts +1169 -0
  29. package/src/WalletAuthenticationManager.ts +153 -0
  30. package/src/WalletLogger.ts +213 -0
  31. package/src/WalletPermissionsManager.ts +3660 -0
  32. package/src/WalletSettingsManager.ts +114 -0
  33. package/src/__tests/CWIStyleWalletManager.test.d.ts.map +1 -0
  34. package/src/__tests/CWIStyleWalletManager.test.js.map +1 -0
  35. package/src/__tests/CWIStyleWalletManager.test.ts +675 -0
  36. package/src/__tests/WalletPermissionsManager.callbacks.test.ts +323 -0
  37. package/src/__tests/WalletPermissionsManager.checks.test.ts +844 -0
  38. package/src/__tests/WalletPermissionsManager.encryption.test.ts +412 -0
  39. package/src/__tests/WalletPermissionsManager.fixtures.ts +307 -0
  40. package/src/__tests/WalletPermissionsManager.flows.test.ts +462 -0
  41. package/src/__tests/WalletPermissionsManager.initialization.test.ts +300 -0
  42. package/src/__tests/WalletPermissionsManager.pmodules.test.ts +798 -0
  43. package/src/__tests/WalletPermissionsManager.proxying.test.ts +724 -0
  44. package/src/__tests/WalletPermissionsManager.tokens.test.ts +503 -0
  45. package/src/index.all.ts +27 -0
  46. package/src/index.client.ts +25 -0
  47. package/src/index.mobile.ts +21 -0
  48. package/src/index.ts +1 -0
  49. package/src/monitor/Monitor.ts +412 -0
  50. package/src/monitor/MonitorDaemon.ts +188 -0
  51. package/src/monitor/README.md +3 -0
  52. package/src/monitor/__test/MonitorDaemon.man.test.ts +45 -0
  53. package/src/monitor/tasks/TaskCheckForProofs.ts +243 -0
  54. package/src/monitor/tasks/TaskCheckNoSends.ts +73 -0
  55. package/src/monitor/tasks/TaskClock.ts +33 -0
  56. package/src/monitor/tasks/TaskFailAbandoned.ts +54 -0
  57. package/src/monitor/tasks/TaskMonitorCallHistory.ts +26 -0
  58. package/src/monitor/tasks/TaskNewHeader.ts +93 -0
  59. package/src/monitor/tasks/TaskPurge.ts +68 -0
  60. package/src/monitor/tasks/TaskReorg.ts +89 -0
  61. package/src/monitor/tasks/TaskReviewStatus.ts +48 -0
  62. package/src/monitor/tasks/TaskSendWaiting.ts +122 -0
  63. package/src/monitor/tasks/TaskSyncWhenIdle.ts +26 -0
  64. package/src/monitor/tasks/TaskUnFail.ts +151 -0
  65. package/src/monitor/tasks/WalletMonitorTask.ts +47 -0
  66. package/src/sdk/CertOpsWallet.ts +18 -0
  67. package/src/sdk/PrivilegedKeyManager.ts +372 -0
  68. package/src/sdk/README.md +13 -0
  69. package/src/sdk/WERR_errors.ts +234 -0
  70. package/src/sdk/WalletError.ts +170 -0
  71. package/src/sdk/WalletErrorFromJson.ts +80 -0
  72. package/src/sdk/WalletServices.interfaces.ts +700 -0
  73. package/src/sdk/WalletSigner.interfaces.ts +11 -0
  74. package/src/sdk/WalletStorage.interfaces.ts +606 -0
  75. package/src/sdk/__test/CertificateLifeCycle.test.ts +131 -0
  76. package/src/sdk/__test/PrivilegedKeyManager.test.ts +738 -0
  77. package/src/sdk/__test/WalletError.test.ts +318 -0
  78. package/src/sdk/__test/validationHelpers.test.ts +21 -0
  79. package/src/sdk/index.ts +10 -0
  80. package/src/sdk/types.ts +226 -0
  81. package/src/services/README.md +11 -0
  82. package/src/services/ServiceCollection.ts +248 -0
  83. package/src/services/Services.ts +603 -0
  84. package/src/services/__tests/ARC.man.test.ts +123 -0
  85. package/src/services/__tests/ARC.timeout.man.test.ts +79 -0
  86. package/src/services/__tests/ArcGorillaPool.man.test.ts +108 -0
  87. package/src/services/__tests/arcServices.test.ts +8 -0
  88. package/src/services/__tests/bitrails.test.ts +56 -0
  89. package/src/services/__tests/getMerklePath.test.ts +15 -0
  90. package/src/services/__tests/getRawTx.test.ts +13 -0
  91. package/src/services/__tests/postBeef.test.ts +104 -0
  92. package/src/services/__tests/verifyBeef.test.ts +50 -0
  93. package/src/services/chaintracker/BHServiceClient.ts +212 -0
  94. package/src/services/chaintracker/ChaintracksChainTracker.ts +71 -0
  95. package/src/services/chaintracker/__tests/ChaintracksChainTracker.test.ts +33 -0
  96. package/src/services/chaintracker/__tests/ChaintracksServiceClient.test.ts +29 -0
  97. package/src/services/chaintracker/chaintracks/Api/BlockHeaderApi.ts +72 -0
  98. package/src/services/chaintracker/chaintracks/Api/BulkIngestorApi.ts +83 -0
  99. package/src/services/chaintracker/chaintracks/Api/BulkStorageApi.ts +92 -0
  100. package/src/services/chaintracker/chaintracks/Api/ChaintracksApi.ts +64 -0
  101. package/src/services/chaintracker/chaintracks/Api/ChaintracksClientApi.ts +189 -0
  102. package/src/services/chaintracker/chaintracks/Api/ChaintracksFetchApi.ts +18 -0
  103. package/src/services/chaintracker/chaintracks/Api/ChaintracksFsApi.ts +58 -0
  104. package/src/services/chaintracker/chaintracks/Api/ChaintracksStorageApi.ts +386 -0
  105. package/src/services/chaintracker/chaintracks/Api/LiveIngestorApi.ts +25 -0
  106. package/src/services/chaintracker/chaintracks/Chaintracks.ts +609 -0
  107. package/src/services/chaintracker/chaintracks/ChaintracksService.ts +199 -0
  108. package/src/services/chaintracker/chaintracks/ChaintracksServiceClient.ts +154 -0
  109. package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.ts +176 -0
  110. package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDN.ts +174 -0
  111. package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorCDNBabbage.ts +18 -0
  112. package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.ts +113 -0
  113. package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainWs.ts +81 -0
  114. package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.ts +86 -0
  115. package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorTeranodeP2P.ts +59 -0
  116. package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.ts +104 -0
  117. package/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainWs.ts +66 -0
  118. package/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.ts +566 -0
  119. package/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainServices.ts +219 -0
  120. package/src/services/chaintracker/chaintracks/Ingest/__tests/BulkIngestorCDNBabbage.test.ts +54 -0
  121. package/src/services/chaintracker/chaintracks/Ingest/__tests/LiveIngestorWhatsOnChainPoll.test.ts +33 -0
  122. package/src/services/chaintracker/chaintracks/Ingest/__tests/WhatsOnChainServices.test.ts +124 -0
  123. package/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.ts +92 -0
  124. package/src/services/chaintracker/chaintracks/Storage/ChaintracksKnexMigrations.ts +104 -0
  125. package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.ts +382 -0
  126. package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.ts +574 -0
  127. package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.ts +438 -0
  128. package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageMemory.ts +29 -0
  129. package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.ts +304 -0
  130. package/src/services/chaintracker/chaintracks/Storage/__tests/ChaintracksStorageIdb.test.ts +102 -0
  131. package/src/services/chaintracker/chaintracks/Storage/__tests/ChaintracksStorageKnex.test.ts +45 -0
  132. package/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.ts +77 -0
  133. package/src/services/chaintracker/chaintracks/__tests/ChaintracksClientApi.test.ts +192 -0
  134. package/src/services/chaintracker/chaintracks/__tests/LocalCdnServer.ts +75 -0
  135. package/src/services/chaintracker/chaintracks/__tests/createIdbChaintracks.test.ts +62 -0
  136. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNetBlockHeaders.json +1 -0
  137. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_0.headers +0 -0
  138. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_1.headers +0 -0
  139. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_2.headers +0 -0
  140. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest349/mainNet_3.headers +0 -0
  141. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNetBlockHeaders.json +1 -0
  142. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_0.headers +0 -0
  143. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_1.headers +0 -0
  144. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_2.headers +0 -0
  145. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest379/mainNet_3.headers +0 -0
  146. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNetBlockHeaders.json +1 -0
  147. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_0.headers +0 -0
  148. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_1.headers +0 -0
  149. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_2.headers +0 -0
  150. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest399/mainNet_3.headers +0 -0
  151. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNetBlockHeaders.json +1 -0
  152. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_0.headers +0 -0
  153. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_1.headers +0 -0
  154. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_2.headers +0 -0
  155. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_3.headers +0 -0
  156. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest402/mainNet_4.headers +0 -0
  157. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNetBlockHeaders.json +1 -0
  158. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_0.headers +0 -0
  159. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_1.headers +0 -0
  160. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_2.headers +0 -0
  161. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_3.headers +0 -0
  162. package/src/services/chaintracker/chaintracks/__tests/data/cdnTest499/mainNet_4.headers +0 -0
  163. package/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.ts +92 -0
  164. package/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.ts +111 -0
  165. package/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.ts +91 -0
  166. package/src/services/chaintracker/chaintracks/createIdbChaintracks.ts +60 -0
  167. package/src/services/chaintracker/chaintracks/createKnexChaintracks.ts +65 -0
  168. package/src/services/chaintracker/chaintracks/createNoDbChaintracks.ts +60 -0
  169. package/src/services/chaintracker/chaintracks/index.all.ts +12 -0
  170. package/src/services/chaintracker/chaintracks/index.client.ts +4 -0
  171. package/src/services/chaintracker/chaintracks/index.mobile.ts +37 -0
  172. package/src/services/chaintracker/chaintracks/util/BulkFileDataManager.ts +975 -0
  173. package/src/services/chaintracker/chaintracks/util/BulkFileDataReader.ts +60 -0
  174. package/src/services/chaintracker/chaintracks/util/BulkFilesReader.ts +336 -0
  175. package/src/services/chaintracker/chaintracks/util/BulkHeaderFile.ts +247 -0
  176. package/src/services/chaintracker/chaintracks/util/ChaintracksFetch.ts +69 -0
  177. package/src/services/chaintracker/chaintracks/util/ChaintracksFs.ts +141 -0
  178. package/src/services/chaintracker/chaintracks/util/HeightRange.ts +153 -0
  179. package/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.ts +76 -0
  180. package/src/services/chaintracker/chaintracks/util/__tests/BulkFileDataManager.test.ts +304 -0
  181. package/src/services/chaintracker/chaintracks/util/__tests/ChaintracksFetch.test.ts +60 -0
  182. package/src/services/chaintracker/chaintracks/util/__tests/HeightRange.test.ts +67 -0
  183. package/src/services/chaintracker/chaintracks/util/__tests/SingleWriterMultiReaderLock.test.ts +49 -0
  184. package/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.ts +573 -0
  185. package/src/services/chaintracker/chaintracks/util/dirtyHashes.ts +29 -0
  186. package/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.ts +432 -0
  187. package/src/services/chaintracker/index.all.ts +4 -0
  188. package/src/services/chaintracker/index.client.ts +4 -0
  189. package/src/services/chaintracker/index.mobile.ts +4 -0
  190. package/src/services/createDefaultWalletServicesOptions.ts +77 -0
  191. package/src/services/index.ts +1 -0
  192. package/src/services/processingErrors/arcSuccessError.json +76 -0
  193. package/src/services/providers/ARC.ts +350 -0
  194. package/src/services/providers/Bitails.ts +256 -0
  195. package/src/services/providers/SdkWhatsOnChain.ts +83 -0
  196. package/src/services/providers/WhatsOnChain.ts +883 -0
  197. package/src/services/providers/__tests/WhatsOnChain.test.ts +242 -0
  198. package/src/services/providers/__tests/exchangeRates.test.ts +18 -0
  199. package/src/services/providers/exchangeRates.ts +265 -0
  200. package/src/services/providers/getBeefForTxid.ts +369 -0
  201. package/src/signer/README.md +5 -0
  202. package/src/signer/WalletSigner.ts +17 -0
  203. package/src/signer/methods/acquireDirectCertificate.ts +52 -0
  204. package/src/signer/methods/buildSignableTransaction.ts +183 -0
  205. package/src/signer/methods/completeSignedTransaction.ts +117 -0
  206. package/src/signer/methods/createAction.ts +172 -0
  207. package/src/signer/methods/internalizeAction.ts +106 -0
  208. package/src/signer/methods/proveCertificate.ts +43 -0
  209. package/src/signer/methods/signAction.ts +54 -0
  210. package/src/storage/README.md +14 -0
  211. package/src/storage/StorageIdb.ts +2304 -0
  212. package/src/storage/StorageKnex.ts +1425 -0
  213. package/src/storage/StorageProvider.ts +810 -0
  214. package/src/storage/StorageReader.ts +194 -0
  215. package/src/storage/StorageReaderWriter.ts +432 -0
  216. package/src/storage/StorageSyncReader.ts +34 -0
  217. package/src/storage/WalletStorageManager.ts +943 -0
  218. package/src/storage/__test/StorageIdb.test.ts +43 -0
  219. package/src/storage/__test/WalletStorageManager.test.ts +275 -0
  220. package/src/storage/__test/adminStats.man.test.ts +89 -0
  221. package/src/storage/__test/getBeefForTransaction.test.ts +385 -0
  222. package/src/storage/index.all.ts +11 -0
  223. package/src/storage/index.client.ts +7 -0
  224. package/src/storage/index.mobile.ts +6 -0
  225. package/src/storage/methods/ListActionsSpecOp.ts +70 -0
  226. package/src/storage/methods/ListOutputsSpecOp.ts +129 -0
  227. package/src/storage/methods/__test/GenerateChange/generateChangeSdk.test.ts +1057 -0
  228. package/src/storage/methods/__test/GenerateChange/randomValsUsed1.ts +20 -0
  229. package/src/storage/methods/__test/offsetKey.test.ts +274 -0
  230. package/src/storage/methods/attemptToPostReqsToNetwork.ts +389 -0
  231. package/src/storage/methods/createAction.ts +947 -0
  232. package/src/storage/methods/generateChange.ts +556 -0
  233. package/src/storage/methods/getBeefForTransaction.ts +139 -0
  234. package/src/storage/methods/getSyncChunk.ts +293 -0
  235. package/src/storage/methods/internalizeAction.ts +562 -0
  236. package/src/storage/methods/listActionsIdb.ts +183 -0
  237. package/src/storage/methods/listActionsKnex.ts +226 -0
  238. package/src/storage/methods/listCertificates.ts +73 -0
  239. package/src/storage/methods/listOutputsIdb.ts +203 -0
  240. package/src/storage/methods/listOutputsKnex.ts +263 -0
  241. package/src/storage/methods/offsetKey.ts +89 -0
  242. package/src/storage/methods/processAction.ts +420 -0
  243. package/src/storage/methods/purgeData.ts +251 -0
  244. package/src/storage/methods/purgeDataIdb.ts +10 -0
  245. package/src/storage/methods/reviewStatus.ts +101 -0
  246. package/src/storage/methods/reviewStatusIdb.ts +43 -0
  247. package/src/storage/methods/utils.Buffer.ts +33 -0
  248. package/src/storage/methods/utils.ts +56 -0
  249. package/src/storage/remoting/StorageClient.ts +567 -0
  250. package/src/storage/remoting/StorageMobile.ts +544 -0
  251. package/src/storage/remoting/StorageServer.ts +291 -0
  252. package/src/storage/remoting/__test/StorageClient.test.ts +113 -0
  253. package/src/storage/schema/KnexMigrations.ts +489 -0
  254. package/src/storage/schema/StorageIdbSchema.ts +150 -0
  255. package/src/storage/schema/entities/EntityBase.ts +210 -0
  256. package/src/storage/schema/entities/EntityCertificate.ts +188 -0
  257. package/src/storage/schema/entities/EntityCertificateField.ts +136 -0
  258. package/src/storage/schema/entities/EntityCommission.ts +148 -0
  259. package/src/storage/schema/entities/EntityOutput.ts +290 -0
  260. package/src/storage/schema/entities/EntityOutputBasket.ts +153 -0
  261. package/src/storage/schema/entities/EntityOutputTag.ts +121 -0
  262. package/src/storage/schema/entities/EntityOutputTagMap.ts +123 -0
  263. package/src/storage/schema/entities/EntityProvenTx.ts +319 -0
  264. package/src/storage/schema/entities/EntityProvenTxReq.ts +580 -0
  265. package/src/storage/schema/entities/EntitySyncState.ts +389 -0
  266. package/src/storage/schema/entities/EntityTransaction.ts +306 -0
  267. package/src/storage/schema/entities/EntityTxLabel.ts +121 -0
  268. package/src/storage/schema/entities/EntityTxLabelMap.ts +123 -0
  269. package/src/storage/schema/entities/EntityUser.ts +112 -0
  270. package/src/storage/schema/entities/MergeEntity.ts +73 -0
  271. package/src/storage/schema/entities/__tests/CertificateFieldTests.test.ts +353 -0
  272. package/src/storage/schema/entities/__tests/CertificateTests.test.ts +354 -0
  273. package/src/storage/schema/entities/__tests/CommissionTests.test.ts +371 -0
  274. package/src/storage/schema/entities/__tests/OutputBasketTests.test.ts +278 -0
  275. package/src/storage/schema/entities/__tests/OutputTagMapTests.test.ts +242 -0
  276. package/src/storage/schema/entities/__tests/OutputTagTests.test.ts +288 -0
  277. package/src/storage/schema/entities/__tests/OutputTests.test.ts +464 -0
  278. package/src/storage/schema/entities/__tests/ProvenTxReqTests.test.ts +340 -0
  279. package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +504 -0
  280. package/src/storage/schema/entities/__tests/SyncStateTests.test.ts +288 -0
  281. package/src/storage/schema/entities/__tests/TransactionTests.test.ts +604 -0
  282. package/src/storage/schema/entities/__tests/TxLabelMapTests.test.ts +361 -0
  283. package/src/storage/schema/entities/__tests/TxLabelTests.test.ts +198 -0
  284. package/src/storage/schema/entities/__tests/stampLogTests.test.ts +90 -0
  285. package/src/storage/schema/entities/__tests/usersTests.test.ts +340 -0
  286. package/src/storage/schema/entities/index.ts +16 -0
  287. package/src/storage/schema/tables/TableCertificate.ts +21 -0
  288. package/src/storage/schema/tables/TableCertificateField.ts +12 -0
  289. package/src/storage/schema/tables/TableCommission.ts +13 -0
  290. package/src/storage/schema/tables/TableMonitorEvent.ts +9 -0
  291. package/src/storage/schema/tables/TableOutput.ts +64 -0
  292. package/src/storage/schema/tables/TableOutputBasket.ts +12 -0
  293. package/src/storage/schema/tables/TableOutputTag.ts +10 -0
  294. package/src/storage/schema/tables/TableOutputTagMap.ts +9 -0
  295. package/src/storage/schema/tables/TableProvenTx.ts +14 -0
  296. package/src/storage/schema/tables/TableProvenTxReq.ts +65 -0
  297. package/src/storage/schema/tables/TableSettings.ts +17 -0
  298. package/src/storage/schema/tables/TableSyncState.ts +18 -0
  299. package/src/storage/schema/tables/TableTransaction.ts +54 -0
  300. package/src/storage/schema/tables/TableTxLabel.ts +10 -0
  301. package/src/storage/schema/tables/TableTxLabelMap.ts +9 -0
  302. package/src/storage/schema/tables/TableUser.ts +16 -0
  303. package/src/storage/schema/tables/index.ts +16 -0
  304. package/src/storage/sync/StorageMySQLDojoReader.ts +696 -0
  305. package/src/storage/sync/index.ts +1 -0
  306. package/src/utility/Format.ts +133 -0
  307. package/src/utility/README.md +3 -0
  308. package/src/utility/ReaderUint8Array.ts +187 -0
  309. package/src/utility/ScriptTemplateBRC29.ts +73 -0
  310. package/src/utility/__tests/utilityHelpers.noBuffer.test.ts +109 -0
  311. package/src/utility/aggregateResults.ts +68 -0
  312. package/src/utility/identityUtils.ts +159 -0
  313. package/src/utility/index.all.ts +7 -0
  314. package/src/utility/index.client.ts +7 -0
  315. package/src/utility/parseTxScriptOffsets.ts +29 -0
  316. package/src/utility/stampLog.ts +69 -0
  317. package/src/utility/tscProofToMerklePath.ts +48 -0
  318. package/src/utility/utilityHelpers.buffer.ts +34 -0
  319. package/src/utility/utilityHelpers.noBuffer.ts +60 -0
  320. package/src/utility/utilityHelpers.ts +275 -0
  321. package/src/wab-client/WABClient.ts +94 -0
  322. package/src/wab-client/__tests/WABClient.man.test.ts +59 -0
  323. package/src/wab-client/auth-method-interactors/AuthMethodInteractor.ts +47 -0
  324. package/src/wab-client/auth-method-interactors/DevConsoleInteractor.ts +73 -0
  325. package/src/wab-client/auth-method-interactors/PersonaIDInteractor.ts +35 -0
  326. package/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.ts +72 -0
  327. package/syncVersions.js +71 -0
  328. package/test/Wallet/StorageClient/storageClient.man.test.ts +75 -0
  329. package/test/Wallet/action/abortAction.test.ts +47 -0
  330. package/test/Wallet/action/createAction.test.ts +299 -0
  331. package/test/Wallet/action/createAction2.test.ts +1273 -0
  332. package/test/Wallet/action/createActionToGenerateBeefs.man.test.ts +293 -0
  333. package/test/Wallet/action/internalizeAction.a.test.ts +286 -0
  334. package/test/Wallet/action/internalizeAction.test.ts +682 -0
  335. package/test/Wallet/action/relinquishOutput.test.ts +37 -0
  336. package/test/Wallet/certificate/acquireCertificate.test.ts +298 -0
  337. package/test/Wallet/certificate/listCertificates.test.ts +346 -0
  338. package/test/Wallet/construct/Wallet.constructor.test.ts +57 -0
  339. package/test/Wallet/get/getHeaderForHeight.test.ts +82 -0
  340. package/test/Wallet/get/getHeight.test.ts +52 -0
  341. package/test/Wallet/get/getKnownTxids.test.ts +86 -0
  342. package/test/Wallet/get/getNetwork.test.ts +27 -0
  343. package/test/Wallet/get/getVersion.test.ts +27 -0
  344. package/test/Wallet/list/listActions.test.ts +279 -0
  345. package/test/Wallet/list/listActions2.test.ts +1381 -0
  346. package/test/Wallet/list/listCertificates.test.ts +118 -0
  347. package/test/Wallet/list/listOutputs.test.ts +447 -0
  348. package/test/Wallet/live/walletLive.man.test.ts +521 -0
  349. package/test/Wallet/local/localWallet.man.test.ts +93 -0
  350. package/test/Wallet/local/localWallet2.man.test.ts +277 -0
  351. package/test/Wallet/signAction/mountaintop.man.test.ts +130 -0
  352. package/test/Wallet/specOps/specOps.man.test.ts +220 -0
  353. package/test/Wallet/support/janitor.man.test.ts +40 -0
  354. package/test/Wallet/support/operations.man.test.ts +407 -0
  355. package/test/Wallet/support/reqErrorReview.2025.05.06.man.test.ts +347 -0
  356. package/test/Wallet/sync/Wallet.sync.test.ts +215 -0
  357. package/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.ts +203 -0
  358. package/test/Wallet/sync/setActive.test.ts +170 -0
  359. package/test/WalletClient/LocalKVStore.man.test.ts +114 -0
  360. package/test/WalletClient/WERR.man.test.ts +35 -0
  361. package/test/bsv-ts-sdk/LocalKVStore.test.ts +102 -0
  362. package/test/checkDB.ts +57 -0
  363. package/test/checkdb +0 -0
  364. package/test/examples/backup.man.test.ts +59 -0
  365. package/test/examples/pushdrop.test.ts +282 -0
  366. package/test/monitor/Monitor.test.ts +620 -0
  367. package/test/services/Services.test.ts +263 -0
  368. package/test/storage/KnexMigrations.test.ts +86 -0
  369. package/test/storage/StorageMySQLDojoReader.man.test.ts +60 -0
  370. package/test/storage/count.test.ts +177 -0
  371. package/test/storage/find.test.ts +195 -0
  372. package/test/storage/findLegacy.test.ts +67 -0
  373. package/test/storage/idb/allocateChange.test.ts +251 -0
  374. package/test/storage/idb/count.test.ts +158 -0
  375. package/test/storage/idb/find.test.ts +177 -0
  376. package/test/storage/idb/idbSpeed.test.ts +36 -0
  377. package/test/storage/idb/insert.test.ts +268 -0
  378. package/test/storage/idb/transactionAbort.test.ts +108 -0
  379. package/test/storage/idb/update.test.ts +999 -0
  380. package/test/storage/insert.test.ts +278 -0
  381. package/test/storage/update.test.ts +1021 -0
  382. package/test/storage/update2.test.ts +897 -0
  383. package/test/utils/TestUtilsWalletStorage.ts +2526 -0
  384. package/test/utils/localWalletMethods.ts +363 -0
  385. package/test/utils/removeFailedFromDatabase.sql +17 -0
  386. package/ts2md.json +44 -0
  387. package/tsconfig.all.json +31 -0
  388. package/tsconfig.client.json +29 -0
  389. package/tsconfig.json +17 -0
  390. package/tsconfig.mobile.json +28 -0
@@ -0,0 +1,2304 @@
1
+ import { deleteDB, IDBPCursorWithValue, IDBPDatabase, IDBPTransaction, openDB } from 'idb'
2
+ import { ListActionsResult, ListOutputsResult, Validation } from '@bsv/sdk'
3
+ import {
4
+ TableCertificate,
5
+ TableCertificateField,
6
+ TableCertificateX,
7
+ TableCommission,
8
+ TableMonitorEvent,
9
+ TableOutput,
10
+ TableOutputBasket,
11
+ TableOutputTag,
12
+ TableOutputTagMap,
13
+ TableProvenTx,
14
+ TableProvenTxReq,
15
+ TableSettings,
16
+ TableSyncState,
17
+ TableTransaction,
18
+ TableTxLabel,
19
+ TableTxLabelMap,
20
+ TableUser
21
+ } from './schema/tables'
22
+ import { verifyOne, verifyOneOrNone } from '../utility/utilityHelpers'
23
+ import { StorageAdminStats, StorageProvider, StorageProviderOptions } from './StorageProvider'
24
+ import { StorageIdbSchema } from './schema/StorageIdbSchema'
25
+ import { DBType } from './StorageReader'
26
+ import { listActionsIdb } from './methods/listActionsIdb'
27
+ import { listOutputsIdb } from './methods/listOutputsIdb'
28
+ import { reviewStatusIdb } from './methods/reviewStatusIdb'
29
+ import { purgeDataIdb } from './methods/purgeDataIdb'
30
+ import {
31
+ AuthId,
32
+ FindCertificateFieldsArgs,
33
+ FindCertificatesArgs,
34
+ FindCommissionsArgs,
35
+ FindForUserSincePagedArgs,
36
+ FindMonitorEventsArgs,
37
+ FindOutputBasketsArgs,
38
+ FindOutputsArgs,
39
+ FindOutputTagMapsArgs,
40
+ FindOutputTagsArgs,
41
+ FindProvenTxReqsArgs,
42
+ FindProvenTxsArgs,
43
+ FindSyncStatesArgs,
44
+ FindTransactionsArgs,
45
+ FindTxLabelMapsArgs,
46
+ FindTxLabelsArgs,
47
+ FindUsersArgs,
48
+ ProvenOrRawTx,
49
+ PurgeParams,
50
+ PurgeResults,
51
+ TrxToken,
52
+ WalletStorageProvider
53
+ } from '../sdk/WalletStorage.interfaces'
54
+ import { WERR_INTERNAL, WERR_INVALID_OPERATION, WERR_INVALID_PARAMETER, WERR_UNAUTHORIZED } from '../sdk/WERR_errors'
55
+ import { EntityTimeStamp, TransactionStatus } from '../sdk/types'
56
+
57
+ export interface StorageIdbOptions extends StorageProviderOptions {}
58
+
59
+ /**
60
+ * This class implements the `StorageProvider` interface using IndexedDB,
61
+ * via the promises wrapper package `idb`.
62
+ */
63
+ export class StorageIdb extends StorageProvider implements WalletStorageProvider {
64
+ dbName: string
65
+ db?: IDBPDatabase<StorageIdbSchema>
66
+
67
+ constructor(options: StorageIdbOptions) {
68
+ super(options)
69
+ this.dbName = `wallet-toolbox-${this.chain}net`
70
+ }
71
+
72
+ /**
73
+ * This method must be called at least once before any other method accesses the database,
74
+ * and each time the schema may have updated.
75
+ *
76
+ * If the database has already been created in this context, `storageName` and `storageIdentityKey`
77
+ * are ignored.
78
+ *
79
+ * @param storageName
80
+ * @param storageIdentityKey
81
+ * @returns
82
+ */
83
+ async migrate(storageName: string, storageIdentityKey: string): Promise<string> {
84
+ const db = await this.verifyDB(storageName, storageIdentityKey)
85
+ return db.version.toString()
86
+ }
87
+
88
+ /**
89
+ * Following initial database initialization, this method verfies that db is ready for use.
90
+ *
91
+ * @throws `WERR_INVALID_OPERATION` if the database has not been initialized by a call to `migrate`.
92
+ *
93
+ * @param storageName
94
+ * @param storageIdentityKey
95
+ *
96
+ * @returns
97
+ */
98
+ async verifyDB(storageName?: string, storageIdentityKey?: string): Promise<IDBPDatabase<StorageIdbSchema>> {
99
+ if (this.db) return this.db
100
+ this.db = await this.initDB(storageName, storageIdentityKey)
101
+ this._settings = (await this.db.getAll('settings'))[0]
102
+ this.whenLastAccess = new Date()
103
+ return this.db
104
+ }
105
+
106
+ /**
107
+ * Convert the standard optional `TrxToken` parameter into either a direct knex database instance,
108
+ * or a Knex.Transaction as appropriate.
109
+ */
110
+ toDbTrx(
111
+ stores: string[],
112
+ mode: 'readonly' | 'readwrite',
113
+ trx?: TrxToken
114
+ ): IDBPTransaction<StorageIdbSchema, string[], 'readwrite' | 'readonly'> {
115
+ if (trx) {
116
+ const t = trx as IDBPTransaction<StorageIdbSchema, string[], 'readwrite' | 'readonly'>
117
+ return t
118
+ } else {
119
+ if (!this.db) throw new Error('not initialized')
120
+ const db = this.db
121
+ const trx = db.transaction(stores || this.allStores, mode || 'readwrite')
122
+ this.whenLastAccess = new Date()
123
+ return trx
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Called by `makeAvailable` to return storage `TableSettings`.
129
+ * Since this is the first async method that must be called by all clients,
130
+ * it is where async initialization occurs.
131
+ *
132
+ * After initialization, cached settings are returned.
133
+ *
134
+ * @param trx
135
+ */
136
+ async readSettings(trx?: TrxToken): Promise<TableSettings> {
137
+ await this.verifyDB()
138
+ return this._settings!
139
+ }
140
+
141
+ async initDB(storageName?: string, storageIdentityKey?: string): Promise<IDBPDatabase<StorageIdbSchema>> {
142
+ const chain = this.chain
143
+ const maxOutputScript = 1024
144
+ const db = await openDB<StorageIdbSchema>(this.dbName, 1, {
145
+ upgrade(db, oldVersion, newVersion, transaction) {
146
+ if (!db.objectStoreNames.contains('proven_txs')) {
147
+ // proven_txs object store
148
+ const provenTxsStore = db.createObjectStore('proven_txs', {
149
+ keyPath: 'provenTxId',
150
+ autoIncrement: true
151
+ })
152
+ provenTxsStore.createIndex('txid', 'txid', { unique: true })
153
+ }
154
+
155
+ if (!db.objectStoreNames.contains('proven_tx_reqs')) {
156
+ // proven_tx_reqs object store
157
+ const provenTxReqsStore = db.createObjectStore('proven_tx_reqs', {
158
+ keyPath: 'provenTxReqId',
159
+ autoIncrement: true
160
+ })
161
+ provenTxReqsStore.createIndex('provenTxId', 'provenTxId')
162
+ provenTxReqsStore.createIndex('txid', 'txid', { unique: true })
163
+ provenTxReqsStore.createIndex('status', 'status')
164
+ provenTxReqsStore.createIndex('batch', 'batch')
165
+ }
166
+ if (!db.objectStoreNames.contains('users')) {
167
+ const users = db.createObjectStore('users', {
168
+ keyPath: 'userId',
169
+ autoIncrement: true
170
+ })
171
+ users.createIndex('identityKey', 'identityKey', { unique: true })
172
+ }
173
+ if (!db.objectStoreNames.contains('certificates')) {
174
+ // certificates object store
175
+ const certificatesStore = db.createObjectStore('certificates', {
176
+ keyPath: 'certificateId',
177
+ autoIncrement: true
178
+ })
179
+ certificatesStore.createIndex('userId', 'userId')
180
+ certificatesStore.createIndex(
181
+ 'userId_type_certifier_serialNumber',
182
+ ['userId', 'type', 'certifier', 'serialNumber'],
183
+ { unique: true }
184
+ )
185
+ }
186
+
187
+ if (!db.objectStoreNames.contains('certificate_fields')) {
188
+ // certificate_fields object store
189
+ const certificateFieldsStore = db.createObjectStore('certificate_fields', {
190
+ keyPath: ['certificateId', 'fieldName'] // Composite key
191
+ })
192
+ certificateFieldsStore.createIndex('userId', 'userId')
193
+ certificateFieldsStore.createIndex('certificateId', 'certificateId')
194
+ }
195
+
196
+ if (!db.objectStoreNames.contains('output_baskets')) {
197
+ // output_baskets object store
198
+ const outputBasketsStore = db.createObjectStore('output_baskets', {
199
+ keyPath: 'basketId',
200
+ autoIncrement: true
201
+ })
202
+ outputBasketsStore.createIndex('userId', 'userId')
203
+ outputBasketsStore.createIndex('name_userId', ['name', 'userId'], { unique: true })
204
+ }
205
+
206
+ if (!db.objectStoreNames.contains('transactions')) {
207
+ // transactions object store
208
+ const transactionsStore = db.createObjectStore('transactions', {
209
+ keyPath: 'transactionId',
210
+ autoIncrement: true
211
+ })
212
+ transactionsStore.createIndex('userId', 'userId')
213
+ ;(transactionsStore.createIndex('status', 'status'),
214
+ transactionsStore.createIndex('status_userId', ['status', 'userId']))
215
+ transactionsStore.createIndex('provenTxId', 'provenTxId')
216
+ transactionsStore.createIndex('reference', 'reference', { unique: true })
217
+ }
218
+
219
+ if (!db.objectStoreNames.contains('commissions')) {
220
+ // commissions object store
221
+ const commissionsStore = db.createObjectStore('commissions', {
222
+ keyPath: 'commissionId',
223
+ autoIncrement: true
224
+ })
225
+ commissionsStore.createIndex('userId', 'userId')
226
+ commissionsStore.createIndex('transactionId', 'transactionId', { unique: true })
227
+ }
228
+
229
+ if (!db.objectStoreNames.contains('outputs')) {
230
+ // outputs object store
231
+ const outputsStore = db.createObjectStore('outputs', {
232
+ keyPath: 'outputId',
233
+ autoIncrement: true
234
+ })
235
+ outputsStore.createIndex('userId', 'userId')
236
+ outputsStore.createIndex('transactionId', 'transactionId')
237
+ outputsStore.createIndex('basketId', 'basketId')
238
+ outputsStore.createIndex('spentBy', 'spentBy')
239
+ outputsStore.createIndex('transactionId_vout_userId', ['transactionId', 'vout', 'userId'], { unique: true })
240
+ }
241
+
242
+ if (!db.objectStoreNames.contains('output_tags')) {
243
+ // output_tags object store
244
+ const outputTagsStore = db.createObjectStore('output_tags', {
245
+ keyPath: 'outputTagId',
246
+ autoIncrement: true
247
+ })
248
+ outputTagsStore.createIndex('userId', 'userId')
249
+ outputTagsStore.createIndex('tag_userId', ['tag', 'userId'], { unique: true })
250
+ }
251
+
252
+ if (!db.objectStoreNames.contains('output_tags_map')) {
253
+ // output_tags_map object store
254
+ const outputTagsMapStore = db.createObjectStore('output_tags_map', {
255
+ keyPath: ['outputTagId', 'outputId']
256
+ })
257
+ outputTagsMapStore.createIndex('outputTagId', 'outputTagId')
258
+ outputTagsMapStore.createIndex('outputId', 'outputId')
259
+ }
260
+
261
+ if (!db.objectStoreNames.contains('tx_labels')) {
262
+ // tx_labels object store
263
+ const txLabelsStore = db.createObjectStore('tx_labels', {
264
+ keyPath: 'txLabelId',
265
+ autoIncrement: true
266
+ })
267
+ txLabelsStore.createIndex('userId', 'userId')
268
+ txLabelsStore.createIndex('label_userId', ['label', 'userId'], { unique: true })
269
+ }
270
+
271
+ if (!db.objectStoreNames.contains('tx_labels_map')) {
272
+ // tx_labels_map object store
273
+ const txLabelsMapStore = db.createObjectStore('tx_labels_map', {
274
+ keyPath: ['txLabelId', 'transactionId']
275
+ })
276
+ txLabelsMapStore.createIndex('txLabelId', 'txLabelId')
277
+ txLabelsMapStore.createIndex('transactionId', 'transactionId')
278
+ }
279
+
280
+ if (!db.objectStoreNames.contains('monitor_events')) {
281
+ // monitor_events object store
282
+ const monitorEventsStore = db.createObjectStore('monitor_events', {
283
+ keyPath: 'id',
284
+ autoIncrement: true
285
+ })
286
+ }
287
+
288
+ if (!db.objectStoreNames.contains('sync_states')) {
289
+ // sync_states object store
290
+ const syncStatesStore = db.createObjectStore('sync_states', {
291
+ keyPath: 'syncStateId',
292
+ autoIncrement: true
293
+ })
294
+ syncStatesStore.createIndex('userId', 'userId')
295
+ syncStatesStore.createIndex('refNum', 'refNum', { unique: true })
296
+ syncStatesStore.createIndex('status', 'status')
297
+ }
298
+
299
+ if (!db.objectStoreNames.contains('settings')) {
300
+ if (!storageName || !storageIdentityKey) {
301
+ throw new WERR_INVALID_OPERATION('migrate must be called before first access')
302
+ }
303
+ const settings = db.createObjectStore('settings', {
304
+ keyPath: 'storageIdentityKey'
305
+ })
306
+ const s: TableSettings = {
307
+ created_at: new Date(),
308
+ updated_at: new Date(),
309
+ storageIdentityKey,
310
+ storageName,
311
+ chain,
312
+ dbtype: 'IndexedDB',
313
+ maxOutputScript
314
+ }
315
+ settings.put(s)
316
+ }
317
+ }
318
+ })
319
+ return db
320
+ }
321
+
322
+ //
323
+ // StorageProvider abstract methods
324
+ //
325
+
326
+ async reviewStatus(args: { agedLimit: Date; trx?: TrxToken }): Promise<{ log: string }> {
327
+ return await reviewStatusIdb(this, args)
328
+ }
329
+
330
+ async purgeData(params: PurgeParams, trx?: TrxToken): Promise<PurgeResults> {
331
+ return await purgeDataIdb(this, params, trx)
332
+ }
333
+
334
+ /**
335
+ * Proceeds in three stages:
336
+ * 1. Find an output that exactly funds the transaction (if exactSatoshis is not undefined).
337
+ * 2. Find an output that overfunds by the least amount (targetSatoshis).
338
+ * 3. Find an output that comes as close to funding as possible (targetSatoshis).
339
+ * 4. Return undefined if no output is found.
340
+ *
341
+ * Outputs must belong to userId and basketId and have spendable true.
342
+ * Their corresponding transaction must have status of 'completed', 'unproven', or 'sending' (if excludeSending is false).
343
+ *
344
+ * @param userId
345
+ * @param basketId
346
+ * @param targetSatoshis
347
+ * @param exactSatoshis
348
+ * @param excludeSending
349
+ * @param transactionId
350
+ * @returns next funding output to add to transaction or undefined if there are none.
351
+ */
352
+ async allocateChangeInput(
353
+ userId: number,
354
+ basketId: number,
355
+ targetSatoshis: number,
356
+ exactSatoshis: number | undefined,
357
+ excludeSending: boolean,
358
+ transactionId: number
359
+ ): Promise<TableOutput | undefined> {
360
+ const dbTrx = this.toDbTrx(['outputs', 'transactions'], 'readwrite')
361
+ try {
362
+ const txStatus: TransactionStatus[] = ['completed', 'unproven']
363
+ if (!excludeSending) txStatus.push('sending')
364
+ const args: FindOutputsArgs = {
365
+ partial: { userId, basketId, spendable: true },
366
+ txStatus,
367
+ trx: dbTrx
368
+ }
369
+ const outputs = await this.findOutputs(args)
370
+ let output: TableOutput | undefined
371
+ let scores: { output: TableOutput; score: number }[] = []
372
+ for (const o of outputs) {
373
+ if (exactSatoshis && o.satoshis === exactSatoshis) {
374
+ output = o
375
+ break
376
+ }
377
+ const score = o.satoshis - targetSatoshis
378
+ scores.push({ output: o, score })
379
+ }
380
+ if (!output) {
381
+ // sort scores increasing by score property
382
+ scores = scores.sort((a, b) => a.score - b.score)
383
+ // find the first score that is greater than or equal to 0
384
+ const o = scores.find(s => s.score >= 0)
385
+ if (o) {
386
+ // stage 2 satisfied (minimally funded)
387
+ output = o.output
388
+ } else if (scores.length > 0) {
389
+ // stage 3 satisfied (minimally under-funded)
390
+ output = scores.slice(-1)[0].output
391
+ } else {
392
+ // no available funding outputs
393
+ output = undefined
394
+ }
395
+ }
396
+ if (output) {
397
+ // mark output as spent by transactionId
398
+ await this.updateOutput(output.outputId, { spendable: false, spentBy: transactionId }, dbTrx)
399
+ }
400
+ return output
401
+ } finally {
402
+ await dbTrx.done
403
+ }
404
+ }
405
+
406
+ async getProvenOrRawTx(txid: string, trx?: TrxToken): Promise<ProvenOrRawTx> {
407
+ const r: ProvenOrRawTx = {
408
+ proven: undefined,
409
+ rawTx: undefined,
410
+ inputBEEF: undefined
411
+ }
412
+
413
+ r.proven = verifyOneOrNone(await this.findProvenTxs({ partial: { txid: txid }, trx }))
414
+ if (!r.proven) {
415
+ const req = verifyOneOrNone(await this.findProvenTxReqs({ partial: { txid: txid }, trx }))
416
+ if (req && ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'].includes(req.status)) {
417
+ r.rawTx = req.rawTx
418
+ r.inputBEEF = req.inputBEEF
419
+ }
420
+ }
421
+
422
+ return r
423
+ }
424
+
425
+ async getRawTxOfKnownValidTransaction(
426
+ txid?: string,
427
+ offset?: number,
428
+ length?: number,
429
+ trx?: TrxToken
430
+ ): Promise<number[] | undefined> {
431
+ if (!txid) return undefined
432
+ if (!this.isAvailable()) await this.makeAvailable()
433
+
434
+ let rawTx: number[] | undefined = undefined
435
+ const r = await this.getProvenOrRawTx(txid, trx)
436
+ if (r.proven) rawTx = r.proven.rawTx
437
+ else rawTx = r.rawTx
438
+ if (rawTx && offset !== undefined && length !== undefined && Number.isInteger(offset) && Number.isInteger(length)) {
439
+ rawTx = rawTx.slice(offset, offset + length)
440
+ }
441
+ return rawTx
442
+ }
443
+
444
+ async getLabelsForTransactionId(transactionId?: number, trx?: TrxToken): Promise<TableTxLabel[]> {
445
+ const maps = await this.findTxLabelMaps({ partial: { transactionId, isDeleted: false }, trx })
446
+ const labelIds = maps.map(m => m.txLabelId)
447
+ const labels: TableTxLabel[] = []
448
+ for (const txLabelId of labelIds) {
449
+ const label = verifyOne(await this.findTxLabels({ partial: { txLabelId, isDeleted: false }, trx }))
450
+ labels.push(label)
451
+ }
452
+ return labels
453
+ }
454
+
455
+ async getTagsForOutputId(outputId: number, trx?: TrxToken): Promise<TableOutputTag[]> {
456
+ const maps = await this.findOutputTagMaps({ partial: { outputId, isDeleted: false }, trx })
457
+ const tagIds = maps.map(m => m.outputTagId)
458
+ const tags: TableOutputTag[] = []
459
+ for (const outputTagId of tagIds) {
460
+ const tag = verifyOne(await this.findOutputTags({ partial: { outputTagId, isDeleted: false }, trx }))
461
+ tags.push(tag)
462
+ }
463
+ return tags
464
+ }
465
+
466
+ async listActions(auth: AuthId, vargs: Validation.ValidListActionsArgs): Promise<ListActionsResult> {
467
+ if (!auth.userId) throw new WERR_UNAUTHORIZED()
468
+ return await listActionsIdb(this, auth, vargs)
469
+ }
470
+
471
+ async listOutputs(auth: AuthId, vargs: Validation.ValidListOutputsArgs): Promise<ListOutputsResult> {
472
+ if (!auth.userId) throw new WERR_UNAUTHORIZED()
473
+ return await listOutputsIdb(this, auth, vargs)
474
+ }
475
+
476
+ async countChangeInputs(userId: number, basketId: number, excludeSending: boolean): Promise<number> {
477
+ const txStatus: TransactionStatus[] = ['completed', 'unproven']
478
+ if (!excludeSending) txStatus.push('sending')
479
+ const args: FindOutputsArgs = { partial: { userId, basketId }, txStatus }
480
+ let count = 0
481
+ await this.filterOutputs(args, r => {
482
+ count++
483
+ })
484
+ return count
485
+ }
486
+
487
+ async findCertificatesAuth(auth: AuthId, args: FindCertificatesArgs): Promise<TableCertificateX[]> {
488
+ if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId)) throw new WERR_UNAUTHORIZED()
489
+ args.partial.userId = auth.userId
490
+ return await this.findCertificates(args)
491
+ }
492
+ async findOutputBasketsAuth(auth: AuthId, args: FindOutputBasketsArgs): Promise<TableOutputBasket[]> {
493
+ if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId)) throw new WERR_UNAUTHORIZED()
494
+ args.partial.userId = auth.userId
495
+ return await this.findOutputBaskets(args)
496
+ }
497
+ async findOutputsAuth(auth: AuthId, args: FindOutputsArgs): Promise<TableOutput[]> {
498
+ if (!auth.userId || (args.partial.userId && args.partial.userId !== auth.userId)) throw new WERR_UNAUTHORIZED()
499
+ args.partial.userId = auth.userId
500
+ return await this.findOutputs(args)
501
+ }
502
+
503
+ async insertCertificateAuth(auth: AuthId, certificate: TableCertificateX): Promise<number> {
504
+ if (!auth.userId || (certificate.userId && certificate.userId !== auth.userId)) throw new WERR_UNAUTHORIZED()
505
+ certificate.userId = auth.userId
506
+ return await this.insertCertificate(certificate)
507
+ }
508
+
509
+ //
510
+ // StorageReaderWriter abstract methods
511
+ //
512
+
513
+ async dropAllData(): Promise<void> {
514
+ await deleteDB(this.dbName)
515
+ }
516
+
517
+ async filterOutputTagMaps(
518
+ args: FindOutputTagMapsArgs,
519
+ filtered: (v: TableOutputTagMap) => void,
520
+ userId?: number
521
+ ): Promise<void> {
522
+ const offset = args.paged?.offset || 0
523
+ let skipped = 0
524
+ let count = 0
525
+ const dbTrx = this.toDbTrx(['output_tags_map'], 'readonly', args.trx)
526
+ let cursor:
527
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags_map', unknown, 'readwrite' | 'readonly'>
528
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags_map', 'outputTagId', 'readwrite' | 'readonly'>
529
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags_map', 'outputId', 'readwrite' | 'readonly'>
530
+ | null
531
+ if (args.partial?.outputTagId !== undefined) {
532
+ cursor = await dbTrx.objectStore('output_tags_map').index('outputTagId').openCursor(args.partial.outputTagId)
533
+ } else if (args.partial?.outputId !== undefined) {
534
+ cursor = await dbTrx.objectStore('output_tags_map').index('outputId').openCursor(args.partial.outputId)
535
+ } else {
536
+ cursor = await dbTrx.objectStore('output_tags_map').openCursor()
537
+ }
538
+ let firstTime = true
539
+ while (cursor) {
540
+ if (!firstTime) cursor = await cursor.continue()
541
+ if (!cursor) break
542
+ firstTime = false
543
+ const r = cursor.value
544
+ if (args.since && args.since > r.updated_at) continue
545
+ if (args.tagIds && !args.tagIds.includes(r.outputTagId)) continue
546
+ if (args.partial) {
547
+ if (args.partial.outputTagId && r.outputTagId !== args.partial.outputTagId) continue
548
+ if (args.partial.outputId && r.outputId !== args.partial.outputId) continue
549
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
550
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
551
+ if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted) continue
552
+ }
553
+ if (userId !== undefined && r.txid) {
554
+ const count = await this.countOutputTags({ partial: { userId, outputTagId: r.outputTagId }, trx: args.trx })
555
+ if (count === 0) continue
556
+ }
557
+ if (skipped < offset) {
558
+ skipped++
559
+ continue
560
+ }
561
+ filtered(r)
562
+ count++
563
+ if (args.paged?.limit && count >= args.paged.limit) break
564
+ }
565
+ if (!args.trx) await dbTrx.done
566
+ }
567
+
568
+ async findOutputTagMaps(args: FindOutputTagMapsArgs): Promise<TableOutputTagMap[]> {
569
+ const results: TableOutputTagMap[] = []
570
+ await this.filterOutputTagMaps(args, r => {
571
+ results.push(this.validateEntity(r))
572
+ })
573
+ return results
574
+ }
575
+
576
+ async filterProvenTxReqs(
577
+ args: FindProvenTxReqsArgs,
578
+ filtered: (v: TableProvenTxReq) => void,
579
+ userId?: number
580
+ ): Promise<void> {
581
+ if (args.partial.rawTx)
582
+ throw new WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxReqs may not be found by rawTx value.`)
583
+ if (args.partial.inputBEEF)
584
+ throw new WERR_INVALID_PARAMETER(
585
+ 'args.partial.inputBEEF',
586
+ `undefined. ProvenTxReqs may not be found by inputBEEF value.`
587
+ )
588
+ const offset = args.paged?.offset || 0
589
+ let skipped = 0
590
+ let count = 0
591
+ const dbTrx = this.toDbTrx(['proven_tx_reqs'], 'readonly', args.trx)
592
+ let cursor:
593
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_tx_reqs', unknown, 'readwrite' | 'readonly'>
594
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_tx_reqs', 'provenTxId', 'readwrite' | 'readonly'>
595
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_tx_reqs', 'txid', 'readwrite' | 'readonly'>
596
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_tx_reqs', 'status', 'readwrite' | 'readonly'>
597
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_tx_reqs', 'batch', 'readwrite' | 'readonly'>
598
+ | null
599
+ if (args.partial?.provenTxReqId) {
600
+ cursor = await dbTrx.objectStore('proven_tx_reqs').openCursor(args.partial.provenTxReqId)
601
+ } else if (args.partial?.provenTxId !== undefined) {
602
+ cursor = await dbTrx.objectStore('proven_tx_reqs').index('provenTxId').openCursor(args.partial.provenTxId)
603
+ } else if (args.partial?.txid !== undefined) {
604
+ cursor = await dbTrx.objectStore('proven_tx_reqs').index('txid').openCursor(args.partial.txid)
605
+ } else if (args.partial?.status !== undefined) {
606
+ cursor = await dbTrx.objectStore('proven_tx_reqs').index('status').openCursor(args.partial.status)
607
+ } else if (args.partial?.batch !== undefined) {
608
+ cursor = await dbTrx.objectStore('proven_tx_reqs').index('batch').openCursor(args.partial.batch)
609
+ } else {
610
+ cursor = await dbTrx.objectStore('proven_tx_reqs').openCursor()
611
+ }
612
+ let firstTime = true
613
+ while (cursor) {
614
+ if (!firstTime) cursor = await cursor.continue()
615
+ if (!cursor) break
616
+ firstTime = false
617
+ const r = cursor.value
618
+ if (args.since && args.since > r.updated_at) continue
619
+ if (args.partial) {
620
+ if (args.partial.provenTxReqId && r.provenTxReqId !== args.partial.provenTxReqId) continue
621
+ if (args.partial.provenTxId && r.provenTxId !== args.partial.provenTxId) continue
622
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
623
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
624
+ if (args.partial.status && r.status !== args.partial.status) continue
625
+ if (args.partial.attempts !== undefined && r.attempts !== args.partial.attempts) continue
626
+ if (args.partial.notified !== undefined && r.notified !== args.partial.notified) continue
627
+ if (args.partial.txid && r.txid !== args.partial.txid) continue
628
+ if (args.partial.batch && r.batch !== args.partial.batch) continue
629
+ if (args.partial.history && r.history !== args.partial.history) continue
630
+ if (args.partial.notify && r.notify !== args.partial.notify) continue
631
+ }
632
+ if (userId !== undefined && r.txid) {
633
+ const count = await this.countTransactions({ partial: { userId, txid: r.txid }, trx: args.trx })
634
+ if (count === 0) continue
635
+ }
636
+ if (skipped < offset) {
637
+ skipped++
638
+ continue
639
+ }
640
+ filtered(r)
641
+ count++
642
+ if (args.paged?.limit && count >= args.paged.limit) break
643
+ }
644
+ if (!args.trx) await dbTrx.done
645
+ }
646
+
647
+ async findProvenTxReqs(args: FindProvenTxReqsArgs): Promise<TableProvenTxReq[]> {
648
+ const results: TableProvenTxReq[] = []
649
+ await this.filterProvenTxReqs(args, r => {
650
+ results.push(this.validateEntity(r))
651
+ })
652
+ return results
653
+ }
654
+
655
+ async filterProvenTxs(args: FindProvenTxsArgs, filtered: (v: TableProvenTx) => void, userId?: number): Promise<void> {
656
+ if (args.partial.rawTx)
657
+ throw new WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. ProvenTxs may not be found by rawTx value.`)
658
+ if (args.partial.merklePath)
659
+ throw new WERR_INVALID_PARAMETER(
660
+ 'args.partial.merklePath',
661
+ `undefined. ProvenTxs may not be found by merklePath value.`
662
+ )
663
+ const offset = args.paged?.offset || 0
664
+ let skipped = 0
665
+ let count = 0
666
+ const dbTrx = this.toDbTrx(['proven_txs'], 'readonly', args.trx)
667
+ let cursor:
668
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_txs', unknown, 'readwrite' | 'readonly'>
669
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'proven_txs', 'txid', 'readwrite' | 'readonly'>
670
+ | null
671
+ if (args.partial?.provenTxId) {
672
+ cursor = await dbTrx.objectStore('proven_txs').openCursor(args.partial.provenTxId)
673
+ } else if (args.partial?.txid !== undefined) {
674
+ cursor = await dbTrx.objectStore('proven_txs').index('txid').openCursor(args.partial.txid)
675
+ } else {
676
+ cursor = await dbTrx.objectStore('proven_txs').openCursor()
677
+ }
678
+ let firstTime = true
679
+ while (cursor) {
680
+ if (!firstTime) cursor = await cursor.continue()
681
+ if (!cursor) break
682
+ firstTime = false
683
+ const r = cursor.value
684
+ if (args.since && args.since > r.updated_at) continue
685
+ if (args.partial) {
686
+ if (args.partial.provenTxId && r.provenTxId !== args.partial.provenTxId) continue
687
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
688
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
689
+ if (args.partial.txid && r.txid !== args.partial.txid) continue
690
+ if (args.partial.height !== undefined && r.height !== args.partial.height) continue
691
+ if (args.partial.index !== undefined && r.index !== args.partial.index) continue
692
+ if (args.partial.blockHash && r.blockHash !== args.partial.blockHash) continue
693
+ if (args.partial.merkleRoot && r.merkleRoot !== args.partial.merkleRoot) continue
694
+ }
695
+ if (userId !== undefined) {
696
+ const count = await this.countTransactions({ partial: { userId, provenTxId: r.provenTxId }, trx: args.trx })
697
+ if (count === 0) continue
698
+ }
699
+ if (skipped < offset) {
700
+ skipped++
701
+ continue
702
+ }
703
+ filtered(r)
704
+ count++
705
+ if (args.paged?.limit && count >= args.paged.limit) break
706
+ }
707
+ if (!args.trx) await dbTrx.done
708
+ }
709
+
710
+ async findProvenTxs(args: FindProvenTxsArgs): Promise<TableProvenTx[]> {
711
+ const results: TableProvenTx[] = []
712
+ await this.filterProvenTxs(args, r => {
713
+ results.push(this.validateEntity(r))
714
+ })
715
+ return results
716
+ }
717
+
718
+ async filterTxLabelMaps(
719
+ args: FindTxLabelMapsArgs,
720
+ filtered: (v: TableTxLabelMap) => void,
721
+ userId?: number
722
+ ): Promise<void> {
723
+ const offset = args.paged?.offset || 0
724
+ let skipped = 0
725
+ let count = 0
726
+ const dbTrx = this.toDbTrx(['tx_labels_map'], 'readonly', args.trx)
727
+ let cursor:
728
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels_map', unknown, 'readwrite' | 'readonly'>
729
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels_map', 'transactionId', 'readwrite' | 'readonly'>
730
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels_map', 'txLabelId', 'readwrite' | 'readonly'>
731
+ | null
732
+ if (args.partial?.transactionId !== undefined) {
733
+ cursor = await dbTrx.objectStore('tx_labels_map').index('transactionId').openCursor(args.partial.transactionId)
734
+ } else if (args.partial?.txLabelId !== undefined) {
735
+ cursor = await dbTrx.objectStore('tx_labels_map').index('txLabelId').openCursor(args.partial.txLabelId)
736
+ } else {
737
+ cursor = await dbTrx.objectStore('tx_labels_map').openCursor()
738
+ }
739
+ let firstTime = true
740
+ while (cursor) {
741
+ if (!firstTime) cursor = await cursor.continue()
742
+ if (!cursor) break
743
+ firstTime = false
744
+ const r = cursor.value
745
+ if (args.since && args.since > r.updated_at) continue
746
+ if (args.partial) {
747
+ if (args.partial.txLabelId && r.txLabelId !== args.partial.txLabelId) continue
748
+ if (args.partial.transactionId && r.transactionId !== args.partial.transactionId) continue
749
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
750
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
751
+ if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted) continue
752
+ }
753
+ if (userId !== undefined) {
754
+ const count = await this.countTxLabels({ partial: { userId, txLabelId: r.txLabelId }, trx: args.trx })
755
+ if (count === 0) continue
756
+ }
757
+ if (skipped < offset) {
758
+ skipped++
759
+ continue
760
+ }
761
+ filtered(r)
762
+ count++
763
+ if (args.paged?.limit && count >= args.paged.limit) break
764
+ }
765
+ if (!args.trx) await dbTrx.done
766
+ }
767
+
768
+ async findTxLabelMaps(args: FindTxLabelMapsArgs): Promise<TableTxLabelMap[]> {
769
+ const results: TableTxLabelMap[] = []
770
+ await this.filterTxLabelMaps(args, r => {
771
+ results.push(this.validateEntity(r))
772
+ })
773
+ return results
774
+ }
775
+
776
+ async countOutputTagMaps(args: FindOutputTagMapsArgs): Promise<number> {
777
+ let count = 0
778
+ await this.filterOutputTagMaps(args, () => {
779
+ count++
780
+ })
781
+ return count
782
+ }
783
+ async countProvenTxReqs(args: FindProvenTxReqsArgs): Promise<number> {
784
+ let count = 0
785
+ await this.filterProvenTxReqs(args, () => {
786
+ count++
787
+ })
788
+ return count
789
+ }
790
+ async countProvenTxs(args: FindProvenTxsArgs): Promise<number> {
791
+ let count = 0
792
+ await this.filterProvenTxs(args, () => {
793
+ count++
794
+ })
795
+ return count
796
+ }
797
+ async countTxLabelMaps(args: FindTxLabelMapsArgs): Promise<number> {
798
+ let count = 0
799
+ await this.filterTxLabelMaps(args, () => {
800
+ count++
801
+ })
802
+ return count
803
+ }
804
+
805
+ async insertCertificate(certificate: TableCertificateX, trx?: TrxToken): Promise<number> {
806
+ const e = await this.validateEntityForInsert(certificate, trx, undefined, ['isDeleted'])
807
+ const fields = e.fields
808
+ if (e.fields) delete e.fields
809
+ if (e.certificateId === 0) delete e.certificateId
810
+
811
+ const dbTrx = this.toDbTrx(['certificates', 'certificate_fields'], 'readwrite', trx)
812
+ const store = dbTrx.objectStore('certificates')
813
+ try {
814
+ const id = Number(await store.add!(e))
815
+ certificate.certificateId = id
816
+
817
+ if (fields) {
818
+ for (const field of fields) {
819
+ field.certificateId = certificate.certificateId
820
+ field.userId = certificate.userId
821
+ await this.insertCertificateField(field, dbTrx)
822
+ }
823
+ }
824
+ } finally {
825
+ if (!trx) await dbTrx.done
826
+ }
827
+ return certificate.certificateId
828
+ }
829
+
830
+ async insertCertificateField(certificateField: TableCertificateField, trx?: TrxToken): Promise<void> {
831
+ const e = await this.validateEntityForInsert(certificateField, trx)
832
+ const dbTrx = this.toDbTrx(['certificate_fields'], 'readwrite', trx)
833
+ const store = dbTrx.objectStore('certificate_fields')
834
+ try {
835
+ await store.add!(e)
836
+ } finally {
837
+ if (!trx) await dbTrx.done
838
+ }
839
+ }
840
+
841
+ async insertCommission(commission: TableCommission, trx?: TrxToken): Promise<number> {
842
+ const e = await this.validateEntityForInsert(commission, trx)
843
+ if (e.commissionId === 0) delete e.commissionId
844
+ const dbTrx = this.toDbTrx(['commissions'], 'readwrite', trx)
845
+ const store = dbTrx.objectStore('commissions')
846
+ try {
847
+ const id = Number(await store.add!(e))
848
+ commission.commissionId = id
849
+ } finally {
850
+ if (!trx) await dbTrx.done
851
+ }
852
+ return commission.commissionId
853
+ }
854
+ async insertMonitorEvent(event: TableMonitorEvent, trx?: TrxToken): Promise<number> {
855
+ const e = await this.validateEntityForInsert(event, trx)
856
+ if (e.id === 0) delete e.id
857
+ const dbTrx = this.toDbTrx(['monitor_events'], 'readwrite', trx)
858
+ const store = dbTrx.objectStore('monitor_events')
859
+ try {
860
+ const id = Number(await store.add!(e))
861
+ event.id = id
862
+ } finally {
863
+ if (!trx) await dbTrx.done
864
+ }
865
+ return event.id
866
+ }
867
+ async insertOutput(output: TableOutput, trx?: TrxToken): Promise<number> {
868
+ const e = await this.validateEntityForInsert(output, trx)
869
+ if (e.outputId === 0) delete e.outputId
870
+ const dbTrx = this.toDbTrx(['outputs'], 'readwrite', trx)
871
+ const store = dbTrx.objectStore('outputs')
872
+ try {
873
+ const id = Number(await store.add!(e))
874
+ output.outputId = id
875
+ } finally {
876
+ if (!trx) await dbTrx.done
877
+ }
878
+ return output.outputId
879
+ }
880
+ async insertOutputBasket(basket: TableOutputBasket, trx?: TrxToken): Promise<number> {
881
+ const e = await this.validateEntityForInsert(basket, trx, undefined, ['isDeleted'])
882
+ if (e.basketId === 0) delete e.basketId
883
+ const dbTrx = this.toDbTrx(['output_baskets'], 'readwrite', trx)
884
+ const store = dbTrx.objectStore('output_baskets')
885
+ try {
886
+ const id = Number(await store.add!(e))
887
+ basket.basketId = id
888
+ } finally {
889
+ if (!trx) await dbTrx.done
890
+ }
891
+ return basket.basketId
892
+ }
893
+ async insertOutputTag(tag: TableOutputTag, trx?: TrxToken): Promise<number> {
894
+ const e = await this.validateEntityForInsert(tag, trx, undefined, ['isDeleted'])
895
+ if (e.outputTagId === 0) delete e.outputTagId
896
+ const dbTrx = this.toDbTrx(['output_tags'], 'readwrite', trx)
897
+ const store = dbTrx.objectStore('output_tags')
898
+ try {
899
+ const id = Number(await store.add!(e))
900
+ tag.outputTagId = id
901
+ } finally {
902
+ if (!trx) await dbTrx.done
903
+ }
904
+ return tag.outputTagId
905
+ }
906
+ async insertOutputTagMap(tagMap: TableOutputTagMap, trx?: TrxToken): Promise<void> {
907
+ const e = await this.validateEntityForInsert(tagMap, trx, undefined, ['isDeleted'])
908
+ const dbTrx = this.toDbTrx(['output_tags_map'], 'readwrite', trx)
909
+ const store = dbTrx.objectStore('output_tags_map')
910
+ try {
911
+ await store.add!(e)
912
+ } finally {
913
+ if (!trx) await dbTrx.done
914
+ }
915
+ }
916
+ async insertProvenTx(tx: TableProvenTx, trx?: TrxToken): Promise<number> {
917
+ const e = await this.validateEntityForInsert(tx, trx)
918
+ if (e.provenTxId === 0) delete e.provenTxId
919
+ const dbTrx = this.toDbTrx(['proven_txs'], 'readwrite', trx)
920
+ const store = dbTrx.objectStore('proven_txs')
921
+ try {
922
+ const id = Number(await store.add!(e))
923
+ tx.provenTxId = id
924
+ } finally {
925
+ if (!trx) await dbTrx.done
926
+ }
927
+ return tx.provenTxId
928
+ }
929
+ async insertProvenTxReq(tx: TableProvenTxReq, trx?: TrxToken): Promise<number> {
930
+ const e = await this.validateEntityForInsert(tx, trx)
931
+ if (e.provenTxReqId === 0) delete e.provenTxReqId
932
+ const dbTrx = this.toDbTrx(['proven_tx_reqs'], 'readwrite', trx)
933
+ const store = dbTrx.objectStore('proven_tx_reqs')
934
+ try {
935
+ const id = Number(await store.add!(e))
936
+ tx.provenTxReqId = id
937
+ } finally {
938
+ if (!trx) await dbTrx.done
939
+ }
940
+ return tx.provenTxReqId
941
+ }
942
+ async insertSyncState(syncState: TableSyncState, trx?: TrxToken): Promise<number> {
943
+ const e = await this.validateEntityForInsert(syncState, trx, ['when'], ['init'])
944
+ if (e.syncStateId === 0) delete e.syncStateId
945
+ const dbTrx = this.toDbTrx(['sync_states'], 'readwrite', trx)
946
+ const store = dbTrx.objectStore('sync_states')
947
+ try {
948
+ const id = Number(await store.add!(e))
949
+ syncState.syncStateId = id
950
+ } finally {
951
+ if (!trx) await dbTrx.done
952
+ }
953
+ return syncState.syncStateId
954
+ }
955
+ async insertTransaction(tx: TableTransaction, trx?: TrxToken): Promise<number> {
956
+ const e = await this.validateEntityForInsert(tx, trx)
957
+ if (e.transactionId === 0) delete e.transactionId
958
+ const dbTrx = this.toDbTrx(['transactions'], 'readwrite', trx)
959
+ const store = dbTrx.objectStore('transactions')
960
+ try {
961
+ const id = Number(await store.add!(e))
962
+ tx.transactionId = id
963
+ } finally {
964
+ if (!trx) await dbTrx.done
965
+ }
966
+ return tx.transactionId
967
+ }
968
+ async insertTxLabel(label: TableTxLabel, trx?: TrxToken): Promise<number> {
969
+ const e = await this.validateEntityForInsert(label, trx, undefined, ['isDeleted'])
970
+ if (e.txLabelId === 0) delete e.txLabelId
971
+ const dbTrx = this.toDbTrx(['tx_labels'], 'readwrite', trx)
972
+ const store = dbTrx.objectStore('tx_labels')
973
+ try {
974
+ const id = Number(await store.add!(e))
975
+ label.txLabelId = id
976
+ } finally {
977
+ if (!trx) await dbTrx.done
978
+ }
979
+ return label.txLabelId
980
+ }
981
+ async insertTxLabelMap(labelMap: TableTxLabelMap, trx?: TrxToken): Promise<void> {
982
+ const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted'])
983
+ const dbTrx = this.toDbTrx(['tx_labels_map'], 'readwrite', trx)
984
+ const store = dbTrx.objectStore('tx_labels_map')
985
+ try {
986
+ await store.add!(e)
987
+ } finally {
988
+ if (!trx) await dbTrx.done
989
+ }
990
+ }
991
+ async insertUser(user: TableUser, trx?: TrxToken): Promise<number> {
992
+ const e = await this.validateEntityForInsert(user, trx)
993
+ if (e.userId === 0) delete e.userId
994
+ const dbTrx = this.toDbTrx(['users'], 'readwrite', trx)
995
+ const store = dbTrx.objectStore('users')
996
+ try {
997
+ const id = Number(await store.add!(e))
998
+ user.userId = id
999
+ } finally {
1000
+ if (!trx) await dbTrx.done
1001
+ }
1002
+ return user.userId
1003
+ }
1004
+
1005
+ async updateIdb<T>(
1006
+ id: number | number[],
1007
+ update: Partial<T>,
1008
+ keyProp: string,
1009
+ storeName: string,
1010
+ trx?: TrxToken
1011
+ ): Promise<number> {
1012
+ if (update[keyProp] !== undefined && (Array.isArray(id) || update[keyProp] !== id)) {
1013
+ throw new WERR_INVALID_PARAMETER(`update.${keyProp}`, `undefined`)
1014
+ }
1015
+ const u = this.validatePartialForUpdate(update)
1016
+ const dbTrx = this.toDbTrx([storeName], 'readwrite', trx)
1017
+ const store = dbTrx.objectStore(storeName)
1018
+ const ids = Array.isArray(id) ? id : [id]
1019
+ try {
1020
+ for (const i of ids) {
1021
+ const e = await store.get(i)
1022
+ if (!e) throw new WERR_INVALID_PARAMETER('id', `an existing record to update ${keyProp} ${i} not found`)
1023
+ const v: T = {
1024
+ ...e,
1025
+ ...u
1026
+ }
1027
+ const uid = await store.put!(v)
1028
+ if (uid !== i) throw new WERR_INTERNAL(`updated id ${uid} does not match original ${id}`)
1029
+ }
1030
+ } finally {
1031
+ if (!trx) await dbTrx.done
1032
+ }
1033
+ return 1
1034
+ }
1035
+
1036
+ async updateIdbKey<T>(
1037
+ key: (number | string)[],
1038
+ update: Partial<T>,
1039
+ keyProps: string[],
1040
+ storeName: string,
1041
+ trx?: TrxToken
1042
+ ): Promise<number> {
1043
+ if (key.length !== keyProps.length)
1044
+ throw new WERR_INTERNAL(`key.length ${key.length} !== keyProps.length ${keyProps.length}`)
1045
+ for (let i = 0; i < key.length; i++) {
1046
+ if (update[keyProps[i]] !== undefined && update[keyProps[i]] !== key[i]) {
1047
+ throw new WERR_INVALID_PARAMETER(`update.${keyProps[i]}`, `undefined`)
1048
+ }
1049
+ }
1050
+ const u = this.validatePartialForUpdate(update)
1051
+ const dbTrx = this.toDbTrx([storeName], 'readwrite', trx)
1052
+ const store = dbTrx.objectStore(storeName)
1053
+ try {
1054
+ const e = await store.get(key)
1055
+ if (!e)
1056
+ throw new WERR_INVALID_PARAMETER(
1057
+ 'key',
1058
+ `an existing record to update ${keyProps.join(',')} ${key.join(',')} not found`
1059
+ )
1060
+ const v: T = {
1061
+ ...e,
1062
+ ...u
1063
+ }
1064
+ const uid = await store.put!(v)
1065
+ for (let i = 0; i < key.length; i++) {
1066
+ if (uid[i] !== key[i]) throw new WERR_INTERNAL(`updated key ${uid[i]} does not match original ${key[i]}`)
1067
+ }
1068
+ } finally {
1069
+ if (!trx) await dbTrx.done
1070
+ }
1071
+
1072
+ return 1
1073
+ }
1074
+
1075
+ async updateCertificate(id: number, update: Partial<TableCertificate>, trx?: TrxToken): Promise<number> {
1076
+ return this.updateIdb(id, update, 'certificateId', 'certificates', trx)
1077
+ }
1078
+
1079
+ async updateCertificateField(
1080
+ certificateId: number,
1081
+ fieldName: string,
1082
+ update: Partial<TableCertificateField>,
1083
+ trx?: TrxToken
1084
+ ): Promise<number> {
1085
+ return this.updateIdbKey(
1086
+ [certificateId, fieldName],
1087
+ update,
1088
+ ['certificateId', 'fieldName'],
1089
+ 'certificate_fields',
1090
+ trx
1091
+ )
1092
+ }
1093
+
1094
+ async updateCommission(id: number, update: Partial<TableCommission>, trx?: TrxToken): Promise<number> {
1095
+ return this.updateIdb(id, update, 'commissionId', 'commissions', trx)
1096
+ }
1097
+ async updateMonitorEvent(id: number, update: Partial<TableMonitorEvent>, trx?: TrxToken): Promise<number> {
1098
+ return this.updateIdb(id, update, 'id', 'monitor_events', trx)
1099
+ }
1100
+ async updateOutput(id: number, update: Partial<TableOutput>, trx?: TrxToken): Promise<number> {
1101
+ return this.updateIdb(id, update, 'outputId', 'outputs', trx)
1102
+ }
1103
+ async updateOutputBasket(id: number, update: Partial<TableOutputBasket>, trx?: TrxToken): Promise<number> {
1104
+ return this.updateIdb(id, update, 'basketId', 'output_baskets', trx)
1105
+ }
1106
+ async updateOutputTag(id: number, update: Partial<TableOutputTag>, trx?: TrxToken): Promise<number> {
1107
+ return this.updateIdb(id, update, 'outputTagId', 'output_tags', trx)
1108
+ }
1109
+ async updateProvenTx(id: number, update: Partial<TableProvenTx>, trx?: TrxToken): Promise<number> {
1110
+ return this.updateIdb(id, update, 'provenTxId', 'proven_txs', trx)
1111
+ }
1112
+ async updateProvenTxReq(id: number | number[], update: Partial<TableProvenTxReq>, trx?: TrxToken): Promise<number> {
1113
+ return this.updateIdb(id, update, 'provenTxReqId', 'proven_tx_reqs', trx)
1114
+ }
1115
+ async updateSyncState(id: number, update: Partial<TableSyncState>, trx?: TrxToken): Promise<number> {
1116
+ return this.updateIdb(id, update, 'syncStateId', 'sync_states', trx)
1117
+ }
1118
+ async updateTransaction(id: number | number[], update: Partial<TableTransaction>, trx?: TrxToken): Promise<number> {
1119
+ return this.updateIdb(id, update, 'transactionId', 'transactions', trx)
1120
+ }
1121
+ async updateTxLabel(id: number, update: Partial<TableTxLabel>, trx?: TrxToken): Promise<number> {
1122
+ return this.updateIdb(id, update, 'txLabelId', 'tx_labels', trx)
1123
+ }
1124
+ async updateUser(id: number, update: Partial<TableUser>, trx?: TrxToken): Promise<number> {
1125
+ return this.updateIdb(id, update, 'userId', 'users', trx)
1126
+ }
1127
+ async updateOutputTagMap(
1128
+ outputId: number,
1129
+ tagId: number,
1130
+ update: Partial<TableOutputTagMap>,
1131
+ trx?: TrxToken
1132
+ ): Promise<number> {
1133
+ return this.updateIdbKey([tagId, outputId], update, ['outputTagId', 'outputId'], 'output_tags_map', trx)
1134
+ }
1135
+ async updateTxLabelMap(
1136
+ transactionId: number,
1137
+ txLabelId: number,
1138
+ update: Partial<TableTxLabelMap>,
1139
+ trx?: TrxToken
1140
+ ): Promise<number> {
1141
+ return this.updateIdbKey([txLabelId, transactionId], update, ['txLabelId', 'transactionId'], 'tx_labels_map', trx)
1142
+ }
1143
+
1144
+ //
1145
+ // StorageReader abstract methods
1146
+ //
1147
+
1148
+ async destroy(): Promise<void> {
1149
+ if (this.db) {
1150
+ this.db.close()
1151
+ }
1152
+ this.db = undefined
1153
+ this._settings = undefined
1154
+ }
1155
+
1156
+ allStores: string[] = [
1157
+ 'certificates',
1158
+ 'certificate_fields',
1159
+ 'commissions',
1160
+ 'monitor_events',
1161
+ 'outputs',
1162
+ 'output_baskets',
1163
+ 'output_tags',
1164
+ 'output_tags_map',
1165
+ 'proven_txs',
1166
+ 'proven_tx_reqs',
1167
+ 'sync_states',
1168
+ 'transactions',
1169
+ 'tx_labels',
1170
+ 'tx_labels_map',
1171
+ 'users'
1172
+ ]
1173
+
1174
+ /**
1175
+ * @param scope
1176
+ * @param trx
1177
+ * @returns
1178
+ */
1179
+ async transaction<T>(scope: (trx: TrxToken) => Promise<T>, trx?: TrxToken): Promise<T> {
1180
+ if (trx) return await scope(trx)
1181
+
1182
+ const stores = this.allStores
1183
+
1184
+ const db = await this.verifyDB()
1185
+ const tx = db.transaction(stores, 'readwrite')
1186
+
1187
+ try {
1188
+ const r = await scope(tx as TrxToken)
1189
+ await tx.done
1190
+ return r
1191
+ } catch (err) {
1192
+ tx.abort()
1193
+ await tx.done
1194
+ throw err
1195
+ }
1196
+ }
1197
+
1198
+ async filterCertificateFields(
1199
+ args: FindCertificateFieldsArgs,
1200
+ filtered: (v: TableCertificateField) => void
1201
+ ): Promise<void> {
1202
+ const offset = args.paged?.offset || 0
1203
+ let skipped = 0
1204
+ let count = 0
1205
+ const dbTrx = this.toDbTrx(['certificate_fields'], 'readonly', args.trx)
1206
+ let cursor:
1207
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'certificate_fields', unknown, 'readwrite' | 'readonly'>
1208
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'certificate_fields', 'userId', 'readwrite' | 'readonly'>
1209
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'certificate_fields', 'certificateId', 'readwrite' | 'readonly'>
1210
+ | null
1211
+ if (args.partial?.certificateId !== undefined) {
1212
+ cursor = await dbTrx
1213
+ .objectStore('certificate_fields')
1214
+ .index('certificateId')
1215
+ .openCursor(args.partial.certificateId)
1216
+ } else if (args.partial?.userId !== undefined) {
1217
+ cursor = await dbTrx.objectStore('certificate_fields').index('userId').openCursor(args.partial.userId)
1218
+ } else {
1219
+ cursor = await dbTrx.objectStore('certificate_fields').openCursor()
1220
+ }
1221
+ let firstTime = true
1222
+ while (cursor) {
1223
+ if (!firstTime) cursor = await cursor.continue()
1224
+ if (!cursor) break
1225
+ firstTime = false
1226
+ const r = cursor.value
1227
+ if (args.since && args.since > r.updated_at) continue
1228
+ if (args.partial) {
1229
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1230
+ if (args.partial.certificateId && r.certificateId !== args.partial.certificateId) continue
1231
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1232
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1233
+ if (args.partial.fieldName && r.fieldName !== args.partial.fieldName) continue
1234
+ if (args.partial.fieldValue && r.fieldValue !== args.partial.fieldValue) continue
1235
+ if (args.partial.masterKey && r.masterKey !== args.partial.masterKey) continue
1236
+ }
1237
+ if (skipped < offset) {
1238
+ skipped++
1239
+ continue
1240
+ }
1241
+ filtered(r)
1242
+ count++
1243
+ if (args.paged?.limit && count >= args.paged.limit) break
1244
+ }
1245
+ if (!args.trx) await dbTrx.done
1246
+ }
1247
+
1248
+ async findCertificateFields(args: FindCertificateFieldsArgs): Promise<TableCertificateField[]> {
1249
+ const result: TableCertificateField[] = []
1250
+ await this.filterCertificateFields(args, r => {
1251
+ result.push(this.validateEntity(r))
1252
+ })
1253
+ return result
1254
+ }
1255
+
1256
+ async filterCertificates(args: FindCertificatesArgs, filtered: (v: TableCertificateX) => void): Promise<void> {
1257
+ const offset = args.paged?.offset || 0
1258
+ let skipped = 0
1259
+ let count = 0
1260
+ const dbTrx = this.toDbTrx(['certificates'], 'readonly', args.trx)
1261
+ let cursor:
1262
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'certificates', unknown, 'readwrite' | 'readonly'>
1263
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'certificates', 'userId', 'readwrite' | 'readonly'>
1264
+ | IDBPCursorWithValue<
1265
+ StorageIdbSchema,
1266
+ string[],
1267
+ 'certificates',
1268
+ 'userId_type_certifier_serialNumber',
1269
+ 'readwrite' | 'readonly'
1270
+ >
1271
+ | null
1272
+ if (args.partial?.certificateId) {
1273
+ cursor = await dbTrx.objectStore('certificates').openCursor(args.partial.certificateId)
1274
+ } else if (args.partial?.userId !== undefined) {
1275
+ if (args.partial?.type && args.partial?.certifier && args.partial?.serialNumber) {
1276
+ cursor = await dbTrx
1277
+ .objectStore('certificates')
1278
+ .index('userId_type_certifier_serialNumber')
1279
+ .openCursor([args.partial.userId, args.partial.type, args.partial.certifier, args.partial.serialNumber])
1280
+ } else {
1281
+ cursor = await dbTrx.objectStore('certificates').index('userId').openCursor(args.partial.userId)
1282
+ }
1283
+ } else {
1284
+ cursor = await dbTrx.objectStore('certificates').openCursor()
1285
+ }
1286
+ let firstTime = true
1287
+ while (cursor) {
1288
+ if (!firstTime) cursor = await cursor.continue()
1289
+ if (!cursor) break
1290
+ firstTime = false
1291
+ const r = cursor.value
1292
+ if (args.since && args.since > r.updated_at) continue
1293
+ if (args.certifiers && !args.certifiers.includes(r.certifier)) continue
1294
+ if (args.types && !args.types.includes(r.type)) continue
1295
+ if (args.partial) {
1296
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1297
+ if (args.partial.certificateId && r.certificateId !== args.partial.certificateId) continue
1298
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1299
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1300
+ if (args.partial.type && r.type !== args.partial.type) continue
1301
+ if (args.partial.serialNumber && r.serialNumber !== args.partial.serialNumber) continue
1302
+ if (args.partial.certifier && r.certifier !== args.partial.certifier) continue
1303
+ if (args.partial.subject && r.subject !== args.partial.subject) continue
1304
+ if (args.partial.verifier && r.verifier !== args.partial.verifier) continue
1305
+ if (args.partial.revocationOutpoint && r.revocationOutpoint !== args.partial.revocationOutpoint) continue
1306
+ if (args.partial.signature && r.signature !== args.partial.signature) continue
1307
+ if (args.partial.isDeleted && r.isDeleted !== args.partial.isDeleted) continue
1308
+ }
1309
+ if (skipped < offset) {
1310
+ skipped++
1311
+ continue
1312
+ }
1313
+ filtered(r)
1314
+ count++
1315
+ if (args.paged?.limit && count >= args.paged.limit) break
1316
+ }
1317
+ if (!args.trx) await dbTrx.done
1318
+ }
1319
+
1320
+ async findCertificates(args: FindCertificatesArgs): Promise<TableCertificateX[]> {
1321
+ const result: TableCertificateX[] = []
1322
+ await this.filterCertificates(args, r => {
1323
+ result.push(this.validateEntity(r))
1324
+ })
1325
+ if (args.includeFields) {
1326
+ for (const c of result) {
1327
+ const fields = await this.findCertificateFields({ partial: { certificateId: c.certificateId }, trx: args.trx })
1328
+ c.fields = fields
1329
+ }
1330
+ }
1331
+ return result
1332
+ }
1333
+
1334
+ async filterCommissions(args: FindCommissionsArgs, filtered: (v: TableCommission) => void): Promise<void> {
1335
+ if (args.partial.lockingScript)
1336
+ throw new WERR_INVALID_PARAMETER(
1337
+ 'partial.lockingScript',
1338
+ `undefined. Commissions may not be found by lockingScript value.`
1339
+ )
1340
+ const offset = args.paged?.offset || 0
1341
+ let skipped = 0
1342
+ let count = 0
1343
+ const dbTrx = this.toDbTrx(['commissions'], 'readonly', args.trx)
1344
+ let cursor:
1345
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'commissions', unknown, 'readwrite' | 'readonly'>
1346
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'commissions', 'userId', 'readwrite' | 'readonly'>
1347
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'commissions', 'transactionId', 'readwrite' | 'readonly'>
1348
+ | null
1349
+ if (args.partial?.commissionId) {
1350
+ cursor = await dbTrx.objectStore('commissions').openCursor(args.partial.commissionId)
1351
+ } else if (args.partial?.userId !== undefined) {
1352
+ cursor = await dbTrx.objectStore('commissions').index('userId').openCursor(args.partial.userId)
1353
+ } else if (args.partial?.transactionId !== undefined) {
1354
+ cursor = await dbTrx.objectStore('commissions').index('transactionId').openCursor(args.partial.transactionId)
1355
+ } else {
1356
+ cursor = await dbTrx.objectStore('commissions').openCursor()
1357
+ }
1358
+ let firstTime = true
1359
+ while (cursor) {
1360
+ if (!firstTime) cursor = await cursor.continue()
1361
+ if (!cursor) break
1362
+ firstTime = false
1363
+ const r = cursor.value
1364
+ if (args.since && args.since > r.updated_at) continue
1365
+ if (args.partial) {
1366
+ if (args.partial.commissionId && r.commissionId !== args.partial.commissionId) continue
1367
+ if (args.partial.transactionId && r.transactionId !== args.partial.transactionId) continue
1368
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1369
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1370
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1371
+ if (args.partial.satoshis !== undefined && r.satoshis !== args.partial.satoshis) continue
1372
+ if (args.partial.keyOffset && r.keyOffset !== args.partial.keyOffset) continue
1373
+ if (args.partial.isRedeemed !== undefined && r.isRedeemed !== args.partial.isRedeemed) continue
1374
+ }
1375
+ if (skipped < offset) {
1376
+ skipped++
1377
+ continue
1378
+ }
1379
+ filtered(r)
1380
+ count++
1381
+ if (args.paged?.limit && count >= args.paged.limit) break
1382
+ }
1383
+ if (!args.trx) await dbTrx.done
1384
+ }
1385
+
1386
+ async findCommissions(args: FindCommissionsArgs): Promise<TableCommission[]> {
1387
+ const result: TableCommission[] = []
1388
+ await this.filterCommissions(args, r => {
1389
+ result.push(this.validateEntity(r))
1390
+ })
1391
+ return result
1392
+ }
1393
+
1394
+ async filterMonitorEvents(args: FindMonitorEventsArgs, filtered: (v: TableMonitorEvent) => void): Promise<void> {
1395
+ const offset = args.paged?.offset || 0
1396
+ let skipped = 0
1397
+ let count = 0
1398
+ const dbTrx = this.toDbTrx(['monitor_events'], 'readonly', args.trx)
1399
+ let cursor: IDBPCursorWithValue<
1400
+ StorageIdbSchema,
1401
+ string[],
1402
+ 'monitor_events',
1403
+ unknown,
1404
+ 'readwrite' | 'readonly'
1405
+ > | null
1406
+ if (args.partial?.id) {
1407
+ cursor = await dbTrx.objectStore('monitor_events').openCursor(args.partial.id)
1408
+ } else {
1409
+ cursor = await dbTrx.objectStore('monitor_events').openCursor()
1410
+ }
1411
+ let firstTime = true
1412
+ while (cursor) {
1413
+ if (!firstTime) cursor = await cursor.continue()
1414
+ if (!cursor) break
1415
+ firstTime = false
1416
+ const r = cursor.value
1417
+ if (args.since && args.since > r.updated_at) continue
1418
+ if (args.partial) {
1419
+ if (args.partial.id && r.id !== args.partial.id) continue
1420
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1421
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1422
+ if (args.partial.event && r.event !== args.partial.event) continue
1423
+ if (args.partial.details && r.details !== args.partial.details) continue
1424
+ }
1425
+ if (skipped < offset) {
1426
+ skipped++
1427
+ continue
1428
+ }
1429
+ filtered(r)
1430
+ count++
1431
+ if (args.paged?.limit && count >= args.paged.limit) break
1432
+ }
1433
+ if (!args.trx) await dbTrx.done
1434
+ }
1435
+
1436
+ async findMonitorEvents(args: FindMonitorEventsArgs): Promise<TableMonitorEvent[]> {
1437
+ const result: TableMonitorEvent[] = []
1438
+ await this.filterMonitorEvents(args, r => {
1439
+ result.push(this.validateEntity(r))
1440
+ })
1441
+ return result
1442
+ }
1443
+
1444
+ async filterOutputBaskets(args: FindOutputBasketsArgs, filtered: (v: TableOutputBasket) => void): Promise<void> {
1445
+ const offset = args.paged?.offset || 0
1446
+ let skipped = 0
1447
+ let count = 0
1448
+ const dbTrx = this.toDbTrx(['output_baskets'], 'readonly', args.trx)
1449
+ let cursor:
1450
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_baskets', unknown, 'readwrite' | 'readonly'>
1451
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_baskets', 'userId', 'readwrite' | 'readonly'>
1452
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_baskets', 'name_userId', 'readwrite' | 'readonly'>
1453
+ | null
1454
+ if (args.partial?.basketId) {
1455
+ cursor = await dbTrx.objectStore('output_baskets').openCursor(args.partial.basketId)
1456
+ } else if (args.partial?.userId !== undefined) {
1457
+ if (args.partial?.name !== undefined) {
1458
+ cursor = await dbTrx
1459
+ .objectStore('output_baskets')
1460
+ .index('name_userId')
1461
+ .openCursor([args.partial.name, args.partial.userId])
1462
+ } else {
1463
+ cursor = await dbTrx.objectStore('output_baskets').index('userId').openCursor(args.partial.userId)
1464
+ }
1465
+ } else {
1466
+ cursor = await dbTrx.objectStore('output_baskets').openCursor()
1467
+ }
1468
+ let firstTime = true
1469
+ while (cursor) {
1470
+ if (!firstTime) cursor = await cursor.continue()
1471
+ if (!cursor) break
1472
+ firstTime = false
1473
+ const r = cursor.value
1474
+ if (args.since && args.since > r.updated_at) continue
1475
+ if (args.partial) {
1476
+ if (args.partial.basketId && r.basketId !== args.partial.basketId) continue
1477
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1478
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1479
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1480
+ if (args.partial.name && r.name !== args.partial.name) continue
1481
+ if (
1482
+ args.partial.numberOfDesiredUTXOs !== undefined &&
1483
+ r.numberOfDesiredUTXOs !== args.partial.numberOfDesiredUTXOs
1484
+ )
1485
+ continue
1486
+ if (
1487
+ args.partial.minimumDesiredUTXOValue !== undefined &&
1488
+ r.numberOfDesiredSatoshis !== args.partial.minimumDesiredUTXOValue
1489
+ )
1490
+ continue
1491
+ if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted) continue
1492
+ }
1493
+ if (skipped < offset) {
1494
+ skipped++
1495
+ continue
1496
+ }
1497
+ filtered(r)
1498
+ count++
1499
+ if (args.paged?.limit && count >= args.paged.limit) break
1500
+ }
1501
+ if (!args.trx) await dbTrx.done
1502
+ }
1503
+
1504
+ async findOutputBaskets(args: FindOutputBasketsArgs): Promise<TableOutputBasket[]> {
1505
+ const result: TableOutputBasket[] = []
1506
+ await this.filterOutputBaskets(args, r => {
1507
+ result.push(this.validateEntity(r))
1508
+ })
1509
+ return result
1510
+ }
1511
+
1512
+ async filterOutputs(
1513
+ args: FindOutputsArgs,
1514
+ filtered: (v: TableOutput) => void,
1515
+ tagIds?: number[],
1516
+ isQueryModeAll?: boolean
1517
+ ): Promise<void> {
1518
+ // args.txStatus
1519
+ // args.noScript
1520
+ if (args.partial.lockingScript)
1521
+ throw new WERR_INVALID_PARAMETER(
1522
+ 'args.partial.lockingScript',
1523
+ `undefined. Outputs may not be found by lockingScript value.`
1524
+ )
1525
+ const offset = args.paged?.offset || 0
1526
+ let skipped = 0
1527
+ let count = 0
1528
+ const stores = ['outputs']
1529
+ if (tagIds && tagIds.length > 0) {
1530
+ stores.push('output_tags_map')
1531
+ }
1532
+ if (args.txStatus) {
1533
+ stores.push('transactions')
1534
+ }
1535
+ const dbTrx = this.toDbTrx(stores, 'readonly', args.trx)
1536
+ let cursor:
1537
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'outputs', unknown, 'readwrite' | 'readonly'>
1538
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'outputs', 'userId', 'readwrite' | 'readonly'>
1539
+ | IDBPCursorWithValue<
1540
+ StorageIdbSchema,
1541
+ string[],
1542
+ 'outputs',
1543
+ 'transactionId_vout_userId',
1544
+ 'readwrite' | 'readonly'
1545
+ >
1546
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'outputs', 'transactionId', 'readwrite' | 'readonly'>
1547
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'outputs', 'basketId', 'readwrite' | 'readonly'>
1548
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'outputs', 'spentBy', 'readwrite' | 'readonly'>
1549
+ | null
1550
+ if (args.partial?.outputId) {
1551
+ cursor = await dbTrx.objectStore('outputs').openCursor(args.partial.outputId)
1552
+ } else if (args.partial?.userId !== undefined) {
1553
+ if (args.partial?.transactionId && args.partial?.vout !== undefined) {
1554
+ cursor = await dbTrx
1555
+ .objectStore('outputs')
1556
+ .index('transactionId_vout_userId')
1557
+ .openCursor([args.partial.transactionId, args.partial.vout, args.partial.userId])
1558
+ } else {
1559
+ cursor = await dbTrx.objectStore('outputs').index('userId').openCursor(args.partial.userId)
1560
+ }
1561
+ } else if (args.partial?.transactionId !== undefined) {
1562
+ cursor = await dbTrx.objectStore('outputs').index('transactionId').openCursor(args.partial.transactionId)
1563
+ } else if (args.partial?.basketId !== undefined) {
1564
+ cursor = await dbTrx.objectStore('outputs').index('basketId').openCursor(args.partial.basketId)
1565
+ } else if (args.partial?.spentBy !== undefined) {
1566
+ cursor = await dbTrx.objectStore('outputs').index('spentBy').openCursor(args.partial.spentBy)
1567
+ } else {
1568
+ cursor = await dbTrx.objectStore('outputs').openCursor()
1569
+ }
1570
+ let firstTime = true
1571
+ while (cursor) {
1572
+ if (!firstTime) cursor = await cursor.continue()
1573
+ if (!cursor) break
1574
+ firstTime = false
1575
+ const r = cursor.value
1576
+ if (args.since && args.since > r.updated_at) continue
1577
+ if (args.partial) {
1578
+ if (args.partial.outputId && r.outputId !== args.partial.outputId) continue
1579
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1580
+ if (args.partial.transactionId && r.transactionId !== args.partial.transactionId) continue
1581
+ if (args.partial.basketId && r.basketId !== args.partial.basketId) continue
1582
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1583
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1584
+ if (args.partial.spendable !== undefined && r.spendable !== args.partial.spendable) continue
1585
+ if (args.partial.change !== undefined && r.change !== args.partial.change) continue
1586
+ if (args.partial.outputDescription && r.outputDescription !== args.partial.outputDescription) continue
1587
+ if (args.partial.vout !== undefined && r.vout !== args.partial.vout) continue
1588
+ if (args.partial.satoshis !== undefined && r.satoshis !== args.partial.satoshis) continue
1589
+ if (args.partial.providedBy && r.providedBy !== args.partial.providedBy) continue
1590
+ if (args.partial.purpose && r.purpose !== args.partial.purpose) continue
1591
+ if (args.partial.type && r.type !== args.partial.type) continue
1592
+ if (args.partial.txid && r.txid !== args.partial.txid) continue
1593
+ if (args.partial.senderIdentityKey && r.senderIdentityKey !== args.partial.senderIdentityKey) continue
1594
+ if (args.partial.derivationPrefix && r.derivationPrefix !== args.partial.derivationPrefix) continue
1595
+ if (args.partial.derivationSuffix && r.derivationSuffix !== args.partial.derivationSuffix) continue
1596
+ if (args.partial.customInstructions && r.customInstructions !== args.partial.customInstructions) continue
1597
+ if (args.partial.spentBy && r.spentBy !== args.partial.spentBy) continue
1598
+ if (args.partial.sequenceNumber !== undefined && r.sequenceNumber !== args.partial.sequenceNumber) continue
1599
+ if (args.partial.scriptLength !== undefined && r.scriptLength !== args.partial.scriptLength) continue
1600
+ if (args.partial.scriptOffset !== undefined && r.scriptOffset !== args.partial.scriptOffset) continue
1601
+ }
1602
+ if (args.txStatus !== undefined) {
1603
+ const count = await this.countTransactions({
1604
+ partial: { transactionId: r.transactionId },
1605
+ status: args.txStatus,
1606
+ trx: dbTrx
1607
+ })
1608
+ if (count === 0) continue
1609
+ }
1610
+ if (tagIds && tagIds.length > 0) {
1611
+ let ids = [...tagIds]
1612
+ await this.filterOutputTagMaps({ partial: { outputId: r.outputId }, trx: dbTrx }, tm => {
1613
+ if (ids.length > 0) {
1614
+ const i = ids.indexOf(tm.outputTagId)
1615
+ if (i >= 0) {
1616
+ if (isQueryModeAll) {
1617
+ ids.splice(i, 1)
1618
+ } else {
1619
+ ids = []
1620
+ }
1621
+ }
1622
+ }
1623
+ })
1624
+ if (ids.length > 0) continue
1625
+ }
1626
+ if (skipped < offset) {
1627
+ skipped++
1628
+ continue
1629
+ }
1630
+ if (args.noScript === true) {
1631
+ r.script = undefined
1632
+ }
1633
+ filtered(r)
1634
+ count++
1635
+ if (args.paged?.limit && count >= args.paged.limit) break
1636
+ }
1637
+ if (!args.trx) await dbTrx.done
1638
+ }
1639
+
1640
+ async findOutputs(args: FindOutputsArgs, tagIds?: number[], isQueryModeAll?: boolean): Promise<TableOutput[]> {
1641
+ const results: TableOutput[] = []
1642
+ await this.filterOutputs(
1643
+ args,
1644
+ r => {
1645
+ results.push(this.validateEntity(r))
1646
+ },
1647
+ tagIds,
1648
+ isQueryModeAll
1649
+ )
1650
+ for (const o of results) {
1651
+ if (!args.noScript) {
1652
+ await this.validateOutputScript(o)
1653
+ } else {
1654
+ o.lockingScript = undefined
1655
+ }
1656
+ }
1657
+ return results
1658
+ }
1659
+
1660
+ async filterOutputTags(args: FindOutputTagsArgs, filtered: (v: TableOutputTag) => void): Promise<void> {
1661
+ const offset = args.paged?.offset || 0
1662
+ let skipped = 0
1663
+ let count = 0
1664
+ const dbTrx = this.toDbTrx(['output_tags'], 'readonly', args.trx)
1665
+ let cursor:
1666
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags', unknown, 'readwrite' | 'readonly'>
1667
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags', 'userId', 'readwrite' | 'readonly'>
1668
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'output_tags', 'tag_userId', 'readwrite' | 'readonly'>
1669
+ | null
1670
+ if (args.partial?.outputTagId) {
1671
+ cursor = await dbTrx.objectStore('output_tags').openCursor(args.partial.outputTagId)
1672
+ } else if (args.partial?.userId !== undefined) {
1673
+ if (args.partial?.tag !== undefined) {
1674
+ cursor = await dbTrx
1675
+ .objectStore('output_tags')
1676
+ .index('tag_userId')
1677
+ .openCursor([args.partial.tag, args.partial.userId])
1678
+ } else {
1679
+ cursor = await dbTrx.objectStore('output_tags').index('userId').openCursor(args.partial.userId)
1680
+ }
1681
+ } else {
1682
+ cursor = await dbTrx.objectStore('output_tags').openCursor()
1683
+ }
1684
+ let firstTime = true
1685
+ while (cursor) {
1686
+ if (!firstTime) cursor = await cursor.continue()
1687
+ if (!cursor) break
1688
+ firstTime = false
1689
+ const r = cursor.value
1690
+ if (args.since && args.since > r.updated_at) continue
1691
+ if (args.partial) {
1692
+ if (args.partial.outputTagId && r.outputTagId !== args.partial.outputTagId) continue
1693
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1694
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1695
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1696
+ if (args.partial.tag && r.tag !== args.partial.tag) continue
1697
+ if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted) continue
1698
+ }
1699
+ if (skipped < offset) {
1700
+ skipped++
1701
+ continue
1702
+ }
1703
+ filtered(r)
1704
+ count++
1705
+ if (args.paged?.limit && count >= args.paged.limit) break
1706
+ }
1707
+ if (!args.trx) await dbTrx.done
1708
+ }
1709
+
1710
+ async findOutputTags(args: FindOutputTagsArgs): Promise<TableOutputTag[]> {
1711
+ const result: TableOutputTag[] = []
1712
+ await this.filterOutputTags(args, r => {
1713
+ result.push(this.validateEntity(r))
1714
+ })
1715
+ return result
1716
+ }
1717
+
1718
+ async filterSyncStates(args: FindSyncStatesArgs, filtered: (v: TableSyncState) => void): Promise<void> {
1719
+ if (args.partial.syncMap)
1720
+ throw new WERR_INVALID_PARAMETER(
1721
+ 'args.partial.syncMap',
1722
+ `undefined. SyncStates may not be found by syncMap value.`
1723
+ )
1724
+ const offset = args.paged?.offset || 0
1725
+ let skipped = 0
1726
+ let count = 0
1727
+ const dbTrx = this.toDbTrx(['sync_states'], 'readonly', args.trx)
1728
+ let cursor:
1729
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'sync_states', unknown, 'readwrite' | 'readonly'>
1730
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'sync_states', 'userId', 'readwrite' | 'readonly'>
1731
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'sync_states', 'refNum', 'readwrite' | 'readonly'>
1732
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'sync_states', 'status', 'readwrite' | 'readonly'>
1733
+ | null
1734
+ if (args.partial?.syncStateId) {
1735
+ cursor = await dbTrx.objectStore('sync_states').openCursor(args.partial.syncStateId)
1736
+ } else if (args.partial?.userId !== undefined) {
1737
+ cursor = await dbTrx.objectStore('sync_states').index('userId').openCursor(args.partial.userId)
1738
+ } else if (args.partial?.refNum !== undefined) {
1739
+ cursor = await dbTrx.objectStore('sync_states').index('refNum').openCursor(args.partial.refNum)
1740
+ } else if (args.partial?.status !== undefined) {
1741
+ cursor = await dbTrx.objectStore('sync_states').index('status').openCursor(args.partial.status)
1742
+ } else {
1743
+ cursor = await dbTrx.objectStore('sync_states').openCursor()
1744
+ }
1745
+ let firstTime = true
1746
+ while (cursor) {
1747
+ if (!firstTime) cursor = await cursor.continue()
1748
+ if (!cursor) break
1749
+ firstTime = false
1750
+ const r = cursor.value
1751
+ if (args.since && args.since > r.updated_at) continue
1752
+ if (args.partial) {
1753
+ if (args.partial.syncStateId && r.syncStateId !== args.partial.syncStateId) continue
1754
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1755
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1756
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1757
+ if (args.partial.storageIdentityKey && r.storageIdentityKey !== args.partial.storageIdentityKey) continue
1758
+ if (args.partial.storageName && r.storageName !== args.partial.storageName) continue
1759
+ if (args.partial.status && r.status !== args.partial.status) continue
1760
+ if (args.partial.init !== undefined && r.init !== args.partial.init) continue
1761
+ if (args.partial.refNum !== undefined && r.refNum !== args.partial.refNum) continue
1762
+ if (args.partial.when && r.when?.getTime() !== args.partial.when.getTime()) continue
1763
+ if (args.partial.satoshis !== undefined && r.satoshis !== args.partial.satoshis) continue
1764
+ if (args.partial.errorLocal && r.errorLocale !== args.partial.errorLocal) continue
1765
+ if (args.partial.errorOther && r.errorOther !== args.partial.errorOther) continue
1766
+ }
1767
+ if (skipped < offset) {
1768
+ skipped++
1769
+ continue
1770
+ }
1771
+ filtered(r)
1772
+ count++
1773
+ if (args.paged?.limit && count >= args.paged.limit) break
1774
+ }
1775
+ if (!args.trx) await dbTrx.done
1776
+ }
1777
+
1778
+ async findSyncStates(args: FindSyncStatesArgs): Promise<TableSyncState[]> {
1779
+ const result: TableSyncState[] = []
1780
+ await this.filterSyncStates(args, r => {
1781
+ result.push(this.validateEntity(r))
1782
+ })
1783
+ return result
1784
+ }
1785
+
1786
+ async filterTransactions(
1787
+ args: FindTransactionsArgs,
1788
+ filtered: (v: TableTransaction) => void,
1789
+ labelIds?: number[],
1790
+ isQueryModeAll?: boolean
1791
+ ): Promise<void> {
1792
+ if (args.partial.rawTx)
1793
+ throw new WERR_INVALID_PARAMETER('args.partial.rawTx', `undefined. Transactions may not be found by rawTx value.`)
1794
+ if (args.partial.inputBEEF)
1795
+ throw new WERR_INVALID_PARAMETER(
1796
+ 'args.partial.inputBEEF',
1797
+ `undefined. Transactions may not be found by inputBEEF value.`
1798
+ )
1799
+ const offset = args.paged?.offset || 0
1800
+ let skipped = 0
1801
+ let count = 0
1802
+ const stores = ['transactions']
1803
+ if (labelIds && labelIds.length > 0) {
1804
+ stores.push('tx_labels_map')
1805
+ }
1806
+ const dbTrx = this.toDbTrx(stores, 'readonly', args.trx)
1807
+ let cursor:
1808
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', unknown, 'readwrite' | 'readonly'>
1809
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', 'userId', 'readwrite' | 'readonly'>
1810
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', 'status', 'readwrite' | 'readonly'>
1811
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', 'status_userId', 'readwrite' | 'readonly'>
1812
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', 'provenTxId', 'readwrite' | 'readonly'>
1813
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'transactions', 'reference', 'readwrite' | 'readonly'>
1814
+ | null
1815
+ if (args.partial?.transactionId) {
1816
+ cursor = await dbTrx.objectStore('transactions').openCursor(args.partial.transactionId)
1817
+ } else if (args.partial?.userId !== undefined) {
1818
+ if (args.partial?.status !== undefined) {
1819
+ cursor = await dbTrx
1820
+ .objectStore('transactions')
1821
+ .index('status_userId')
1822
+ .openCursor([args.partial.status, args.partial.userId])
1823
+ } else {
1824
+ cursor = await dbTrx.objectStore('transactions').index('userId').openCursor(args.partial.userId)
1825
+ }
1826
+ } else if (args.partial?.status !== undefined) {
1827
+ cursor = await dbTrx.objectStore('transactions').index('status').openCursor(args.partial.status)
1828
+ } else if (args.partial?.provenTxId !== undefined) {
1829
+ cursor = await dbTrx.objectStore('transactions').index('provenTxId').openCursor(args.partial.provenTxId)
1830
+ } else if (args.partial?.reference !== undefined) {
1831
+ cursor = await dbTrx.objectStore('transactions').index('reference').openCursor(args.partial.reference)
1832
+ } else {
1833
+ cursor = await dbTrx.objectStore('transactions').openCursor()
1834
+ }
1835
+ let firstTime = true
1836
+ while (cursor) {
1837
+ if (!firstTime) cursor = await cursor.continue()
1838
+ if (!cursor) break
1839
+ firstTime = false
1840
+ const r = cursor.value
1841
+ if (args.since && args.since > r.updated_at) continue
1842
+ if (args.status && !args.status.includes(r.status)) continue
1843
+ if (args.partial) {
1844
+ if (args.partial.transactionId && r.transactionId !== args.partial.transactionId) continue
1845
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1846
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1847
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1848
+ if (args.partial.provenTxId && r.provenTxId !== args.partial.provenTxId) continue
1849
+ if (args.partial.status && r.status !== args.partial.status) continue
1850
+ if (args.partial.reference && r.reference !== args.partial.reference) continue
1851
+ if (args.partial.isOutgoing !== undefined && r.isOutgoing !== args.partial.isOutgoing) continue
1852
+ if (args.partial.satoshis !== undefined && r.satoshis !== args.partial.satoshis) continue
1853
+ if (args.partial.description && r.description !== args.partial.description) continue
1854
+ if (args.partial.version !== undefined && r.version !== args.partial.version) continue
1855
+ if (args.partial.lockTime !== undefined && r.lockTime !== args.partial.lockTime) continue
1856
+ if (args.partial.txid && r.txid !== args.partial.txid) continue
1857
+ }
1858
+ if (labelIds && labelIds.length > 0) {
1859
+ let ids = [...labelIds]
1860
+ await this.filterTxLabelMaps({ partial: { transactionId: r.transactionId }, trx: dbTrx }, lm => {
1861
+ if (ids.length > 0) {
1862
+ const i = ids.indexOf(lm.txLabelId)
1863
+ if (i >= 0) {
1864
+ if (isQueryModeAll) {
1865
+ ids.splice(i, 1)
1866
+ } else {
1867
+ ids = []
1868
+ }
1869
+ }
1870
+ }
1871
+ })
1872
+ if (ids.length > 0) continue
1873
+ }
1874
+ if (skipped < offset) {
1875
+ skipped++
1876
+ continue
1877
+ }
1878
+ filtered(r)
1879
+ count++
1880
+ if (args.paged?.limit && count >= args.paged.limit) break
1881
+ }
1882
+ if (!args.trx) await dbTrx.done
1883
+ }
1884
+
1885
+ async findTransactions(
1886
+ args: FindTransactionsArgs,
1887
+ labelIds?: number[],
1888
+ isQueryModeAll?: boolean
1889
+ ): Promise<TableTransaction[]> {
1890
+ const results: TableTransaction[] = []
1891
+ await this.filterTransactions(
1892
+ args,
1893
+ r => {
1894
+ results.push(this.validateEntity(r))
1895
+ },
1896
+ labelIds,
1897
+ isQueryModeAll
1898
+ )
1899
+ for (const t of results) {
1900
+ if (!args.noRawTx) {
1901
+ await this.validateRawTransaction(t, args.trx)
1902
+ } else {
1903
+ t.rawTx = undefined
1904
+ t.inputBEEF = undefined
1905
+ }
1906
+ }
1907
+ return results
1908
+ }
1909
+
1910
+ async filterTxLabels(args: FindTxLabelsArgs, filtered: (v: TableTxLabel) => void): Promise<void> {
1911
+ const offset = args.paged?.offset || 0
1912
+ let skipped = 0
1913
+ let count = 0
1914
+ const dbTrx = this.toDbTrx(['tx_labels'], 'readonly', args.trx)
1915
+ let cursor:
1916
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels', unknown, 'readwrite' | 'readonly'>
1917
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels', 'userId', 'readwrite' | 'readonly'>
1918
+ | IDBPCursorWithValue<StorageIdbSchema, string[], 'tx_labels', 'label_userId', 'readwrite' | 'readonly'>
1919
+ | null
1920
+ if (args.partial?.txLabelId) {
1921
+ cursor = await dbTrx.objectStore('tx_labels').openCursor(args.partial.txLabelId)
1922
+ } else if (args.partial?.userId !== undefined) {
1923
+ if (args.partial?.label !== undefined) {
1924
+ cursor = await dbTrx
1925
+ .objectStore('tx_labels')
1926
+ .index('label_userId')
1927
+ .openCursor([args.partial.label, args.partial.userId])
1928
+ } else {
1929
+ cursor = await dbTrx.objectStore('tx_labels').index('userId').openCursor(args.partial.userId)
1930
+ }
1931
+ } else {
1932
+ cursor = await dbTrx.objectStore('tx_labels').openCursor()
1933
+ }
1934
+ let firstTime = true
1935
+ while (cursor) {
1936
+ if (!firstTime) cursor = await cursor.continue()
1937
+ if (!cursor) break
1938
+ firstTime = false
1939
+ const r = cursor.value
1940
+ if (args.since && args.since > r.updated_at) continue
1941
+ if (args.partial) {
1942
+ if (args.partial.txLabelId && r.txLabelId !== args.partial.txLabelId) continue
1943
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1944
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1945
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1946
+ if (args.partial.label && r.label !== args.partial.label) continue
1947
+ if (args.partial.isDeleted !== undefined && r.isDeleted !== args.partial.isDeleted) continue
1948
+ }
1949
+ if (skipped < offset) {
1950
+ skipped++
1951
+ continue
1952
+ }
1953
+ filtered(r)
1954
+ count++
1955
+ if (args.paged?.limit && count >= args.paged.limit) break
1956
+ }
1957
+ if (!args.trx) await dbTrx.done
1958
+ }
1959
+
1960
+ async findTxLabels(args: FindTxLabelsArgs): Promise<TableTxLabel[]> {
1961
+ const result: TableTxLabel[] = []
1962
+ await this.filterTxLabels(args, r => {
1963
+ result.push(this.validateEntity(r))
1964
+ })
1965
+ return result
1966
+ }
1967
+
1968
+ async filterUsers(args: FindUsersArgs, filtered: (v: TableUser) => void): Promise<void> {
1969
+ const offset = args.paged?.offset || 0
1970
+ let skipped = 0
1971
+ let count = 0
1972
+ const dbTrx = this.toDbTrx(['users'], 'readonly', args.trx)
1973
+ let cursor = await dbTrx.objectStore('users').openCursor()
1974
+ let firstTime = true
1975
+ while (cursor) {
1976
+ if (!firstTime) cursor = await cursor.continue()
1977
+ if (!cursor) break
1978
+ firstTime = false
1979
+ const r = cursor.value
1980
+ if (args.since && args.since > r.updated_at) continue
1981
+ if (args.partial) {
1982
+ if (args.partial.userId && r.userId !== args.partial.userId) continue
1983
+ if (args.partial.created_at && r.created_at.getTime() !== args.partial.created_at.getTime()) continue
1984
+ if (args.partial.updated_at && r.updated_at.getTime() !== args.partial.updated_at.getTime()) continue
1985
+ if (args.partial.identityKey && r.identityKey !== args.partial.identityKey) continue
1986
+ if (args.partial.activeStorage && r.activeStorage !== args.partial.activeStorage) continue
1987
+ }
1988
+ if (skipped < offset) {
1989
+ skipped++
1990
+ continue
1991
+ }
1992
+ filtered(r)
1993
+ count++
1994
+ if (args.paged?.limit && count >= args.paged.limit) break
1995
+ }
1996
+ if (!args.trx) await dbTrx.done
1997
+ }
1998
+
1999
+ async findUsers(args: FindUsersArgs): Promise<TableUser[]> {
2000
+ const result: TableUser[] = []
2001
+ await this.filterUsers(args, r => {
2002
+ result.push(this.validateEntity(r))
2003
+ })
2004
+ return result
2005
+ }
2006
+
2007
+ async countCertificateFields(args: FindCertificateFieldsArgs): Promise<number> {
2008
+ let count = 0
2009
+ await this.filterCertificateFields(args, () => {
2010
+ count++
2011
+ })
2012
+ return count
2013
+ }
2014
+ async countCertificates(args: FindCertificatesArgs): Promise<number> {
2015
+ let count = 0
2016
+ await this.filterCertificates(args, () => {
2017
+ count++
2018
+ })
2019
+ return count
2020
+ }
2021
+ async countCommissions(args: FindCommissionsArgs): Promise<number> {
2022
+ let count = 0
2023
+ await this.filterCommissions(args, () => {
2024
+ count++
2025
+ })
2026
+ return count
2027
+ }
2028
+ async countMonitorEvents(args: FindMonitorEventsArgs): Promise<number> {
2029
+ let count = 0
2030
+ await this.filterMonitorEvents(args, () => {
2031
+ count++
2032
+ })
2033
+ return count
2034
+ }
2035
+ async countOutputBaskets(args: FindOutputBasketsArgs): Promise<number> {
2036
+ let count = 0
2037
+ await this.filterOutputBaskets(args, () => {
2038
+ count++
2039
+ })
2040
+ return count
2041
+ }
2042
+ async countOutputs(args: FindOutputsArgs, tagIds?: number[], isQueryModeAll?: boolean): Promise<number> {
2043
+ let count = 0
2044
+ await this.filterOutputs(
2045
+ { ...args, noScript: true },
2046
+ () => {
2047
+ count++
2048
+ },
2049
+ tagIds,
2050
+ isQueryModeAll
2051
+ )
2052
+ return count
2053
+ }
2054
+ async countOutputTags(args: FindOutputTagsArgs): Promise<number> {
2055
+ let count = 0
2056
+ await this.filterOutputTags(args, () => {
2057
+ count++
2058
+ })
2059
+ return count
2060
+ }
2061
+ async countSyncStates(args: FindSyncStatesArgs): Promise<number> {
2062
+ let count = 0
2063
+ await this.filterSyncStates(args, () => {
2064
+ count++
2065
+ })
2066
+ return count
2067
+ }
2068
+ async countTransactions(args: FindTransactionsArgs, labelIds?: number[], isQueryModeAll?: boolean): Promise<number> {
2069
+ let count = 0
2070
+ await this.filterTransactions(
2071
+ { ...args, noRawTx: true },
2072
+ () => {
2073
+ count++
2074
+ },
2075
+ labelIds,
2076
+ isQueryModeAll
2077
+ )
2078
+ return count
2079
+ }
2080
+ async countTxLabels(args: FindTxLabelsArgs): Promise<number> {
2081
+ let count = 0
2082
+ await this.filterTxLabels(args, () => {
2083
+ count++
2084
+ })
2085
+ return count
2086
+ }
2087
+ async countUsers(args: FindUsersArgs): Promise<number> {
2088
+ let count = 0
2089
+ await this.filterUsers(args, () => {
2090
+ count++
2091
+ })
2092
+ return count
2093
+ }
2094
+
2095
+ async getProvenTxsForUser(args: FindForUserSincePagedArgs): Promise<TableProvenTx[]> {
2096
+ const results: TableProvenTx[] = []
2097
+ const fargs: FindProvenTxsArgs = {
2098
+ partial: {},
2099
+ since: args.since,
2100
+ paged: args.paged,
2101
+ trx: args.trx
2102
+ }
2103
+ await this.filterProvenTxs(
2104
+ fargs,
2105
+ r => {
2106
+ results.push(this.validateEntity(r))
2107
+ },
2108
+ args.userId
2109
+ )
2110
+ return results
2111
+ }
2112
+
2113
+ async getProvenTxReqsForUser(args: FindForUserSincePagedArgs): Promise<TableProvenTxReq[]> {
2114
+ const results: TableProvenTxReq[] = []
2115
+ const fargs: FindProvenTxReqsArgs = {
2116
+ partial: {},
2117
+ since: args.since,
2118
+ paged: args.paged,
2119
+ trx: args.trx
2120
+ }
2121
+ await this.filterProvenTxReqs(
2122
+ fargs,
2123
+ r => {
2124
+ results.push(this.validateEntity(r))
2125
+ },
2126
+ args.userId
2127
+ )
2128
+ return results
2129
+ }
2130
+
2131
+ async getTxLabelMapsForUser(args: FindForUserSincePagedArgs): Promise<TableTxLabelMap[]> {
2132
+ const results: TableTxLabelMap[] = []
2133
+ const fargs: FindTxLabelMapsArgs = {
2134
+ partial: {},
2135
+ since: args.since,
2136
+ paged: args.paged,
2137
+ trx: args.trx
2138
+ }
2139
+ await this.filterTxLabelMaps(
2140
+ fargs,
2141
+ r => {
2142
+ results.push(this.validateEntity(r))
2143
+ },
2144
+ args.userId
2145
+ )
2146
+ return results
2147
+ }
2148
+
2149
+ async getOutputTagMapsForUser(args: FindForUserSincePagedArgs): Promise<TableOutputTagMap[]> {
2150
+ const results: TableOutputTagMap[] = []
2151
+ const fargs: FindOutputTagMapsArgs = {
2152
+ partial: {},
2153
+ since: args.since,
2154
+ paged: args.paged,
2155
+ trx: args.trx
2156
+ }
2157
+ await this.filterOutputTagMaps(
2158
+ fargs,
2159
+ r => {
2160
+ results.push(this.validateEntity(r))
2161
+ },
2162
+ args.userId
2163
+ )
2164
+ return results
2165
+ }
2166
+
2167
+ async verifyReadyForDatabaseAccess(trx?: TrxToken): Promise<DBType> {
2168
+ if (!this._settings) {
2169
+ this._settings = await this.readSettings()
2170
+ }
2171
+
2172
+ return this._settings.dbtype
2173
+ }
2174
+
2175
+ /**
2176
+ * Helper to force uniform behavior across database engines.
2177
+ * Use to process all individual records with time stamps or number[] retreived from database.
2178
+ */
2179
+ validateEntity<T extends EntityTimeStamp>(entity: T, dateFields?: string[], booleanFields?: string[]): T {
2180
+ entity.created_at = this.validateDate(entity.created_at)
2181
+ entity.updated_at = this.validateDate(entity.updated_at)
2182
+ if (dateFields) {
2183
+ for (const df of dateFields) {
2184
+ if (entity[df]) entity[df] = this.validateDate(entity[df])
2185
+ }
2186
+ }
2187
+ if (booleanFields) {
2188
+ for (const df of booleanFields) {
2189
+ if (entity[df] !== undefined) entity[df] = !!entity[df]
2190
+ }
2191
+ }
2192
+ for (const key of Object.keys(entity)) {
2193
+ const val = entity[key]
2194
+ if (val === null) {
2195
+ entity[key] = undefined
2196
+ } else if (val instanceof Uint8Array) {
2197
+ entity[key] = Array.from(val)
2198
+ }
2199
+ }
2200
+ return entity
2201
+ }
2202
+
2203
+ /**
2204
+ * Helper to force uniform behavior across database engines.
2205
+ * Use to process all arrays of records with time stamps retreived from database.
2206
+ * @returns input `entities` array with contained values validated.
2207
+ */
2208
+ validateEntities<T extends EntityTimeStamp>(entities: T[], dateFields?: string[], booleanFields?: string[]): T[] {
2209
+ for (let i = 0; i < entities.length; i++) {
2210
+ entities[i] = this.validateEntity(entities[i], dateFields, booleanFields)
2211
+ }
2212
+ return entities
2213
+ }
2214
+ /**
2215
+ * Helper to force uniform behavior across database engines.
2216
+ * Use to process the update template for entities being updated.
2217
+ */
2218
+ validatePartialForUpdate<T extends EntityTimeStamp>(
2219
+ update: Partial<T>,
2220
+ dateFields?: string[],
2221
+ booleanFields?: string[]
2222
+ ): Partial<T> {
2223
+ if (!this.dbtype) throw new WERR_INTERNAL('must call verifyReadyForDatabaseAccess first')
2224
+ const v: any = { ...update }
2225
+ if (v.created_at) v.created_at = this.validateEntityDate(v.created_at)
2226
+ if (v.updated_at) v.updated_at = this.validateEntityDate(v.updated_at)
2227
+ if (!v.created_at) delete v.created_at
2228
+ if (!v.updated_at) v.updated_at = this.validateEntityDate(new Date())
2229
+
2230
+ if (dateFields) {
2231
+ for (const df of dateFields) {
2232
+ if (v[df]) v[df] = this.validateOptionalEntityDate(v[df])
2233
+ }
2234
+ }
2235
+ if (booleanFields) {
2236
+ for (const df of booleanFields) {
2237
+ if (update[df] !== undefined) update[df] = !!update[df] ? 1 : 0
2238
+ }
2239
+ }
2240
+ for (const key of Object.keys(v)) {
2241
+ const val = v[key]
2242
+ if (Array.isArray(val) && (val.length === 0 || Number.isInteger(val[0]))) {
2243
+ v[key] = Uint8Array.from(val)
2244
+ } else if (val === null) {
2245
+ v[key] = undefined
2246
+ }
2247
+ }
2248
+ this.isDirty = true
2249
+ return v
2250
+ }
2251
+
2252
+ /**
2253
+ * Helper to force uniform behavior across database engines.
2254
+ * Use to process new entities being inserted into the database.
2255
+ */
2256
+ async validateEntityForInsert<T extends EntityTimeStamp>(
2257
+ entity: T,
2258
+ trx?: TrxToken,
2259
+ dateFields?: string[],
2260
+ booleanFields?: string[]
2261
+ ): Promise<any> {
2262
+ await this.verifyReadyForDatabaseAccess(trx)
2263
+ const v: any = { ...entity }
2264
+ v.created_at = this.validateOptionalEntityDate(v.created_at, true)!
2265
+ v.updated_at = this.validateOptionalEntityDate(v.updated_at, true)!
2266
+ if (!v.created_at) delete v.created_at
2267
+ if (!v.updated_at) delete v.updated_at
2268
+ if (dateFields) {
2269
+ for (const df of dateFields) {
2270
+ if (v[df]) v[df] = this.validateOptionalEntityDate(v[df])
2271
+ }
2272
+ }
2273
+ if (booleanFields) {
2274
+ for (const df of booleanFields) {
2275
+ if (entity[df] !== undefined) entity[df] = !!entity[df] ? 1 : 0
2276
+ }
2277
+ }
2278
+ for (const key of Object.keys(v)) {
2279
+ const val = v[key]
2280
+ if (Array.isArray(val) && (val.length === 0 || Number.isInteger(val[0]))) {
2281
+ v[key] = Uint8Array.from(val)
2282
+ } else if (val === null) {
2283
+ v[key] = undefined
2284
+ }
2285
+ }
2286
+ this.isDirty = true
2287
+ return v
2288
+ }
2289
+
2290
+ async validateRawTransaction(t: TableTransaction, trx?: TrxToken): Promise<void> {
2291
+ // if there is no txid or there is a rawTransaction return what we have.
2292
+ if (t.rawTx || !t.txid) return
2293
+
2294
+ // rawTransaction is missing, see if we moved it ...
2295
+
2296
+ const rawTx = await this.getRawTxOfKnownValidTransaction(t.txid, undefined, undefined, trx)
2297
+ if (!rawTx) return
2298
+ t.rawTx = rawTx
2299
+ }
2300
+
2301
+ async adminStats(adminIdentityKey: string): Promise<StorageAdminStats> {
2302
+ throw new Error('Method intentionally not implemented for personal storage.')
2303
+ }
2304
+ }