@bsv/wallet-toolbox-client 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.
- package/out/src/Wallet.d.ts.map +1 -1
- package/out/src/Wallet.js.map +1 -1
- package/out/src/WalletLogger.d.ts.map +1 -1
- package/out/src/WalletLogger.js.map +1 -1
- package/out/src/monitor/LeasedMonitorTask.d.ts +43 -0
- package/out/src/monitor/LeasedMonitorTask.d.ts.map +1 -0
- package/out/src/monitor/LeasedMonitorTask.js +89 -0
- package/out/src/monitor/LeasedMonitorTask.js.map +1 -0
- package/out/src/monitor/Monitor.d.ts +7 -0
- package/out/src/monitor/Monitor.d.ts.map +1 -1
- package/out/src/monitor/Monitor.js +7 -0
- package/out/src/monitor/Monitor.js.map +1 -1
- package/out/src/monitor/V7LeasedTask.d.ts +43 -0
- package/out/src/monitor/V7LeasedTask.d.ts.map +1 -0
- package/out/src/monitor/V7LeasedTask.js +89 -0
- package/out/src/monitor/V7LeasedTask.js.map +1 -0
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts +2 -0
- package/out/src/monitor/tasks/TaskCheckForProofs.d.ts.map +1 -1
- package/out/src/monitor/tasks/TaskCheckForProofs.js +55 -0
- package/out/src/monitor/tasks/TaskCheckForProofs.js.map +1 -1
- package/out/src/monitor/tasks/TaskSendWaiting.d.ts.map +1 -1
- package/out/src/monitor/tasks/TaskSendWaiting.js.map +1 -1
- package/out/src/sdk/WalletStorage.interfaces.d.ts +59 -59
- package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
- package/out/src/sdk/types.d.ts +32 -0
- package/out/src/sdk/types.d.ts.map +1 -1
- package/out/src/sdk/types.js +50 -1
- package/out/src/sdk/types.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorWhatsOnChainPoll.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultIdbChaintracksOptions.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createDefaultNoDbChaintracksOptions.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createIdbChaintracks.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/createNoDbChaintracks.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/SingleWriterMultiReaderLock.js.map +1 -1
- package/out/src/storage/StorageIdb.d.ts.map +1 -1
- package/out/src/storage/StorageIdb.js +10 -5
- package/out/src/storage/StorageIdb.js.map +1 -1
- package/out/src/storage/StorageProvider.d.ts +114 -1
- package/out/src/storage/StorageProvider.d.ts.map +1 -1
- package/out/src/storage/StorageProvider.js +164 -4
- package/out/src/storage/StorageProvider.js.map +1 -1
- package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/out/src/storage/WalletStorageManager.js.map +1 -1
- package/out/src/storage/idbHelpers.d.ts +5 -0
- package/out/src/storage/idbHelpers.d.ts.map +1 -1
- package/out/src/storage/idbHelpers.js +42 -0
- package/out/src/storage/idbHelpers.js.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js +116 -4
- package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +1 -1
- package/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/out/src/storage/methods/createAction.js +22 -5
- package/out/src/storage/methods/createAction.js.map +1 -1
- package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
- package/out/src/storage/methods/internalizeAction.js +172 -5
- package/out/src/storage/methods/internalizeAction.js.map +1 -1
- package/out/src/storage/methods/processAction.d.ts.map +1 -1
- package/out/src/storage/methods/processAction.js +82 -14
- package/out/src/storage/methods/processAction.js.map +1 -1
- package/out/src/storage/schema/StorageIdbSchema.d.ts +43 -2
- package/out/src/storage/schema/StorageIdbSchema.d.ts.map +1 -1
- package/out/src/storage/schema/monitorLease.d.ts +57 -0
- package/out/src/storage/schema/monitorLease.d.ts.map +1 -0
- package/out/src/storage/schema/monitorLease.js +101 -0
- package/out/src/storage/schema/monitorLease.js.map +1 -0
- package/out/src/storage/schema/processingFsm.d.ts +27 -0
- package/out/src/storage/schema/processingFsm.d.ts.map +1 -0
- package/out/src/storage/schema/processingFsm.js +132 -0
- package/out/src/storage/schema/processingFsm.js.map +1 -0
- package/out/src/storage/schema/tables/TableAction.d.ts +38 -0
- package/out/src/storage/schema/tables/TableAction.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableAction.js +3 -0
- package/out/src/storage/schema/tables/TableAction.js.map +1 -0
- package/out/src/storage/schema/tables/TableChainTip.d.ts +17 -0
- package/out/src/storage/schema/tables/TableChainTip.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableChainTip.js +3 -0
- package/out/src/storage/schema/tables/TableChainTip.js.map +1 -0
- package/out/src/storage/schema/tables/TableMonitorLease.d.ts +23 -0
- package/out/src/storage/schema/tables/TableMonitorLease.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableMonitorLease.js +3 -0
- package/out/src/storage/schema/tables/TableMonitorLease.js.map +1 -0
- package/out/src/storage/schema/tables/TableOutput.d.ts +7 -0
- package/out/src/storage/schema/tables/TableOutput.d.ts.map +1 -1
- package/out/src/storage/schema/tables/TableOutput.js.map +1 -1
- package/out/src/storage/schema/tables/TableTransactionNew.d.ts +50 -0
- package/out/src/storage/schema/tables/TableTransactionNew.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableTransactionNew.js +3 -0
- package/out/src/storage/schema/tables/TableTransactionNew.js.map +1 -0
- package/out/src/storage/schema/tables/TableTransactionV7.d.ts +50 -0
- package/out/src/storage/schema/tables/TableTransactionV7.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableTransactionV7.js +3 -0
- package/out/src/storage/schema/tables/TableTransactionV7.js.map +1 -0
- package/out/src/storage/schema/tables/TableTxAudit.d.ts +26 -0
- package/out/src/storage/schema/tables/TableTxAudit.d.ts.map +1 -0
- package/out/src/storage/schema/tables/TableTxAudit.js +3 -0
- package/out/src/storage/schema/tables/TableTxAudit.js.map +1 -0
- package/out/src/storage/schema/tables/index.d.ts +5 -0
- package/out/src/storage/schema/tables/index.d.ts.map +1 -1
- package/out/src/storage/schema/tables/index.js +5 -0
- package/out/src/storage/schema/tables/index.js.map +1 -1
- package/out/src/storage/schema/transactionCrud.d.ts +41 -0
- package/out/src/storage/schema/transactionCrud.d.ts.map +1 -0
- package/out/src/storage/schema/transactionCrud.js +205 -0
- package/out/src/storage/schema/transactionCrud.js.map +1 -0
- package/out/src/storage/schema/transactionService.d.ts +315 -0
- package/out/src/storage/schema/transactionService.d.ts.map +1 -0
- package/out/src/storage/schema/transactionService.js +783 -0
- package/out/src/storage/schema/transactionService.js.map +1 -0
- package/out/src/storage/schema/txAudit.d.ts +33 -0
- package/out/src/storage/schema/txAudit.d.ts.map +1 -0
- package/out/src/storage/schema/txAudit.js +64 -0
- package/out/src/storage/schema/txAudit.js.map +1 -0
- package/out/src/storage/schema/v7Crud.d.ts +41 -0
- package/out/src/storage/schema/v7Crud.d.ts.map +1 -0
- package/out/src/storage/schema/v7Crud.js +205 -0
- package/out/src/storage/schema/v7Crud.js.map +1 -0
- package/out/src/storage/schema/v7Fsm.d.ts +27 -0
- package/out/src/storage/schema/v7Fsm.d.ts.map +1 -0
- package/out/src/storage/schema/v7Fsm.js +124 -0
- package/out/src/storage/schema/v7Fsm.js.map +1 -0
- package/out/src/storage/schema/v7MonitorLease.d.ts +57 -0
- package/out/src/storage/schema/v7MonitorLease.d.ts.map +1 -0
- package/out/src/storage/schema/v7MonitorLease.js +101 -0
- package/out/src/storage/schema/v7MonitorLease.js.map +1 -0
- package/out/src/storage/schema/v7Service.d.ts +305 -0
- package/out/src/storage/schema/v7Service.d.ts.map +1 -0
- package/out/src/storage/schema/v7Service.js +757 -0
- package/out/src/storage/schema/v7Service.js.map +1 -0
- package/out/src/storage/schema/v7TxAudit.d.ts +33 -0
- package/out/src/storage/schema/v7TxAudit.d.ts.map +1 -0
- package/out/src/storage/schema/v7TxAudit.js +64 -0
- package/out/src/storage/schema/v7TxAudit.js.map +1 -0
- package/out/src/storage/storageProviderHelpers.js +1 -1
- package/out/src/storage/storageProviderHelpers.js.map +1 -1
- package/out/tsconfig.client.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransactionService = void 0;
|
|
4
|
+
exports.indexFromMerklePath = indexFromMerklePath;
|
|
5
|
+
const sdk_1 = require("@bsv/sdk");
|
|
6
|
+
const transactionCrud_1 = require("./transactionCrud");
|
|
7
|
+
const txAudit_1 = require("./txAudit");
|
|
8
|
+
const monitorLease_1 = require("./monitorLease");
|
|
9
|
+
/**
|
|
10
|
+
* High-level service over the new-schema storage primitives.
|
|
11
|
+
*
|
|
12
|
+
* Storage methods and the Monitor call into this surface rather than the
|
|
13
|
+
* lower-level CRUD/FSM/audit/lease modules so that:
|
|
14
|
+
* - Every processing transition is audited.
|
|
15
|
+
* - Optimistic concurrency is uniformly enforced.
|
|
16
|
+
* - Chain tip + monitor lease access have one canonical entry point.
|
|
17
|
+
*
|
|
18
|
+
* Construction takes a Knex handle; instances are stateless and cheap to
|
|
19
|
+
* create — typically one per request or per Monitor task tick.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Extract the merkle leaf index for `txid` from a BUMP-encoded merkle path.
|
|
23
|
+
*
|
|
24
|
+
* The BUMP format encodes level 0 leaves with `txid: true` and an `offset`
|
|
25
|
+
* equal to the position of the transaction in its block. For a single-tx
|
|
26
|
+
* proof exactly one leaf is flagged; for trimmed compound proofs the leaf
|
|
27
|
+
* matching `txid` is selected.
|
|
28
|
+
*/
|
|
29
|
+
function indexFromMerklePath(merklePath, txid) {
|
|
30
|
+
var _a;
|
|
31
|
+
const mp = sdk_1.MerklePath.fromBinary(merklePath);
|
|
32
|
+
const level0 = (_a = mp.path[0]) !== null && _a !== void 0 ? _a : [];
|
|
33
|
+
const leaf = level0.find(l => l.txid === true && l.hash === txid);
|
|
34
|
+
if (leaf == null)
|
|
35
|
+
throw new Error(`txid ${txid} not present in merklePath`);
|
|
36
|
+
return leaf.offset;
|
|
37
|
+
}
|
|
38
|
+
class TransactionService {
|
|
39
|
+
constructor(knex) {
|
|
40
|
+
this.knex = knex;
|
|
41
|
+
}
|
|
42
|
+
// -----------------------
|
|
43
|
+
// Transactions
|
|
44
|
+
// -----------------------
|
|
45
|
+
async findByTxid(txid) {
|
|
46
|
+
return await (0, transactionCrud_1.findTransactionNewByTxid)(this.knex, txid);
|
|
47
|
+
}
|
|
48
|
+
async findById(transactionId) {
|
|
49
|
+
return await (0, transactionCrud_1.findTransactionNew)(this.knex, transactionId);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Insert a new new transaction row. The row is created in `queued` state
|
|
53
|
+
* unless the caller overrides `processing`.
|
|
54
|
+
*/
|
|
55
|
+
async create(args) {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
58
|
+
const row = {
|
|
59
|
+
txid: args.txid,
|
|
60
|
+
processing: (_b = args.processing) !== null && _b !== void 0 ? _b : 'queued',
|
|
61
|
+
processingChangedAt: now,
|
|
62
|
+
nextActionAt: undefined,
|
|
63
|
+
attempts: 0,
|
|
64
|
+
rebroadcastCycles: 0,
|
|
65
|
+
wasBroadcast: false,
|
|
66
|
+
idempotencyKey: args.idempotencyKey,
|
|
67
|
+
batch: args.batch,
|
|
68
|
+
rawTx: args.rawTx,
|
|
69
|
+
inputBeef: args.inputBeef,
|
|
70
|
+
height: undefined,
|
|
71
|
+
merkleIndex: undefined,
|
|
72
|
+
merklePath: undefined,
|
|
73
|
+
merkleRoot: undefined,
|
|
74
|
+
blockHash: undefined,
|
|
75
|
+
isCoinbase: args.isCoinbase === true,
|
|
76
|
+
lastProvider: undefined,
|
|
77
|
+
lastProviderStatus: undefined,
|
|
78
|
+
frozenReason: undefined,
|
|
79
|
+
rowVersion: 0
|
|
80
|
+
};
|
|
81
|
+
const id = await (0, transactionCrud_1.insertTransactionNew)(this.knex, row, now);
|
|
82
|
+
await (0, txAudit_1.auditProcessingTransition)(this.knex, id, row.processing, row.processing, { reason: 'create' }, now);
|
|
83
|
+
const stored = await this.findById(id);
|
|
84
|
+
if (stored == null)
|
|
85
|
+
throw new Error(`new transaction ${id} disappeared after insert`);
|
|
86
|
+
return stored;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Transition processing state with optimistic concurrency. Returns
|
|
90
|
+
* `undefined` when the FSM rejects the move OR the row's current state no
|
|
91
|
+
* longer matches `expectedFrom`.
|
|
92
|
+
*/
|
|
93
|
+
async transition(args) {
|
|
94
|
+
return await (0, transactionCrud_1.transitionProcessing)(this.knex, {
|
|
95
|
+
transactionId: args.transactionId,
|
|
96
|
+
expectedFromState: args.expectedFrom,
|
|
97
|
+
toState: args.to,
|
|
98
|
+
provider: args.provider,
|
|
99
|
+
providerStatus: args.providerStatus,
|
|
100
|
+
details: args.details
|
|
101
|
+
}, args.now);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Record acquisition of a Merkle proof for a transaction. Atomically:
|
|
105
|
+
* - Updates proof columns (height, index, merkle_path, merkle_root, block_hash)
|
|
106
|
+
* - Transitions processing to `confirmed` from any spendable-class state.
|
|
107
|
+
* - Writes a `proof.acquired` audit row.
|
|
108
|
+
*
|
|
109
|
+
* The merkle leaf index is derived from `merklePath` (BUMP) using the row's
|
|
110
|
+
* `txid`; callers do not pass it.
|
|
111
|
+
*/
|
|
112
|
+
async recordProof(args) {
|
|
113
|
+
var _a;
|
|
114
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
115
|
+
const existing = await this.findById(args.transactionId);
|
|
116
|
+
if (existing == null)
|
|
117
|
+
return undefined;
|
|
118
|
+
const merkleIndex = indexFromMerklePath(args.merklePath, existing.txid);
|
|
119
|
+
const next = await this.transition({
|
|
120
|
+
transactionId: args.transactionId,
|
|
121
|
+
expectedFrom: args.expectedFrom,
|
|
122
|
+
to: 'confirmed',
|
|
123
|
+
details: { source: 'recordProof', height: args.height },
|
|
124
|
+
now
|
|
125
|
+
});
|
|
126
|
+
if (next == null)
|
|
127
|
+
return undefined;
|
|
128
|
+
await this.knex('transactions').where({ transactionId: args.transactionId }).update({
|
|
129
|
+
height: args.height,
|
|
130
|
+
merkle_index: merkleIndex,
|
|
131
|
+
merkle_path: Buffer.from(args.merklePath),
|
|
132
|
+
merkle_root: args.merkleRoot,
|
|
133
|
+
block_hash: args.blockHash,
|
|
134
|
+
updated_at: now
|
|
135
|
+
});
|
|
136
|
+
return await this.findById(args.transactionId);
|
|
137
|
+
}
|
|
138
|
+
// -----------------------
|
|
139
|
+
// Actions
|
|
140
|
+
// -----------------------
|
|
141
|
+
async findActionForUser(userId, transactionId) {
|
|
142
|
+
return await (0, transactionCrud_1.findAction)(this.knex, userId, transactionId);
|
|
143
|
+
}
|
|
144
|
+
async createAction(args) {
|
|
145
|
+
return await (0, transactionCrud_1.insertAction)(this.knex, {
|
|
146
|
+
userId: args.userId,
|
|
147
|
+
transactionId: args.transactionId,
|
|
148
|
+
reference: args.reference,
|
|
149
|
+
description: args.description,
|
|
150
|
+
isOutgoing: args.isOutgoing,
|
|
151
|
+
satoshisDelta: args.satoshisDelta,
|
|
152
|
+
userNosend: args.userNosend === true,
|
|
153
|
+
hidden: false,
|
|
154
|
+
userAborted: false,
|
|
155
|
+
notifyJson: args.notifyJson,
|
|
156
|
+
rowVersion: 0
|
|
157
|
+
}, args.now);
|
|
158
|
+
}
|
|
159
|
+
// -----------------------
|
|
160
|
+
// Chain tip
|
|
161
|
+
// -----------------------
|
|
162
|
+
async getChainTip() {
|
|
163
|
+
const tip = await (0, transactionCrud_1.getChainTip)(this.knex);
|
|
164
|
+
if (tip == null)
|
|
165
|
+
return undefined;
|
|
166
|
+
return { height: tip.height, blockHash: tip.blockHash };
|
|
167
|
+
}
|
|
168
|
+
async setChainTip(args) {
|
|
169
|
+
await (0, transactionCrud_1.setChainTip)(this.knex, args, args.now);
|
|
170
|
+
}
|
|
171
|
+
// -----------------------
|
|
172
|
+
// Monitor lease
|
|
173
|
+
// -----------------------
|
|
174
|
+
async tryClaimLease(claim, now) {
|
|
175
|
+
return await (0, monitorLease_1.tryClaimLease)(this.knex, claim, now);
|
|
176
|
+
}
|
|
177
|
+
async renewLease(renew, now) {
|
|
178
|
+
return await (0, monitorLease_1.renewLease)(this.knex, renew, now);
|
|
179
|
+
}
|
|
180
|
+
async releaseLease(release) {
|
|
181
|
+
return await (0, monitorLease_1.releaseLease)(this.knex, release);
|
|
182
|
+
}
|
|
183
|
+
// -----------------------
|
|
184
|
+
// Net-new methods (§3)
|
|
185
|
+
// -----------------------
|
|
186
|
+
/**
|
|
187
|
+
* #1 — Look up an action + its backing transaction by (userId, reference).
|
|
188
|
+
*/
|
|
189
|
+
async findActionByReference(userId, reference) {
|
|
190
|
+
const actionRow = await this.knex('actions').where({ userId, reference }).first();
|
|
191
|
+
if (actionRow == null)
|
|
192
|
+
return undefined;
|
|
193
|
+
const action = (0, transactionCrud_1.mapActionRow)(actionRow);
|
|
194
|
+
const tx = await this.findById(action.transactionId);
|
|
195
|
+
if (tx == null)
|
|
196
|
+
return undefined;
|
|
197
|
+
return { action, transaction: tx };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* #2 — Look up an action + its backing transaction by (userId, txid).
|
|
201
|
+
*/
|
|
202
|
+
async findActionByUserTxid(userId, txid) {
|
|
203
|
+
const txRow = await this.knex('transactions').where({ txid }).first();
|
|
204
|
+
if (txRow == null)
|
|
205
|
+
return undefined;
|
|
206
|
+
const tx = (0, transactionCrud_1.mapTransactionRow)(txRow);
|
|
207
|
+
const actionRow = await this.knex('actions')
|
|
208
|
+
.where({ userId, transactionId: tx.transactionId })
|
|
209
|
+
.first();
|
|
210
|
+
if (actionRow == null)
|
|
211
|
+
return undefined;
|
|
212
|
+
return { action: (0, transactionCrud_1.mapActionRow)(actionRow), transaction: tx };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* #3 — Upsert: find existing action for (userId, txid) or create both the new
|
|
216
|
+
* transaction row and the action row.
|
|
217
|
+
*/
|
|
218
|
+
async findOrCreateActionForTxid(args) {
|
|
219
|
+
var _a, _b;
|
|
220
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
221
|
+
// Try to find existing transaction
|
|
222
|
+
let tx = await this.findByTxid(args.txid);
|
|
223
|
+
let isNew = false;
|
|
224
|
+
if (tx == null) {
|
|
225
|
+
tx = await this.create({
|
|
226
|
+
txid: args.txid,
|
|
227
|
+
processing: (_b = args.processing) !== null && _b !== void 0 ? _b : 'queued',
|
|
228
|
+
rawTx: args.rawTx,
|
|
229
|
+
inputBeef: args.inputBeef,
|
|
230
|
+
now
|
|
231
|
+
});
|
|
232
|
+
isNew = true;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Patch rawTx / inputBeef if the caller is supplying them for the first time.
|
|
236
|
+
const patches = { updated_at: now };
|
|
237
|
+
if (args.rawTx != null && tx.rawTx == null)
|
|
238
|
+
patches.raw_tx = Buffer.from(args.rawTx);
|
|
239
|
+
if (args.inputBeef != null && tx.inputBeef == null)
|
|
240
|
+
patches.input_beef = Buffer.from(args.inputBeef);
|
|
241
|
+
if (Object.keys(patches).length > 1) {
|
|
242
|
+
await this.knex('transactions').where({ transactionId: tx.transactionId }).update(patches);
|
|
243
|
+
const refreshed = await this.findById(tx.transactionId);
|
|
244
|
+
if (refreshed != null)
|
|
245
|
+
tx = refreshed;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Find or create the action for this user.
|
|
249
|
+
let actionRow = await this.knex('actions')
|
|
250
|
+
.where({ userId: args.userId, transactionId: tx.transactionId })
|
|
251
|
+
.first();
|
|
252
|
+
if (actionRow == null) {
|
|
253
|
+
await (0, transactionCrud_1.insertAction)(this.knex, {
|
|
254
|
+
userId: args.userId,
|
|
255
|
+
transactionId: tx.transactionId,
|
|
256
|
+
reference: args.reference,
|
|
257
|
+
description: args.description,
|
|
258
|
+
isOutgoing: args.isOutgoing,
|
|
259
|
+
satoshisDelta: args.satoshisDelta,
|
|
260
|
+
userNosend: false,
|
|
261
|
+
hidden: false,
|
|
262
|
+
userAborted: false,
|
|
263
|
+
rowVersion: 0
|
|
264
|
+
}, now);
|
|
265
|
+
actionRow = await this.knex('actions')
|
|
266
|
+
.where({ userId: args.userId, transactionId: tx.transactionId })
|
|
267
|
+
.first();
|
|
268
|
+
isNew = true;
|
|
269
|
+
}
|
|
270
|
+
return { action: (0, transactionCrud_1.mapActionRow)(actionRow), transaction: tx, isNew };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* #4 — Atomically update the satoshisDelta column on an action row.
|
|
274
|
+
*/
|
|
275
|
+
async updateActionSatoshisDelta(actionId, delta, now) {
|
|
276
|
+
await this.knex('actions')
|
|
277
|
+
.where({ actionId })
|
|
278
|
+
.update({ satoshis_delta: delta, updated_at: now !== null && now !== void 0 ? now : new Date() });
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* #5 — Create a new transaction row already in `confirmed` state with all proof
|
|
282
|
+
* columns populated. Useful for internalised transactions that arrive with a
|
|
283
|
+
* Merkle proof (bump) already attached.
|
|
284
|
+
*/
|
|
285
|
+
async createWithProof(args) {
|
|
286
|
+
var _a;
|
|
287
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
288
|
+
const merkleIndex = indexFromMerklePath(args.merklePath, args.txid);
|
|
289
|
+
const row = {
|
|
290
|
+
txid: args.txid,
|
|
291
|
+
processing: 'confirmed',
|
|
292
|
+
processingChangedAt: now,
|
|
293
|
+
nextActionAt: undefined,
|
|
294
|
+
attempts: 0,
|
|
295
|
+
rebroadcastCycles: 0,
|
|
296
|
+
wasBroadcast: true,
|
|
297
|
+
idempotencyKey: undefined,
|
|
298
|
+
batch: undefined,
|
|
299
|
+
rawTx: args.rawTx,
|
|
300
|
+
inputBeef: args.inputBeef,
|
|
301
|
+
height: args.height,
|
|
302
|
+
merkleIndex,
|
|
303
|
+
merklePath: args.merklePath,
|
|
304
|
+
merkleRoot: args.merkleRoot,
|
|
305
|
+
blockHash: args.blockHash,
|
|
306
|
+
isCoinbase: args.isCoinbase === true,
|
|
307
|
+
lastProvider: undefined,
|
|
308
|
+
lastProviderStatus: undefined,
|
|
309
|
+
frozenReason: undefined,
|
|
310
|
+
rowVersion: 0
|
|
311
|
+
};
|
|
312
|
+
const id = await (0, transactionCrud_1.insertTransactionNew)(this.knex, row, now);
|
|
313
|
+
await (0, txAudit_1.auditProcessingTransition)(this.knex, id, 'confirmed', 'confirmed', { reason: 'createWithProof' }, now);
|
|
314
|
+
const stored = await this.findById(id);
|
|
315
|
+
if (stored == null)
|
|
316
|
+
throw new Error(`new transaction ${id} disappeared after createWithProof insert`);
|
|
317
|
+
return stored;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* #6 — Find an existing new transaction row for the given txid (suitable for
|
|
321
|
+
* the broadcast queue) or create a new one in `queued` state.
|
|
322
|
+
*/
|
|
323
|
+
async findOrCreateForBroadcast(args) {
|
|
324
|
+
var _a, _b;
|
|
325
|
+
const existing = await this.findByTxid(args.txid);
|
|
326
|
+
if (existing != null) {
|
|
327
|
+
// Patch rawTx / batch if the row was previously created without them.
|
|
328
|
+
const patches = { updated_at: (_a = args.now) !== null && _a !== void 0 ? _a : new Date() };
|
|
329
|
+
if (existing.rawTx == null)
|
|
330
|
+
patches.raw_tx = Buffer.from(args.rawTx);
|
|
331
|
+
if (args.inputBeef != null && existing.inputBeef == null)
|
|
332
|
+
patches.input_beef = Buffer.from(args.inputBeef);
|
|
333
|
+
if (args.batch != null && existing.batch == null)
|
|
334
|
+
patches.batch = args.batch;
|
|
335
|
+
if (Object.keys(patches).length > 1) {
|
|
336
|
+
await this.knex('transactions').where({ transactionId: existing.transactionId }).update(patches);
|
|
337
|
+
const refreshed = await this.findById(existing.transactionId);
|
|
338
|
+
return { transaction: refreshed !== null && refreshed !== void 0 ? refreshed : existing, isNew: false };
|
|
339
|
+
}
|
|
340
|
+
return { transaction: existing, isNew: false };
|
|
341
|
+
}
|
|
342
|
+
const tx = await this.create({
|
|
343
|
+
txid: args.txid,
|
|
344
|
+
processing: (_b = args.processing) !== null && _b !== void 0 ? _b : 'queued',
|
|
345
|
+
rawTx: args.rawTx,
|
|
346
|
+
inputBeef: args.inputBeef,
|
|
347
|
+
batch: args.batch,
|
|
348
|
+
now: args.now
|
|
349
|
+
});
|
|
350
|
+
return { transaction: tx, isNew: true };
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* #7 — Bulk transition: attempt `transition` for each id; collect results.
|
|
354
|
+
* When `expectedFrom` is omitted the current state of each row is used as
|
|
355
|
+
* the expected source (lenient mode — only the FSM is checked).
|
|
356
|
+
*/
|
|
357
|
+
async transitionMany(args) {
|
|
358
|
+
var _a;
|
|
359
|
+
const updated = [];
|
|
360
|
+
const skipped = [];
|
|
361
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
362
|
+
for (const id of args.transactionIds) {
|
|
363
|
+
let expectedFrom = args.expectedFrom;
|
|
364
|
+
if (expectedFrom == null) {
|
|
365
|
+
const row = await this.findById(id);
|
|
366
|
+
if (row == null) {
|
|
367
|
+
skipped.push(id);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
expectedFrom = row.processing;
|
|
371
|
+
}
|
|
372
|
+
const result = await this.transition({
|
|
373
|
+
transactionId: id,
|
|
374
|
+
expectedFrom,
|
|
375
|
+
to: args.to,
|
|
376
|
+
provider: args.provider,
|
|
377
|
+
providerStatus: args.providerStatus,
|
|
378
|
+
details: args.details,
|
|
379
|
+
now
|
|
380
|
+
});
|
|
381
|
+
if (result != null) {
|
|
382
|
+
updated.push(id);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
skipped.push(id);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { updated, skipped };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* #8 — Bulk-set the `batch` column for a list of transaction ids.
|
|
392
|
+
* Pass `undefined` to clear the batch tag.
|
|
393
|
+
*/
|
|
394
|
+
async setBatch(transactionIds, batch, now) {
|
|
395
|
+
if (transactionIds.length === 0)
|
|
396
|
+
return;
|
|
397
|
+
await this.knex('transactions')
|
|
398
|
+
.whereIn('transactionId', transactionIds)
|
|
399
|
+
.update({ batch: batch !== null && batch !== void 0 ? batch : null, updated_at: now !== null && now !== void 0 ? now : new Date() });
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* #9 — Atomically increment the `attempts` counter for one transaction and
|
|
403
|
+
* write an `attempts.incremented` audit entry.
|
|
404
|
+
*/
|
|
405
|
+
async incrementAttempts(transactionId, now) {
|
|
406
|
+
const ts = now !== null && now !== void 0 ? now : new Date();
|
|
407
|
+
const updated = await this.knex('transactions')
|
|
408
|
+
.where({ transactionId })
|
|
409
|
+
.increment('attempts', 1)
|
|
410
|
+
.update({ updated_at: ts });
|
|
411
|
+
if (updated === 0)
|
|
412
|
+
return undefined;
|
|
413
|
+
await (0, txAudit_1.appendTxAudit)(this.knex, { transactionId, event: 'attempts.incremented' }, ts);
|
|
414
|
+
return await this.findById(transactionId);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* #10 — Record the outcome of a broadcast attempt. Transitions processing
|
|
418
|
+
* state, updates `wasBroadcast` and `lastProvider*` columns, and writes an
|
|
419
|
+
* audit row.
|
|
420
|
+
*/
|
|
421
|
+
async recordBroadcastResult(args) {
|
|
422
|
+
var _a;
|
|
423
|
+
const now = (_a = args.now) !== null && _a !== void 0 ? _a : new Date();
|
|
424
|
+
const current = await this.findById(args.transactionId);
|
|
425
|
+
if (current == null)
|
|
426
|
+
return undefined;
|
|
427
|
+
const next = await this.transition({
|
|
428
|
+
transactionId: args.transactionId,
|
|
429
|
+
expectedFrom: current.processing,
|
|
430
|
+
to: args.status,
|
|
431
|
+
provider: args.provider,
|
|
432
|
+
providerStatus: args.providerStatus,
|
|
433
|
+
details: args.details,
|
|
434
|
+
now
|
|
435
|
+
});
|
|
436
|
+
if (next == null)
|
|
437
|
+
return undefined;
|
|
438
|
+
// Update wasBroadcast if the broadcast actually reached the network.
|
|
439
|
+
if (args.wasBroadcast === true && !next.wasBroadcast) {
|
|
440
|
+
await this.knex('transactions')
|
|
441
|
+
.where({ transactionId: args.transactionId })
|
|
442
|
+
.update({ was_broadcast: true, updated_at: now });
|
|
443
|
+
}
|
|
444
|
+
return await this.findById(args.transactionId);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* #11 — Append a free-form history note to the audit log for a transaction.
|
|
448
|
+
*/
|
|
449
|
+
async recordHistoryNote(transactionId, note, now) {
|
|
450
|
+
const { what, ...rest } = note;
|
|
451
|
+
await (0, txAudit_1.appendTxAudit)(this.knex, {
|
|
452
|
+
transactionId,
|
|
453
|
+
event: 'history.note',
|
|
454
|
+
details: { what, ...rest }
|
|
455
|
+
}, now !== null && now !== void 0 ? now : new Date());
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* #12 — For each txid that exists in the new transactions table, merge the
|
|
459
|
+
* raw transaction bytes and (where available) the Merkle path into `beef`.
|
|
460
|
+
* Txids not present in new-schema are silently skipped.
|
|
461
|
+
*/
|
|
462
|
+
async mergeBeefForTxids(beef, txids) {
|
|
463
|
+
if (txids.length === 0)
|
|
464
|
+
return;
|
|
465
|
+
const rows = await this.knex('transactions')
|
|
466
|
+
.whereIn('txid', txids)
|
|
467
|
+
.select('txid', 'raw_tx', 'merkle_path');
|
|
468
|
+
for (const row of rows) {
|
|
469
|
+
if (row.raw_tx != null) {
|
|
470
|
+
const rawBytes = Array.from(row.raw_tx.values());
|
|
471
|
+
beef.mergeRawTx(rawBytes);
|
|
472
|
+
}
|
|
473
|
+
if (row.merkle_path != null) {
|
|
474
|
+
const mp = sdk_1.MerklePath.fromBinary(Array.from(row.merkle_path.values()));
|
|
475
|
+
beef.mergeBump(mp);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* #13 — Collect broadcast-readiness info and a populated Beef for a list of
|
|
481
|
+
* txids. Each entry is classified as:
|
|
482
|
+
* - `readyToSend` — queued/sending → still needs broadcast
|
|
483
|
+
* - `alreadySent` — sent/seen/seen_multi/unconfirmed/confirmed → already on network
|
|
484
|
+
* - `error` — invalid/doubleSpend → terminal failure
|
|
485
|
+
* - `unknown` — not found in new-schema
|
|
486
|
+
*/
|
|
487
|
+
async collectReqsAndBeef(txids, extraTxids) {
|
|
488
|
+
const beef = new sdk_1.Beef();
|
|
489
|
+
const details = [];
|
|
490
|
+
const allTxids = Array.from(new Set([...txids, ...(extraTxids !== null && extraTxids !== void 0 ? extraTxids : [])]));
|
|
491
|
+
if (allTxids.length > 0) {
|
|
492
|
+
const rows = await this.knex('transactions')
|
|
493
|
+
.whereIn('txid', allTxids)
|
|
494
|
+
.select('txid', 'processing', 'raw_tx', 'merkle_path');
|
|
495
|
+
const rowByTxid = new Map();
|
|
496
|
+
for (const row of rows)
|
|
497
|
+
rowByTxid.set(row.txid, row);
|
|
498
|
+
for (const txid of txids) {
|
|
499
|
+
const row = rowByTxid.get(txid);
|
|
500
|
+
if (row == null) {
|
|
501
|
+
details.push({ txid, status: 'unknown' });
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const p = row.processing;
|
|
505
|
+
let status;
|
|
506
|
+
if (p === 'queued' || p === 'sending' || p === 'nonfinal') {
|
|
507
|
+
status = 'readyToSend';
|
|
508
|
+
}
|
|
509
|
+
else if (p === 'sent' || p === 'seen' || p === 'seen_multi' || p === 'unconfirmed' || p === 'confirmed') {
|
|
510
|
+
status = 'alreadySent';
|
|
511
|
+
}
|
|
512
|
+
else if (p === 'invalid' || p === 'doubleSpend') {
|
|
513
|
+
status = 'error';
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
status = 'readyToSend';
|
|
517
|
+
}
|
|
518
|
+
details.push({ txid, status, reason: p });
|
|
519
|
+
// Merge raw tx + proof bytes for txids that have them.
|
|
520
|
+
if (row.raw_tx != null) {
|
|
521
|
+
beef.mergeRawTx(Array.from(row.raw_tx.values()));
|
|
522
|
+
}
|
|
523
|
+
if (row.merkle_path != null) {
|
|
524
|
+
const mp = sdk_1.MerklePath.fromBinary(Array.from(row.merkle_path.values()));
|
|
525
|
+
beef.mergeBump(mp);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Also merge extra txids that may be input ancestors.
|
|
529
|
+
for (const txid of extraTxids !== null && extraTxids !== void 0 ? extraTxids : []) {
|
|
530
|
+
const row = rowByTxid.get(txid);
|
|
531
|
+
if (row == null)
|
|
532
|
+
continue;
|
|
533
|
+
if (row.raw_tx != null) {
|
|
534
|
+
beef.mergeRawTx(Array.from(row.raw_tx.values()));
|
|
535
|
+
}
|
|
536
|
+
if (row.merkle_path != null) {
|
|
537
|
+
const mp = sdk_1.MerklePath.fromBinary(Array.from(row.merkle_path.values()));
|
|
538
|
+
beef.mergeBump(mp);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return { beef, details };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* #14 — Paginated list of actions (per-user transaction views) with optional
|
|
546
|
+
* status and label filters.
|
|
547
|
+
*
|
|
548
|
+
* After the the schema cutover `tx_labels_map.transactionId` references `actions.actionId`
|
|
549
|
+
* (not `transactions.transactionId`).
|
|
550
|
+
*/
|
|
551
|
+
async listActionsForUser(args) {
|
|
552
|
+
let q = this.knex('actions as a')
|
|
553
|
+
.join('transactions as t', 't.transactionId', 'a.transactionId')
|
|
554
|
+
.where('a.userId', args.userId)
|
|
555
|
+
.where('a.hidden', false);
|
|
556
|
+
if (args.statusFilter != null && args.statusFilter.length > 0) {
|
|
557
|
+
q = q.whereIn('t.processing', args.statusFilter);
|
|
558
|
+
}
|
|
559
|
+
if (args.createdAtFrom != null) {
|
|
560
|
+
// IDB path uses >= for `from` (inclusive); mirror that here.
|
|
561
|
+
q = q.where('a.created_at', '>=', args.createdAtFrom);
|
|
562
|
+
}
|
|
563
|
+
if (args.createdAtTo != null) {
|
|
564
|
+
// IDB path uses EXCLUSIVE `to` semantics:
|
|
565
|
+
// r.created_at.getTime() >= args.to.getTime() → exclude
|
|
566
|
+
// Mirror that: exclude rows where created_at >= createdAtTo (use '<' not '<=').
|
|
567
|
+
q = q.where('a.created_at', '<', args.createdAtTo);
|
|
568
|
+
}
|
|
569
|
+
// Label filtering via tx_labels_map (post-cutover: transactionId = actionId)
|
|
570
|
+
if (args.labelIds != null && args.labelIds.length > 0) {
|
|
571
|
+
if (args.labelQueryMode === 'all') {
|
|
572
|
+
// Must have ALL specified labels
|
|
573
|
+
for (const labelId of args.labelIds) {
|
|
574
|
+
q = q.whereExists(this.knex('tx_labels_map as lm')
|
|
575
|
+
.where('lm.transactionId', this.knex.ref('a.actionId'))
|
|
576
|
+
.where('lm.txLabelId', labelId)
|
|
577
|
+
.whereNot('lm.isDeleted', true)
|
|
578
|
+
.select(this.knex.raw('1')));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Default: 'any' — must have at least one of the labels
|
|
583
|
+
q = q.whereExists(this.knex('tx_labels_map as lm')
|
|
584
|
+
.where('lm.transactionId', this.knex.ref('a.actionId'))
|
|
585
|
+
.whereIn('lm.txLabelId', args.labelIds)
|
|
586
|
+
.whereNot('lm.isDeleted', true)
|
|
587
|
+
.select(this.knex.raw('1')));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const countRow = await q.clone().count({ c: 'a.actionId' }).first();
|
|
591
|
+
const total = countRow != null ? Number(countRow.c) : undefined;
|
|
592
|
+
const rows = await q
|
|
593
|
+
.orderBy('a.created_at', 'desc')
|
|
594
|
+
.orderBy('a.actionId', 'asc')
|
|
595
|
+
.limit(args.limit)
|
|
596
|
+
.offset(args.offset)
|
|
597
|
+
.select('a.actionId', 'a.userId', 'a.transactionId', 'a.reference', 'a.description', 'a.isOutgoing', 'a.satoshis_delta', 'a.user_nosend', 'a.hidden', 'a.user_aborted', 'a.notify_json', 'a.row_version', 'a.created_at', 'a.updated_at', 't.txid', 't.processing', 't.height');
|
|
598
|
+
const mapped = rows.map((row) => {
|
|
599
|
+
var _a, _b;
|
|
600
|
+
return ({
|
|
601
|
+
actionId: row.actionId,
|
|
602
|
+
userId: row.userId,
|
|
603
|
+
transactionId: row.transactionId,
|
|
604
|
+
reference: row.reference,
|
|
605
|
+
description: row.description,
|
|
606
|
+
isOutgoing: !!row.isOutgoing,
|
|
607
|
+
satoshisDelta: row.satoshis_delta,
|
|
608
|
+
userNosend: !!row.user_nosend,
|
|
609
|
+
hidden: !!row.hidden,
|
|
610
|
+
userAborted: !!row.user_aborted,
|
|
611
|
+
notifyJson: (_a = row.notify_json) !== null && _a !== void 0 ? _a : undefined,
|
|
612
|
+
rowVersion: row.row_version,
|
|
613
|
+
created_at: new Date(row.created_at),
|
|
614
|
+
updated_at: new Date(row.updated_at),
|
|
615
|
+
txid: row.txid,
|
|
616
|
+
processing: row.processing,
|
|
617
|
+
height: (_b = row.height) !== null && _b !== void 0 ? _b : undefined
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
return { rows: mapped, total };
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* #15 — Paginated list of outputs with their backing transaction processing
|
|
624
|
+
* state. Optional filters: basket, tag set, processing state, spent flag.
|
|
625
|
+
*/
|
|
626
|
+
async listOutputsForUser(args) {
|
|
627
|
+
let q = this.knex('outputs as o')
|
|
628
|
+
.join('transactions as t', 't.transactionId', 'o.transactionId')
|
|
629
|
+
.where('o.userId', args.userId);
|
|
630
|
+
if (args.processingFilter.length > 0) {
|
|
631
|
+
q = q.whereIn('t.processing', args.processingFilter);
|
|
632
|
+
}
|
|
633
|
+
if (!args.includeSpent) {
|
|
634
|
+
q = q.whereNull('o.spentBy');
|
|
635
|
+
}
|
|
636
|
+
if (args.basketId != null) {
|
|
637
|
+
q = q.where('o.basketId', args.basketId);
|
|
638
|
+
}
|
|
639
|
+
// Tag filtering via output_tags_map
|
|
640
|
+
if (args.tagIds != null && args.tagIds.length > 0) {
|
|
641
|
+
if (args.tagQueryMode === 'all') {
|
|
642
|
+
for (const tagId of args.tagIds) {
|
|
643
|
+
q = q.whereExists(this.knex('output_tags_map as otm')
|
|
644
|
+
.where('otm.outputId', this.knex.ref('o.outputId'))
|
|
645
|
+
.where('otm.outputTagId', tagId)
|
|
646
|
+
.whereNot('otm.isDeleted', true)
|
|
647
|
+
.select(this.knex.raw('1')));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
q = q.whereExists(this.knex('output_tags_map as otm')
|
|
652
|
+
.where('otm.outputId', this.knex.ref('o.outputId'))
|
|
653
|
+
.whereIn('otm.outputTagId', args.tagIds)
|
|
654
|
+
.whereNot('otm.isDeleted', true)
|
|
655
|
+
.select(this.knex.raw('1')));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const countRow = await q.clone().count({ c: 'o.outputId' }).first();
|
|
659
|
+
const total = countRow != null ? Number(countRow.c) : undefined;
|
|
660
|
+
const columns = [
|
|
661
|
+
'o.outputId',
|
|
662
|
+
'o.userId',
|
|
663
|
+
'o.transactionId',
|
|
664
|
+
'o.basketId',
|
|
665
|
+
'o.spendable',
|
|
666
|
+
'o.change',
|
|
667
|
+
'o.outputDescription',
|
|
668
|
+
'o.vout',
|
|
669
|
+
'o.satoshis',
|
|
670
|
+
'o.providedBy',
|
|
671
|
+
'o.purpose',
|
|
672
|
+
'o.type',
|
|
673
|
+
'o.txid',
|
|
674
|
+
'o.senderIdentityKey',
|
|
675
|
+
'o.derivationPrefix',
|
|
676
|
+
'o.derivationSuffix',
|
|
677
|
+
'o.customInstructions',
|
|
678
|
+
'o.spentBy',
|
|
679
|
+
'o.sequenceNumber',
|
|
680
|
+
'o.spendingDescription',
|
|
681
|
+
'o.scriptLength',
|
|
682
|
+
'o.scriptOffset',
|
|
683
|
+
'o.created_at',
|
|
684
|
+
'o.updated_at',
|
|
685
|
+
't.processing'
|
|
686
|
+
];
|
|
687
|
+
if (args.includeLockingScripts === true) {
|
|
688
|
+
columns.push('o.lockingScript');
|
|
689
|
+
}
|
|
690
|
+
const rows = await q
|
|
691
|
+
.orderBy('o.outputId', 'asc')
|
|
692
|
+
.limit(args.limit)
|
|
693
|
+
.offset(args.offset)
|
|
694
|
+
.select(columns);
|
|
695
|
+
const mapped = rows.map((row) => {
|
|
696
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
697
|
+
const out = {
|
|
698
|
+
outputId: row.outputId,
|
|
699
|
+
userId: row.userId,
|
|
700
|
+
transactionId: row.transactionId,
|
|
701
|
+
basketId: (_a = row.basketId) !== null && _a !== void 0 ? _a : undefined,
|
|
702
|
+
spendable: !!row.spendable,
|
|
703
|
+
change: !!row.change,
|
|
704
|
+
outputDescription: row.outputDescription,
|
|
705
|
+
vout: row.vout,
|
|
706
|
+
satoshis: row.satoshis,
|
|
707
|
+
providedBy: row.providedBy,
|
|
708
|
+
purpose: row.purpose,
|
|
709
|
+
type: row.type,
|
|
710
|
+
txid: (_b = row.txid) !== null && _b !== void 0 ? _b : undefined,
|
|
711
|
+
senderIdentityKey: (_c = row.senderIdentityKey) !== null && _c !== void 0 ? _c : undefined,
|
|
712
|
+
derivationPrefix: (_d = row.derivationPrefix) !== null && _d !== void 0 ? _d : undefined,
|
|
713
|
+
derivationSuffix: (_e = row.derivationSuffix) !== null && _e !== void 0 ? _e : undefined,
|
|
714
|
+
customInstructions: (_f = row.customInstructions) !== null && _f !== void 0 ? _f : undefined,
|
|
715
|
+
spentBy: (_g = row.spentBy) !== null && _g !== void 0 ? _g : undefined,
|
|
716
|
+
sequenceNumber: (_h = row.sequenceNumber) !== null && _h !== void 0 ? _h : undefined,
|
|
717
|
+
spendingDescription: (_j = row.spendingDescription) !== null && _j !== void 0 ? _j : undefined,
|
|
718
|
+
scriptLength: (_k = row.scriptLength) !== null && _k !== void 0 ? _k : undefined,
|
|
719
|
+
scriptOffset: (_l = row.scriptOffset) !== null && _l !== void 0 ? _l : undefined,
|
|
720
|
+
lockingScript: args.includeLockingScripts === true && row.lockingScript != null
|
|
721
|
+
? Array.from(row.lockingScript.values())
|
|
722
|
+
: undefined,
|
|
723
|
+
created_at: new Date(row.created_at),
|
|
724
|
+
updated_at: new Date(row.updated_at),
|
|
725
|
+
processing: row.processing
|
|
726
|
+
};
|
|
727
|
+
return out;
|
|
728
|
+
});
|
|
729
|
+
return { rows: mapped, total };
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Post-cutover helper: rewrite `tx_labels_map.transactionId` rows that were
|
|
733
|
+
* written with the legacy transactionId (before the real txid + actionId were
|
|
734
|
+
* known) so that they now point at the new-schema `actions.actionId`.
|
|
735
|
+
*
|
|
736
|
+
* Call this once per new outgoing transaction immediately after
|
|
737
|
+
* `findOrCreateActionForTxid` resolves the actionId.
|
|
738
|
+
*
|
|
739
|
+
* This is a no-op when:
|
|
740
|
+
* - `legacyTransactionId` has no rows in `tx_labels_map` (no labels on the tx)
|
|
741
|
+
* - `legacyTransactionId === actionId` (should not happen in practice but
|
|
742
|
+
* is safe to call anyway)
|
|
743
|
+
*/
|
|
744
|
+
async repointLabelsToActionId(legacyTransactionId, actionId, now) {
|
|
745
|
+
if (legacyTransactionId === actionId)
|
|
746
|
+
return;
|
|
747
|
+
const ts = now !== null && now !== void 0 ? now : new Date();
|
|
748
|
+
await this.knex('tx_labels_map')
|
|
749
|
+
.where({ transactionId: legacyTransactionId })
|
|
750
|
+
.update({ transactionId: actionId, updated_at: ts });
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* After `processAction` creates the new `transactions` row, remap
|
|
754
|
+
* `outputs.transactionId` and `outputs.spentBy` from the bridge-period
|
|
755
|
+
* `transactions_legacy.transactionId` to the real `transactions.transactionId`.
|
|
756
|
+
*
|
|
757
|
+
* During `createAction`, new outputs are inserted with `transactionId =
|
|
758
|
+
* legacyTransactionId` (bypassing FK constraints). `listActionsKnex` queries
|
|
759
|
+
* outputs by new transactionId, so without this remap the outputs would be
|
|
760
|
+
* invisible to `listActions`.
|
|
761
|
+
*
|
|
762
|
+
* This is a no-op when `legacyTransactionId === newTransactionId`.
|
|
763
|
+
*/
|
|
764
|
+
async repointOutputsToNewTransactionId(legacyTransactionId, newTransactionId, now) {
|
|
765
|
+
if (legacyTransactionId === newTransactionId)
|
|
766
|
+
return;
|
|
767
|
+
const ts = now !== null && now !== void 0 ? now : new Date();
|
|
768
|
+
// Remap outputs.transactionId (the new-schema FK for "which tx created this output")
|
|
769
|
+
await this.knex('outputs')
|
|
770
|
+
.where({ transactionId: legacyTransactionId })
|
|
771
|
+
.update({ transactionId: newTransactionId, updated_at: ts });
|
|
772
|
+
// Remap outputs.spentBy (the new-schema FK for "which tx spent this output")
|
|
773
|
+
await this.knex('outputs')
|
|
774
|
+
.where({ spentBy: legacyTransactionId })
|
|
775
|
+
.update({ spentBy: newTransactionId, updated_at: ts });
|
|
776
|
+
// Remap commissions.transactionId
|
|
777
|
+
await this.knex('commissions')
|
|
778
|
+
.where({ transactionId: legacyTransactionId })
|
|
779
|
+
.update({ transactionId: newTransactionId, updated_at: ts });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
exports.TransactionService = TransactionService;
|
|
783
|
+
//# sourceMappingURL=transactionService.js.map
|