@bsv/wallet-toolbox 2.1.25 → 3.0.0-alpha.0

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 (257) hide show
  1. package/docs/CREATEACTION_BLOCKERS.md +391 -0
  2. package/docs/CUTOVER_RUNBOOK.md +95 -0
  3. package/docs/REQUIREMENTS_COMPLIANCE.md +157 -0
  4. package/docs/ROLLOUT_PLAN.md +273 -0
  5. package/docs/SESSION_HANDOFF.md +298 -0
  6. package/docs/STORAGE_METHOD_WIRING.md +176 -0
  7. package/docs/client.md +3765 -1325
  8. package/docs/monitor.md +255 -33
  9. package/docs/services.md +304 -280
  10. package/docs/setup.md +24 -24
  11. package/docs/storage.md +2783 -251
  12. package/docs/v3-upgrade/index.html +911 -0
  13. package/docs/wallet.md +4956 -9455
  14. package/out/src/Wallet.d.ts.map +1 -1
  15. package/out/src/Wallet.js.map +1 -1
  16. package/out/src/WalletLogger.d.ts.map +1 -1
  17. package/out/src/WalletLogger.js.map +1 -1
  18. package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -1
  19. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
  20. package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
  21. package/out/src/entropy/EntropyCollector.d.ts.map +1 -1
  22. package/out/src/entropy/EntropyCollector.js.map +1 -1
  23. package/out/src/monitor/LeasedMonitorTask.d.ts +43 -0
  24. package/out/src/monitor/LeasedMonitorTask.d.ts.map +1 -0
  25. package/out/src/monitor/LeasedMonitorTask.js +89 -0
  26. package/out/src/monitor/LeasedMonitorTask.js.map +1 -0
  27. package/out/src/monitor/Monitor.d.ts +7 -0
  28. package/out/src/monitor/Monitor.d.ts.map +1 -1
  29. package/out/src/monitor/Monitor.js +7 -0
  30. package/out/src/monitor/Monitor.js.map +1 -1
  31. package/out/src/monitor/MonitorDaemon.d.ts.map +1 -1
  32. package/out/src/monitor/MonitorDaemon.js.map +1 -1
  33. package/out/src/monitor/V7LeasedTask.d.ts +43 -0
  34. package/out/src/monitor/V7LeasedTask.d.ts.map +1 -0
  35. package/out/src/monitor/V7LeasedTask.js +89 -0
  36. package/out/src/monitor/V7LeasedTask.js.map +1 -0
  37. package/out/src/monitor/index.all.d.ts +1 -0
  38. package/out/src/monitor/index.all.d.ts.map +1 -1
  39. package/out/src/monitor/index.all.js +1 -0
  40. package/out/src/monitor/index.all.js.map +1 -1
  41. package/out/src/monitor/tasks/TaskCheckForProofs.d.ts +2 -0
  42. package/out/src/monitor/tasks/TaskCheckForProofs.d.ts.map +1 -1
  43. package/out/src/monitor/tasks/TaskCheckForProofs.js +55 -0
  44. package/out/src/monitor/tasks/TaskCheckForProofs.js.map +1 -1
  45. package/out/src/monitor/tasks/TaskSendWaiting.d.ts.map +1 -1
  46. package/out/src/monitor/tasks/TaskSendWaiting.js.map +1 -1
  47. package/out/src/sdk/WalletStorage.interfaces.d.ts +59 -59
  48. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  49. package/out/src/sdk/types.d.ts +32 -0
  50. package/out/src/sdk/types.d.ts.map +1 -1
  51. package/out/src/sdk/types.js +50 -1
  52. package/out/src/sdk/types.js.map +1 -1
  53. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.d.ts.map +1 -1
  54. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.js.map +1 -1
  55. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
  56. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
  57. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.d.ts.map +1 -1
  58. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js.map +1 -1
  59. package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.d.ts.map +1 -1
  60. package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.js.map +1 -1
  61. package/out/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.d.ts.map +1 -1
  62. package/out/src/services/chaintracker/chaintracks/createDefaultKnexChaintracksOptions.js.map +1 -1
  63. package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.d.ts.map +1 -1
  64. package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.js.map +1 -1
  65. package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.d.ts.map +1 -1
  66. package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.js.map +1 -1
  67. package/out/src/services/chaintracker/chaintracks/createKnexChaintracks.d.ts.map +1 -1
  68. package/out/src/services/chaintracker/chaintracks/createKnexChaintracks.js.map +1 -1
  69. package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.d.ts.map +1 -1
  70. package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.js.map +1 -1
  71. package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts.map +1 -1
  72. package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js.map +1 -1
  73. package/out/src/services/chaintracker/chaintracks/util/ChaintracksFs.d.ts.map +1 -1
  74. package/out/src/services/chaintracker/chaintracks/util/ChaintracksFs.js.map +1 -1
  75. package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.d.ts.map +1 -1
  76. package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.js.map +1 -1
  77. package/out/src/services/chaintracker/chaintracks/util/__tests/SingleWriterMultiReaderLock.test.js.map +1 -1
  78. package/out/src/storage/StorageIdb.d.ts.map +1 -1
  79. package/out/src/storage/StorageIdb.js +10 -5
  80. package/out/src/storage/StorageIdb.js.map +1 -1
  81. package/out/src/storage/StorageKnex.d.ts +133 -3
  82. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  83. package/out/src/storage/StorageKnex.js +691 -94
  84. package/out/src/storage/StorageKnex.js.map +1 -1
  85. package/out/src/storage/StorageProvider.d.ts +114 -1
  86. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  87. package/out/src/storage/StorageProvider.js +164 -4
  88. package/out/src/storage/StorageProvider.js.map +1 -1
  89. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  90. package/out/src/storage/WalletStorageManager.js.map +1 -1
  91. package/out/src/storage/idbHelpers.d.ts +5 -0
  92. package/out/src/storage/idbHelpers.d.ts.map +1 -1
  93. package/out/src/storage/idbHelpers.js +42 -0
  94. package/out/src/storage/idbHelpers.js.map +1 -1
  95. package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
  96. package/out/src/storage/methods/attemptToPostReqsToNetwork.js +116 -4
  97. package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +1 -1
  98. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  99. package/out/src/storage/methods/createAction.js +22 -5
  100. package/out/src/storage/methods/createAction.js.map +1 -1
  101. package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
  102. package/out/src/storage/methods/internalizeAction.js +172 -5
  103. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  104. package/out/src/storage/methods/listActionsKnex.d.ts +49 -0
  105. package/out/src/storage/methods/listActionsKnex.d.ts.map +1 -1
  106. package/out/src/storage/methods/listActionsKnex.js +179 -69
  107. package/out/src/storage/methods/listActionsKnex.js.map +1 -1
  108. package/out/src/storage/methods/listOutputsKnex.d.ts.map +1 -1
  109. package/out/src/storage/methods/listOutputsKnex.js +51 -4
  110. package/out/src/storage/methods/listOutputsKnex.js.map +1 -1
  111. package/out/src/storage/methods/processAction.d.ts.map +1 -1
  112. package/out/src/storage/methods/processAction.js +82 -14
  113. package/out/src/storage/methods/processAction.js.map +1 -1
  114. package/out/src/storage/methods/purgeData.d.ts.map +1 -1
  115. package/out/src/storage/methods/purgeData.js +22 -14
  116. package/out/src/storage/methods/purgeData.js.map +1 -1
  117. package/out/src/storage/methods/reviewStatus.d.ts.map +1 -1
  118. package/out/src/storage/methods/reviewStatus.js +18 -9
  119. package/out/src/storage/methods/reviewStatus.js.map +1 -1
  120. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  121. package/out/src/storage/schema/KnexMigrations.js +134 -0
  122. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  123. package/out/src/storage/schema/StorageIdbSchema.d.ts +43 -2
  124. package/out/src/storage/schema/StorageIdbSchema.d.ts.map +1 -1
  125. package/out/src/storage/schema/__tests/backfill.runner.test.d.ts +2 -0
  126. package/out/src/storage/schema/__tests/backfill.runner.test.d.ts.map +1 -0
  127. package/out/src/storage/schema/__tests/backfill.runner.test.js +148 -0
  128. package/out/src/storage/schema/__tests/backfill.runner.test.js.map +1 -0
  129. package/out/src/storage/schema/__tests/backfill.test.d.ts +2 -0
  130. package/out/src/storage/schema/__tests/backfill.test.d.ts.map +1 -0
  131. package/out/src/storage/schema/__tests/backfill.test.js +96 -0
  132. package/out/src/storage/schema/__tests/backfill.test.js.map +1 -0
  133. package/out/src/storage/schema/__tests/processingFsm.test.d.ts +2 -0
  134. package/out/src/storage/schema/__tests/processingFsm.test.d.ts.map +1 -0
  135. package/out/src/storage/schema/__tests/processingFsm.test.js +42 -0
  136. package/out/src/storage/schema/__tests/processingFsm.test.js.map +1 -0
  137. package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.d.ts +2 -0
  138. package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.d.ts.map +1 -0
  139. package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.js +82 -0
  140. package/out/src/storage/schema/__tests/processingFsmLegacyMapping.test.js.map +1 -0
  141. package/out/src/storage/schema/__tests/spendabilityRule.test.d.ts +2 -0
  142. package/out/src/storage/schema/__tests/spendabilityRule.test.d.ts.map +1 -0
  143. package/out/src/storage/schema/__tests/spendabilityRule.test.js +29 -0
  144. package/out/src/storage/schema/__tests/spendabilityRule.test.js.map +1 -0
  145. package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.d.ts +2 -0
  146. package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.d.ts.map +1 -0
  147. package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.js +77 -0
  148. package/out/src/storage/schema/__tests/v7FsmLegacyMapping.test.js.map +1 -0
  149. package/out/src/storage/schema/backfill.d.ts +35 -0
  150. package/out/src/storage/schema/backfill.d.ts.map +1 -0
  151. package/out/src/storage/schema/backfill.idb.d.ts +32 -0
  152. package/out/src/storage/schema/backfill.idb.d.ts.map +1 -0
  153. package/out/src/storage/schema/backfill.idb.js +95 -0
  154. package/out/src/storage/schema/backfill.idb.js.map +1 -0
  155. package/out/src/storage/schema/backfill.js +150 -0
  156. package/out/src/storage/schema/backfill.js.map +1 -0
  157. package/out/src/storage/schema/backfill.knex.d.ts +32 -0
  158. package/out/src/storage/schema/backfill.knex.d.ts.map +1 -0
  159. package/out/src/storage/schema/backfill.knex.js +240 -0
  160. package/out/src/storage/schema/backfill.knex.js.map +1 -0
  161. package/out/src/storage/schema/backfill.runner.d.ts +63 -0
  162. package/out/src/storage/schema/backfill.runner.d.ts.map +1 -0
  163. package/out/src/storage/schema/backfill.runner.js +64 -0
  164. package/out/src/storage/schema/backfill.runner.js.map +1 -0
  165. package/out/src/storage/schema/coinbaseMaturityBackfill.d.ts +25 -0
  166. package/out/src/storage/schema/coinbaseMaturityBackfill.d.ts.map +1 -0
  167. package/out/src/storage/schema/coinbaseMaturityBackfill.js +75 -0
  168. package/out/src/storage/schema/coinbaseMaturityBackfill.js.map +1 -0
  169. package/out/src/storage/schema/monitorLease.d.ts +57 -0
  170. package/out/src/storage/schema/monitorLease.d.ts.map +1 -0
  171. package/out/src/storage/schema/monitorLease.js +101 -0
  172. package/out/src/storage/schema/monitorLease.js.map +1 -0
  173. package/out/src/storage/schema/processingFsm.d.ts +27 -0
  174. package/out/src/storage/schema/processingFsm.d.ts.map +1 -0
  175. package/out/src/storage/schema/processingFsm.js +132 -0
  176. package/out/src/storage/schema/processingFsm.js.map +1 -0
  177. package/out/src/storage/schema/schemaCutover.d.ts +34 -0
  178. package/out/src/storage/schema/schemaCutover.d.ts.map +1 -0
  179. package/out/src/storage/schema/schemaCutover.js +230 -0
  180. package/out/src/storage/schema/schemaCutover.js.map +1 -0
  181. package/out/src/storage/schema/schemaCutoverIdb.d.ts +26 -0
  182. package/out/src/storage/schema/schemaCutoverIdb.d.ts.map +1 -0
  183. package/out/src/storage/schema/schemaCutoverIdb.js +90 -0
  184. package/out/src/storage/schema/schemaCutoverIdb.js.map +1 -0
  185. package/out/src/storage/schema/spendabilityRefresh.d.ts +49 -0
  186. package/out/src/storage/schema/spendabilityRefresh.d.ts.map +1 -0
  187. package/out/src/storage/schema/spendabilityRefresh.js +120 -0
  188. package/out/src/storage/schema/spendabilityRefresh.js.map +1 -0
  189. package/out/src/storage/schema/spendabilityRule.d.ts +45 -0
  190. package/out/src/storage/schema/spendabilityRule.d.ts.map +1 -0
  191. package/out/src/storage/schema/spendabilityRule.js +52 -0
  192. package/out/src/storage/schema/spendabilityRule.js.map +1 -0
  193. package/out/src/storage/schema/tables/TableAction.d.ts +3 -3
  194. package/out/src/storage/schema/tables/TableAction.d.ts.map +1 -1
  195. package/out/src/storage/schema/tables/TableChainTip.d.ts +1 -1
  196. package/out/src/storage/schema/tables/TableMonitorLease.d.ts +1 -1
  197. package/out/src/storage/schema/tables/TableOutput.d.ts +7 -0
  198. package/out/src/storage/schema/tables/TableOutput.d.ts.map +1 -1
  199. package/out/src/storage/schema/tables/TableOutput.js.map +1 -1
  200. package/out/src/storage/schema/tables/TableTransactionNew.d.ts +50 -0
  201. package/out/src/storage/schema/tables/TableTransactionNew.d.ts.map +1 -0
  202. package/out/src/storage/schema/tables/TableTransactionNew.js +3 -0
  203. package/out/src/storage/schema/tables/TableTransactionNew.js.map +1 -0
  204. package/out/src/storage/schema/tables/TableTxAudit.d.ts +1 -1
  205. package/out/src/storage/schema/tables/index.d.ts +5 -0
  206. package/out/src/storage/schema/tables/index.d.ts.map +1 -1
  207. package/out/src/storage/schema/tables/index.js +5 -0
  208. package/out/src/storage/schema/tables/index.js.map +1 -1
  209. package/out/src/storage/schema/transactionCrud.d.ts +41 -0
  210. package/out/src/storage/schema/transactionCrud.d.ts.map +1 -0
  211. package/out/src/storage/schema/transactionCrud.js +205 -0
  212. package/out/src/storage/schema/transactionCrud.js.map +1 -0
  213. package/out/src/storage/schema/transactionService.d.ts +315 -0
  214. package/out/src/storage/schema/transactionService.d.ts.map +1 -0
  215. package/out/src/storage/schema/transactionService.js +783 -0
  216. package/out/src/storage/schema/transactionService.js.map +1 -0
  217. package/out/src/storage/schema/txAudit.d.ts +33 -0
  218. package/out/src/storage/schema/txAudit.d.ts.map +1 -0
  219. package/out/src/storage/schema/txAudit.js +64 -0
  220. package/out/src/storage/schema/txAudit.js.map +1 -0
  221. package/out/src/storage/schema/v7Backfill.d.ts.map +1 -1
  222. package/out/src/storage/schema/v7Backfill.js +4 -1
  223. package/out/src/storage/schema/v7Backfill.js.map +1 -1
  224. package/out/src/storage/schema/v7Backfill.runner.d.ts.map +1 -1
  225. package/out/src/storage/schema/v7Backfill.runner.js +3 -1
  226. package/out/src/storage/schema/v7Backfill.runner.js.map +1 -1
  227. package/out/src/storage/schema/v7CoinbaseMaturityBackfill.d.ts +25 -0
  228. package/out/src/storage/schema/v7CoinbaseMaturityBackfill.d.ts.map +1 -0
  229. package/out/src/storage/schema/v7CoinbaseMaturityBackfill.js +75 -0
  230. package/out/src/storage/schema/v7CoinbaseMaturityBackfill.js.map +1 -0
  231. package/out/src/storage/schema/v7Crud.d.ts +5 -3
  232. package/out/src/storage/schema/v7Crud.d.ts.map +1 -1
  233. package/out/src/storage/schema/v7Crud.js +11 -9
  234. package/out/src/storage/schema/v7Crud.js.map +1 -1
  235. package/out/src/storage/schema/v7Cutover.d.ts +34 -0
  236. package/out/src/storage/schema/v7Cutover.d.ts.map +1 -0
  237. package/out/src/storage/schema/v7Cutover.js +223 -0
  238. package/out/src/storage/schema/v7Cutover.js.map +1 -0
  239. package/out/src/storage/schema/v7CutoverIdb.d.ts +26 -0
  240. package/out/src/storage/schema/v7CutoverIdb.d.ts.map +1 -0
  241. package/out/src/storage/schema/v7CutoverIdb.js +90 -0
  242. package/out/src/storage/schema/v7CutoverIdb.js.map +1 -0
  243. package/out/src/storage/schema/v7Fsm.d.ts.map +1 -1
  244. package/out/src/storage/schema/v7Fsm.js +22 -6
  245. package/out/src/storage/schema/v7Fsm.js.map +1 -1
  246. package/out/src/storage/schema/v7Service.d.ts +305 -0
  247. package/out/src/storage/schema/v7Service.d.ts.map +1 -0
  248. package/out/src/storage/schema/v7Service.js +757 -0
  249. package/out/src/storage/schema/v7Service.js.map +1 -0
  250. package/out/src/storage/schema/v7SpendabilityRefresh.d.ts +49 -0
  251. package/out/src/storage/schema/v7SpendabilityRefresh.d.ts.map +1 -0
  252. package/out/src/storage/schema/v7SpendabilityRefresh.js +111 -0
  253. package/out/src/storage/schema/v7SpendabilityRefresh.js.map +1 -0
  254. package/out/src/storage/storageProviderHelpers.js +1 -1
  255. package/out/src/storage/storageProviderHelpers.js.map +1 -1
  256. package/out/src/utility/Format.js.map +1 -1
  257. package/package.json +7 -4
@@ -10,17 +10,204 @@ const listOutputsKnex_1 = require("./methods/listOutputsKnex");
10
10
  const reviewStatus_1 = require("./methods/reviewStatus");
11
11
  const WERR_errors_1 = require("../sdk/WERR_errors");
12
12
  const utilityHelpers_1 = require("../utility/utilityHelpers");
13
+ const types_1 = require("../sdk/types");
14
+ const transactionService_1 = require("./schema/transactionService");
13
15
  class StorageKnex extends StorageProvider_1.StorageProvider {
14
16
  constructor(options) {
15
17
  super(options);
18
+ this._postCutoverCache = undefined;
16
19
  this._verifiedReadyForDatabaseAccess = false;
17
20
  if (!options.knex)
18
21
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('options.knex', 'valid');
19
22
  this.knex = options.knex;
20
23
  }
24
+ getTransactionService() {
25
+ // The new-schema transaction service is only valid post-cutover. Pre-cutover,
26
+ // the `transactions` table is the legacy schema and queries via this service
27
+ // would return legacy ids that don't satisfy the `tx_audit.transactionId`
28
+ // FK (which targets `transactions_new`). Callers gate on `undefined` and
29
+ // fall back to the legacy storage path.
30
+ if (this._postCutoverCache !== true)
31
+ return undefined;
32
+ return new transactionService_1.TransactionService(this.knex);
33
+ }
34
+ /**
35
+ * Eagerly warm the `_postCutoverCache` so sync query builders such as
36
+ * `findOutputsQuery` can branch on cutover state without an extra await.
37
+ * Callers that have invoked `makeAvailable` are guaranteed the cache has
38
+ * been populated.
39
+ */
40
+ async makeAvailable() {
41
+ const settings = await super.makeAvailable();
42
+ if (this._postCutoverCache === undefined) {
43
+ this._postCutoverCache = await this.knex.schema.hasTable('transactions_legacy');
44
+ }
45
+ return settings;
46
+ }
47
+ /**
48
+ * Returns true when the database has been through `runSchemaCutover` (i.e.
49
+ * `transactions_legacy` exists). Result is cached for the lifetime of this
50
+ * StorageKnex instance.
51
+ */
52
+ async isPostCutover() {
53
+ if (this._postCutoverCache === undefined) {
54
+ this._postCutoverCache = await this.knex.schema.hasTable('transactions_legacy');
55
+ }
56
+ return this._postCutoverCache;
57
+ }
58
+ /**
59
+ * Maps legacy TransactionStatus values to their ProcessingStatus equivalents
60
+ * for use in `outputs JOIN transactions` WHERE clauses post-cutover.
61
+ */
62
+ legacyStatiToProcessing(stati) {
63
+ const result = new Set();
64
+ for (const s of stati) {
65
+ result.add((0, types_1.transactionStatusToProcessing)(s));
66
+ if (s === 'unproven') {
67
+ // 'sent','seen','seen_multi','unconfirmed' all map to legacy 'unproven'
68
+ result.add('sent');
69
+ result.add('seen');
70
+ result.add('seen_multi');
71
+ result.add('unconfirmed');
72
+ }
73
+ }
74
+ return [...result];
75
+ }
76
+ /**
77
+ * Returns the canonical name of the `proven_txs` table — `proven_txs_legacy`
78
+ * post-cutover, `proven_txs` otherwise.
79
+ *
80
+ * Public so that helper modules (reviewStatus, purgeData) can resolve the
81
+ * correct table name without duplicating the post-cutover detection logic.
82
+ */
83
+ async provenTxsTableName() {
84
+ return (await this.isPostCutover()) ? 'proven_txs_legacy' : 'proven_txs';
85
+ }
86
+ /**
87
+ * Returns the canonical name of the `proven_tx_reqs` table —
88
+ * `proven_tx_reqs_legacy` post-cutover, `proven_tx_reqs` otherwise.
89
+ *
90
+ * Public so that helper modules (reviewStatus, purgeData) can resolve the
91
+ * correct table name without duplicating the post-cutover detection logic.
92
+ */
93
+ async provenTxReqsTableName() {
94
+ return (await this.isPostCutover()) ? 'proven_tx_reqs_legacy' : 'proven_tx_reqs';
95
+ }
96
+ /**
97
+ * Insert a legacy-shaped transaction row into the correct table:
98
+ * - Post-cutover: `transactions_legacy` (the renamed legacy schema table)
99
+ * - Pre-cutover: `transactions` (the standard table, same behaviour as `insertTransaction`)
100
+ *
101
+ * This allows `createAction` to store unsigned rows (txid unknown) without
102
+ * conflicting with the new `transactions` table's NOT NULL txid constraint.
103
+ * A new `transactions` row + `actions` row are created later by `processAction`
104
+ * once the real txid is known.
105
+ *
106
+ * On SQLite, foreign key enforcement is temporarily disabled while inserting
107
+ * into `transactions_legacy` because the referenced `proven_txs` table was
108
+ * renamed to `proven_txs_legacy` during the the schema cutover. Since `provenTxId`
109
+ * is always NULL for new unsigned transactions this is semantically safe.
110
+ */
111
+ async insertLegacyTransaction(tx, trx) {
112
+ const e = await this.validateEntityForInsert(tx, trx);
113
+ if (e.transactionId === 0)
114
+ delete e.transactionId;
115
+ const hasLegacyTable = await this.knex.schema.hasTable('transactions_legacy');
116
+ const tableName = hasLegacyTable ? 'transactions_legacy' : 'transactions';
117
+ const isSqlite = this.dbtype === 'SQLite';
118
+ // On SQLite post-cutover the `transactions_legacy` FK to `proven_txs` is dangling
119
+ // (proven_txs was renamed to proven_txs_legacy). Temporarily disable FK checks for
120
+ // this insert since provenTxId is always NULL for a brand-new unsigned transaction.
121
+ if (isSqlite && hasLegacyTable)
122
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
123
+ try {
124
+ const [id] = await this.toDb(trx)(tableName).insert(e);
125
+ tx.transactionId = id;
126
+ return tx.transactionId;
127
+ }
128
+ finally {
129
+ if (isSqlite && hasLegacyTable)
130
+ await this.knex.raw('PRAGMA foreign_keys = ON');
131
+ }
132
+ }
133
+ /**
134
+ * Insert a `tx_labels_map` row for a legacy transaction that does not yet
135
+ * have a `actions.actionId`. On post-cutover SQLite, temporarily disables
136
+ * FK checks so that the legacy transactionId (which is not yet an actionId)
137
+ * can be written. processAction will repoint it via repointLabelsToActionId.
138
+ *
139
+ * Note: bypasses `validateEntityForInsert` (and therefore `verifyReadyForDatabaseAccess`
140
+ * which would re-enable FK checks) to ensure the PRAGMA=OFF persists across
141
+ * the insert statement.
142
+ */
143
+ async insertLegacyTxLabelMap(labelMap, trx) {
144
+ const isSqlite = this.dbtype === 'SQLite';
145
+ const hasLegacyTable = isSqlite ? await this.knex.schema.hasTable('transactions_legacy') : false;
146
+ if (!isSqlite || !hasLegacyTable) {
147
+ // Pre-cutover or non-SQLite: standard insert is safe (no dangling FK issue).
148
+ await this.insertTxLabelMap(labelMap, trx);
149
+ return;
150
+ }
151
+ // Post-cutover SQLite: tx_labels_map.transactionId FK now points to actions.actionId.
152
+ // We are writing a legacyTransactionId (no corresponding actions row yet).
153
+ // Temporarily disable FK checks for this single insert.
154
+ const now = new Date();
155
+ const e = {
156
+ created_at: now,
157
+ updated_at: now,
158
+ txLabelId: labelMap.txLabelId,
159
+ transactionId: labelMap.transactionId,
160
+ isDeleted: labelMap.isDeleted ? 1 : 0
161
+ };
162
+ // Use the raw knex handle (not toDb(trx)) to send the PRAGMA on the same connection.
163
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
164
+ try {
165
+ await this.knex('tx_labels_map').insert(e);
166
+ }
167
+ finally {
168
+ await this.knex.raw('PRAGMA foreign_keys = ON');
169
+ }
170
+ }
21
171
  async readSettings() {
22
172
  return this.validateEntity((0, utilityHelpers_1.verifyOne)(await this.toDb(undefined)('settings')));
23
173
  }
174
+ /**
175
+ * Synthesise a `ProvenOrRawTx` from a `TableTransactionNew` row.
176
+ *
177
+ * - If the new schema row carries a `merklePath` (i.e. processing is `proven`), build
178
+ * a `TableProvenTx`-shaped object so the BEEF assembly code can extract the
179
+ * merkle path and merge it into the Beef without hitting the legacy tables.
180
+ * - If the new schema row only has `rawTx` (broadcast/queued state), return it as the
181
+ * raw-tx path.
182
+ * - Returns `null` when the new schema row has neither rawTx nor merklePath.
183
+ */
184
+ newTxToProvenOrRawTx(row, txid) {
185
+ var _a, _b, _c, _d;
186
+ // Only use the new-schema row as the primary source when it carries a *non-empty*
187
+ // merklePath and a rawTx — both are required for `handleProvenTxBranch` to assemble
188
+ // a valid BEEF. An empty merklePath (e.g. a backfill race or a req whose proof was
189
+ // never stored) must fall through to the legacy `proven_txs_legacy` path.
190
+ // Raw-tx-only rows (no merklePath) are likewise forwarded to the legacy path so
191
+ // that the legacy `inputBEEF` column is available for recursive BEEF assembly.
192
+ if (row.merklePath != null && row.merklePath.length > 0 && row.rawTx != null && row.rawTx.length > 0) {
193
+ // Full proof available — synthesise TableProvenTx shape
194
+ const proven = {
195
+ created_at: row.created_at,
196
+ updated_at: row.updated_at,
197
+ provenTxId: row.transactionId,
198
+ txid,
199
+ height: (_a = row.height) !== null && _a !== void 0 ? _a : 0,
200
+ index: (_b = row.merkleIndex) !== null && _b !== void 0 ? _b : 0,
201
+ merklePath: row.merklePath,
202
+ rawTx: row.rawTx,
203
+ blockHash: (_c = row.blockHash) !== null && _c !== void 0 ? _c : '',
204
+ merkleRoot: (_d = row.merkleRoot) !== null && _d !== void 0 ? _d : ''
205
+ };
206
+ return { proven, rawTx: undefined, inputBEEF: undefined };
207
+ }
208
+ // No complete proof in new-schema row — let the caller fall through to legacy tables.
209
+ return null;
210
+ }
24
211
  async getProvenOrRawTx(txid, trx) {
25
212
  const k = this.toDb(trx);
26
213
  const r = {
@@ -28,9 +215,29 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
28
215
  rawTx: undefined,
29
216
  inputBEEF: undefined
30
217
  };
218
+ // Post-cutover: try new `transactions` table first. The new schema row is the single
219
+ // source of truth for rawTx and merklePath; legacy tables are read-only
220
+ // archives after cutover.
221
+ if (await this.isPostCutover()) {
222
+ try {
223
+ const txSvc = this.getTransactionService();
224
+ if (txSvc != null) {
225
+ const newTx = await txSvc.findByTxid(txid);
226
+ if (newTx != null) {
227
+ const newTxResult = this.newTxToProvenOrRawTx(newTx, txid);
228
+ if (newTxResult != null)
229
+ return newTxResult;
230
+ }
231
+ }
232
+ }
233
+ catch (_a) {
234
+ // new table lookup failed (e.g. table not yet available) — fall through to legacy
235
+ }
236
+ }
31
237
  r.proven = (0, utilityHelpers_1.verifyOneOrNone)(await this.findProvenTxs({ partial: { txid } }));
32
238
  if (r.proven == null) {
33
- const reqRawTx = (0, utilityHelpers_1.verifyOneOrNone)(await k('proven_tx_reqs')
239
+ const reqTable = await this.provenTxReqsTableName();
240
+ const reqRawTx = (0, utilityHelpers_1.verifyOneOrNone)(await k(reqTable)
34
241
  .where('txid', txid)
35
242
  .whereIn('status', ['unsent', 'unmined', 'unconfirmed', 'sending', 'nosend', 'completed'])
36
243
  .select('rawTx', 'inputBEEF'));
@@ -52,12 +259,31 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
52
259
  return rs;
53
260
  }
54
261
  async getRawTxSlice(txid, offset, length, trx) {
262
+ // Post-cutover: try new transactions table first — the new table is the canonical store
263
+ // for rawTx after the cutover. Apply the slice in JS to avoid raw SQL
264
+ // column naming differences between the two schemas.
265
+ if (await this.isPostCutover()) {
266
+ try {
267
+ const txSvc = this.getTransactionService();
268
+ if (txSvc != null) {
269
+ const newTx = await txSvc.findByTxid(txid);
270
+ if ((newTx === null || newTx === void 0 ? void 0 : newTx.rawTx) != null) {
271
+ return newTx.rawTx.slice(offset, offset + length);
272
+ }
273
+ }
274
+ }
275
+ catch (_a) {
276
+ // new table lookup failed — fall through to legacy tables
277
+ }
278
+ }
55
279
  const sub = this.dbTypeSubstring('rawTx', offset + 1, length);
56
- let rs = await this.toDb(trx).raw(`select ${sub} as rawTx from proven_txs where txid = '${txid}'`);
280
+ const provenTable = await this.provenTxsTableName();
281
+ const reqTable = await this.provenTxReqsTableName();
282
+ let rs = await this.toDb(trx).raw(`select ${sub} as rawTx from ${provenTable} where txid = '${txid}'`);
57
283
  const proven = (0, utilityHelpers_1.verifyOneOrNone)(this.normaliseKnexRawResult(rs));
58
284
  if ((proven === null || proven === void 0 ? void 0 : proven.rawTx) != null)
59
285
  return Array.from(proven.rawTx);
60
- rs = await this.toDb(trx).raw(`select ${sub} as rawTx from proven_tx_reqs where txid = '${txid}' and status in ('unsent', 'nosend', 'sending', 'unmined', 'completed', 'unfail')`);
286
+ rs = await this.toDb(trx).raw(`select ${sub} as rawTx from ${reqTable} where txid = '${txid}' and status in ('unsent', 'nosend', 'sending', 'unmined', 'completed', 'unfail')`);
61
287
  const req = (0, utilityHelpers_1.verifyOneOrNone)(this.normaliseKnexRawResult(rs));
62
288
  return (req === null || req === void 0 ? void 0 : req.rawTx) != null ? Array.from(req.rawTx) : undefined;
63
289
  }
@@ -67,7 +293,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
67
293
  if (!this.isAvailable())
68
294
  await this.makeAvailable();
69
295
  if (Number.isInteger(offset) && Number.isInteger(length)) {
70
- return this.getRawTxSlice(txid, offset, length, trx);
296
+ return await this.getRawTxSlice(txid, offset, length, trx);
71
297
  }
72
298
  const r = await this.getProvenOrRawTx(txid, trx);
73
299
  return r.proven != null ? r.proven.rawTx : r.rawTx;
@@ -89,6 +315,25 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
89
315
  return q;
90
316
  }
91
317
  async getProvenTxsForUser(args) {
318
+ const postCutover = await this.isPostCutover();
319
+ if (postCutover) {
320
+ // Post-cutover: proven_txs → proven_txs_legacy; transactions → transactions_legacy
321
+ const k = this.toDb(args.trx);
322
+ let q = k('proven_txs_legacy').where(function () {
323
+ this.whereExists(k
324
+ .select('*')
325
+ .from('transactions_legacy')
326
+ .whereRaw(`proven_txs_legacy.provenTxId = transactions_legacy.provenTxId and transactions_legacy.userId = ${args.userId}`));
327
+ });
328
+ if (args.paged != null) {
329
+ q = q.limit(args.paged.limit);
330
+ q = q.offset(args.paged.offset || 0);
331
+ }
332
+ if (args.since != null)
333
+ q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
334
+ const rs = await q;
335
+ return this.validateEntities(rs);
336
+ }
92
337
  const q = this.getProvenTxsForUserQuery(args);
93
338
  const rs = await q;
94
339
  return this.validateEntities(rs);
@@ -110,6 +355,25 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
110
355
  return q;
111
356
  }
112
357
  async getProvenTxReqsForUser(args) {
358
+ const postCutover = await this.isPostCutover();
359
+ if (postCutover) {
360
+ // Post-cutover: proven_tx_reqs → proven_tx_reqs_legacy; transactions → transactions_legacy
361
+ const k = this.toDb(args.trx);
362
+ let q = k('proven_tx_reqs_legacy').where(function () {
363
+ this.whereExists(k
364
+ .select('*')
365
+ .from('transactions_legacy')
366
+ .whereRaw(`proven_tx_reqs_legacy.txid = transactions_legacy.txid and transactions_legacy.userId = ${args.userId}`));
367
+ });
368
+ if (args.paged != null) {
369
+ q = q.limit(args.paged.limit);
370
+ q = q.offset(args.paged.offset || 0);
371
+ }
372
+ if (args.since != null)
373
+ q = q.where('updated_at', '>=', this.validateDateForWhere(args.since));
374
+ const rs = await q;
375
+ return this.validateEntities(rs, undefined, ['notified']);
376
+ }
113
377
  const q = this.getProvenTxReqsForUserQuery(args);
114
378
  const rs = await q;
115
379
  return this.validateEntities(rs, undefined, ['notified']);
@@ -166,7 +430,8 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
166
430
  const e = await this.validateEntityForInsert(tx, trx);
167
431
  if (e.provenTxId === 0)
168
432
  delete e.provenTxId;
169
- const [id] = await this.toDb(trx)('proven_txs').insert(e);
433
+ const tableName = await this.provenTxsTableName();
434
+ const [id] = await this.toDb(trx)(tableName).insert(e);
170
435
  tx.provenTxId = id;
171
436
  return tx.provenTxId;
172
437
  }
@@ -174,7 +439,10 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
174
439
  const e = await this.validateEntityForInsert(tx, trx);
175
440
  if (e.provenTxReqId === 0)
176
441
  delete e.provenTxReqId;
177
- const [id] = await this.toDb(trx)('proven_tx_reqs').insert(e);
442
+ // Post-cutover: route to proven_tx_reqs_legacy.
443
+ // FK bypass is handled by the caller (disableForeignKeys before any transaction).
444
+ const reqTable = await this.provenTxReqsTableName();
445
+ const [id] = await this.toDb(trx)(reqTable).insert(e);
178
446
  tx.provenTxReqId = id;
179
447
  return tx.provenTxReqId;
180
448
  }
@@ -228,6 +496,28 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
228
496
  const e = await this.validateEntityForInsert(tx, trx);
229
497
  if (e.transactionId === 0)
230
498
  delete e.transactionId;
499
+ // Post-cutover the canonical `transactions` table is the new schema which
500
+ // does not carry legacy columns (description, status, satoshis, version...).
501
+ // Legacy-shaped insertions belong in `transactions_legacy`. We route there
502
+ // automatically so existing callers that still speak the legacy shape keep
503
+ // working without a code change at every call site. FK enforcement is
504
+ // bypassed because the renamed legacy table still references the renamed
505
+ // `proven_txs_legacy` via its original `proven_txs` FK column name.
506
+ const isSqlite = this.dbtype === 'SQLite';
507
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
508
+ if (postCutover) {
509
+ if (isSqlite)
510
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
511
+ try {
512
+ const [id] = await this.toDb(trx)('transactions_legacy').insert(e);
513
+ tx.transactionId = id;
514
+ return tx.transactionId;
515
+ }
516
+ finally {
517
+ if (isSqlite)
518
+ await this.knex.raw('PRAGMA foreign_keys = ON');
519
+ }
520
+ }
231
521
  const [id] = await this.toDb(trx)('transactions').insert(e);
232
522
  tx.transactionId = id;
233
523
  return tx.transactionId;
@@ -236,21 +526,42 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
236
526
  const e = await this.validateEntityForInsert(commission, trx);
237
527
  if (e.commissionId === 0)
238
528
  delete e.commissionId;
239
- const [id] = await this.toDb(trx)('commissions').insert(e);
240
- commission.commissionId = id;
241
- return commission.commissionId;
529
+ // Post-cutover bridge-period: commissions.transactionId FKs new transactions.
530
+ // During createAction the transactionId references transactions_legacy, so bypass FK.
531
+ const isSqlite = this.dbtype === 'SQLite';
532
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
533
+ if (postCutover)
534
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
535
+ try {
536
+ const [id] = await this.toDb(trx)('commissions').insert(e);
537
+ commission.commissionId = id;
538
+ return commission.commissionId;
539
+ }
540
+ finally {
541
+ if (postCutover)
542
+ await this.knex.raw('PRAGMA foreign_keys = ON');
543
+ }
242
544
  }
243
545
  async insertOutput(output, trx) {
546
+ const e = await this.validateEntityForInsert(output, trx);
547
+ if (e.outputId === 0)
548
+ delete e.outputId;
549
+ // Post-cutover bridge-period: outputs.transactionId FKs new transactions,
550
+ // but new unsigned outputs reference transactions_legacy (bridge-period rows).
551
+ // Temporarily disable FK checks on SQLite. Safe because once processAction
552
+ // runs, spentBy and transactionId are remapped to real new-schema IDs.
553
+ const isSqlite = this.dbtype === 'SQLite';
554
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
555
+ if (postCutover)
556
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
244
557
  try {
245
- const e = await this.validateEntityForInsert(output, trx);
246
- if (e.outputId === 0)
247
- delete e.outputId;
248
558
  const [id] = await this.toDb(trx)('outputs').insert(e);
249
559
  output.outputId = id;
250
560
  return output.outputId;
251
561
  }
252
- catch (e) {
253
- throw e;
562
+ finally {
563
+ if (postCutover)
564
+ await this.knex.raw('PRAGMA foreign_keys = ON');
254
565
  }
255
566
  }
256
567
  async insertOutputTag(tag, trx) {
@@ -275,6 +586,22 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
275
586
  }
276
587
  async insertTxLabelMap(labelMap, trx) {
277
588
  const e = await this.validateEntityForInsert(labelMap, trx, undefined, ['isDeleted']);
589
+ // Post-cutover SQLite: tx_labels_map.transactionId references actions.actionId
590
+ // (rebuilt during schema cutover). Sync paths that import legacy rows still
591
+ // carry the legacy transactionId until repointLabelsToActionId remaps them.
592
+ // Temporarily disable FK checks so the import does not fail.
593
+ const isSqlite = this.dbtype === 'SQLite';
594
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
595
+ if (postCutover) {
596
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
597
+ try {
598
+ await this.toDb(trx)('tx_labels_map').insert(e);
599
+ }
600
+ finally {
601
+ await this.knex.raw('PRAGMA foreign_keys = ON');
602
+ }
603
+ return;
604
+ }
278
605
  await this.toDb(trx)('tx_labels_map').insert(e);
279
606
  }
280
607
  async insertMonitorEvent(event, trx) {
@@ -307,9 +634,22 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
307
634
  }
308
635
  async updateCommission(id, update, trx) {
309
636
  await this.verifyReadyForDatabaseAccess(trx);
310
- return await this.toDb(trx)('commissions')
311
- .where({ commissionId: id })
312
- .update(this.validatePartialForUpdate(update));
637
+ // Post-cutover SQLite: commissions.transactionId FK references the new
638
+ // schema `transactions` table. Legacy callers carry transactionId values
639
+ // that still point at transactions_legacy. Bypass FK so the update lands.
640
+ const isSqlite = this.dbtype === 'SQLite';
641
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
642
+ if (postCutover)
643
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
644
+ try {
645
+ return await this.toDb(trx)('commissions')
646
+ .where({ commissionId: id })
647
+ .update(this.validatePartialForUpdate(update));
648
+ }
649
+ finally {
650
+ if (postCutover)
651
+ await this.knex.raw('PRAGMA foreign_keys = ON');
652
+ }
313
653
  }
314
654
  async updateOutputBasket(id, update, trx) {
315
655
  await this.verifyReadyForDatabaseAccess(trx);
@@ -337,14 +677,15 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
337
677
  }
338
678
  async updateProvenTxReq(id, update, trx) {
339
679
  await this.verifyReadyForDatabaseAccess(trx);
680
+ const reqTable = await this.provenTxReqsTableName();
340
681
  let r;
341
682
  if (Array.isArray(id)) {
342
- r = await this.toDb(trx)('proven_tx_reqs')
683
+ r = await this.toDb(trx)(reqTable)
343
684
  .whereIn('provenTxReqId', id)
344
685
  .update(this.validatePartialForUpdate(update));
345
686
  }
346
687
  else if (Number.isInteger(id)) {
347
- r = await this.toDb(trx)('proven_tx_reqs')
688
+ r = await this.toDb(trx)(reqTable)
348
689
  .where({ provenTxReqId: id })
349
690
  .update(this.validatePartialForUpdate(update));
350
691
  }
@@ -355,7 +696,8 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
355
696
  }
356
697
  async updateProvenTx(id, update, trx) {
357
698
  await this.verifyReadyForDatabaseAccess(trx);
358
- return await this.toDb(trx)('proven_txs')
699
+ const tableName = await this.provenTxsTableName();
700
+ return await this.toDb(trx)(tableName)
359
701
  .where({ provenTxId: id })
360
702
  .update(this.validatePartialForUpdate(update));
361
703
  }
@@ -367,21 +709,36 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
367
709
  }
368
710
  async updateTransaction(id, update, trx) {
369
711
  await this.verifyReadyForDatabaseAccess(trx);
370
- let r;
371
- if (Array.isArray(id)) {
372
- r = await this.toDb(trx)('transactions')
373
- .whereIn('transactionId', id)
374
- .update(this.validatePartialForUpdate(update));
375
- }
376
- else if (Number.isInteger(id)) {
377
- r = await this.toDb(trx)('transactions')
378
- .where({ transactionId: id })
379
- .update(this.validatePartialForUpdate(update));
712
+ // Post-cutover the canonical `transactions` table is the new schema which
713
+ // lacks legacy columns (userId, status, description, satoshis, version, ...).
714
+ // Legacy-shaped updates belong in `transactions_legacy`, matching the
715
+ // routing performed by `insertTransaction`.
716
+ const isSqlite = this.dbtype === 'SQLite';
717
+ const postCutover = isSqlite ? await this.isPostCutover() : false;
718
+ const tableName = postCutover ? 'transactions_legacy' : 'transactions';
719
+ if (postCutover && isSqlite)
720
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
721
+ try {
722
+ let r;
723
+ if (Array.isArray(id)) {
724
+ r = await this.toDb(trx)(tableName)
725
+ .whereIn('transactionId', id)
726
+ .update(this.validatePartialForUpdate(update));
727
+ }
728
+ else if (Number.isInteger(id)) {
729
+ r = await this.toDb(trx)(tableName)
730
+ .where({ transactionId: id })
731
+ .update(this.validatePartialForUpdate(update));
732
+ }
733
+ else {
734
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
735
+ }
736
+ return r;
380
737
  }
381
- else {
382
- throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
738
+ finally {
739
+ if (postCutover && isSqlite)
740
+ await this.knex.raw('PRAGMA foreign_keys = ON');
383
741
  }
384
- return r;
385
742
  }
386
743
  async updateTxLabelMap(transactionId, txLabelId, update, trx) {
387
744
  await this.verifyReadyForDatabaseAccess(trx);
@@ -430,15 +787,18 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
430
787
  sortColumn = 'outputTagId';
431
788
  break;
432
789
  case 'proven_tx_reqs':
790
+ case 'proven_tx_reqs_legacy':
433
791
  sortColumn = 'provenTxReqId';
434
792
  break;
435
793
  case 'proven_txs':
794
+ case 'proven_txs_legacy':
436
795
  sortColumn = 'provenTxId';
437
796
  break;
438
797
  case 'sync_states':
439
798
  sortColumn = 'syncStateId';
440
799
  break;
441
800
  case 'transactions':
801
+ case 'transactions_legacy':
442
802
  sortColumn = 'transactionId';
443
803
  break;
444
804
  case 'tx_labels':
@@ -489,7 +849,19 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
489
849
  }
490
850
  const q = this.setupQuery('outputs', args);
491
851
  if ((args.txStatus != null) && args.txStatus.length > 0) {
492
- q.whereRaw(`(select status from transactions where transactions.transactionId = outputs.transactionId) in (${args.txStatus.map(s => "'" + s + "'").join(',')})`);
852
+ // Post-cutover the `transactions` table carries `processing`
853
+ // (ProcessingStatus), not the legacy `status` column. We translate
854
+ // the requested legacy status set to its ProcessingStatus equivalents
855
+ // when the cutover has been applied; pre-cutover the original `status`
856
+ // column lookup remains correct.
857
+ if (this._postCutoverCache === true) {
858
+ const processingFilter = this.legacyStatiToProcessing(args.txStatus);
859
+ const filterList = processingFilter.map(s => "'" + s + "'").join(',');
860
+ q.whereRaw(`(select processing from transactions where transactions.transactionId = outputs.transactionId) in (${filterList})`);
861
+ }
862
+ else {
863
+ q.whereRaw(`(select status from transactions where transactions.transactionId = outputs.transactionId) in (${args.txStatus.map(s => "'" + s + "'").join(',')})`);
864
+ }
493
865
  }
494
866
  if (args.noScript && !count) {
495
867
  const columns = tables_1.outputColumnsWithoutLockingScript.map(c => `outputs.${c}`);
@@ -506,14 +878,14 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
506
878
  findOutputTagsQuery(args) {
507
879
  return this.setupQuery('output_tags', args);
508
880
  }
509
- findProvenTxReqsQuery(args) {
881
+ findProvenTxReqsQuery(args, tableName = 'proven_tx_reqs') {
510
882
  if (args.partial.rawTx != null) {
511
883
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', 'undefined. ProvenTxReqs may not be found by rawTx value.');
512
884
  }
513
885
  if (args.partial.inputBEEF != null) {
514
886
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', 'undefined. ProvenTxReqs may not be found by inputBEEF value.');
515
887
  }
516
- const q = this.setupQuery('proven_tx_reqs', args);
888
+ const q = this.setupQuery(tableName, args);
517
889
  if ((args.status != null) && args.status.length > 0)
518
890
  q.whereIn('status', args.status);
519
891
  if (args.txids != null) {
@@ -523,17 +895,17 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
523
895
  }
524
896
  return q;
525
897
  }
526
- findProvenTxsQuery(args) {
898
+ findProvenTxsQuery(args, tableName = 'proven_txs') {
527
899
  if (args.partial.rawTx != null) {
528
900
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.rawTx', 'undefined. ProvenTxs may not be found by rawTx value.');
529
901
  }
530
902
  if (args.partial.merklePath != null) {
531
903
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.merklePath', 'undefined. ProvenTxs may not be found by merklePath value.');
532
904
  }
533
- return this.setupQuery('proven_txs', args);
905
+ return this.setupQuery(tableName, args);
534
906
  }
535
- findStaleMerkleRootsQuery(args) {
536
- const q = this.toDb(args.trx)('proven_txs');
907
+ findStaleMerkleRootsQuery(args, tableName = 'proven_txs') {
908
+ const q = this.toDb(args.trx)(tableName);
537
909
  q.where('height', '=', args.height);
538
910
  q.where('merkleRoot', '!=', args.merkleRoot);
539
911
  q.select('merkleRoot');
@@ -550,7 +922,16 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
550
922
  if (args.partial.inputBEEF != null) {
551
923
  throw new WERR_errors_1.WERR_INVALID_PARAMETER('args.partial.inputBEEF', 'undefined. Transactions may not be found by inputBEEF value.');
552
924
  }
553
- const q = this.setupQuery('transactions', args);
925
+ // Post-cutover the original per-user `transactions` table has been
926
+ // renamed to `transactions_legacy`. `findTransactions` returns the
927
+ // legacy shape (status / userId / satoshis / description / etc.) so it
928
+ // must route at the renamed table to preserve the legacy read surface
929
+ // through the bridge period. New-canonical per-txid reads go through
930
+ // TransactionService.
931
+ const tableName = this._postCutoverCache === true
932
+ ? 'transactions_legacy'
933
+ : 'transactions';
934
+ const q = this.setupQuery(tableName, args);
554
935
  if ((args.status != null) && args.status.length > 0)
555
936
  q.whereIn('status', args.status);
556
937
  if (args.from != null)
@@ -558,7 +939,7 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
558
939
  if (args.to != null)
559
940
  q.where('created_at', '<', this.validateDateForWhere(args.to));
560
941
  if (args.noRawTx && !count) {
561
- const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `transactions.${c}`);
942
+ const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `${tableName}.${c}`);
562
943
  q.select(columns);
563
944
  }
564
945
  return q;
@@ -644,17 +1025,20 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
644
1025
  return this.validateEntities(r, undefined, ['isDeleted']);
645
1026
  }
646
1027
  async findProvenTxReqs(args) {
647
- const q = this.findProvenTxReqsQuery(args);
1028
+ const reqTable = await this.provenTxReqsTableName();
1029
+ const q = this.findProvenTxReqsQuery(args, reqTable);
648
1030
  const r = await q;
649
1031
  return this.validateEntities(r, undefined, ['notified', 'wasBroadcast']);
650
1032
  }
651
1033
  async findProvenTxs(args) {
652
- const q = this.findProvenTxsQuery(args);
1034
+ const tableName = await this.provenTxsTableName();
1035
+ const q = this.findProvenTxsQuery(args, tableName);
653
1036
  const r = await q;
654
1037
  return this.validateEntities(r);
655
1038
  }
656
1039
  async findStaleMerkleRoots(args) {
657
- const q = this.findStaleMerkleRootsQuery(args);
1040
+ const tableName = await this.provenTxsTableName();
1041
+ const q = this.findStaleMerkleRootsQuery(args, tableName);
658
1042
  const r = await q;
659
1043
  return r.map((row) => row.merkleRoot);
660
1044
  }
@@ -673,6 +1057,172 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
673
1057
  }
674
1058
  return this.validateEntities(r, undefined, ['isOutgoing']);
675
1059
  }
1060
+ /**
1061
+ * Post-cutover: queries `transactions_legacy` for unsigned/pending rows
1062
+ * created by `createAction` (which have no real txid yet and therefore have no
1063
+ * new-schema counterpart). Falls back to the standard `findTransactions` pre-cutover.
1064
+ *
1065
+ * Used by `processAction.validateCommitNewTxToStorageArgs` to locate the
1066
+ * unsigned transaction row by `{userId, reference}`.
1067
+ *
1068
+ * Mapping §2: legacy `unsigned` / `unprocessed` → these rows only exist in
1069
+ * `transactions_legacy` post-cutover; new `transactions` has `processing` not
1070
+ * `status` and no unsigned-state rows.
1071
+ */
1072
+ async findLegacyTransactions(args) {
1073
+ const postCutover = await this.isPostCutover();
1074
+ if (!postCutover) {
1075
+ // Pre-cutover: standard transactions table
1076
+ return await this.findTransactions(args);
1077
+ }
1078
+ // Post-cutover: unsigned/unprocessed rows live in transactions_legacy
1079
+ const q = this.setupQuery('transactions_legacy', args);
1080
+ if ((args.status != null) && args.status.length > 0)
1081
+ q.whereIn('status', args.status);
1082
+ if (args.from != null)
1083
+ q.where('created_at', '>=', this.validateDateForWhere(args.from));
1084
+ if (args.to != null)
1085
+ q.where('created_at', '<', this.validateDateForWhere(args.to));
1086
+ if (args.noRawTx) {
1087
+ const columns = tables_1.transactionColumnsWithoutRawTx.map(c => `transactions_legacy.${c}`);
1088
+ q.select(columns);
1089
+ }
1090
+ const r = await q;
1091
+ if (!args.noRawTx) {
1092
+ for (const t of r) {
1093
+ await this.validateRawTransaction(t, args.trx);
1094
+ }
1095
+ }
1096
+ return this.validateEntities(r, undefined, ['isOutgoing']);
1097
+ }
1098
+ /**
1099
+ * Post-cutover: updates `transactions_legacy` (where unsigned rows live).
1100
+ * Pre-cutover: delegates to `updateTransaction`.
1101
+ *
1102
+ * Used by `processAction.commitNewTxToStorage` to write back the real txid
1103
+ * and status to the legacy row that was created by `createAction`.
1104
+ *
1105
+ * Mapping §2: legacy `unsigned` → `unprocessed` transition and txid write-back
1106
+ * must go to `transactions_legacy` post-cutover, not new `transactions`.
1107
+ */
1108
+ async updateLegacyTransaction(id, update, trx) {
1109
+ const postCutover = await this.isPostCutover();
1110
+ if (!postCutover) {
1111
+ return await this.updateTransaction(id, update, trx);
1112
+ }
1113
+ // Post-cutover: unsigned rows are in transactions_legacy
1114
+ await this.verifyReadyForDatabaseAccess(trx);
1115
+ let r;
1116
+ if (Array.isArray(id)) {
1117
+ r = await this.toDb(trx)('transactions_legacy')
1118
+ .whereIn('transactionId', id)
1119
+ .update(this.validatePartialForUpdate(update));
1120
+ }
1121
+ else if (Number.isInteger(id)) {
1122
+ r = await this.toDb(trx)('transactions_legacy')
1123
+ .where({ transactionId: id })
1124
+ .update(this.validatePartialForUpdate(update));
1125
+ }
1126
+ else {
1127
+ throw new WERR_errors_1.WERR_INVALID_PARAMETER('id', 'transactionId or array of transactionId');
1128
+ }
1129
+ return r;
1130
+ }
1131
+ /**
1132
+ * Post-cutover SQLite: temporarily disable FK enforcement around the
1133
+ * `outputs.spentBy = legacyTransactionId` UPDATE. The `outputs.spentBy` FK
1134
+ * references `transactions.transactionId` after cutover, but unsigned
1135
+ * transactions from `createAction` live in `transactions_legacy` (their new-schema
1136
+ * counterpart is created later by `processAction`).
1137
+ *
1138
+ * IMPORTANT: We bypass `updateOutput` (and therefore `verifyReadyForDatabaseAccess`)
1139
+ * because `verifyReadyForDatabaseAccess` always re-enables FK with
1140
+ * `PRAGMA foreign_keys = ON`, which would undo our bypass. Instead we
1141
+ * directly run the UPDATE via the raw knex handle.
1142
+ *
1143
+ * Pre-cutover or MySQL: delegates to `updateOutput` unchanged.
1144
+ *
1145
+ * Mapping §2: bridge-period spentBy references transactions_legacy during
1146
+ * createAction; FK bypass covers this until processAction wires the new-schema.
1147
+ */
1148
+ async markOutputAsSpentBy(outputId, update, trx) {
1149
+ const postCutover = await this.isPostCutover();
1150
+ const isSqlite = this.dbtype === 'SQLite';
1151
+ if (postCutover && isSqlite) {
1152
+ // Build the UPDATE payload the same way validatePartialForUpdate does,
1153
+ // but WITHOUT calling verifyReadyForDatabaseAccess (which always issues
1154
+ // PRAGMA foreign_keys = ON, undoing our bypass).
1155
+ const now = new Date().toISOString();
1156
+ const payload = { updated_at: now };
1157
+ for (const [k, v] of Object.entries(update)) {
1158
+ if (v === undefined) {
1159
+ payload[k] = null;
1160
+ }
1161
+ else if (Array.isArray(v) && (v.length === 0 || typeof v[0] === 'number')) {
1162
+ payload[k] = Buffer.from(v);
1163
+ }
1164
+ else {
1165
+ payload[k] = v;
1166
+ }
1167
+ }
1168
+ // Strategy: FK is a connection-level setting in SQLite. PRAGMA changes
1169
+ // inside a Knex transaction are no-ops (SQLite restriction), but PRAGMA
1170
+ // changes OUTSIDE a transaction persist.
1171
+ //
1172
+ // When called with trx (inside a transaction): the caller is expected to have
1173
+ // already disabled FK before opening the transaction. We run the UPDATE via
1174
+ // the transaction handle (no PRAGMA calls needed here — they'd be no-ops or
1175
+ // pool-exhausting anyway).
1176
+ //
1177
+ // When called without trx (outside a transaction): disable FK, run UPDATE,
1178
+ // re-enable FK.
1179
+ if (trx != null) {
1180
+ // Inside caller's transaction — FK was disabled before the transaction.
1181
+ // Just run the UPDATE; do NOT try to PRAGMA here (no-op or timeout).
1182
+ this.whenLastAccess = new Date();
1183
+ await (this.toDb(trx))('outputs')
1184
+ .where({ outputId })
1185
+ .update(payload);
1186
+ }
1187
+ else {
1188
+ // Outside any transaction — we can safely toggle FK on the bare connection.
1189
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
1190
+ try {
1191
+ this.whenLastAccess = new Date();
1192
+ await this.knex('outputs').where({ outputId }).update(payload);
1193
+ }
1194
+ finally {
1195
+ await this.knex.raw('PRAGMA foreign_keys = ON');
1196
+ }
1197
+ }
1198
+ }
1199
+ else {
1200
+ await this.updateOutput(outputId, update, trx);
1201
+ }
1202
+ }
1203
+ /**
1204
+ * Post-cutover SQLite: disable FK on the bare connection BEFORE opening a
1205
+ * transaction. PRAGMA foreign_keys changes inside SQLite transactions are no-ops,
1206
+ * so this MUST be called before `this.knex.transaction()`.
1207
+ *
1208
+ * Only acts when post-cutover AND SQLite. Pre-cutover or MySQL: no-op.
1209
+ */
1210
+ async disableForeignKeys() {
1211
+ const postCutover = await this.isPostCutover();
1212
+ if (postCutover && this.dbtype === 'SQLite') {
1213
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Re-enable FK after the transaction opened via `disableForeignKeys()` completes.
1218
+ * Only acts when post-cutover AND SQLite.
1219
+ */
1220
+ async enableForeignKeys() {
1221
+ const postCutover = await this.isPostCutover();
1222
+ if (postCutover && this.dbtype === 'SQLite') {
1223
+ await this.knex.raw('PRAGMA foreign_keys = ON');
1224
+ }
1225
+ }
676
1226
  async findTxLabelMaps(args) {
677
1227
  const q = this.findTxLabelMapsQuery(args);
678
1228
  const r = await q;
@@ -734,10 +1284,12 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
734
1284
  return await this.getCount(this.findOutputTagsQuery(args));
735
1285
  }
736
1286
  async countProvenTxReqs(args) {
737
- return await this.getCount(this.findProvenTxReqsQuery(args));
1287
+ const reqTable = await this.provenTxReqsTableName();
1288
+ return await this.getCount(this.findProvenTxReqsQuery(args, reqTable));
738
1289
  }
739
1290
  async countProvenTxs(args) {
740
- return await this.getCount(this.findProvenTxsQuery(args));
1291
+ const tableName = await this.provenTxsTableName();
1292
+ return await this.getCount(this.findProvenTxsQuery(args, tableName));
741
1293
  }
742
1294
  async countSyncStates(args) {
743
1295
  return await this.getCount(this.findSyncStatesQuery(args));
@@ -985,13 +1537,19 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
985
1537
  * - sending (if excludeSending is false)
986
1538
  */
987
1539
  async countChangeInputs(userId, basketId, excludeSending) {
988
- const status = ['completed', 'unproven'];
1540
+ const legacyStatus = ['completed', 'unproven'];
989
1541
  if (!excludeSending)
990
- status.push('sending');
1542
+ legacyStatus.push('sending');
1543
+ const postCutover = await this.isPostCutover();
991
1544
  const q = this.knex('outputs as o')
992
1545
  .join('transactions as t', 'o.transactionId', 't.transactionId')
993
- .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
994
- .whereIn('t.status', status);
1546
+ .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId });
1547
+ if (postCutover) {
1548
+ q.whereIn('t.processing', this.legacyStatiToProcessing(legacyStatus));
1549
+ }
1550
+ else {
1551
+ q.whereIn('t.status', legacyStatus);
1552
+ }
995
1553
  const count = await this.getCount(q);
996
1554
  return count;
997
1555
  }
@@ -1072,15 +1630,20 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1072
1630
  return byTag;
1073
1631
  }
1074
1632
  async sumSpendableSatoshisInBasket(userId, basketId, excludeSending, trx) {
1075
- const status = ['completed', 'unproven'];
1633
+ const legacyStatus = ['completed', 'unproven'];
1076
1634
  if (!excludeSending)
1077
- status.push('sending');
1078
- const row = await this.toDb(trx)('outputs as o')
1635
+ legacyStatus.push('sending');
1636
+ const postCutover = await this.isPostCutover();
1637
+ const q = this.toDb(trx)('outputs as o')
1079
1638
  .join('transactions as t', 'o.transactionId', 't.transactionId')
1080
- .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId })
1081
- .whereIn('t.status', status)
1082
- .sum({ totalSatoshis: 'o.satoshis' })
1083
- .first();
1639
+ .where({ 'o.userId': userId, 'o.spendable': true, 'o.basketId': basketId });
1640
+ if (postCutover) {
1641
+ q.whereIn('t.processing', this.legacyStatiToProcessing(legacyStatus));
1642
+ }
1643
+ else {
1644
+ q.whereIn('t.status', legacyStatus);
1645
+ }
1646
+ const row = await q.sum({ totalSatoshis: 'o.satoshis' }).first();
1084
1647
  return Number(((row != null) && row.totalSatoshis) || 0);
1085
1648
  }
1086
1649
  /**
@@ -1089,44 +1652,78 @@ class StorageKnex extends StorageProvider_1.StorageProvider {
1089
1652
  * Transactionally allocate the output such that
1090
1653
  */
1091
1654
  async allocateChangeInput(userId, basketId, targetSatoshis, exactSatoshis, excludeSending, transactionId) {
1092
- const status = ['completed', 'unproven'];
1655
+ const legacyStatus = ['completed', 'unproven'];
1093
1656
  if (!excludeSending)
1094
- status.push('sending');
1095
- const r = await this.knex.transaction(async (trx) => {
1096
- const baseQuery = () => trx('outputs as o')
1097
- .join('transactions as t', 'o.transactionId', 't.transactionId')
1098
- .where('o.userId', userId)
1099
- .where('o.spendable', true)
1100
- .where('o.basketId', basketId)
1101
- .whereIn('t.status', status)
1102
- .select('o.*');
1103
- let output;
1104
- if (exactSatoshis !== undefined) {
1105
- output = await baseQuery().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
1657
+ legacyStatus.push('sending');
1658
+ const postCutover = await this.isPostCutover();
1659
+ const processingFilter = postCutover ? this.legacyStatiToProcessing(legacyStatus) : undefined;
1660
+ // Post-cutover bridge-period: `transactionId` here is a legacy row in
1661
+ // `transactions_legacy` (new unsigned tx created by createAction). The
1662
+ // `outputs.spentBy` FK now references `transactions.transactionId`, so
1663
+ // setting spentBy to a transactions_legacy ID would violate the constraint.
1664
+ // PRAGMA foreign_keys is silently ignored inside SQLite transactions, so
1665
+ // we toggle FK enforcement on the bare connection BEFORE opening the
1666
+ // transaction. processAction will remap spentBy to the real new transactionId
1667
+ // once the txid is known and the new schema row is created.
1668
+ const isSqlite = this.dbtype === 'SQLite';
1669
+ if (postCutover && isSqlite) {
1670
+ await this.knex.raw('PRAGMA foreign_keys = OFF');
1671
+ }
1672
+ let r;
1673
+ try {
1674
+ r = await this.knex.transaction(async (trx) => {
1675
+ const baseQuery = () => {
1676
+ const q = trx('outputs as o')
1677
+ .join('transactions as t', 'o.transactionId', 't.transactionId')
1678
+ .where('o.userId', userId)
1679
+ .where('o.spendable', true)
1680
+ .where('o.basketId', basketId)
1681
+ .select('o.*');
1682
+ if (processingFilter != null) {
1683
+ q.whereIn('t.processing', processingFilter);
1684
+ }
1685
+ else {
1686
+ q.whereIn('t.status', legacyStatus);
1687
+ }
1688
+ return q;
1689
+ };
1690
+ let output;
1691
+ if (exactSatoshis !== undefined) {
1692
+ output = await baseQuery().where('o.satoshis', exactSatoshis).orderBy('o.outputId', 'asc').first();
1693
+ }
1694
+ output !== null && output !== void 0 ? output : (output = await baseQuery()
1695
+ .where('o.satoshis', '>=', targetSatoshis)
1696
+ .orderBy('o.satoshis', 'asc')
1697
+ .orderBy('o.outputId', 'asc')
1698
+ .first());
1699
+ output !== null && output !== void 0 ? output : (output = await baseQuery()
1700
+ .where('o.satoshis', '<', targetSatoshis)
1701
+ .orderBy('o.satoshis', 'desc')
1702
+ .orderBy('o.outputId', 'desc')
1703
+ .first());
1704
+ if (output == null)
1705
+ return undefined;
1706
+ // Post-cutover: transactionId is in transactions_legacy; FK would fail.
1707
+ // markOutputAsSpentBy disables FK via raw knex handle, bypassing
1708
+ // verifyReadyForDatabaseAccess that would re-enable it.
1709
+ // Mapping §2: bridge-period spentBy → transactions_legacy during createAction.
1710
+ await this.markOutputAsSpentBy(output.outputId, {
1711
+ spendable: false,
1712
+ spentBy: transactionId
1713
+ }, trx);
1714
+ // Keep behavior identical to the pre-optimization path: ensure lockingScript
1715
+ // is present even when it was offloaded from outputs into rawTx storage.
1716
+ await this.validateOutputScript(output, trx);
1717
+ output.spendable = false;
1718
+ output.spentBy = transactionId;
1719
+ return output;
1720
+ });
1721
+ }
1722
+ finally {
1723
+ if (postCutover && isSqlite) {
1724
+ await this.knex.raw('PRAGMA foreign_keys = ON');
1106
1725
  }
1107
- output !== null && output !== void 0 ? output : (output = await baseQuery()
1108
- .where('o.satoshis', '>=', targetSatoshis)
1109
- .orderBy('o.satoshis', 'asc')
1110
- .orderBy('o.outputId', 'asc')
1111
- .first());
1112
- output !== null && output !== void 0 ? output : (output = await baseQuery()
1113
- .where('o.satoshis', '<', targetSatoshis)
1114
- .orderBy('o.satoshis', 'desc')
1115
- .orderBy('o.outputId', 'desc')
1116
- .first());
1117
- if (output == null)
1118
- return undefined;
1119
- await this.updateOutput(output.outputId, {
1120
- spendable: false,
1121
- spentBy: transactionId
1122
- }, trx);
1123
- // Keep behavior identical to the pre-optimization path: ensure lockingScript
1124
- // is present even when it was offloaded from outputs into rawTx storage.
1125
- await this.validateOutputScript(output, trx);
1126
- output.spendable = false;
1127
- output.spentBy = transactionId;
1128
- return output;
1129
- });
1726
+ }
1130
1727
  return r;
1131
1728
  }
1132
1729
  /** Convert null→undefined and Buffer→number[] on a retrieved entity in-place. */