evernode-js-client 0.4.53 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }