evernode-js-client 0.4.53 → 0.5.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.
@@ -0,0 +1,309 @@
1
+ const https = require('https');
2
+ const { DefaultValues } = require('../defaults');
3
+
4
+ const FirestoreOperations = {
5
+ EQUAL: 'EQUAL',
6
+ AND: 'AND'
7
+ }
8
+
9
+ class FirestoreHandler {
10
+ #projectId = null;
11
+ #collectionPrefix = null;
12
+
13
+ constructor(options = {}) {
14
+ this.#projectId = options.stateIndexId || DefaultValues.stateIndexId;
15
+ this.#collectionPrefix = options.collectionPrefix || DefaultValues.registryAddress;
16
+ }
17
+
18
+ /**
19
+ * Convert given document value object to real format and snake_case key to camelCase.
20
+ * @param key Name of the property.
21
+ * @param value Value to be parsed.
22
+ * @returns Parsed key and value.
23
+ */
24
+ #parseValue(key, value) {
25
+ // Convert snake_case to camelCase.
26
+ const ccKey = key.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
27
+ const type = Object.keys(value)[0];
28
+ let parsed;
29
+ switch (type) {
30
+ case 'integerValue':
31
+ parsed = parseInt(value[type]);
32
+ break;
33
+ case 'floatValue':
34
+ parsed = parseFloat(value[type]);
35
+ break;
36
+ case 'mapValue':
37
+ parsed = {};
38
+ for (const [subKey, subValue] of Object.entries(value[type].fields)) {
39
+ const field = this.#parseValue(subKey, subValue);
40
+ parsed[field.key] = field.value;
41
+ }
42
+ break;
43
+ default:
44
+ parsed = value[type];
45
+ break;
46
+ }
47
+ return { key: ccKey, value: parsed };
48
+ }
49
+
50
+ /**
51
+ * Get values filtered according to the given body.
52
+ * @param body Body to parsed in to the query reqest.
53
+ * @returns Result set.
54
+ */
55
+ async #runQuery(body) {
56
+ const url = this.buildApiPath(null, null, true);
57
+ return await this.sendRequest('POST', url, null, body);
58
+ }
59
+
60
+ /**
61
+ * Get documents from a collection.
62
+ * @param collectionId Name of the collection.
63
+ * @param pageSize Optianal page size if result set needed to be paginated
64
+ * @param nextPageToken Next page token of a paginated result set.
65
+ * @returns Result set.
66
+ */
67
+ async #read(collectionId, pageSize = null, nextPageToken = null) {
68
+ if (!collectionId)
69
+ throw { type: 'Validation Error', message: 'collectionId is required' };
70
+
71
+ const url = this.buildApiPath(collectionId);
72
+ let params = (pageSize || nextPageToken) ? {} : null;
73
+ if (pageSize)
74
+ params = { pageSize: pageSize };
75
+ if (nextPageToken)
76
+ params = { pageToken: nextPageToken, ...params };
77
+
78
+ return await this.sendRequest('GET', url, params);
79
+ }
80
+
81
+ /**
82
+ * Get documents from a collection with filtering support.
83
+ * @param collectionId Name of the collection.
84
+ * @param filter Optional filters to filter documents.
85
+ * @param pageSize Optianal page size if result set needed to be paginated
86
+ * @param nextPageToken Next page token of a paginated result set.
87
+ * @returns Parsed readable result set.
88
+ */
89
+ async #getDocuments(collectionId, filters = null, pageSize = null, nextPageToken = null) {
90
+ if (filters && (pageSize || nextPageToken))
91
+ throw { type: 'Validation Error', message: 'Pagination isn\'t supported with filter.' };
92
+
93
+ let data;
94
+ // If there are filters send requst in query mode.
95
+ if (filters) {
96
+ // Prepare the query body from given filters.
97
+ let where = {
98
+ compositeFilter: {
99
+ filters: Object.entries(filters).map(([key, value]) => {
100
+ const field = this.convertValue(key, value);
101
+ return {
102
+ fieldFilter: {
103
+ field: { fieldPath: field.key },
104
+ op: FirestoreOperations.EQUAL,
105
+ value: field.value
106
+ }
107
+ }
108
+ }),
109
+ op: filters.operator ? filters.operator : FirestoreOperations.AND
110
+ }
111
+ };
112
+ for (const [key, value] of Object.entries(filters)) {
113
+ const field = this.convertValue(key, value);
114
+ let fieldFilter = {
115
+ field: { fieldPath: field.key },
116
+ op: FirestoreOperations.EQUAL,
117
+ value: field.value
118
+ }
119
+ where.compositeFilter.filters.push({ fieldFilter: fieldFilter });
120
+ }
121
+
122
+ let body = {
123
+ structuredQuery: {
124
+ where: where,
125
+ from: [{ collectionId: collectionId }]
126
+ }
127
+ };
128
+ data = await this.#runQuery(body);
129
+ data = data ? JSON.parse(data) : [];
130
+ if (data && data.length && data[0].document) {
131
+ return data.map(d => {
132
+ return this.parseDocument(d.document);
133
+ });
134
+ }
135
+ }
136
+ else {
137
+ data = await this.#read(collectionId, pageSize, nextPageToken);
138
+ data = data ? JSON.parse(data) : {};
139
+ if (data.documents && data.documents.length) {
140
+ const list = data.documents.map(d => {
141
+ return this.parseDocument(d);
142
+ });
143
+ return data.nextPageToken ? {
144
+ data: list,
145
+ nextPageToken: data.nextPageToken
146
+ } : list;
147
+ }
148
+ }
149
+
150
+ return [];
151
+ }
152
+
153
+ getCollectionId(collection) {
154
+ // Document if is generated with given prefix.
155
+ return `${this.#collectionPrefix}_${collection}`;
156
+ }
157
+
158
+ /**
159
+ * Send http requst.
160
+ * @param httpMethod GET/POST/PATCH/DELETE
161
+ * @param url Url for the request.
162
+ * @param params Optional query params.
163
+ * @param data Optional request body.
164
+ * @param options Optional options.
165
+ * @returns Result set.
166
+ */
167
+ async sendRequest(httpMethod, url, params = null, data = null, options = null) {
168
+ const urlObj = new URL(url);
169
+ // Populate uri params to the URL object.
170
+ if (params) {
171
+ for (const [key, value] of Object.entries(params)) {
172
+ if (value) {
173
+ // If value is a array, populate it with same name.
174
+ if (typeof value === 'object') {
175
+ for (const val of value) {
176
+ if (val)
177
+ urlObj.searchParams.append(key, val);
178
+ }
179
+ }
180
+ else
181
+ urlObj.searchParams.set(key, value);
182
+ }
183
+ }
184
+ }
185
+
186
+ let reqOptions = {
187
+ method: httpMethod,
188
+ protocol: urlObj.protocol,
189
+ hostname: urlObj.hostname,
190
+ port: urlObj.port,
191
+ path: `${urlObj.pathname}?${urlObj.searchParams.toString()}`,
192
+ ...(options ? options : {})
193
+ };
194
+
195
+ return new Promise(async (resolve, reject) => {
196
+ // Send request and collect data chuncks to a buffer. Then return the result set on end event.
197
+ // Resolve only if the status is 200.
198
+ const req = https.request(reqOptions, (res) => {
199
+ let resData = '';
200
+ res.on('data', (d) => {
201
+ resData += d;
202
+ });
203
+ res.on('end', async () => {
204
+ if (res.statusCode === 200) {
205
+ resolve(resData);
206
+ return;
207
+ }
208
+ reject({ status: res.statusCode, data: resData });
209
+ return;
210
+ });
211
+ }).on('error', async (e) => {
212
+ reject(e);
213
+ return;
214
+ });
215
+
216
+ if (data)
217
+ req.write(typeof data === 'object' ? JSON.stringify(data) : data);
218
+
219
+ req.end();
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Generate url for firestore.
225
+ * @param collectionId Optional collection name.
226
+ * @param documentId Optional document name.
227
+ * @param isQuery Whether a query request or not.
228
+ * @returns The generated url.
229
+ */
230
+ buildApiPath(collectionId = null, documentId = null, isQuery = false) {
231
+ let path = `https://firestore.googleapis.com/v1/projects/${this.#projectId}/databases/(default)/documents`;
232
+ if (collectionId)
233
+ path += `/${collectionId.toString()}`;
234
+ if (documentId)
235
+ path += `/${documentId.toString()}`;
236
+ if (isQuery)
237
+ path += ':runQuery';
238
+ return path;
239
+ }
240
+
241
+ /**
242
+ * Generate firestore document value object from given key and value, Convert camelCase key to snake_case.
243
+ * @param key Name of the value.
244
+ * @param value Value to be parsed.
245
+ * @returns Generated firestore document object.
246
+ */
247
+ convertValue(key, value) {
248
+ // Convert camelCase to snake_case.
249
+ const uKey = key.replace(/([A-Z])/g, function (g) { return `_${g[0].toLocaleLowerCase()}`; });
250
+ let val = {};
251
+ let type
252
+ switch (typeof value) {
253
+ case 'number':
254
+ type = (value % 1 > 0 ? 'float' : 'integer');
255
+ val = value;
256
+ break;
257
+ case 'object':
258
+ type = 'map';
259
+ val = {
260
+ fields: {}
261
+ }
262
+ // Prepare the firestore write body with the given data object.
263
+ for (const [subKey, subValue] of Object.entries(value)) {
264
+ const field = this.convertValue(subKey, subValue);
265
+ val.fields[field.key] = field.value;
266
+ }
267
+ break;
268
+ default:
269
+ type = 'string';
270
+ val = value;
271
+ break;
272
+ }
273
+ type = `${type}Value`;
274
+ let obj = {};
275
+ obj[type] = val;
276
+ return { key: uKey, value: obj };
277
+ }
278
+
279
+ /**
280
+ * Convert the firestore document to human readable simplified json object.
281
+ * @param document Firestore document.
282
+ * @returns Simplified json object for the document.
283
+ */
284
+ parseDocument(document) {
285
+ let item = {
286
+ id: document.name.split('/').pop(),
287
+ createTime: new Date(document.createTime),
288
+ updateTime: new Date(document.updateTime)
289
+ };
290
+ for (const [key, value] of Object.entries(document.fields)) {
291
+ const field = this.#parseValue(key, value);
292
+ item[field.key] = field.value;
293
+ }
294
+ return item;
295
+ }
296
+
297
+ async getHosts(filters = null, pageSize = null, nextPageToken = null) {
298
+ return await this.#getDocuments(this.getCollectionId('hosts'), filters, pageSize, nextPageToken);
299
+ }
300
+
301
+ async getConfigs(filters = null, pageSize = null, nextPageToken = null) {
302
+ return await this.#getDocuments(this.getCollectionId('configs'), filters, pageSize, nextPageToken);
303
+ }
304
+ }
305
+
306
+ module.exports = {
307
+ FirestoreHandler,
308
+ FirestoreOperations
309
+ }
package/src/index.js ADDED
@@ -0,0 +1,37 @@
1
+ const { Defaults } = require('./defaults');
2
+ const { RegistryClient, RegistryEvents } = require("./clients/registry-client");
3
+ const { TenantClient, TenantEvents } = require("./clients/tenant-client");
4
+ const { HostClient, HostEvents } = require("./clients/host-client");
5
+ const { XrplApi } = require('./xrpl-api');
6
+ const { XrplApiEvents, XrplConstants } = require('./xrpl-common');
7
+ const { XrplAccount } = require('./xrpl-account');
8
+ const { EvernodeConstants, HookStateKeys, MemoTypes } = require('./evernode-common');
9
+ const { XflHelpers } = require('./xfl-helpers');
10
+ const { FirestoreHandler } = require('./firestore/firestore-handler');
11
+ const { StateHelpers } = require('./state-helpers');
12
+ const { UtilHelpers } = require('./util-helpers');
13
+ const { TransactionHelper } = require('./transaction-helper');
14
+ const { EncryptionHelper } = require('./encryption-helper');
15
+
16
+ module.exports = {
17
+ RegistryClient,
18
+ RegistryEvents,
19
+ TenantClient,
20
+ TenantEvents,
21
+ HostClient,
22
+ HostEvents,
23
+ XrplApi,
24
+ XrplApiEvents,
25
+ XrplConstants,
26
+ XrplAccount,
27
+ EvernodeConstants,
28
+ Defaults,
29
+ XflHelpers,
30
+ StateHelpers,
31
+ FirestoreHandler,
32
+ UtilHelpers,
33
+ TransactionHelper,
34
+ EncryptionHelper,
35
+ HookStateKeys,
36
+ MemoTypes
37
+ }
@@ -0,0 +1,283 @@
1
+ const codec = require('ripple-address-codec');
2
+ const { Buffer } = require('buffer');
3
+ const { HookStateKeys, EvernodeConstants } = require('./evernode-common');
4
+ const { XflHelpers } = require('./xfl-helpers');
5
+ const crypto = require("crypto");
6
+
7
+ const NFTOKEN_PREFIX = '00000000';
8
+
9
+ const EPOCH_OFFSET = 0;
10
+ const SAVED_MOMENT_OFFSET = 1;
11
+ const PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 5;
12
+ const CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 9;
13
+ const EPOCH_POOL_OFFSET = 13;
14
+
15
+ const EPOCH_COUNT_OFFSET = 0;
16
+ const FIRST_EPOCH_REWARD_QUOTA_OFFSET = 1;
17
+ const EPOCH_REWARD_AMOUNT_OFFSET = 5;
18
+ const REWARD_START_MOMENT_OFFSET = 9;
19
+
20
+ const HOST_TOKEN_ID_OFFSET = 0;
21
+ const HOST_COUNTRY_CODE_OFFSET = 32;
22
+ const HOST_RESERVED_OFFSET = 34;
23
+ const HOST_DESCRIPTION_OFFSET = 42;
24
+ const HOST_REG_LEDGER_OFFSET = 68;
25
+ const HOST_REG_FEE_OFFSET = 76;
26
+ const HOST_TOT_INS_COUNT_OFFSET = 84;
27
+ const HOST_ACT_INS_COUNT_OFFSET = 88;
28
+ const HOST_HEARTBEAT_LEDGER_IDX_OFFSET = 92;
29
+ const HOST_VERSION_OFFSET = 100;
30
+
31
+ const HOST_ADDRESS_OFFSET = 0;
32
+ const HOST_CPU_MODEL_NAME_OFFSET = 20;
33
+ const HOST_CPU_COUNT_OFFSET = 60;
34
+ const HOST_CPU_SPEED_OFFSET = 62;
35
+ const HOST_CPU_MICROSEC_OFFSET = 64;
36
+ const HOST_RAM_MB_OFFSET = 68;
37
+ const HOST_DISK_MB_OFFSET = 72;
38
+
39
+ const STATE_KEY_TYPES = {
40
+ TOKEN_ID: 2,
41
+ HOST_ADDR: 3
42
+ }
43
+
44
+ const EVERNODE_PREFIX = 'EVR';
45
+ const HOST_ADDR_KEY_ZERO_COUNT = 8;
46
+ const HOOK_STATE_LEDGER_TYPE_PREFIX = 118; // Decimal value of ASCII 'v'
47
+
48
+ class StateHelpers {
49
+ static StateTypes = {
50
+ TOKEN_ID: 'tokenId',
51
+ HOST_ADDR: 'hostAddr',
52
+ SIGLETON: 'singleton',
53
+ CONFIGURATION: 'configuration'
54
+ }
55
+
56
+ static decodeHostAddressState(stateKeyBuf, stateDataBuf) {
57
+ return {
58
+ address: codec.encodeAccountID(stateKeyBuf.slice(12)),
59
+ nfTokenId: stateDataBuf.slice(HOST_TOKEN_ID_OFFSET, HOST_COUNTRY_CODE_OFFSET).toString('hex').toUpperCase(),
60
+ countryCode: stateDataBuf.slice(HOST_COUNTRY_CODE_OFFSET, HOST_RESERVED_OFFSET).toString(),
61
+ description: stateDataBuf.slice(HOST_DESCRIPTION_OFFSET, HOST_REG_LEDGER_OFFSET).toString().replace(/\0/g, ''),
62
+ registrationLedger: Number(stateDataBuf.readBigUInt64BE(HOST_REG_LEDGER_OFFSET)),
63
+ registrationFee: Number(stateDataBuf.readBigUInt64BE(HOST_REG_FEE_OFFSET)),
64
+ maxInstances: stateDataBuf.readUInt32BE(HOST_TOT_INS_COUNT_OFFSET),
65
+ activeInstances: stateDataBuf.readUInt32BE(HOST_ACT_INS_COUNT_OFFSET),
66
+ lastHeartbeatLedger: Number(stateDataBuf.readBigUInt64BE(HOST_HEARTBEAT_LEDGER_IDX_OFFSET)),
67
+ version: `${stateDataBuf.readUInt8(HOST_VERSION_OFFSET)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 1)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 2)}`
68
+ }
69
+ }
70
+
71
+ static decodeTokenIdState(stateDataBuf) {
72
+ return {
73
+ address: codec.encodeAccountID(stateDataBuf.slice(HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)),
74
+ cpuModelName: stateDataBuf.slice(HOST_CPU_MODEL_NAME_OFFSET, HOST_CPU_COUNT_OFFSET).toString().replace(/\x00+$/, ''), // Remove trailing \x00 characters.
75
+ cpuCount: stateDataBuf.readUInt16BE(HOST_CPU_COUNT_OFFSET),
76
+ cpuMHz: stateDataBuf.readUInt16BE(HOST_CPU_SPEED_OFFSET),
77
+ cpuMicrosec: stateDataBuf.readUInt32BE(HOST_CPU_MICROSEC_OFFSET),
78
+ ramMb: stateDataBuf.readUInt32BE(HOST_RAM_MB_OFFSET),
79
+ diskMb: stateDataBuf.readUInt32BE(HOST_DISK_MB_OFFSET)
80
+ }
81
+ }
82
+
83
+ static decodeStateData(stateKey, stateData) {
84
+ const hexKey = stateKey.toString('hex').toUpperCase();
85
+ if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
86
+ return {
87
+ type: this.StateTypes.HOST_ADDR,
88
+ key: hexKey,
89
+ ...this.decodeHostAddressState(stateKey, stateData)
90
+ }
91
+ }
92
+ else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
93
+ // Generate the address state key.
94
+ const addressKeyBuf = Buffer.alloc(32, 0);
95
+ Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').copy(addressKeyBuf);
96
+ stateData.copy(addressKeyBuf, 12, HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)
97
+ return {
98
+ type: this.StateTypes.TOKEN_ID,
99
+ key: hexKey,
100
+ addressKey: addressKeyBuf.toString('hex').toUpperCase(),
101
+ ...this.decodeTokenIdState(stateData)
102
+ }
103
+ }
104
+ else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0) {
105
+ return {
106
+ type: this.StateTypes.SIGLETON,
107
+ key: hexKey,
108
+ value: stateData.readUInt32BE()
109
+ }
110
+ }
111
+ else if (Buffer.from(HookStateKeys.MOMENT_BASE_IDX, 'hex').compare(stateKey) === 0) {
112
+ return {
113
+ type: this.StateTypes.SIGLETON,
114
+ key: hexKey,
115
+ value: Number(stateData.readBigInt64BE())
116
+ }
117
+ }
118
+ else if (Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0) {
119
+ return {
120
+ type: this.StateTypes.SIGLETON,
121
+ key: hexKey,
122
+ value: Number(stateData.readBigUInt64BE())
123
+ }
124
+ }
125
+ else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0) {
126
+ return {
127
+ type: this.StateTypes.CONFIGURATION,
128
+ key: hexKey,
129
+ value: codec.encodeAccountID(stateData)
130
+ }
131
+ }
132
+ else if (Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
133
+ Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) === 0 ||
134
+ Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0) {
135
+ return {
136
+ type: this.StateTypes.CONFIGURATION,
137
+ key: hexKey,
138
+ value: stateData.readUInt16BE()
139
+ }
140
+ }
141
+ else if (Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0) {
142
+ return {
143
+ type: this.StateTypes.CONFIGURATION,
144
+ key: hexKey,
145
+ value: Number(stateData.readBigUInt64BE())
146
+ }
147
+ }
148
+ else if (Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0) {
149
+ const xfl = stateData.readBigInt64BE(0);
150
+ const val = XflHelpers.toString(xfl);
151
+ return {
152
+ type: this.StateTypes.CONFIGURATION,
153
+ key: hexKey,
154
+ value: val
155
+ }
156
+ }
157
+ else if (Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0) {
158
+ return {
159
+ type: this.StateTypes.CONFIGURATION,
160
+ key: hexKey,
161
+ value: {
162
+ epochCount: stateData.readUInt8(EPOCH_COUNT_OFFSET),
163
+ firstEpochRewardQuota: stateData.readUInt32BE(FIRST_EPOCH_REWARD_QUOTA_OFFSET),
164
+ epochRewardAmount: stateData.readUInt32BE(EPOCH_REWARD_AMOUNT_OFFSET),
165
+ rewardStartMoment: stateData.readUInt32BE(REWARD_START_MOMENT_OFFSET)
166
+ }
167
+ }
168
+ }
169
+ else if (Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
170
+ return {
171
+ type: this.StateTypes.SIGLETON,
172
+ key: hexKey,
173
+ value: {
174
+ epoch: stateData.readUInt8(EPOCH_OFFSET),
175
+ savedMoment: stateData.readUInt32BE(SAVED_MOMENT_OFFSET),
176
+ prevMomentActiveHostCount: stateData.readUInt32BE(PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
177
+ curMomentActiveHostCount: stateData.readUInt32BE(CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
178
+ epochPool: XflHelpers.toString(stateData.readBigInt64BE(EPOCH_POOL_OFFSET))
179
+ }
180
+ }
181
+ }
182
+ else if (Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0) {
183
+ return {
184
+ type: this.StateTypes.CONFIGURATION,
185
+ key: hexKey,
186
+ value: stateData.readUInt16BE()
187
+ }
188
+ }
189
+ else
190
+ throw { type: 'Validation Error', message: 'Invalid state key.' };
191
+ }
192
+
193
+ static decodeStateKey(stateKey) {
194
+ const hexKey = stateKey.toString('hex').toUpperCase();
195
+ if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
196
+ return {
197
+ key: hexKey,
198
+ type: this.StateTypes.HOST_ADDR
199
+ };
200
+ }
201
+ else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
202
+ return {
203
+ key: hexKey,
204
+ type: this.StateTypes.TOKEN_ID
205
+ };
206
+ }
207
+ else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0 ||
208
+ Buffer.from(HookStateKeys.MOMENT_BASE_IDX, 'hex').compare(stateKey) === 0 ||
209
+ Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 ||
210
+ Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0 ||
211
+ Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
212
+ return {
213
+ key: hexKey,
214
+ type: this.STATE_TYPES.SIGLETON
215
+ };
216
+ }
217
+ else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 ||
218
+ Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0 ||
219
+ Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
220
+ Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0 ||
221
+ Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) ||
222
+ Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 ||
223
+ Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0 ||
224
+ Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0 ||
225
+ Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0 ||
226
+ Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0) {
227
+ return {
228
+ key: hexKey,
229
+ type: this.STATE_TYPES.CONFIGURATION
230
+ };
231
+ }
232
+ else
233
+ throw { type: 'Validation Error', message: 'Invalid state key.' };
234
+ }
235
+
236
+ static generateTokenIdStateKey(nfTokenId) {
237
+ // 1 byte - Key Type.
238
+ let buf = Buffer.allocUnsafe(1);
239
+ buf.writeUInt8(STATE_KEY_TYPES.TOKEN_ID);
240
+
241
+ const nfTokenIdBuf = Buffer.from(nfTokenId, "hex");
242
+ const stateKeyBuf = (Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, nfTokenIdBuf.slice(4, 32)]));
243
+ return stateKeyBuf.toString('hex').toUpperCase();
244
+ }
245
+
246
+ static generateHostAddrStateKey(address) {
247
+ // 1 byte - Key Type.
248
+ // 8 bytes - Zeros.
249
+ let buf = Buffer.allocUnsafe(9);
250
+ buf.writeUInt8(STATE_KEY_TYPES.HOST_ADDR);
251
+ for (let i = 0; i < HOST_ADDR_KEY_ZERO_COUNT; i++) {
252
+ buf.writeUInt8(0, i + 1);
253
+ }
254
+
255
+ const addrBuf = Buffer.from(codec.decodeAccountID(address), "hex");
256
+ const stateKeyBuf = Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, addrBuf]);
257
+ return stateKeyBuf.toString('hex').toUpperCase();
258
+ }
259
+
260
+ static getHookStateIndex(hookAccount, stateKey, hookNamespace = EvernodeConstants.HOOK_NAMESPACE) {
261
+ const typeBuf = Buffer.allocUnsafe(2);
262
+ typeBuf.writeInt16BE(HOOK_STATE_LEDGER_TYPE_PREFIX);
263
+
264
+ const accIdBuf = codec.decodeAccountID(hookAccount);
265
+ const stateKeyBuf = Buffer.from(stateKey, 'hex');
266
+ const namespaceBuf = Buffer.from(hookNamespace, 'hex');
267
+
268
+ let hash = crypto.createHash('sha512');
269
+
270
+ let data = hash.update(typeBuf);
271
+ data = hash.update(accIdBuf);
272
+ data = hash.update(stateKeyBuf);
273
+ data = hash.update(namespaceBuf);
274
+
275
+ const digest = data.digest('hex');
276
+ // Get the first 32 bytes of hash.
277
+ return digest.substring(0, 64).toUpperCase();
278
+ }
279
+ }
280
+
281
+ module.exports = {
282
+ StateHelpers
283
+ }
@@ -0,0 +1,62 @@
1
+ const { MemoFormats } = require('./evernode-common');
2
+
3
+ class TransactionHelper {
4
+
5
+ // Convert memos from our object type to xrpl lib object type.
6
+ static formatMemos(memos) {
7
+ return memos ? memos.filter(m => m.type).map(m => {
8
+ const data = (m.format === MemoFormats.HEX) ? m.data :
9
+ TransactionHelper.asciiToHex((typeof m.data === "object") ? JSON.stringify(m.data) : m.data)
10
+ return {
11
+ Memo: {
12
+ MemoType: TransactionHelper.asciiToHex(m.type),
13
+ MemoFormat: TransactionHelper.asciiToHex(m.format),
14
+ MemoData: data
15
+ }
16
+ }
17
+ }) : [];
18
+ }
19
+
20
+ // Convert memos from xrpl lib object type to our object type.
21
+ static deserializeMemos(memos) {
22
+ if (!memos)
23
+ return [];
24
+
25
+ return memos.filter(m => m.Memo).map(m => {
26
+ const format = m.Memo.MemoFormat ? TransactionHelper.hexToASCII(m.Memo.MemoFormat) : null;
27
+ const data = m.Memo.MemoData ?
28
+ ((format === MemoFormats.HEX) ? m.Memo.MemoData : TransactionHelper.hexToASCII(m.Memo.MemoData)) : null;
29
+ return {
30
+ type: m.Memo.MemoType ? TransactionHelper.hexToASCII(m.Memo.MemoType) : null,
31
+ format: format,
32
+ data: data
33
+ }
34
+ })
35
+ }
36
+
37
+ static hexToASCII(hex) {
38
+ if (!hex)
39
+ return "";
40
+
41
+ let str = "";
42
+ for (let n = 0; n < hex.length; n += 2) {
43
+ str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
44
+ }
45
+ return str;
46
+ }
47
+
48
+ static asciiToHex(str) {
49
+ if (!str)
50
+ return "";
51
+
52
+ let hex = "";
53
+ for (let n = 0; n < str.length; n++) {
54
+ hex += str.charCodeAt(n).toString(16)
55
+ }
56
+ return hex;
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ TransactionHelper
62
+ }