@rosen-bridge/tx-pot 0.1.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 (49) hide show
  1. package/.eslintignore +1 -0
  2. package/README.md +36 -0
  3. package/dist/db/entities/TransactionEntity.d.ts +15 -0
  4. package/dist/db/entities/TransactionEntity.d.ts.map +1 -0
  5. package/dist/db/entities/TransactionEntity.js +81 -0
  6. package/dist/db/migrations/index.d.ts +7 -0
  7. package/dist/db/migrations/index.d.ts.map +1 -0
  8. package/dist/db/migrations/index.js +7 -0
  9. package/dist/db/migrations/postgres/1706350644686-migration.d.ts +7 -0
  10. package/dist/db/migrations/postgres/1706350644686-migration.d.ts.map +1 -0
  11. package/dist/db/migrations/postgres/1706350644686-migration.js +28 -0
  12. package/dist/db/migrations/sqlite/1706007154531-migration.d.ts +7 -0
  13. package/dist/db/migrations/sqlite/1706007154531-migration.d.ts.map +1 -0
  14. package/dist/db/migrations/sqlite/1706007154531-migration.js +28 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +6 -0
  18. package/dist/network/AbstractPotChainManager.d.ts +36 -0
  19. package/dist/network/AbstractPotChainManager.d.ts.map +1 -0
  20. package/dist/network/AbstractPotChainManager.js +3 -0
  21. package/dist/transaction/TxPot.d.ts +164 -0
  22. package/dist/transaction/TxPot.d.ts.map +1 -0
  23. package/dist/transaction/TxPot.js +386 -0
  24. package/dist/transaction/types.d.ts +35 -0
  25. package/dist/transaction/types.d.ts.map +1 -0
  26. package/dist/transaction/types.js +21 -0
  27. package/dist/transaction/utils.d.ts +8 -0
  28. package/dist/transaction/utils.d.ts.map +1 -0
  29. package/dist/transaction/utils.js +56 -0
  30. package/lib/db/entities/TransactionEntity.ts +44 -0
  31. package/lib/db/migrations/index.ts +7 -0
  32. package/lib/db/migrations/postgres/1706350644686-migration.ts +31 -0
  33. package/lib/db/migrations/sqlite/1706007154531-migration.ts +31 -0
  34. package/lib/index.ts +5 -0
  35. package/lib/network/AbstractPotChainManager.ts +44 -0
  36. package/lib/transaction/TxPot.ts +519 -0
  37. package/lib/transaction/types.ts +46 -0
  38. package/lib/transaction/utils.ts +59 -0
  39. package/package.json +39 -0
  40. package/tests/.gitkeep +0 -0
  41. package/tests/db/dataSource.mock.ts +18 -0
  42. package/tests/network/TestPotChainManager.ts +23 -0
  43. package/tests/transaction/TestTxPot.ts +32 -0
  44. package/tests/transaction/TxPot.spec.ts +1517 -0
  45. package/tests/transaction/testData.ts +84 -0
  46. package/tsconfig.build.json +8 -0
  47. package/tsconfig.build.tsbuildinfo +1 -0
  48. package/tsconfig.json +9 -0
  49. package/vitest.config.ts +13 -0
@@ -0,0 +1,31 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class Migration1706350644686 implements MigrationInterface {
4
+ name = 'Migration1706350644686';
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`
8
+ CREATE TABLE "transaction_entity" (
9
+ "txId" character varying NOT NULL,
10
+ "chain" character varying NOT NULL,
11
+ "txType" character varying NOT NULL,
12
+ "status" character varying NOT NULL,
13
+ "requiredSign" integer NOT NULL,
14
+ "lastCheck" integer NOT NULL,
15
+ "lastStatusUpdate" character varying NOT NULL,
16
+ "failedInSign" boolean NOT NULL,
17
+ "signFailedCount" integer NOT NULL,
18
+ "serializedTx" character varying NOT NULL,
19
+ "extra" character varying,
20
+ "extra2" character varying,
21
+ CONSTRAINT "PK_cafcc9d8e76fef57bc0cf385caa" PRIMARY KEY ("txId", "chain")
22
+ )
23
+ `);
24
+ }
25
+
26
+ public async down(queryRunner: QueryRunner): Promise<void> {
27
+ await queryRunner.query(`
28
+ DROP TABLE "transaction_entity"
29
+ `);
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class Migration1706007154531 implements MigrationInterface {
4
+ name = 'Migration1706007154531';
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`
8
+ CREATE TABLE "transaction_entity" (
9
+ "txId" varchar NOT NULL,
10
+ "chain" varchar NOT NULL,
11
+ "txType" varchar NOT NULL,
12
+ "status" varchar NOT NULL,
13
+ "requiredSign" integer NOT NULL,
14
+ "lastCheck" integer NOT NULL,
15
+ "lastStatusUpdate" varchar NOT NULL,
16
+ "failedInSign" boolean NOT NULL,
17
+ "signFailedCount" integer NOT NULL,
18
+ "serializedTx" varchar NOT NULL,
19
+ "extra" varchar,
20
+ "extra2" varchar,
21
+ PRIMARY KEY ("txId", "chain")
22
+ )
23
+ `);
24
+ }
25
+
26
+ public async down(queryRunner: QueryRunner): Promise<void> {
27
+ await queryRunner.query(`
28
+ DROP TABLE "transaction_entity"
29
+ `);
30
+ }
31
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { AbstractPotChainManager } from './network/AbstractPotChainManager';
2
+ export { TxPot } from './transaction/TxPot';
3
+ export * from './transaction/types';
4
+ export { TransactionEntity } from './db/entities/TransactionEntity';
5
+ export { migrations } from './db/migrations/index';
@@ -0,0 +1,44 @@
1
+ import { SigningStatus } from '../transaction/types';
2
+
3
+ export abstract class AbstractPotChainManager {
4
+ /**
5
+ * gets the blockchain current height
6
+ */
7
+ abstract getHeight: () => Promise<number>;
8
+
9
+ /**
10
+ * returns required number of confirmation
11
+ * @param txType
12
+ */
13
+ abstract getTxRequiredConfirmation: (txType: string) => number;
14
+
15
+ /**
16
+ * gets number of confirmation for a tx
17
+ * returns -1 if tx is not in the blockchain
18
+ * @param txId
19
+ */
20
+ abstract getTxConfirmation: (txId: string) => Promise<number>;
21
+
22
+ /**
23
+ * checks if a tx is still valid and can be sent to the network
24
+ * @param serializedTx
25
+ * @param signingStatus
26
+ */
27
+ abstract isTxValid: (
28
+ serializedTx: string,
29
+ signingStatus: SigningStatus
30
+ ) => Promise<boolean>;
31
+
32
+ /**
33
+ * submits a tx to the blockchain
34
+ * @param serializedTx
35
+ */
36
+ abstract submitTransaction: (serializedTx: string) => Promise<void>;
37
+
38
+ /**
39
+ * checks if a tx is in mempool
40
+ * returns false if the chain has no mempool
41
+ * @param txId
42
+ */
43
+ abstract isTxInMempool: (txId: string) => Promise<boolean>;
44
+ }
@@ -0,0 +1,519 @@
1
+ import { DataSource, Repository } from 'typeorm';
2
+ import { TransactionEntity } from '../db/entities/TransactionEntity';
3
+ import { AbstractLogger, DummyLogger } from '@rosen-bridge/logger-interface';
4
+ import {
5
+ CallbackFunction,
6
+ SigningStatus,
7
+ TransactionStatus,
8
+ TxOptions,
9
+ UnregisteredChain,
10
+ ValidatorFunction,
11
+ } from './types';
12
+ import { txOptionToClause } from './utils';
13
+ import { AbstractPotChainManager } from '../network/AbstractPotChainManager';
14
+
15
+ export class TxPot {
16
+ protected static instance: TxPot;
17
+ protected readonly txRepository: Repository<TransactionEntity>;
18
+ protected chains = new Map<string, AbstractPotChainManager>();
19
+ protected validators = new Map<string, Map<string, ValidatorFunction>>();
20
+ protected txTypeCallbacks = new Map<
21
+ string,
22
+ Map<TransactionStatus, CallbackFunction>
23
+ >();
24
+ protected logger: AbstractLogger;
25
+
26
+ protected constructor(dataSource: DataSource, logger?: AbstractLogger) {
27
+ this.txRepository = dataSource.getRepository(TransactionEntity);
28
+ this.logger = logger ?? new DummyLogger();
29
+ this.logger.debug('TxPot instantiated');
30
+ }
31
+
32
+ /**
33
+ * initiates TxPot
34
+ * @param dataSource typeorm data source
35
+ * @param logger
36
+ * @returns
37
+ */
38
+ public static setup = (
39
+ dataSource: DataSource,
40
+ logger?: AbstractLogger
41
+ ): TxPot => {
42
+ TxPot.instance = new TxPot(dataSource, logger);
43
+ return TxPot.instance;
44
+ };
45
+
46
+ /**
47
+ * returns TxPot instance (throws error if none exists)
48
+ * @returns TxPot instance
49
+ */
50
+ public static getInstance = (): TxPot => {
51
+ if (!TxPot.instance) throw Error(`TxPot instance doesn't exist`);
52
+ return TxPot.instance;
53
+ };
54
+
55
+ /**
56
+ * registers a chain to TxPot
57
+ * @param chain
58
+ * @param chainManager
59
+ */
60
+ registerChain = (
61
+ chain: string,
62
+ chainManager: AbstractPotChainManager
63
+ ): void => {
64
+ this.chains.set(chain, chainManager);
65
+ this.logger.debug(
66
+ `A TxPot chain manager is registered for chain [${chain}]`
67
+ );
68
+ };
69
+
70
+ /**
71
+ * registers a validator function
72
+ * @param chain
73
+ * @param txType
74
+ * @param validator
75
+ */
76
+ registerValidator = (
77
+ chain: string,
78
+ txType: string,
79
+ validator: ValidatorFunction
80
+ ): void => {
81
+ let chainValidators = this.validators.get(chain);
82
+ if (!chainValidators) {
83
+ chainValidators = new Map<string, ValidatorFunction>();
84
+ this.validators.set(chain, chainValidators);
85
+ }
86
+
87
+ chainValidators.set(txType, validator);
88
+ this.logger.debug(
89
+ `A tx validator function is registered for chain [${chain}] and type [${txType}]`
90
+ );
91
+ };
92
+
93
+ /**
94
+ * registers a callback function
95
+ * the callback will be called when status of any transactions
96
+ * of given type changes to given status
97
+ * @param txType
98
+ * @param status
99
+ * @param callback
100
+ */
101
+ registerCallback = (
102
+ txType: string,
103
+ status: TransactionStatus,
104
+ callback: CallbackFunction
105
+ ): void => {
106
+ let typeCallbacks = this.txTypeCallbacks.get(txType);
107
+ if (!typeCallbacks) {
108
+ typeCallbacks = new Map<TransactionStatus, CallbackFunction>();
109
+ this.txTypeCallbacks.set(txType, typeCallbacks);
110
+ }
111
+
112
+ typeCallbacks.set(status, callback);
113
+ this.logger.debug(
114
+ `A tx status callback function is registered for type [${txType}] and status [${status}]`
115
+ );
116
+ };
117
+
118
+ /**
119
+ * returns chain manager for given chain
120
+ * throws error if no manager is registered for it
121
+ * @param chain
122
+ */
123
+ protected getChainManager = (chain: string): AbstractPotChainManager => {
124
+ const manager = this.chains.get(chain);
125
+ if (!manager)
126
+ throw new UnregisteredChain(
127
+ `No manager is registered for chain [${chain}]`
128
+ );
129
+ return manager;
130
+ };
131
+
132
+ /**
133
+ * sets the tx as invalid if enough blocks is passed from last check
134
+ * @param tx
135
+ */
136
+ protected setTransactionAsInvalid = async (
137
+ tx: TransactionEntity
138
+ ): Promise<void> => {
139
+ const manager = this.getChainManager(tx.chain);
140
+
141
+ const currentHeight = await manager.getHeight();
142
+ const requiredConfirmation = manager.getTxRequiredConfirmation(tx.txType);
143
+
144
+ if (currentHeight - tx.lastCheck >= requiredConfirmation) {
145
+ await this.setTxStatus(tx, TransactionStatus.INVALID);
146
+ this.logger.info(`Tx [${tx.txId}] is invalid`);
147
+ } else {
148
+ this.logger.info(
149
+ `Tx [${
150
+ tx.txId
151
+ }] seems invalid. Waiting for enough confirmation of this proposition [${
152
+ currentHeight - tx.lastCheck
153
+ }/${requiredConfirmation}]`
154
+ );
155
+ }
156
+ };
157
+
158
+ /**
159
+ * validates a transaction
160
+ * returns true if no validator functions is set or tx is valid
161
+ * otherwise handle the tx as invalid and returns false
162
+ * @param tx
163
+ */
164
+ protected validateTx = async (tx: TransactionEntity): Promise<boolean> => {
165
+ const validator = this.validators.get(tx.chain)?.get(tx.txType);
166
+ if (validator === undefined) {
167
+ // tx is valid since no validator is found
168
+ this.logger.debug(
169
+ `No validator function is found for chain [${tx.chain}] and type [${tx.txType}]`
170
+ );
171
+ return true;
172
+ }
173
+ if (await validator(tx)) return true;
174
+
175
+ await this.setTransactionAsInvalid(tx);
176
+ return false;
177
+ };
178
+
179
+ /**
180
+ * updates the status of a tx
181
+ * @param txKey tx id and chain
182
+ * @param status new status
183
+ */
184
+ protected setTxStatus = async (
185
+ tx: TransactionEntity,
186
+ status: TransactionStatus
187
+ ): Promise<void> => {
188
+ await this.txRepository.update(
189
+ {
190
+ txId: tx.txId,
191
+ chain: tx.chain,
192
+ },
193
+ {
194
+ status: status,
195
+ lastStatusUpdate: this.currentTime(),
196
+ }
197
+ );
198
+ const callback = this.txTypeCallbacks.get(tx.txType)?.get(status);
199
+ if (callback)
200
+ callback(tx, status).catch((e) => {
201
+ this.logger.debug(
202
+ `An error occurred while handling tx [${tx.txId}] status change: ${e}`
203
+ );
204
+ if (e instanceof Error && e.stack) this.logger.debug(e.stack);
205
+ });
206
+ else
207
+ this.logger.debug(
208
+ `No callback function is set for type [${tx.txType}] and status [${status}]`
209
+ );
210
+ };
211
+
212
+ /**
213
+ * @returns current timestamp in seconds and string format
214
+ */
215
+ protected currentTime = () => String(Math.round(Date.now() / 1000));
216
+
217
+ /**
218
+ * submits the signed transaction to the blockchain
219
+ * @param tx
220
+ */
221
+ protected processSignedTx = async (tx: TransactionEntity): Promise<void> => {
222
+ const manager = this.getChainManager(tx.chain);
223
+ try {
224
+ await manager.submitTransaction(tx.serializedTx);
225
+ } catch (e) {
226
+ this.logger.warn(
227
+ `Failed to submit tx [${tx.txId}] to chain [${tx.chain}]: ${e}`
228
+ );
229
+ if (e instanceof Error && e.stack) this.logger.warn(e.stack);
230
+ }
231
+ await this.setTxStatus(tx, TransactionStatus.SENT);
232
+ };
233
+
234
+ /**
235
+ * processes the sent transaction
236
+ * @param tx
237
+ */
238
+ protected processesSentTx = async (tx: TransactionEntity): Promise<void> => {
239
+ const manager = this.getChainManager(tx.chain);
240
+
241
+ const txConfirmation = await manager.getTxConfirmation(tx.txId);
242
+ const requiredConfirmation = manager.getTxRequiredConfirmation(tx.txType);
243
+
244
+ if (txConfirmation >= requiredConfirmation) {
245
+ // tx is confirmed enough
246
+ await this.setTxStatus(tx, TransactionStatus.COMPLETED);
247
+ } else if (txConfirmation === -1) {
248
+ // tx is not mined, checking mempool...
249
+ if (await manager.isTxInMempool(tx.txId)) {
250
+ // tx is in mempool, updating last check...
251
+ const height = await manager.getHeight();
252
+ await this.updateTxLastCheck(tx.txId, tx.chain, height);
253
+ this.logger.info(`Tx [${tx.txId}] is in mempool`);
254
+ } else {
255
+ // tx is not in mempool, checking if tx is still valid
256
+ const isValidTx = await manager.isTxValid(
257
+ tx.serializedTx,
258
+ SigningStatus.Signed
259
+ );
260
+ const isValidToType = await this.validateTx(tx);
261
+
262
+ if (isValidTx && isValidToType) {
263
+ // tx is valid. resending...
264
+ this.logger.info(`Tx [${tx.txId}] is still valid. Resending tx...`);
265
+ await manager.submitTransaction(tx.serializedTx);
266
+ } else {
267
+ // tx seems invalid. reset status if enough blocks past.
268
+ await this.setTransactionAsInvalid(tx);
269
+ }
270
+ }
271
+ } else {
272
+ // tx is mined, but is not confirmed enough, updating last check...
273
+ const height = await manager.getHeight();
274
+ await this.updateTxLastCheck(tx.txId, tx.chain, height);
275
+ this.logger.info(
276
+ `Tx [${tx.txId}] is in confirmation process [${txConfirmation}/${requiredConfirmation}]`
277
+ );
278
+ }
279
+ };
280
+
281
+ /**
282
+ * runs all jobs of TxPot
283
+ * - process signed txs
284
+ * - process sent txs
285
+ */
286
+ update = async (): Promise<void> => {
287
+ // process signed txs
288
+ const signedTxs = await this.getTxsByStatus(TransactionStatus.SIGNED);
289
+ for (const tx of signedTxs) {
290
+ try {
291
+ await this.processSignedTx(tx);
292
+ } catch (e) {
293
+ this.logger.warn(
294
+ `An error occurred while processing tx [${tx.txId}] with status [${TransactionStatus.SIGNED}]: ${e}`
295
+ );
296
+ if (e instanceof Error && e.stack) this.logger.warn(e.stack);
297
+ }
298
+ }
299
+ this.logger.debug(
300
+ `Processed [${signedTxs.length}] txs with status [${TransactionStatus.SIGNED}]`
301
+ );
302
+
303
+ // process sent txs
304
+ const sentTxs = await this.getTxsByStatus(TransactionStatus.SENT);
305
+ for (const tx of sentTxs) {
306
+ try {
307
+ await this.processesSentTx(tx);
308
+ } catch (e) {
309
+ this.logger.warn(
310
+ `An error occurred while processing tx [${tx.txId}] with status [${TransactionStatus.SENT}]: ${e}`
311
+ );
312
+ if (e instanceof Error && e.stack) this.logger.warn(e.stack);
313
+ }
314
+ }
315
+ this.logger.debug(
316
+ `Processed [${sentTxs.length}] txs with status [${TransactionStatus.SENT}]`
317
+ );
318
+ };
319
+
320
+ /**
321
+ * gets transactions by status
322
+ * @param status
323
+ * @param validate
324
+ * @returns
325
+ */
326
+ getTxsByStatus = async (
327
+ status: TransactionStatus,
328
+ validate = false
329
+ ): Promise<Array<TransactionEntity>> => {
330
+ const txs = await this.txRepository.find({
331
+ where: {
332
+ status: status,
333
+ },
334
+ });
335
+ if (!validate) return txs;
336
+
337
+ // validate the transactions
338
+ const validTxs: Array<TransactionEntity> = [];
339
+ for (const tx of txs) {
340
+ if (await this.validateTx(tx)) validTxs.push(tx);
341
+ }
342
+ return validTxs;
343
+ };
344
+
345
+ /**
346
+ * inserts a new transaction into db
347
+ * @param txId
348
+ * @param chain
349
+ * @param txType
350
+ * @param requiredSign
351
+ * @param serializedTx
352
+ * @param initialStatus
353
+ * @param lastCheck
354
+ */
355
+ addTx = async (
356
+ txId: string,
357
+ chain: string,
358
+ txType: string,
359
+ requiredSign: number,
360
+ serializedTx: string,
361
+ initialStatus = TransactionStatus.APPROVED,
362
+ lastCheck = 0
363
+ ): Promise<void> => {
364
+ await this.txRepository.insert({
365
+ txId: txId,
366
+ chain: chain,
367
+ txType: txType,
368
+ status: initialStatus,
369
+ requiredSign: requiredSign,
370
+ lastCheck: lastCheck,
371
+ lastStatusUpdate: this.currentTime(),
372
+ failedInSign: false,
373
+ signFailedCount: 0,
374
+ serializedTx: serializedTx,
375
+ });
376
+ };
377
+
378
+ /**
379
+ * updates the status of a tx
380
+ * @param txId
381
+ * @param chain
382
+ * @param status new status
383
+ */
384
+ setTxStatusById = async (
385
+ txId: string,
386
+ chain: string,
387
+ status: TransactionStatus
388
+ ): Promise<void> => {
389
+ const tx = await this.txRepository.findOneOrFail({
390
+ where: { txId, chain },
391
+ });
392
+ await this.setTxStatus(tx, status);
393
+ };
394
+
395
+ /**
396
+ * updates tx info when failed in sign process
397
+ * @param txId
398
+ * @param chain
399
+ */
400
+ setTxAsSignFailed = async (txId: string, chain: string): Promise<void> => {
401
+ await this.txRepository.update(
402
+ {
403
+ txId: txId,
404
+ chain: chain,
405
+ status: TransactionStatus.IN_SIGN,
406
+ },
407
+ {
408
+ status: TransactionStatus.SIGN_FAILED,
409
+ lastStatusUpdate: this.currentTime(),
410
+ signFailedCount: () => '"signFailedCount" + 1',
411
+ failedInSign: true,
412
+ }
413
+ );
414
+ };
415
+
416
+ /**
417
+ * updates the tx and set status as signed
418
+ * @param txId
419
+ * @param chain
420
+ * @param serializedTx
421
+ * @param currentHeight current height of the blockchain
422
+ * @param extra
423
+ * @param extra2
424
+ */
425
+ setTxAsSigned = async (
426
+ txId: string,
427
+ chain: string,
428
+ serializedTx: string,
429
+ currentHeight: number,
430
+ extra?: string,
431
+ extra2?: string
432
+ ): Promise<void> => {
433
+ const updatedFields: Partial<TransactionEntity> = {
434
+ serializedTx: serializedTx,
435
+ status: TransactionStatus.SIGNED,
436
+ lastStatusUpdate: this.currentTime(),
437
+ lastCheck: currentHeight,
438
+ };
439
+ if (extra) updatedFields.extra = extra;
440
+ if (extra2) updatedFields.extra2 = extra2;
441
+
442
+ await this.txRepository.update({ txId, chain }, updatedFields);
443
+ };
444
+
445
+ /**
446
+ * updates last check value of a tx
447
+ * @param txId
448
+ * @param chain
449
+ * @param currentHeight current height of the blockchain
450
+ */
451
+ updateTxLastCheck = async (
452
+ txId: string,
453
+ chain: string,
454
+ currentHeight: number
455
+ ): Promise<void> => {
456
+ await this.txRepository.update(
457
+ { txId, chain },
458
+ { lastCheck: currentHeight }
459
+ );
460
+ };
461
+
462
+ /**
463
+ * updates failedInSign field of a transaction to false
464
+ * @param txId
465
+ * @param chain
466
+ */
467
+ resetFailedInSign = async (txId: string, chain: string): Promise<void> => {
468
+ await this.txRepository.update(
469
+ { txId, chain },
470
+ {
471
+ failedInSign: false,
472
+ }
473
+ );
474
+ };
475
+
476
+ /**
477
+ * updates requiredSign field of a transaction
478
+ * @param txId
479
+ * @param chain
480
+ * @param requiredSign
481
+ */
482
+ updateRequiredSign = async (
483
+ txId: string,
484
+ chain: string,
485
+ requiredSign: number
486
+ ): Promise<void> => {
487
+ await this.txRepository.update(
488
+ { txId, chain },
489
+ {
490
+ requiredSign: requiredSign,
491
+ }
492
+ );
493
+ };
494
+
495
+ /**
496
+ * gets the transaction by its id and chain
497
+ * @param txId
498
+ * @param chain
499
+ */
500
+ getTxByKey = async (
501
+ txId: string,
502
+ chain: string
503
+ ): Promise<TransactionEntity | null> => {
504
+ return await this.txRepository.findOne({
505
+ where: { txId, chain },
506
+ });
507
+ };
508
+
509
+ /**
510
+ * @returns the transactions with valid status
511
+ */
512
+ getTxsQuery = (
513
+ options: Array<TxOptions> = []
514
+ ): Promise<TransactionEntity[]> => {
515
+ return this.txRepository.find({
516
+ where: options.map(txOptionToClause),
517
+ });
518
+ };
519
+ }
@@ -0,0 +1,46 @@
1
+ import { TransactionEntity } from '../db/entities/TransactionEntity';
2
+
3
+ export type ChainRequiredConfirmations = Record<string, number>; // tx type => required number
4
+ export type RequiredConfirmations = Record<string, ChainRequiredConfirmations>;
5
+
6
+ export type ValidatorFunction = (tx: TransactionEntity) => Promise<boolean>;
7
+ export type CallbackFunction = (
8
+ tx: TransactionEntity,
9
+ newStatus: TransactionStatus
10
+ ) => Promise<void>;
11
+
12
+ export enum TransactionStatus {
13
+ APPROVED = 'approved',
14
+ IN_SIGN = 'in-sign',
15
+ SIGN_FAILED = 'sign-failed',
16
+ SIGNED = 'signed',
17
+ SENT = 'sent',
18
+ INVALID = 'invalid',
19
+ COMPLETED = 'completed',
20
+ }
21
+
22
+ export enum SigningStatus {
23
+ Signed,
24
+ UnSigned,
25
+ }
26
+
27
+ export type FieldValue<T> = T | Array<T>;
28
+ export interface FieldOption<T> {
29
+ not: boolean;
30
+ value: FieldValue<T>;
31
+ }
32
+
33
+ export interface TxOptions {
34
+ txId?: FieldValue<string>;
35
+ chain?: string;
36
+ txType?: string;
37
+ status?: FieldOption<TransactionStatus>;
38
+ failedInSign?: boolean;
39
+ extra?: FieldValue<string>;
40
+ }
41
+
42
+ export class UnregisteredChain extends Error {
43
+ constructor(msg: string) {
44
+ super('UnregisteredChain: ' + msg);
45
+ }
46
+ }