@rosen-bridge/abstract-extractor 0.1.0-2afdd7

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 (81) hide show
  1. package/.eslintignore +1 -0
  2. package/README.md +24 -0
  3. package/dist/AbstractExtractor.d.ts +25 -0
  4. package/dist/AbstractExtractor.d.ts.map +1 -0
  5. package/dist/AbstractExtractor.js +3 -0
  6. package/dist/constants.d.ts +4 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +4 -0
  9. package/dist/ergo/AbstractErgoExtractor.d.ts +39 -0
  10. package/dist/ergo/AbstractErgoExtractor.d.ts.map +1 -0
  11. package/dist/ergo/AbstractErgoExtractor.js +57 -0
  12. package/dist/ergo/AbstractErgoExtractorAction.d.ts +27 -0
  13. package/dist/ergo/AbstractErgoExtractorAction.d.ts.map +1 -0
  14. package/dist/ergo/AbstractErgoExtractorAction.js +3 -0
  15. package/dist/ergo/index.d.ts +8 -0
  16. package/dist/ergo/index.d.ts.map +1 -0
  17. package/dist/ergo/index.js +8 -0
  18. package/dist/ergo/initializable/AbstractInitializable.d.ts +48 -0
  19. package/dist/ergo/initializable/AbstractInitializable.d.ts.map +1 -0
  20. package/dist/ergo/initializable/AbstractInitializable.js +84 -0
  21. package/dist/ergo/initializable/AbstractInitializableAction.d.ts +9 -0
  22. package/dist/ergo/initializable/AbstractInitializableAction.d.ts.map +1 -0
  23. package/dist/ergo/initializable/AbstractInitializableAction.js +4 -0
  24. package/dist/ergo/initializable/InitializableByAddress.d.ts +26 -0
  25. package/dist/ergo/initializable/InitializableByAddress.d.ts.map +1 -0
  26. package/dist/ergo/initializable/InitializableByAddress.js +38 -0
  27. package/dist/ergo/initializable/InitializableByToken.d.ts +26 -0
  28. package/dist/ergo/initializable/InitializableByToken.d.ts.map +1 -0
  29. package/dist/ergo/initializable/InitializableByToken.js +38 -0
  30. package/dist/ergo/initializable/index.d.ts +5 -0
  31. package/dist/ergo/initializable/index.d.ts.map +1 -0
  32. package/dist/ergo/initializable/index.js +5 -0
  33. package/dist/ergo/interfaces.d.ts +56 -0
  34. package/dist/ergo/interfaces.d.ts.map +1 -0
  35. package/dist/ergo/interfaces.js +6 -0
  36. package/dist/ergo/network/AbstractNetwork.d.ts +32 -0
  37. package/dist/ergo/network/AbstractNetwork.d.ts.map +1 -0
  38. package/dist/ergo/network/AbstractNetwork.js +3 -0
  39. package/dist/ergo/network/ExplorerNetwork.d.ts +35 -0
  40. package/dist/ergo/network/ExplorerNetwork.d.ts.map +1 -0
  41. package/dist/ergo/network/ExplorerNetwork.js +53 -0
  42. package/dist/ergo/network/NodeNetwork.d.ts +42 -0
  43. package/dist/ergo/network/NodeNetwork.d.ts.map +1 -0
  44. package/dist/ergo/network/NodeNetwork.js +71 -0
  45. package/dist/ergo/utils.d.ts +8 -0
  46. package/dist/ergo/utils.d.ts.map +1 -0
  47. package/dist/ergo/utils.js +16 -0
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +4 -0
  51. package/dist/interfaces.d.ts +12 -0
  52. package/dist/interfaces.d.ts.map +1 -0
  53. package/dist/interfaces.js +2 -0
  54. package/lib/AbstractExtractor.ts +31 -0
  55. package/lib/constants.ts +3 -0
  56. package/lib/ergo/AbstractErgoExtractor.ts +107 -0
  57. package/lib/ergo/AbstractErgoExtractorAction.ts +39 -0
  58. package/lib/ergo/index.ts +7 -0
  59. package/lib/ergo/initializable/AbstractInitializable.ts +148 -0
  60. package/lib/ergo/initializable/AbstractInitializableAction.ts +11 -0
  61. package/lib/ergo/initializable/InitializableByAddress.ts +53 -0
  62. package/lib/ergo/initializable/InitializableByToken.ts +53 -0
  63. package/lib/ergo/initializable/index.ts +4 -0
  64. package/lib/ergo/interfaces.ts +64 -0
  65. package/lib/ergo/network/AbstractNetwork.ts +35 -0
  66. package/lib/ergo/network/ExplorerNetwork.ts +68 -0
  67. package/lib/ergo/network/NodeNetwork.ts +93 -0
  68. package/lib/ergo/utils.ts +15 -0
  69. package/lib/index.ts +3 -0
  70. package/lib/interfaces.ts +12 -0
  71. package/package.json +42 -0
  72. package/tests/AbstractExtractor.mock.ts +26 -0
  73. package/tests/AbstractExtractor.spec.ts +106 -0
  74. package/tests/initializable/AbstractInitializable.mock.ts +31 -0
  75. package/tests/initializable/AbstractInitializable.spec.ts +258 -0
  76. package/tests/initializable/testData.ts +59 -0
  77. package/tests/testData.ts +111 -0
  78. package/tsconfig.build.json +7 -0
  79. package/tsconfig.build.tsbuildinfo +1 -0
  80. package/tsconfig.json +7 -0
  81. package/vitest.config.ts +17 -0
@@ -0,0 +1,148 @@
1
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
2
+ import JsonBigInt from '@rosen-bridge/json-bigint';
3
+
4
+ import { ErgoBox, ErgoExtractedData } from '../interfaces';
5
+ import { API_LIMIT, RETRIAL_COUNT } from '../../constants';
6
+ import { AbstractErgoExtractor } from '../AbstractErgoExtractor';
7
+ import { AbstractInitializableErgoExtractorAction } from './AbstractInitializableAction';
8
+ import { BlockInfo } from '../../interfaces';
9
+
10
+ export abstract class AbstractInitializableErgoExtractor<
11
+ ExtractedData extends ErgoExtractedData
12
+ > extends AbstractErgoExtractor<ExtractedData> {
13
+ protected initialize: boolean;
14
+ protected abstract actions: AbstractInitializableErgoExtractorAction<ExtractedData>;
15
+
16
+ /**
17
+ * create an initializable ergo extractor
18
+ * @param initialize ignore the initialization step if its false
19
+ * @param logger
20
+ */
21
+ constructor(initialize = true, logger?: AbstractLogger) {
22
+ super(logger);
23
+ this.initialize = initialize;
24
+ }
25
+
26
+ /**
27
+ * return init required boxes with offset limit
28
+ * @param offset
29
+ * @param limit
30
+ * @return boxes in batch
31
+ */
32
+ abstract getBoxesWithOffsetLimit: (
33
+ offset: number,
34
+ limit: number
35
+ ) => Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }>;
36
+
37
+ /**
38
+ * return block information of specified tx
39
+ * @param txId
40
+ * @return block info
41
+ */
42
+ abstract getTxBlock: (txId: string) => Promise<BlockInfo>;
43
+
44
+ /**
45
+ * return all related data below the initial height (including the init height)
46
+ * @param initialHeight
47
+ * @return extracted data in batch
48
+ */
49
+ fetchDataWithOffsetLimit = async (
50
+ initialHeight: number,
51
+ offset: number,
52
+ limit: number
53
+ ): Promise<{
54
+ extractedBoxes: Array<ExtractedData>;
55
+ hasNextBatch: boolean;
56
+ }> => {
57
+ const extractedBoxes: Array<ExtractedData> = [];
58
+ const apiOutput = await this.getBoxesWithOffsetLimit(offset, limit);
59
+
60
+ const filteredBoxes = apiOutput.boxes.filter(
61
+ (box: ErgoBox) => box.creationHeight <= initialHeight && this.hasData(box)
62
+ );
63
+ for (const box of filteredBoxes) {
64
+ const data = this.extractBoxData(box, box.blockId, box.creationHeight);
65
+ if (!data) continue;
66
+ let spendBlock, spendHeight;
67
+ if (box.spentTransactionId) {
68
+ const block = await this.getTxBlock(box.spentTransactionId);
69
+ if (block.height <= initialHeight) {
70
+ this.logger.debug(
71
+ `Box with id ${box.boxId} spent at block ${JsonBigInt.stringify(
72
+ block
73
+ )} bellow the initial height`
74
+ );
75
+ spendBlock = block.hash;
76
+ spendHeight = block.height;
77
+ }
78
+ }
79
+ const extractedData = {
80
+ ...data,
81
+ spendBlock,
82
+ spendHeight,
83
+ } as ExtractedData;
84
+ this.logger.debug(
85
+ `Extracted data ${JsonBigInt.stringify(extractedData)} from box ${
86
+ box.boxId
87
+ } in initialization phase`
88
+ );
89
+ extractedBoxes.push(extractedData);
90
+ }
91
+ return { extractedBoxes, hasNextBatch: apiOutput.hasNextBatch };
92
+ };
93
+
94
+ /**
95
+ * initialize extractor database with data created below the initial height
96
+ * ignore initialization if this feature is off
97
+ * try to get data multiple times to pass accidental network problems
98
+ * @param initialBlock
99
+ */
100
+ initializeBoxes = async (initialBlock: BlockInfo) => {
101
+ let trial = 1;
102
+ if (this.initialize) {
103
+ this.logger.debug(
104
+ `Initializing ${this.getId()} started, removing all existing data`
105
+ );
106
+ await this.actions.removeAllData(this.getId());
107
+
108
+ let hasNextBatch = true;
109
+ let offset = 0;
110
+ while (hasNextBatch) {
111
+ try {
112
+ const data = await this.fetchDataWithOffsetLimit(
113
+ initialBlock.height,
114
+ offset,
115
+ API_LIMIT
116
+ );
117
+ this.logger.info(
118
+ `Inserting ${
119
+ data.extractedBoxes.length
120
+ } new extracted data in ${this.getId()} initialization`
121
+ );
122
+ const insertSuccess = await this.actions.insertBoxes(
123
+ data.extractedBoxes,
124
+ this.getId()
125
+ );
126
+ if (!insertSuccess) throw new Error('Could not store extracted data');
127
+
128
+ hasNextBatch = data.hasNextBatch;
129
+ offset += API_LIMIT;
130
+ } catch (e) {
131
+ this.logger.warn(
132
+ `Initialization for ${this.getId()} failed with error :${e}`
133
+ );
134
+ if (trial == RETRIAL_COUNT)
135
+ throw Error(
136
+ `Initialization for ${this.getId()} failed after ${RETRIAL_COUNT} retrial`
137
+ );
138
+ trial += 1;
139
+ this.logger.info(
140
+ `Trying again to initialize ${this.getId()} with trial step ${trial}`
141
+ );
142
+ }
143
+ }
144
+ } else {
145
+ this.logger.info(`Initialization for ${this.getId()} is turned off`);
146
+ }
147
+ };
148
+ }
@@ -0,0 +1,11 @@
1
+ import { AbstractErgoExtractorAction } from '../AbstractErgoExtractorAction';
2
+
3
+ export abstract class AbstractInitializableErgoExtractorAction<
4
+ ExtractedData
5
+ > extends AbstractErgoExtractorAction<ExtractedData> {
6
+ /**
7
+ * remove all existing data for the extractor
8
+ * @param extractorId
9
+ */
10
+ abstract removeAllData: (extractorId: string) => Promise<void>;
11
+ }
@@ -0,0 +1,53 @@
1
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
2
+
3
+ import { ErgoExtractedData, ErgoNetworkType, ErgoBox } from '../interfaces';
4
+ import { AbstractInitializableErgoExtractor } from './AbstractInitializable';
5
+ import { BlockInfo } from '../../interfaces';
6
+ import { ExplorerNetwork } from '../network/ExplorerNetwork';
7
+ import { NodeNetwork } from '../network/NodeNetwork';
8
+ import { AbstractNetwork } from '../network/AbstractNetwork';
9
+
10
+ export abstract class AbstractInitializableByAddressErgoExtractor<
11
+ ExtractedData extends ErgoExtractedData
12
+ > extends AbstractInitializableErgoExtractor<ExtractedData> {
13
+ private address: string;
14
+ private network: AbstractNetwork;
15
+
16
+ constructor(
17
+ type: ErgoNetworkType,
18
+ url: string,
19
+ address: string,
20
+ initialize?: boolean,
21
+ logger?: AbstractLogger
22
+ ) {
23
+ super(initialize, logger);
24
+ this.address = address;
25
+ if (type == ErgoNetworkType.Explorer) {
26
+ this.network = new ExplorerNetwork(url);
27
+ } else if (type == ErgoNetworkType.Node) {
28
+ this.network = new NodeNetwork(url);
29
+ } else throw new Error(`Network type ${type} is not supported`);
30
+ }
31
+
32
+ /**
33
+ * return boxes by token id from the specified network source
34
+ * @param offset
35
+ * @param limit
36
+ * @return boxes in batch
37
+ */
38
+ getBoxesWithOffsetLimit = (
39
+ offset: number,
40
+ limit: number
41
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
42
+ return this.network.getBoxesByAddress(this.address, offset, limit);
43
+ };
44
+
45
+ /**
46
+ * return block information from the specified network source
47
+ * @param txId
48
+ * @return block info
49
+ */
50
+ getTxBlock = (txId: string): Promise<BlockInfo> => {
51
+ return this.network.getTxBlock(txId);
52
+ };
53
+ }
@@ -0,0 +1,53 @@
1
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
2
+
3
+ import { ErgoExtractedData, ErgoNetworkType, ErgoBox } from '../interfaces';
4
+ import { AbstractInitializableErgoExtractor } from './AbstractInitializable';
5
+ import { BlockInfo } from '../../interfaces';
6
+ import { ExplorerNetwork } from '../network/ExplorerNetwork';
7
+ import { NodeNetwork } from '../network/NodeNetwork';
8
+ import { AbstractNetwork } from '../network/AbstractNetwork';
9
+
10
+ export abstract class AbstractInitializableByTokenErgoExtractor<
11
+ ExtractedData extends ErgoExtractedData
12
+ > extends AbstractInitializableErgoExtractor<ExtractedData> {
13
+ private tokenId: string;
14
+ private network: AbstractNetwork;
15
+
16
+ constructor(
17
+ type: ErgoNetworkType,
18
+ url: string,
19
+ tokenId: string,
20
+ initialize?: boolean,
21
+ logger?: AbstractLogger
22
+ ) {
23
+ super(initialize, logger);
24
+ this.tokenId = tokenId;
25
+ if (type == ErgoNetworkType.Explorer) {
26
+ this.network = new ExplorerNetwork(url);
27
+ } else if (type == ErgoNetworkType.Node) {
28
+ this.network = new NodeNetwork(url);
29
+ } else throw new Error(`Network type ${type} is not supported`);
30
+ }
31
+
32
+ /**
33
+ * return boxes by token id from the specified network source
34
+ * @param offset
35
+ * @param limit
36
+ * @return boxes in batch
37
+ */
38
+ getBoxesWithOffsetLimit = (
39
+ offset: number,
40
+ limit: number
41
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
42
+ return this.network.getBoxesByTokenId(this.tokenId, offset, limit);
43
+ };
44
+
45
+ /**
46
+ * return block information from the specified network source
47
+ * @param txId
48
+ * @return block info
49
+ */
50
+ getTxBlock = (txId: string): Promise<BlockInfo> => {
51
+ return this.network.getTxBlock(txId);
52
+ };
53
+ }
@@ -0,0 +1,4 @@
1
+ export * from './AbstractInitializable';
2
+ export * from './AbstractInitializableAction';
3
+ export * from './InitializableByAddress';
4
+ export * from './InitializableByToken';
@@ -0,0 +1,64 @@
1
+ export enum ErgoNetworkType {
2
+ Explorer = 'explorer',
3
+ Node = 'node',
4
+ }
5
+
6
+ export type InputBox = {
7
+ boxId: string;
8
+ };
9
+
10
+ export type DataInput = {
11
+ boxId: string;
12
+ };
13
+
14
+ export type Asset = {
15
+ tokenId: string;
16
+ amount: bigint;
17
+ };
18
+
19
+ export type AdditionalRegisters = {
20
+ R4?: string;
21
+ R5?: string;
22
+ R6?: string;
23
+ R7?: string;
24
+ R8?: string;
25
+ R9?: string;
26
+ };
27
+
28
+ export type OutputBox = {
29
+ boxId: string;
30
+ value: bigint;
31
+ ergoTree: string;
32
+ creationHeight: number;
33
+ assets?: Array<Asset>;
34
+ additionalRegisters?: AdditionalRegisters;
35
+ transactionId: string;
36
+ index: bigint | number;
37
+ };
38
+
39
+ export interface ErgoBox extends OutputBox {
40
+ blockId: string;
41
+ spentTransactionId?: string;
42
+ }
43
+
44
+ export type Transaction = {
45
+ id: string;
46
+ inputs: Array<InputBox>;
47
+ dataInputs: Array<DataInput>;
48
+ outputs: Array<OutputBox>;
49
+ size?: bigint;
50
+ };
51
+
52
+ export interface SpendInfo {
53
+ boxId: string;
54
+ txId: string;
55
+ index: number;
56
+ }
57
+
58
+ export interface ErgoExtractedData {
59
+ boxId: string;
60
+ height: number;
61
+ blockId: string;
62
+ spendBlock?: string;
63
+ spendHeight?: number;
64
+ }
@@ -0,0 +1,35 @@
1
+ import { BlockInfo } from '../../interfaces';
2
+ import { ErgoBox } from '../interfaces';
3
+ export abstract class AbstractNetwork {
4
+ /**
5
+ * return block information of specified tx
6
+ * @param txId
7
+ */
8
+ abstract getTxBlock: (txId: string) => Promise<BlockInfo>;
9
+
10
+ /**
11
+ * return related boxes by specified address with limit offset
12
+ * @param address
13
+ * @param offset
14
+ * @param limit
15
+ * @returns related boxes
16
+ */
17
+ abstract getBoxesByAddress: (
18
+ address: string,
19
+ offset: number,
20
+ limit: number
21
+ ) => Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }>;
22
+
23
+ /**
24
+ * return related boxes by specified token with limit offset
25
+ * @param tokenId
26
+ * @param offset
27
+ * @param limit
28
+ * @returns related boxes
29
+ */
30
+ abstract getBoxesByTokenId: (
31
+ tokenId: string,
32
+ offset: number,
33
+ limit: number
34
+ ) => Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }>;
35
+ }
@@ -0,0 +1,68 @@
1
+ import ergoExplorerClientFactory from '@rosen-clients/ergo-explorer';
2
+
3
+ import { BlockInfo } from '../../interfaces';
4
+ import { ErgoBox } from '../interfaces';
5
+ import { AbstractNetwork } from './AbstractNetwork';
6
+
7
+ export class ExplorerNetwork extends AbstractNetwork {
8
+ private api;
9
+
10
+ constructor(url: string) {
11
+ super();
12
+ this.api = ergoExplorerClientFactory(url);
13
+ }
14
+
15
+ /**
16
+ * return block information of specified tx
17
+ * @param txId
18
+ */
19
+ getTxBlock = async (txId: string): Promise<BlockInfo> => {
20
+ const tx = await this.api.v1.getApiV1TransactionsP1(txId);
21
+ return {
22
+ hash: tx.blockId,
23
+ height: tx.inclusionHeight,
24
+ };
25
+ };
26
+
27
+ /**
28
+ * use explorer api to return related boxes by specified address
29
+ * @param address
30
+ * @param offset
31
+ * @param limit
32
+ * @returns related boxes
33
+ */
34
+ getBoxesByAddress = async (
35
+ address: string,
36
+ offset: number,
37
+ limit: number
38
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
39
+ const boxes = await this.api.v1.getApiV1BoxesByaddressP1(address, {
40
+ offset: offset,
41
+ limit: limit,
42
+ });
43
+ if (!boxes.items)
44
+ throw new Error('Explorer BoxesByAddress api expected to have items');
45
+ return { boxes: boxes.items, hasNextBatch: boxes.total > offset + limit };
46
+ };
47
+
48
+ /**
49
+ * use explorer api to return related boxes by specified token id
50
+ * @param tokenId
51
+ * @param offset
52
+ * @param limit
53
+ * @returns related boxes
54
+ */
55
+ getBoxesByTokenId = async (
56
+ tokenId: string,
57
+ offset: number,
58
+ limit: number
59
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
60
+ const boxes = await this.api.v1.getApiV1BoxesBytokenidP1(tokenId, {
61
+ offset: offset,
62
+ limit: limit,
63
+ });
64
+ if (!boxes.items)
65
+ throw new Error('Explorer BoxesByTokeId api expected to have items');
66
+ return { boxes: boxes.items, hasNextBatch: boxes.total > offset + limit };
67
+ };
68
+ }
@@ -0,0 +1,93 @@
1
+ import ergoNodeClientFactory, {
2
+ IndexedErgoBox,
3
+ } from '@rosen-clients/ergo-node';
4
+
5
+ import { BlockInfo } from '../../interfaces';
6
+ import { ErgoBox } from '../interfaces';
7
+ import { AbstractNetwork } from './AbstractNetwork';
8
+
9
+ export class NodeNetwork extends AbstractNetwork {
10
+ private api;
11
+
12
+ constructor(url: string) {
13
+ super();
14
+ this.api = ergoNodeClientFactory(url);
15
+ }
16
+
17
+ /**
18
+ * covert node api boxes to ErgoBox interface
19
+ * @param box
20
+ * @returns ErgoBox
21
+ */
22
+ convertToErgoBox = async (box: IndexedErgoBox): Promise<ErgoBox> => ({
23
+ transactionId: box.transactionId || '',
24
+ index: box.index || 0,
25
+ value: box.value || 0n,
26
+ ergoTree: box.ergoTree || '',
27
+ creationHeight: box.creationHeight || 0,
28
+ assets: box.assets || [],
29
+ additionalRegisters: box.additionalRegisters,
30
+ boxId: box.boxId || '',
31
+ blockId: (await this.getTxBlock(box.transactionId!)).hash,
32
+ });
33
+
34
+ /**
35
+ * return block information of specified tx
36
+ * @param txId
37
+ */
38
+ getTxBlock = async (txId: string): Promise<BlockInfo> => {
39
+ const tx = await this.api.getTxById(txId);
40
+ return {
41
+ hash: tx.blockId,
42
+ height: tx.inclusionHeight,
43
+ };
44
+ };
45
+
46
+ /**
47
+ * use node api to return related boxes by specified address
48
+ * @param address
49
+ * @param offset
50
+ * @param limit
51
+ * @returns related boxes
52
+ */
53
+ getBoxesByAddress = async (
54
+ address: string,
55
+ offset: number,
56
+ limit: number
57
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
58
+ const boxes = await this.api.getBoxesByAddress(address, {
59
+ offset: offset,
60
+ limit: limit,
61
+ });
62
+ if (!boxes.items)
63
+ throw new Error('Ergo node BoxesByAddress api expected to have items');
64
+ const ergoBoxes = await Promise.all(
65
+ boxes.items.map(async (box) => await this.convertToErgoBox(box))
66
+ );
67
+ return { boxes: ergoBoxes, hasNextBatch: boxes.items.length > 0 };
68
+ };
69
+
70
+ /**
71
+ * use node api to return related boxes by specified token id
72
+ * @param tokenId
73
+ * @param offset
74
+ * @param limit
75
+ * @returns related boxes
76
+ */
77
+ getBoxesByTokenId = async (
78
+ tokenId: string,
79
+ offset: number,
80
+ limit: number
81
+ ): Promise<{ boxes: ErgoBox[]; hasNextBatch: boolean }> => {
82
+ const boxes = await this.api.getBoxesByTokenId(tokenId, {
83
+ offset: offset,
84
+ limit: limit,
85
+ });
86
+ if (!boxes.items)
87
+ throw new Error('Ergo node BoxesByTokenId api expected to have items');
88
+ const ergoBoxes = await Promise.all(
89
+ boxes.items.map(async (box) => await this.convertToErgoBox(box))
90
+ );
91
+ return { boxes: ergoBoxes, hasNextBatch: boxes.items.length > 0 };
92
+ };
93
+ }
@@ -0,0 +1,15 @@
1
+ import { intersection } from 'lodash-es';
2
+ import { OutputBox } from './interfaces';
3
+
4
+ /**
5
+ * Check box to have specified tokens
6
+ * @param box
7
+ * @return true if box has the required token and false otherwise
8
+ */
9
+ export const boxHasToken = (box: OutputBox, tokenIds: string[]) => {
10
+ if (!box.assets) return false;
11
+ const boxTokens = box.assets.map((token) => token.tokenId);
12
+ const requiredTokens = intersection(tokenIds, boxTokens);
13
+ if (requiredTokens.length == tokenIds.length) return true;
14
+ return false;
15
+ };
package/lib/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './interfaces';
2
+ export * from './ergo';
3
+ export * from './AbstractExtractor';
@@ -0,0 +1,12 @@
1
+ export interface Block {
2
+ parentHash: string;
3
+ hash: string;
4
+ height: number;
5
+ timestamp: number;
6
+ extra?: string;
7
+ }
8
+
9
+ export interface BlockInfo {
10
+ height: number;
11
+ hash: string;
12
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@rosen-bridge/abstract-extractor",
3
+ "version": "0.1.0-2afdd7",
4
+ "description": "Rosen Bridge extractor interfaces to work with scanner",
5
+ "repository": "",
6
+ "license": "GPL-3.0",
7
+ "author": "Rosen Team",
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc --build tsconfig.build.json",
13
+ "coverage": "npm run test -- --coverage",
14
+ "lint": "eslint --fix . && npm run prettify",
15
+ "prettify": "prettier --write . --ignore-path ./.gitignore",
16
+ "release": "npm run test -- --run && npm run build && npm publish --access public",
17
+ "test": "NODE_OPTIONS=--loader=extensionless vitest",
18
+ "type-check": "tsc --noEmit"
19
+ },
20
+ "devDependencies": {
21
+ "@types/lodash-es": "^4.17.12",
22
+ "@types/node": "^20.11.9",
23
+ "@typescript-eslint/eslint-plugin": "^6.19.1",
24
+ "@typescript-eslint/parser": "^6.19.1",
25
+ "@vitest/coverage-istanbul": "^1.2.2",
26
+ "eslint": "^8.56.0",
27
+ "eslint-config-prettier": "^9.1.0",
28
+ "extensionless": "^1.9.6",
29
+ "prettier": "^3.2.4",
30
+ "typescript": "^5.3.3",
31
+ "vitest": "^1.2.2"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.11.0"
35
+ },
36
+ "dependencies": {
37
+ "@rosen-bridge/abstract-logger": "^1.0.0",
38
+ "@rosen-clients/ergo-explorer": "^1.1.1",
39
+ "@rosen-clients/ergo-node": "^1.1.1",
40
+ "lodash-es": "^4.17.21"
41
+ }
42
+ }
@@ -0,0 +1,26 @@
1
+ import { V1 } from '@rosen-clients/ergo-explorer';
2
+ import {
3
+ AbstractErgoExtractor,
4
+ BlockInfo,
5
+ OutputBox,
6
+ ErgoExtractedData,
7
+ AbstractErgoExtractorAction,
8
+ } from '../lib';
9
+
10
+ export class MockedErgoExtractor extends AbstractErgoExtractor<ErgoExtractedData> {
11
+ actions: AbstractErgoExtractorAction<ErgoExtractedData>;
12
+
13
+ getId = () => 'Test';
14
+
15
+ initializeBoxes: (initialBlock: BlockInfo) => Promise<void>;
16
+
17
+ hasData = (box: V1.OutputInfo | OutputBox) => false;
18
+
19
+ extractBoxData = (
20
+ box: V1.OutputInfo | OutputBox,
21
+ blockId: string,
22
+ height: number
23
+ ): Omit<ErgoExtractedData, 'spendBlock' | 'spendHeight'> | undefined => {
24
+ return undefined;
25
+ };
26
+ }