@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.
- package/.eslintignore +1 -0
- package/README.md +24 -0
- package/dist/AbstractExtractor.d.ts +25 -0
- package/dist/AbstractExtractor.d.ts.map +1 -0
- package/dist/AbstractExtractor.js +3 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/ergo/AbstractErgoExtractor.d.ts +39 -0
- package/dist/ergo/AbstractErgoExtractor.d.ts.map +1 -0
- package/dist/ergo/AbstractErgoExtractor.js +57 -0
- package/dist/ergo/AbstractErgoExtractorAction.d.ts +27 -0
- package/dist/ergo/AbstractErgoExtractorAction.d.ts.map +1 -0
- package/dist/ergo/AbstractErgoExtractorAction.js +3 -0
- package/dist/ergo/index.d.ts +8 -0
- package/dist/ergo/index.d.ts.map +1 -0
- package/dist/ergo/index.js +8 -0
- package/dist/ergo/initializable/AbstractInitializable.d.ts +48 -0
- package/dist/ergo/initializable/AbstractInitializable.d.ts.map +1 -0
- package/dist/ergo/initializable/AbstractInitializable.js +84 -0
- package/dist/ergo/initializable/AbstractInitializableAction.d.ts +9 -0
- package/dist/ergo/initializable/AbstractInitializableAction.d.ts.map +1 -0
- package/dist/ergo/initializable/AbstractInitializableAction.js +4 -0
- package/dist/ergo/initializable/InitializableByAddress.d.ts +26 -0
- package/dist/ergo/initializable/InitializableByAddress.d.ts.map +1 -0
- package/dist/ergo/initializable/InitializableByAddress.js +38 -0
- package/dist/ergo/initializable/InitializableByToken.d.ts +26 -0
- package/dist/ergo/initializable/InitializableByToken.d.ts.map +1 -0
- package/dist/ergo/initializable/InitializableByToken.js +38 -0
- package/dist/ergo/initializable/index.d.ts +5 -0
- package/dist/ergo/initializable/index.d.ts.map +1 -0
- package/dist/ergo/initializable/index.js +5 -0
- package/dist/ergo/interfaces.d.ts +56 -0
- package/dist/ergo/interfaces.d.ts.map +1 -0
- package/dist/ergo/interfaces.js +6 -0
- package/dist/ergo/network/AbstractNetwork.d.ts +32 -0
- package/dist/ergo/network/AbstractNetwork.d.ts.map +1 -0
- package/dist/ergo/network/AbstractNetwork.js +3 -0
- package/dist/ergo/network/ExplorerNetwork.d.ts +35 -0
- package/dist/ergo/network/ExplorerNetwork.d.ts.map +1 -0
- package/dist/ergo/network/ExplorerNetwork.js +53 -0
- package/dist/ergo/network/NodeNetwork.d.ts +42 -0
- package/dist/ergo/network/NodeNetwork.d.ts.map +1 -0
- package/dist/ergo/network/NodeNetwork.js +71 -0
- package/dist/ergo/utils.d.ts +8 -0
- package/dist/ergo/utils.d.ts.map +1 -0
- package/dist/ergo/utils.js +16 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/interfaces.d.ts +12 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +2 -0
- package/lib/AbstractExtractor.ts +31 -0
- package/lib/constants.ts +3 -0
- package/lib/ergo/AbstractErgoExtractor.ts +107 -0
- package/lib/ergo/AbstractErgoExtractorAction.ts +39 -0
- package/lib/ergo/index.ts +7 -0
- package/lib/ergo/initializable/AbstractInitializable.ts +148 -0
- package/lib/ergo/initializable/AbstractInitializableAction.ts +11 -0
- package/lib/ergo/initializable/InitializableByAddress.ts +53 -0
- package/lib/ergo/initializable/InitializableByToken.ts +53 -0
- package/lib/ergo/initializable/index.ts +4 -0
- package/lib/ergo/interfaces.ts +64 -0
- package/lib/ergo/network/AbstractNetwork.ts +35 -0
- package/lib/ergo/network/ExplorerNetwork.ts +68 -0
- package/lib/ergo/network/NodeNetwork.ts +93 -0
- package/lib/ergo/utils.ts +15 -0
- package/lib/index.ts +3 -0
- package/lib/interfaces.ts +12 -0
- package/package.json +42 -0
- package/tests/AbstractExtractor.mock.ts +26 -0
- package/tests/AbstractExtractor.spec.ts +106 -0
- package/tests/initializable/AbstractInitializable.mock.ts +31 -0
- package/tests/initializable/AbstractInitializable.spec.ts +258 -0
- package/tests/initializable/testData.ts +59 -0
- package/tests/testData.ts +111 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- 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,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
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
|
+
}
|